├── 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)) --------------------------------------------------------------------------------