├── 20251027093953.png
├── 20251028094010.png
├── README.md
└── URLReplayer.py
/20251027093953.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w-sega/Burp_URLReplayer/HEAD/20251027093953.png
--------------------------------------------------------------------------------
/20251028094010.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/w-sega/Burp_URLReplayer/HEAD/20251028094010.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## URLReplayer
2 |
3 | URLReplayer是一个burp插件,用于监听匹配所有响应中的接口,拼接来源地址,提供一键发送请求的功能,用于快速验证未授权漏洞,自动识别请求类型,不再需要从各种插件中复制接口到intruder中遍历爆破
4 |
5 | ### 使用方法
6 | 下载URLReplayer.py,在burp中添加extensions
7 |
8 | 默认开启监听,shift可选中多个要测试的url,点击send。未配置请求头即默认来源的请求头包含cookie,右侧的请求列表右键可发送到repeater
9 |
10 | 1127更新
11 |
12 | 支持了按响应长度排序
13 |
14 | 1028更新
15 |
16 | 新增搜索功能、自定义请求头
17 |
18 |
19 | 下方配置白名单地址
20 |
21 |
22 |
23 | 自定义请求头
24 |
25 |
--------------------------------------------------------------------------------
/URLReplayer.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from burp import IBurpExtender
3 | from burp import IHttpListener
4 | from burp import ITab
5 | from burp import IMessageEditor
6 |
7 | from javax.swing import JScrollPane, JList, JButton, JPanel, JTextArea, JSplitPane, DefaultListModel
8 | from javax.swing import JTable, JTabbedPane, JLabel, JTextField
9 | from javax.swing import ListSelectionModel, JToggleButton
10 | from javax.swing.table import DefaultTableModel
11 | from javax.swing.event import ListSelectionListener, DocumentListener
12 | from java.awt import BorderLayout, FlowLayout
13 | from java.net import URL
14 | from javax.swing import JPopupMenu, JMenuItem
15 | from java.awt.event import MouseAdapter
16 | from java.awt.event import MouseEvent
17 |
18 | from java.util.concurrent import ConcurrentHashMap
19 | from java.lang import String
20 | import re
21 | from java.lang import Runnable, Thread
22 | from javax.swing import SwingUtilities
23 |
24 | import json
25 |
26 |
27 | class FilterDocumentListener(DocumentListener):
28 | def __init__(self, callback):
29 | self.callback = callback
30 | def insertUpdate(self, e):
31 | self.callback()
32 | def removeUpdate(self, e):
33 | self.callback()
34 | def changedUpdate(self, e):
35 | self.callback()
36 |
37 |
38 | class BurpExtender(IBurpExtender, IHttpListener, ITab):
39 |
40 | def registerExtenderCallbacks(self, callbacks):
41 | self._callbacks = callbacks
42 | self._helpers = callbacks.getHelpers()
43 | callbacks.setExtensionName("URL Replayer")
44 |
45 | self.url_map = ConcurrentHashMap()
46 | self.http_results = []
47 | self.is_listening = True
48 | self.sort_order_ascending = True
49 | self.sorted_column_name = "ID"
50 |
51 | self.IGNORED_EXTENSIONS = [
52 | ".js", ".css", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico",
53 | ".woff", ".woff2", ".ttf", ".eot", ".map", ".htc",
54 | ".mp4", ".mp3", ".webm", ".ogg", ".vue", ".ts", ".jsx"
55 | ]
56 |
57 | self.DEFAULT_HOST_EXCLUSIONS = [
58 | "google.com",
59 | "mozilla.org",
60 | "microsoft.com",
61 | "apple.com",
62 | "127.0.0.1"
63 | ]
64 |
65 | self.PATH_REGEX = re.compile(r'["\']([a-zA-Z0-9_./-]{3,})["\']')
66 |
67 | self._main_panel = JPanel(BorderLayout())
68 |
69 | self.main_split_pane = JSplitPane(JSplitPane.HORIZONTAL_SPLIT)
70 | self._main_panel.add(self.main_split_pane, BorderLayout.CENTER)
71 |
72 | self.list_model = DefaultListModel()
73 | self.url_list = JList(self.list_model)
74 | self.url_list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
75 | list_scroll_pane = JScrollPane(self.url_list)
76 |
77 | searchable_list_panel = JPanel(BorderLayout())
78 |
79 | self.search_field = JTextField()
80 | self.search_field.getDocument().addDocumentListener(FilterDocumentListener(self.filterUrlList))
81 |
82 | search_panel = JPanel(BorderLayout())
83 | search_panel.add(JLabel(" Search: "), BorderLayout.WEST)
84 | search_panel.add(self.search_field, BorderLayout.CENTER)
85 |
86 | searchable_list_panel.add(search_panel, BorderLayout.NORTH)
87 | searchable_list_panel.add(list_scroll_pane, BorderLayout.CENTER)
88 |
89 | left_panel = searchable_list_panel
90 |
91 | self.results_split_pane = JSplitPane(JSplitPane.VERTICAL_SPLIT)
92 |
93 | self.results_model = DefaultTableModel(None, ["ID", "Method", "URL", "Status", "Length/Error"])
94 | self.results_table = JTable(self.results_model)
95 | results_table_scroll = JScrollPane(self.results_table)
96 |
97 | header = self.results_table.getTableHeader()
98 | header.addMouseListener(TableSorter(self, self.results_table))
99 |
100 | popup_menu = JPopupMenu()
101 | menu_item = JMenuItem("Send to Repeater", actionPerformed=self.onSendToRepeater)
102 | popup_menu.add(menu_item)
103 | self.results_table.setComponentPopupMenu(popup_menu)
104 |
105 | tabs = JTabbedPane()
106 | self.request_viewer = self._callbacks.createMessageEditor(None, False)
107 | self.response_viewer = self._callbacks.createMessageEditor(None, False)
108 | tabs.addTab("Request", self.request_viewer.getComponent())
109 | tabs.addTab("Response", self.response_viewer.getComponent())
110 |
111 | self.results_split_pane.setTopComponent(results_table_scroll)
112 | self.results_split_pane.setBottomComponent(tabs)
113 | self.results_split_pane.setResizeWeight(0.4)
114 |
115 | self.main_split_pane.setLeftComponent(left_panel)
116 | self.main_split_pane.setRightComponent(self.results_split_pane)
117 | self.main_split_pane.setResizeWeight(0.4)
118 |
119 | config_panel = JPanel(BorderLayout())
120 |
121 | button_panel = JPanel(FlowLayout(FlowLayout.LEFT))
122 | self.toggle_listen_button = JToggleButton(
123 | "Listening (On)",
124 | selected=True,
125 | actionPerformed=self.onToggleListen
126 | )
127 | self.send_button = JButton(
128 | "Send Request",
129 | actionPerformed=self.onSendRequestClick
130 | )
131 | self.clear_button = JButton(
132 | "Clear List",
133 | actionPerformed=self.onClearClick
134 | )
135 | button_panel.add(self.toggle_listen_button)
136 | button_panel.add(self.send_button)
137 | button_panel.add(self.clear_button)
138 | config_panel.add(button_panel, BorderLayout.NORTH)
139 |
140 | exclusion_panel = JPanel(BorderLayout())
141 | exclusion_label = JLabel(" Exclude Hosts (comma-separated): ")
142 | self.exclude_hosts_field = JTextField(20)
143 | self.exclude_hosts_field.setText(",".join(self.DEFAULT_HOST_EXCLUSIONS))
144 |
145 | exclusion_panel.add(exclusion_label, BorderLayout.WEST)
146 | exclusion_panel.add(self.exclude_hosts_field, BorderLayout.CENTER)
147 | config_panel.add(exclusion_panel, BorderLayout.SOUTH)
148 |
149 | headers_panel = JPanel(BorderLayout())
150 | headers_label = JLabel(" Custom Headers (one per line, e.g., 'Cookie: value')")
151 | headers_panel.add(headers_label, BorderLayout.NORTH)
152 |
153 | self.custom_headers_area = JTextArea(5, 20)
154 | headers_scroll = JScrollPane(self.custom_headers_area)
155 | headers_panel.add(headers_scroll, BorderLayout.CENTER)
156 |
157 | config_panel.add(headers_panel, BorderLayout.CENTER)
158 |
159 | self._main_panel.add(config_panel, BorderLayout.SOUTH)
160 |
161 | self.results_table.getSelectionModel().addListSelectionListener(self.onResultSelected)
162 |
163 | callbacks.registerHttpListener(self)
164 | callbacks.addSuiteTab(self)
165 |
166 | self.updateTableHeaderText()
167 |
168 | print("URL Replayer Plugin LOADED (V1.3 - Search + Headers)")
169 |
170 | def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
171 |
172 | if not self.is_listening:
173 | return
174 |
175 | if not messageIsRequest and toolFlag == self._callbacks.TOOL_PROXY:
176 |
177 | try:
178 | host = messageInfo.getHttpService().getHost()
179 | exclusion_text = self.exclude_hosts_field.getText()
180 |
181 | if exclusion_text:
182 | excluded_domains = exclusion_text.split(",")
183 | for domain in excluded_domains:
184 | clean_domain = domain.strip()
185 | if clean_domain and host.endswith(clean_domain):
186 | return
187 |
188 | except Exception as e:
189 | print("Error in host exclusion check: " + str(e))
190 | return
191 |
192 | response_bytes = messageInfo.getResponse()
193 | if not response_bytes:
194 | return
195 | response_info = self._helpers.analyzeResponse(response_bytes)
196 | body_bytes = response_bytes[response_info.getBodyOffset():]
197 | try:
198 | body_string = self._helpers.bytesToString(body_bytes)
199 | except Exception as e:
200 | return
201 | source_url = messageInfo.getUrl()
202 | source_request_bytes = messageInfo.getRequest()
203 |
204 | json_parsed = False
205 | try:
206 | data = json.loads(body_string)
207 | if isinstance(data, dict) and "paths" in data and ("swagger" in data or "openapi" in data):
208 | self.parseSwaggerDoc(data, source_url, source_request_bytes)
209 | json_parsed = True
210 | except Exception as e:
211 | pass
212 |
213 | if not json_parsed:
214 | self.parseWithRegex(body_string, source_url, source_request_bytes)
215 |
216 |
217 | def parseSwaggerDoc(self, data, source_url, source_request_bytes):
218 | try:
219 | paths = data.get('paths', {})
220 | for path, methods in paths.items():
221 | if not isinstance(methods, dict):
222 | continue
223 |
224 | for method, details in methods.items():
225 | method_upper = method.upper()
226 | if method_upper not in ["GET", "POST", "PUT", "DELETE"]:
227 | continue
228 |
229 | try:
230 | new_url = URL(source_url, path)
231 | new_url_string = str(new_url).split("?")[0].split("#")[0]
232 |
233 | if not self.url_map.containsKey(new_url_string):
234 | data_to_store = {"request": source_request_bytes, "method": method_upper}
235 | self.url_map.put(new_url_string, data_to_store)
236 | SwingUtilities.invokeLater(
237 | lambda new_url_to_add=new_url_string: self.addUrlToListModel(new_url_to_add)
238 | )
239 | except Exception as e:
240 | pass
241 | except Exception as e:
242 | print("Error parsing Swagger JSON: " + str(e))
243 |
244 |
245 | def parseWithRegex(self, body_string, source_url, source_request_bytes):
246 | try:
247 | potential_paths = self.PATH_REGEX.finditer(body_string)
248 | except Exception as e:
249 | return
250 |
251 | for match in potential_paths:
252 | try:
253 | path = match.group(1).strip()
254 |
255 | if not path or "/" not in path:
256 | continue
257 |
258 | path_lower = path.lower()
259 | is_static = False
260 | for ext in self.IGNORED_EXTENSIONS:
261 | if path_lower.endswith(ext):
262 | is_static = True
263 | break
264 | if is_static:
265 | continue
266 |
267 |
268 | method = "GET"
269 | start_pos = match.start()
270 | end_pos = match.end()
271 |
272 | context_before = body_string[max(0, start_pos - 100) : start_pos].lower()
273 |
274 | pos_post = max(context_before.rfind(".post("), context_before.rfind("\"post\":"))
275 | pos_get = max(context_before.rfind(".get("), context_before.rfind("\"get\":"))
276 | pos_put = max(context_before.rfind(".put("), context_before.rfind("\"put\":"))
277 | pos_delete = max(context_before.rfind(".delete("), context_before.rfind("\"delete\":"))
278 |
279 | positions = {
280 | "POST": pos_post,
281 | "GET": pos_get,
282 | "PUT": pos_put,
283 | "DELETE": pos_delete
284 | }
285 |
286 | found_methods = {m: p for m, p in positions.items() if p != -1}
287 |
288 | if found_methods:
289 | method = max(found_methods, key=found_methods.get)
290 | else:
291 | context_after = body_string[end_pos : min(len(body_string), end_pos + 50)].lower()
292 |
293 | if "method: \"post\"" in context_after or "type: \"post\"" in context_after:
294 | method = "POST"
295 | elif "method: \"put\"" in context_after or "type: \"put\"" in context_after:
296 | method = "PUT"
297 | elif "method: \"delete\"" in context_after or "type: \"delete\"" in context_after:
298 | method = "DELETE"
299 | elif "method: \"get\"" in context_after or "type: \"get\"" in context_after:
300 | method = "GET"
301 |
302 | new_url = URL(source_url, path)
303 | new_url_string = str(new_url).split("?")[0].split("#")[0]
304 |
305 | if not self.url_map.containsKey(new_url_string):
306 | data = {"request": source_request_bytes, "method": method}
307 | self.url_map.put(new_url_string, data)
308 | SwingUtilities.invokeLater(
309 | lambda new_url_to_add=new_url_string: self.addUrlToListModel(new_url_to_add)
310 | )
311 | except Exception as e:
312 | pass
313 |
314 | def addUrlToListModel(self, url_to_add):
315 | try:
316 | search_text = self.search_field.getText().lower()
317 | if search_text in url_to_add.lower():
318 | self.list_model.addElement(url_to_add)
319 | except Exception as e:
320 | print("Error in addUrlToListModel: " + str(e))
321 |
322 | def filterUrlList(self):
323 | try:
324 | search_text = self.search_field.getText().lower()
325 |
326 | selected_items = set(self.url_list.getSelectedValues())
327 |
328 | self.list_model.clear()
329 |
330 | all_urls = sorted(list(self.url_map.keySet()))
331 |
332 | new_selection_indices = []
333 | current_index = 0
334 |
335 | for url in all_urls:
336 | url_str = str(url)
337 | if search_text in url_str.lower():
338 | self.list_model.addElement(url_str)
339 | if url_str in selected_items:
340 | new_selection_indices.append(current_index)
341 | current_index += 1
342 |
343 | self.url_list.setSelectedIndices(new_selection_indices)
344 |
345 | except Exception as e:
346 | print("Error in filterUrlList: " + str(e))
347 |
348 | def updateTableHeaderText(self):
349 | # Visually updates the column header text with an arrow for the sorted column
350 |
351 | default_headers = ["ID", "Method", "URL", "Status", "Length/Error"]
352 |
353 | # Determine the arrow based on sorting direction
354 | arrow = u'\u25b2' if self.sort_order_ascending else u'\u25bc'
355 |
356 | current_identifiers = list(default_headers)
357 |
358 | if self.sorted_column_name in default_headers:
359 | col_index = default_headers.index(self.sorted_column_name)
360 |
361 | # Update the specific column header
362 | new_header_name = u"{} {}".format(self.sorted_column_name, arrow)
363 | current_identifiers[col_index] = new_header_name
364 |
365 | # Reapply all identifiers to update the header text
366 | self.results_model.setColumnIdentifiers(current_identifiers)
367 |
368 |
369 | def sortResults(self, column_index):
370 | # Only sort by 'Length/Error' (index 4)
371 | if column_index != 4:
372 | # If a different column is clicked, reset to default ID sort, but keep the header clean
373 | self.sorted_column_name = self.results_model.getColumnName(0) # 'ID'
374 | self.sort_order_ascending = True
375 | self.updateTableHeaderText()
376 | return
377 |
378 | column_name = self.results_model.getColumnName(column_index)
379 |
380 | # Toggle sort direction if the same column is clicked
381 | if self.sorted_column_name == "Length/Error":
382 | self.sort_order_ascending = not self.sort_order_ascending
383 | else:
384 | self.sort_order_ascending = False # Default to descending (largest length first)
385 |
386 | self.sorted_column_name = "Length/Error"
387 | reverse_sort = not self.sort_order_ascending
388 |
389 | try:
390 | # Step 1: Extract all rows and their sort keys
391 | all_rows_data = []
392 | row_count = self.results_model.getRowCount()
393 |
394 | for i in range(row_count):
395 | length_error_val = self.results_model.getValueAt(i, 4)
396 | try:
397 | sort_key = int(length_error_val)
398 | except ValueError:
399 | sort_key = -1
400 |
401 | row_data = [self.results_model.getValueAt(i, c) for c in range(self.results_model.getColumnCount())]
402 | all_rows_data.append((sort_key, row_data))
403 |
404 | # Step 2: Sort the data
405 | sorted_rows = sorted(all_rows_data, key=lambda x: x[0], reverse=reverse_sort)
406 |
407 | # Step 3: Clear the table and re-add sorted rows
408 | self.results_model.setRowCount(0)
409 | for sort_key, row_data in sorted_rows:
410 | self.results_model.addRow(row_data)
411 |
412 | self.results_table.clearSelection()
413 |
414 | # Step 4: Update the table header text to show the arrow/indicator
415 | self.updateTableHeaderText()
416 |
417 | except Exception as e:
418 | print("Error in sortResults: " + str(e))
419 |
420 | def getTabCaption(self):
421 | return "URL Replayer"
422 |
423 | def getUiComponent(self):
424 | return self._main_panel
425 |
426 | def onToggleListen(self, event):
427 | if self.toggle_listen_button.isSelected():
428 | self.is_listening = True
429 | self.toggle_listen_button.setText("Listening (On)")
430 | print("URL Replayer: Listening STARTED")
431 | else:
432 | self.is_listening = False
433 | self.toggle_listen_button.setText("Stopped (Off)")
434 | print("URL Replayer: Listening STOPPED")
435 |
436 | def onResultSelected(self, event):
437 | if event.getValueIsAdjusting():
438 | return
439 | selected_row = self.results_table.getSelectedRow()
440 | if selected_row != -1:
441 | try:
442 | result_id = int(self.results_model.getValueAt(selected_row, 0))
443 | if result_id < len(self.http_results):
444 | http_message = self.http_results[result_id]
445 | if http_message:
446 | self.request_viewer.setMessage(http_message.getRequest(), True)
447 | self.response_viewer.setMessage(http_message.getResponse(), False)
448 | else:
449 | self.request_viewer.setMessage(None, True)
450 | self.response_viewer.setMessage(None, False)
451 | except Exception as e:
452 | print("Error in onResultSelected: " + str(e))
453 |
454 | def onSendToRepeater(self, event):
455 | selected_row = self.results_table.getSelectedRow()
456 | if selected_row == -1:
457 | return
458 |
459 | try:
460 | result_id = int(self.results_model.getValueAt(selected_row, 0))
461 | if result_id < len(self.http_results):
462 | http_message = self.http_results[result_id]
463 | if http_message:
464 | self._callbacks.sendToRepeater(
465 | http_message.getHttpService().getHost(),
466 | http_message.getHttpService().getPort(),
467 | (http_message.getHttpService().getProtocol() == "https"),
468 | http_message.getRequest(),
469 | "Replayer " + str(result_id)
470 | )
471 | except Exception as e:
472 | print("Error in onSendToRepeater: " + str(e))
473 |
474 | def onSendRequestClick(self, event):
475 | try:
476 | self.results_model.setRowCount(0)
477 | self.http_results = []
478 |
479 | selected_urls_obj = self.url_list.getSelectedValues()
480 |
481 | if not selected_urls_obj:
482 | self.results_model.addRow([0, "N/A", "Error: Please select one or more URLs.", "", ""])
483 | return
484 |
485 | custom_headers_text = self.custom_headers_area.getText()
486 |
487 | for url_obj in selected_urls_obj:
488 | url_str = str(url_obj)
489 | source_data = self.url_map.get(url_str)
490 | if source_data:
491 | method = source_data.get("method")
492 | task = RequestTask(self, url_str, method, custom_headers_text)
493 | Thread(task).start()
494 | else:
495 | print("Error: No source data found for URL: " + url_str)
496 |
497 | except Exception as e:
498 | print("CRITICAL ERROR in onSendRequestClick: %s" % str(e))
499 | try:
500 | self.results_model.addRow([0, "N/A", "CRITICAL ERROR", "See Extender Output", str(e)])
501 | except:
502 | pass
503 |
504 | def onClearClick(self, event):
505 | try:
506 | self.search_field.setText("")
507 | self.url_map.clear()
508 | self.list_model.clear()
509 | self.results_model.setRowCount(0)
510 | self.http_results = []
511 |
512 | self.custom_headers_area.setText("")
513 |
514 | self.sorted_column_name = "ID"
515 | self.sort_order_ascending = True
516 | self.updateTableHeaderText()
517 |
518 | try:
519 | self.request_viewer.setMessage(None, True)
520 | self.response_viewer.setMessage(None, False)
521 | except Exception as e:
522 | pass
523 |
524 | except Exception as e:
525 | print("CRITICAL ERROR in onClearClick: %s" % str(e))
526 |
527 |
528 | class TableSorter(MouseAdapter):
529 | def __init__(self, extender, table):
530 | self.extender = extender
531 | self.table = table
532 |
533 | def mouseClicked(self, e):
534 | try:
535 | header = self.table.getTableHeader()
536 | if e.getSource() == header:
537 | column_index = header.columnAtPoint(e.getPoint())
538 | model_index = self.table.convertColumnIndexToModel(column_index)
539 |
540 | if e.getButton() == MouseEvent.BUTTON1:
541 | SwingUtilities.invokeLater(
542 | lambda index=model_index: self.extender.sortResults(index)
543 | )
544 | except Exception as ex:
545 | print("Error in TableSorter mouseClicked: " + str(ex))
546 |
547 |
548 | class RequestTask(Runnable):
549 |
550 | def __init__(self, extender, url, method, custom_headers_text):
551 | self.extender = extender
552 | self.url = url
553 | self.method = method
554 | self.custom_headers_text = custom_headers_text
555 |
556 | def run(self):
557 | try:
558 | parsed_url = URL(self.url)
559 | host = parsed_url.getHost()
560 | port = parsed_url.getPort()
561 | protocol = parsed_url.getProtocol()
562 | if port == -1:
563 | port = 80 if protocol == "http" else 443
564 | use_https = (protocol == "https")
565 | path = parsed_url.getPath() or "/"
566 |
567 | source_data = self.extender.url_map.get(self.url)
568 | if not source_data:
569 | raise Exception("Source request not found in map for " + self.url)
570 |
571 | source_request_bytes = source_data.get("request")
572 |
573 | source_info = self.extender._helpers.analyzeRequest(source_request_bytes)
574 | source_headers = source_info.getHeaders()
575 |
576 | parsed_custom_headers = []
577 | custom_header_names = set()
578 | if self.custom_headers_text:
579 | for line in self.custom_headers_text.splitlines():
580 | line = line.strip()
581 | if line and ":" in line:
582 | header_name = line.split(":", 1)[0].strip()
583 | custom_header_names.add(header_name.lower())
584 | parsed_custom_headers.append(line)
585 |
586 | new_request_lines = []
587 | request_line = "%s %s HTTP/1.1" % (self.method, path)
588 | new_request_lines.append(request_line)
589 |
590 | for header in source_headers:
591 | if header.lower().startswith("get ") or \
592 | header.lower().startswith("post ") or \
593 | header.lower().startswith("put ") or \
594 | header.lower().startswith("delete ") or \
595 | header.lower().startswith("host:"):
596 | continue
597 |
598 | try:
599 | header_name = header.split(":", 1)[0].strip().lower()
600 | if header_name in custom_header_names:
601 | continue
602 | except Exception as e:
603 | pass
604 |
605 | new_request_lines.append(header)
606 |
607 | new_request_lines.append("Host: %s" % host)
608 |
609 | for custom_header in parsed_custom_headers:
610 | new_request_lines.append(custom_header)
611 |
612 | if self.method == "POST" or self.method == "PUT":
613 | has_custom_cl = False
614 | for h in parsed_custom_headers:
615 | if h.lower().startswith("content-length:"):
616 | has_custom_cl = True
617 | break
618 |
619 | if not has_custom_cl and not any("content-length:" in h.lower() for h in new_request_lines):
620 | new_request_lines.append("Content-Length: 0")
621 |
622 | if not any("connection:" in h.lower() for h in new_request_lines):
623 | new_request_lines.append("Connection: close")
624 |
625 | request_string = "\r\n".join(new_request_lines) + "\r\n\r\n"
626 | request_bytes = self.extender._helpers.stringToBytes(request_string)
627 |
628 | http_service = self.extender._helpers.buildHttpService(host, port, use_https)
629 | response_message = self.extender._callbacks.makeHttpRequest(
630 | http_service,
631 | request_bytes
632 | )
633 |
634 | status_code = -1
635 | length = -1
636 | if response_message and response_message.getResponse():
637 | response_info = self.extender._helpers.analyzeResponse(response_message.getResponse())
638 | status_code = response_info.getStatusCode()
639 | length = len(response_message.getResponse()) - response_info.getBodyOffset()
640 | else:
641 | status_code = 0
642 | length = 0
643 |
644 | result_data = {
645 | "url": self.url,
646 | "method": self.method,
647 | "status": status_code,
648 | "length": length,
649 | "message": response_message
650 | }
651 |
652 | SwingUtilities.invokeLater(UpdateTableTask(self.extender, result_data))
653 |
654 | except Exception as e:
655 | error_data = {
656 | "url": self.url,
657 | "method": self.method,
658 | "status": -1,
659 | "length": -1,
660 | "message": None,
661 | "error": str(e)
662 | }
663 | SwingUtilities.invokeLater(UpdateTableTask(self.extender, error_data))
664 |
665 |
666 | class UpdateTableTask(Runnable):
667 | def __init__(self, extender, data):
668 | self.extender = extender
669 | self.data = data
670 |
671 | def run(self):
672 | try:
673 | new_id = len(self.extender.http_results)
674 |
675 | self.extender.http_results.append(self.data.get("message"))
676 |
677 | if "error" in self.data:
678 | self.extender.results_model.addRow([
679 | new_id,
680 | self.data.get("method"),
681 | self.data["url"],
682 | "ERROR",
683 | self.data["error"]
684 | ])
685 | else:
686 | self.extender.results_model.addRow([
687 | new_id,
688 | self.data.get("method"),
689 | self.data["url"],
690 | self.data["status"],
691 | self.data["length"]
692 | ])
693 | except Exception as e:
694 | print("CRITICAL ERROR in UpdateTableTask.run: %s" % str(e))
--------------------------------------------------------------------------------