├── LICENSE
├── README.md
└── swagger_parser.py
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Trendyol
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwaggerParser-BurpExtension
2 |
3 | With this extension, you can parse Swagger Documents. You can view the parsed requests in the table and send them to Repeater, Intruder, Scanner.
4 |
5 | ## How to use
6 |
7 | **1- Extension written in Python. That's why he works with Jython. We need to add the Jython jar file to Burp.**
8 |
9 | 
10 |
11 |
12 | **2- After adding Jython to Burp, we can also add the Extension to Burp with the Extension's python file.**
13 |
14 | 
15 |
16 |
17 | **3- If the extension has been installed successfully, the "Swagger Parser" tab will be added. You can see the extension screen by clicking this tab.**
18 |
19 |
20 |
21 | **Add New Swagger Document Panel:** This is the part where new Swagger Documents are added and edited.
22 |
23 |
24 |
25 |
26 | **Request Detail Panel:** This is the section where the details of the parsed requests are displayed.
27 |
28 |
29 |
30 |
31 | **Custom Headers Panel:** Headers written below in this panel are added to all requests while parsing.
32 |
33 | 
34 |
35 |
36 | **Output Panel:** After the parse process is completed, all endpoints are listed in Markdown format.
37 |
38 | 
39 |
40 |
41 | **Request History Panel:** After the parse process is completed, the requests are listed in the table and can be sent to the Repeater, Intruder, Scanner.
42 |
43 | 
44 |
45 |
46 | **4- We right-click on the Swagger Document request we want to parse and select the "Send to Swagger Parser" option and the parsing process begins.**
47 |
48 | 
49 |
--------------------------------------------------------------------------------
/swagger_parser.py:
--------------------------------------------------------------------------------
1 | from burp import IBurpExtender
2 | from burp import IProxyListener
3 | from burp import IContextMenuFactory
4 | from java.util import ArrayList
5 | from javax.swing import JMenuItem
6 | from java.awt.event import MouseAdapter, MouseEvent
7 | from javax.swing import (JTabbedPane, DefaultComboBoxModel, BoxLayout,GroupLayout, JPanel, JComboBox, JCheckBox, JTextField, JTextArea, JLabel, JButton, JScrollPane, JTable, JPopupMenu, JTextPane, JFrame)
8 | from java.awt import (Insets, BorderLayout, GridBagLayout, GridBagConstraints, Dimension, Toolkit, FlowLayout, GridLayout)
9 | from javax import swing
10 | from burp import ITab
11 | from javax.swing.table import (DefaultTableModel)
12 | import javax.swing.KeyStroke as KeyStroke
13 | import java.awt.event.KeyEvent as KeyEvent
14 | import javax.swing.AbstractAction as AbstractAction
15 | import java.awt.event.ComponentAdapter as ComponentAdapter
16 |
17 | import threading
18 | import json
19 | import re
20 | import random
21 | import string
22 |
23 | class SwaggerParser:
24 | def __init__(self, swagger_url, headers):
25 | self.swagger_url = swagger_url
26 | self.headers = headers
27 |
28 |
29 | def urlEncodingForEnum(self, param):
30 | return str(param).replace(" ","%20").replace(":", "%3A").replace("=","%3D").replace(",","%2C").replace(";","%3B")
31 |
32 | def randomValueGenerator(self, _param_name, _param_type, _schema):
33 |
34 | try:
35 | _temp_value = None
36 | _enum = _schema.get("enum")
37 | _default = _schema.get("default")
38 | _items = _schema.get("items")
39 | _format = _schema.get("format")
40 |
41 | if _param_type is None:
42 | _param_type = _schema.get("type")
43 |
44 | if _param_type == "string":
45 |
46 | _temp_value = ''.join(random.sample(string.ascii_lowercase, 8))
47 |
48 | if _default is not None:
49 |
50 | if str(_default).strip() == "":
51 | _default = "xxx"
52 |
53 | _temp_value = _default
54 | elif _enum is not None and type(_enum) == list and len(_enum) > 0:
55 | _temp_value = self.urlEncodingForEnum(_enum[0])
56 | elif _format is not None:
57 | if _format in ["date", "date-time"]:
58 | _temp_value = "01-01-2023"
59 |
60 | elif _param_type == "integer":
61 |
62 | _temp_value = random.randrange(100)
63 | elif _param_type == "number":
64 |
65 | _temp_value = random.randrange(100)
66 | elif _param_type == "boolean":
67 |
68 | _temp_value = True
69 | elif _param_type == "file":
70 |
71 | _temp_value = "RandomStringInput"#''.join(random.choices(string.ascii_lowercase, k=5))
72 | elif _param_type == "array":
73 |
74 | return [self.randomValueGenerator(None, None, _items)]
75 | elif len(_schema.keys()) > 0:
76 |
77 | _temp_obj = {}
78 | for _item_key in _schema:
79 | _temp_obj[_item_key] = self.randomValueGenerator(None, None, _schema.get(_item_key))
80 | return _temp_obj
81 | else:
82 | "" #for debugging
83 |
84 | if _param_name is not None:
85 | return {_param_name: _temp_value}
86 | else:
87 | return _temp_value
88 |
89 | except Exception as e:
90 | print("randomValueGenerator error")
91 | print(e)
92 |
93 | def refObjectParser(self, _swagger_obj, _ref_value):
94 |
95 | _request_body_parameters_obj = _swagger_obj
96 |
97 | if "/" in _ref_value:
98 | _splitted_ref = str(_ref_value).split("/")
99 | for _ref_path in _splitted_ref:
100 | if _ref_path != "#" and _ref_path != "":
101 | if _ref_path in _request_body_parameters_obj:
102 |
103 | _request_body_parameters_obj = _request_body_parameters_obj.get(_ref_path)
104 |
105 | if "properties" in _request_body_parameters_obj.keys() and _request_body_parameters_obj.get(
106 | "type") == "object":
107 | _request_body_parameters_obj = _request_body_parameters_obj["properties"]
108 |
109 | else:
110 | return {}
111 |
112 | return _request_body_parameters_obj
113 |
114 | def findAndParseAllRefs(self, _swagger_obj, _constat_swagger_obj):
115 |
116 | if type(_swagger_obj) == dict:
117 | for _key in _swagger_obj.keys():
118 | if _key == "$ref":
119 | _temp_parsed_ref_obj = self.refObjectParser(_constat_swagger_obj, _swagger_obj.get(_key))
120 |
121 | for _temp_key in _temp_parsed_ref_obj:
122 | if _temp_key == "-":
123 | continue
124 | _swagger_obj[_temp_key] = _temp_parsed_ref_obj.get(_temp_key)
125 | _swagger_obj.pop(_key)
126 | break
127 |
128 | self.findAndParseAllRefs(_swagger_obj.get(_key), _constat_swagger_obj)
129 | elif type(_swagger_obj) == list:
130 | for _item in _swagger_obj:
131 | self.findAndParseAllRefs(_item, _constat_swagger_obj)
132 | else:
133 | "" #for debugging
134 |
135 | return _swagger_obj
136 |
137 | _all_keys = {}
138 |
139 | def generateRequest(self, _path, _method, _request_obj, _output_obj):
140 |
141 | if type(_request_obj) == dict:
142 | for _key in _request_obj.keys():
143 | self._all_keys[_key] = {}
144 | _temp_key = _key
145 |
146 | _schema = _request_obj.get("schema")
147 | _value = _request_obj.get(_temp_key)
148 | _name = _request_obj.get("name")
149 | _type = _request_obj.get("type")
150 | _enum = _request_obj.get("enum")
151 |
152 | if _temp_key in ["responses"]:
153 | continue
154 |
155 | if _temp_key == "requestBody":
156 | _value = "body"
157 | _temp_key = "in"
158 |
159 | if _request_obj.get("requestBody").get("content").get("application/json") is not None:
160 | _schema = _request_obj.get("requestBody").get("content").get("application/json").get("schema")
161 | elif _request_obj.get("requestBody").get("content").get("multipart/form-data") is not None:
162 | _schema = _request_obj.get("requestBody").get("content").get("multipart/form-data").get("schema")
163 |
164 | if _schema is None:
165 | _schema = _request_obj
166 |
167 | if _temp_key == "in":
168 |
169 | _new_value = self.randomValueGenerator(_name, _type, _schema)
170 |
171 | if _value == "path":
172 |
173 | if _output_obj.get("path") is not None:
174 | _path = _output_obj.get("path")
175 |
176 | _temp_value = None
177 |
178 | if type(_new_value) == dict:
179 | _temp_value = str(_new_value.get(_name))
180 | elif type(_new_value) == list:
181 | _temp_value = ",".join(str(v) for v in _new_value)
182 | else:
183 | _temp_value = str(_new_value)
184 |
185 | _path = str(_path).replace("{" + _name + "}", _temp_value)
186 |
187 | _output_obj["path"] = _path
188 |
189 | elif _value == "query":
190 |
191 | if _output_obj.get("query_string") is None:
192 | _output_obj["query_string"] = {}
193 |
194 | if type(_new_value) == dict:
195 | _temp_value = _new_value.get(_name)
196 |
197 | for _query_key in _new_value.keys():
198 | _output_obj["query_string"][_query_key] = _new_value.get(_query_key)
199 |
200 | else:
201 | _output_obj["query_string"][_name] = _new_value
202 |
203 |
204 | elif _value == "header":
205 |
206 | if _output_obj.get("header") is None:
207 | _output_obj["header"] = {}
208 |
209 | _output_obj["header"][_name] = str(_new_value.get(_name))
210 |
211 | elif _value == "body":
212 |
213 | _output_obj["body"] = _new_value
214 |
215 | if _output_obj.get("header") is None:
216 | _output_obj["header"] = {}
217 |
218 | _output_obj["header"]["Content-Type"] = "application/json"
219 |
220 | elif _value == "formData":
221 |
222 | #application/x-www-form-urlencoded or multipart/form-data
223 |
224 | _output_obj["formData"] = _new_value
225 |
226 | if _output_obj.get("header") is None:
227 | _output_obj["header"] = {}
228 |
229 | _output_obj["header"]["Content-Type"] = "application/x-www-form-urlencoded"
230 |
231 | #todo
232 |
233 | if type(_request_obj.get(_key)) == str:
234 | continue
235 | self.generateRequest(_path, _method, _request_obj.get(_key), _output_obj)
236 | elif type(_request_obj) == list:
237 | for _item in _request_obj:
238 | if type(_item) == str:
239 | continue
240 | self.generateRequest(_path, _method, _item, _output_obj)
241 | else:
242 | "" #for debugging
243 |
244 | if _output_obj.get("path") is None:
245 | _output_obj["path"] = _path
246 |
247 | _output_obj["method"] = _method
248 |
249 | return _output_obj
250 |
251 |
252 | def parseResponse(self, _url, _response):
253 | global SERVICE_URL
254 |
255 | _swagger_raw_json = ""
256 | _swagger_json_object = {}
257 |
258 | if "/swagger/swagger-ui-init.js" in _url or "/docs/swagger-ui-init.js" in _url:
259 | _search_result = re.search("[var|let] options = (.*?);", str(_response).replace("\n",""))
260 |
261 | if _search_result and len(_search_result.groups()) > 0:
262 | _swagger_raw_json = _search_result.groups()[0]
263 | _swagger_raw_json = _swagger_raw_json.replace("\n", "").replace(" ", "").replace("\\n", "")
264 | _temp_json_obj = json.loads(_swagger_raw_json)
265 | if "swaggerDoc" in _temp_json_obj.keys():
266 | _swagger_json_object = _temp_json_obj["swaggerDoc"]
267 | else:
268 | _swagger_json_object = json.loads(_response)
269 |
270 | _parsed_swagger_json_object = self.findAndParseAllRefs(_swagger_json_object, _swagger_json_object)
271 |
272 | _total_path = 0
273 | _endpoints = []
274 | _markup_endpoints = []
275 |
276 | _root_url = self.getRootUrl(self.swagger_url)
277 |
278 | for _path in _swagger_json_object["paths"].keys():
279 | _path_obj = _swagger_json_object["paths"][_path]
280 |
281 | if len(_path_obj.keys()) > 0:
282 | for _method in _path_obj.keys():
283 | _method_obj = _path_obj[_method]
284 |
285 | _temp_tag = "default"
286 |
287 | if "tags" in _method_obj.keys():
288 | _temp_tag = _method_obj["tags"][0]
289 |
290 |
291 |
292 | _all_root_keys = list(_method_obj.keys())
293 |
294 | for _key in _all_root_keys:
295 | if _key not in ["parameters", "requestBody"]:
296 | _method_obj.pop(_key)
297 |
298 | _request_obj = self.generateRequest(_path, _method, _method_obj, {})
299 |
300 | _request_obj["raw_path"] = _path
301 |
302 | _markup_endpoints.append("- [ ] " + str(_method).upper() + " " + _path)
303 | _endpoints.append(_request_obj)
304 |
305 |
306 | _total_path += len(_swagger_json_object["paths"][_path].keys())
307 |
308 | print("Total endpoint: " + str(_total_path))
309 |
310 | basePath = ""
311 |
312 | if _swagger_json_object.get("basePath") != None:
313 | basePath = _swagger_json_object.get("basePath")
314 |
315 | return {"endpoints": _endpoints, "base_path": basePath, "markup_endpoints": _markup_endpoints}
316 |
317 |
318 | def generateQueryString(self, param_obj):
319 | param_obj = dict(param_obj)
320 | temp_query_string = ""
321 |
322 | for _key in param_obj.keys():
323 |
324 | _value = param_obj.get(_key)
325 |
326 | if type(_value) == list:
327 | _value = json.dumps(_value)
328 | else:
329 | _value = str(_value)
330 |
331 | temp_query_string += str(_key) + "=" + _value + "&"
332 |
333 |
334 | if temp_query_string.endswith("&"):
335 | temp_query_string = temp_query_string.strip("&")
336 |
337 | return temp_query_string
338 |
339 | def bytesToString(self, _bytes):
340 |
341 | char_arr = []
342 |
343 | for b in _bytes:
344 | print(b)
345 | if b < 257 and b > -1:
346 | char_arr.append(chr(b))
347 |
348 | return "".join(char_arr)
349 |
350 | def getRootUrl(self, _url):
351 |
352 | _protocol = "https://"
353 |
354 | if str(_url).startswith("https://"):
355 | _protocol = "https://"
356 | elif str(_url).startswith("http://"):
357 | _protocol = "http://"
358 |
359 |
360 | _url = str(_url).replace(_protocol, "")
361 |
362 | if "/" in _url:
363 | _url = str(_url).split("/")[0]
364 |
365 | return _protocol + _url
366 |
367 |
368 | main_panel = None
369 | header_text_editor = None
370 | request_detail_text_editor = None
371 | output_text_editor = None
372 | history_table = None
373 | popup_menu = None
374 | extracted_requests = []
375 | output_scroll_pane = None
376 | parsable_docs_combobox = None
377 | parsable_docs = {}
378 | popup_frame = None
379 | remove_confirmation_popup_frame = None
380 | global_parent_self = None
381 | tabbedPane = None
382 | tabbedPane2 = None
383 |
384 |
385 | def isValidSwaggerDoc(doc):
386 | doc = str(doc)
387 | return (doc.startswith("http://") or doc.startswith("https://")) and (
388 | doc.endswith("json") or "api-docs" in doc or doc.endswith("swagger-ui-init.js"))
389 |
390 | class MenuClickListener(MouseAdapter):
391 | def __init__(self, extender, invocation):
392 | self._extender = extender
393 | self._invocation = invocation
394 |
395 | def mouseReleased(self, e):
396 | self._extender.menuItemClicked(self._invocation)
397 |
398 | last_table_selections = []
399 |
400 |
401 | class MoveAction(AbstractAction):
402 | def __init__(self, table, direction):
403 | self.table = table
404 | self.direction = direction
405 |
406 | def actionPerformed(self, e):
407 | row = self.table.getSelectedRow() + self.direction
408 |
409 | if row >= 0 and row < self.table.getRowCount():
410 | self.table.setRowSelectionInterval(row, row)
411 |
412 | request_item = extracted_requests[row]
413 |
414 | http_request = request_item["http_request"]
415 |
416 | request_detail_text_editor.setText(global_parent_self._helpers.bytesToString(http_request))
417 |
418 | tabbedPane.setSelectedIndex(1)
419 |
420 |
421 | class TableMenuClickListener(MouseAdapter):
422 | def __init__(self, extender, invocation):
423 | self._extender = extender
424 | self._invocation = invocation
425 |
426 | def mouseReleased(self, e):
427 | global popup_menu
428 | global history_table
429 | global header_text_editor
430 | global last_table_selections
431 | global extracted_requests
432 | global global_parent_self
433 | global request_detail_text_editor
434 | global tabbedPane
435 |
436 | if e.getButton() == MouseEvent.BUTTON3:
437 | popup_menu.show(e.getComponent(), e.getX(), e.getY())
438 |
439 | if e.getButton() == MouseEvent.BUTTON1:
440 | current_selections = list(history_table.getSelectedRows())
441 |
442 | temp_selection = list(set(current_selections) - set(last_table_selections))
443 | single_selection = -1
444 |
445 | if len(temp_selection) > 0:
446 | single_selection = temp_selection[0]
447 |
448 | if single_selection != -1:
449 |
450 | request_item = extracted_requests[single_selection]
451 |
452 | http_request = request_item["http_request"]
453 |
454 | request_detail_text_editor.setText(global_parent_self._helpers.bytesToString(http_request))
455 |
456 | tabbedPane.setSelectedIndex(1)
457 |
458 | last_table_selections = current_selections
459 |
460 |
461 |
462 | class RemoveConfirmationPopup(swing.JPanel):
463 | parent_self = None
464 |
465 | def __init__(self, remove_all, parent):
466 | super(RemoveConfirmationPopup, self).__init__()
467 |
468 | self.parent_self = parent
469 | popup_title = "Are you sure you want to remove Selected Items?"
470 |
471 | if remove_all:
472 | popup_title = "Are you sure you want to remove All Items?"
473 |
474 | layout = GridBagLayout()
475 | self.setLayout(layout)
476 | gbc = GridBagConstraints()
477 |
478 | label = swing.JLabel(popup_title)
479 | gbc.gridx = 0
480 | gbc.gridy = 0
481 | gbc.gridwidth = 2
482 | gbc.insets = Insets(5, 5, 5, 5)
483 | self.add(label, gbc)
484 |
485 | blank_panel = swing.JPanel()
486 | blank_panel.setPreferredSize(Dimension(1, 100))
487 | gbc.gridx = 0
488 | gbc.gridy = 1
489 | gbc.gridwidth = 2
490 | self.add(blank_panel, gbc)
491 |
492 | button_panel = swing.JPanel()
493 | button_panel.setLayout(GridLayout(1, 2, 10, 0))
494 |
495 | no_button = swing.JButton("No", actionPerformed=self.close_popup)
496 | no_button.setPreferredSize(Dimension(60, 25))
497 | button_panel.add(no_button)
498 |
499 | if remove_all:
500 | yes_button = swing.JButton("Yes", actionPerformed=self.confirm_all_removal)
501 | yes_button.setPreferredSize(Dimension(60, 25))
502 | button_panel.add(yes_button)
503 | else:
504 | yes_button = swing.JButton("Yes", actionPerformed=self.confirm_removal)
505 | yes_button.setPreferredSize(Dimension(60, 25))
506 | button_panel.add(yes_button)
507 |
508 |
509 |
510 | gbc.gridx = 0
511 | gbc.gridy = 2
512 | gbc.gridwidth = 2
513 | gbc.insets = Insets(0, 0, 10, 0)
514 | self.add(button_panel, gbc)
515 |
516 | def close_popup(self, event):
517 | frame = swing.SwingUtilities.getWindowAncestor(self)
518 | frame.dispose()
519 |
520 | def confirm_all_removal(self, event):
521 | self.parent_self.clearTable(event)
522 | self.close_popup(None)
523 |
524 | def confirm_removal(self, event):
525 | self.parent_self.removeSelectedItems(event)
526 | self.close_popup(None)
527 |
528 |
529 | class ResizeListener(ComponentAdapter):
530 | def componentResized(self, e):
531 |
532 | if main_panel is not None:
533 | screen_size = main_panel.getSize()
534 | screen_height = screen_size.height
535 | screen_width = screen_size.width
536 |
537 | output_components_max_height = int(screen_height / 3)
538 | output_components_max_width = int(screen_width / 2)
539 |
540 | tabbedPane.setMinimumSize(
541 | Dimension(output_components_max_width - 10, output_components_max_height))
542 |
543 | tabbedPane.setMaximumSize(
544 | Dimension(output_components_max_width - 10, output_components_max_height))
545 |
546 | tabbedPane2.setMinimumSize(
547 | Dimension(output_components_max_width - 10, output_components_max_height - 2))
548 |
549 | tabbedPane2.setMaximumSize(
550 | Dimension(output_components_max_width - 10, output_components_max_height - 2))
551 |
552 |
553 | class SwaggerParserTab(ITab):
554 | parent_self = None
555 |
556 | def __init__(self, callbacks, parent):
557 | global global_parent_self
558 |
559 | self._callbacks = callbacks
560 | self.parent_self = parent
561 | global_parent_self = parent
562 |
563 | class NonEditableTableModel(DefaultTableModel):
564 | def isCellEditable(self, row, column):
565 | return False
566 |
567 | def getTabCaption(self):
568 | return "Swagger Parser"
569 |
570 | def stringToBytes(self, text, encoding='utf-8'):
571 | return text.encode(encoding)
572 |
573 | def bytesToString(self, _bytes):
574 |
575 | char_arr = []
576 |
577 | for b in _bytes:
578 | if b < 257 and b > -1:
579 | char_arr.append(chr(b))
580 |
581 | return "".join(char_arr)
582 |
583 | def sendHttpRequest(self, url):
584 | global parsable_docs
585 |
586 | url = str(url)
587 |
588 | protocol = "https"
589 | port = 443
590 |
591 | if not url.startswith(protocol):
592 | protocol = "http"
593 | port = 80
594 |
595 | hostname = url.replace(protocol + "://", "").split("/")[0]
596 |
597 | if ":" in hostname:
598 | temp_hostname_split = hostname.split(":")
599 | hostname = temp_hostname_split[0]
600 | port = int(temp_hostname_split[1])
601 |
602 |
603 | if port in [80, 443]:
604 | temp_url_1 = url.replace("://" + hostname + "/", "://" + hostname + ":" + str(port) + "/")
605 | temp_url_2 = url.replace("://" + hostname + ":" + str(port) + "/", "://" + hostname + "/")
606 |
607 | if temp_url_1 != temp_url_2 and temp_url_1 in list(parsable_docs.keys()) and temp_url_2 in list(parsable_docs.keys()):
608 | del parsable_docs[temp_url_1]
609 | url = temp_url_2
610 |
611 |
612 |
613 | swagger_doc_path = url.replace(protocol + "://" + hostname, "")
614 |
615 | http_service = self.parent_self._helpers.buildHttpService(hostname, port, protocol)
616 |
617 | request = "GET " + swagger_doc_path + " HTTP/2\r\nHost: " + hostname + "\r\n\r\n"
618 |
619 |
620 | def make_request():
621 | global parsable_docs
622 |
623 | response = self.parent_self._callbacks.makeHttpRequest(http_service, request.encode())
624 |
625 | ref_url = str(response.getUrl())
626 |
627 | parsable_docs[ref_url] = response
628 |
629 |
630 | thread = threading.Thread(target=make_request)
631 | thread.start()
632 |
633 | return
634 |
635 | def add_component(self, component, gridx, gridy, anchor):
636 | gbc = GridBagConstraints()
637 | gbc.gridx = gridx
638 | gbc.gridy = gridy
639 | gbc.anchor = anchor
640 | self.right_panel.add(component, gbc)
641 |
642 |
643 | def openRemoveConfirmationPopup(self, event, remove_all):
644 | global remove_confirmation_popup_frame
645 |
646 | if remove_confirmation_popup_frame is not None:
647 | remove_confirmation_popup_frame.dispose()
648 |
649 | frame_width = 300
650 |
651 | if not remove_all:
652 | frame_width = 320
653 |
654 | remove_confirmation_popup_frame = JFrame("Confirm", size=(frame_width, 125))
655 | remove_confirmation_popup_frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE)
656 | remove_confirmation_popup_frame.setLayout(BorderLayout())
657 | remove_confirmation_popup_frame.setResizable(False)
658 |
659 | remove_confirmation_popup = RemoveConfirmationPopup(remove_all, self)
660 | remove_confirmation_popup_frame.add(remove_confirmation_popup)
661 |
662 | remove_confirmation_popup_frame.setLocationRelativeTo(None)
663 | remove_confirmation_popup_frame.setVisible(True)
664 |
665 | def addNewUrl(self, event):
666 | global popup_frame
667 |
668 | if popup_frame is not None:
669 | popup_frame.dispose()
670 |
671 | popup_frame = JFrame("Add New Swagger Document", size=(612, 300))
672 | popup_frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE)
673 | popup_frame.setLayout(BorderLayout())
674 | popup_frame.setResizable(False)
675 |
676 | self.main_panel = JPanel()
677 | self.main_panel.setLayout(BorderLayout())
678 | self.main_panel.setPreferredSize(Dimension(608, 300))
679 |
680 | self.left_panel = JPanel()
681 | self.left_panel.setLayout(BorderLayout())
682 |
683 | self.text_field = JTextField(20)
684 | self.text_field.setPreferredSize(Dimension(500, 25))
685 |
686 | self.table_model = self.NonEditableTableModel()
687 | self.table_model.addColumn("URL")
688 |
689 | temp_combobox_items = self.getComboboxItems()
690 |
691 | if len(temp_combobox_items) > 0:
692 | for item in temp_combobox_items:
693 | self.table_model.addRow([item])
694 |
695 | self.table = JTable(self.table_model)
696 | self.table.setPreferredScrollableViewportSize(Dimension(500, 150))
697 | self.table.setTableHeader(None)
698 |
699 | self.scroll_pane = JScrollPane(self.table)
700 |
701 | self.left_panel.add(self.text_field, BorderLayout.NORTH)
702 | self.left_panel.add(self.scroll_pane, BorderLayout.CENTER)
703 |
704 | self.right_panel = JPanel()
705 | self.right_panel.setPreferredSize(Dimension(110, 300))
706 | self.right_panel.setLayout(GridBagLayout())
707 |
708 | self.button1 = JButton("Add")
709 | self.button2 = JButton("Remove")
710 | self.button3 = JButton("Remove All")
711 |
712 | self.empty_panel = JPanel()
713 | self.empty_panel.setPreferredSize(Dimension(10, 176))
714 |
715 | button_size = Dimension(100, 25)
716 | self.button1.setPreferredSize(button_size)
717 | self.button2.setPreferredSize(button_size)
718 | self.button3.setPreferredSize(button_size)
719 |
720 | gbc = GridBagConstraints()
721 | gbc.fill = GridBagConstraints.HORIZONTAL
722 | gbc.anchor = GridBagConstraints.NORTHWEST
723 |
724 | gbc.insets = Insets(2, 0, 2, 0)
725 | gbc.gridx = 0
726 | gbc.gridy = 0
727 | self.right_panel.add(self.button1, gbc)
728 |
729 | gbc.gridy = 1
730 | self.right_panel.add(self.button2, gbc)
731 |
732 | gbc.gridy = 2
733 | self.right_panel.add(self.button3, gbc)
734 |
735 | gbc.gridy = 3
736 | self.right_panel.add(self.empty_panel, gbc)
737 |
738 | self.button1.addActionListener(self.addUrlToTable)
739 | self.button2.addActionListener(lambda event: self.openRemoveConfirmationPopup(event, False))
740 | self.button3.addActionListener(lambda event: self.openRemoveConfirmationPopup(event, True))
741 |
742 | self.main_panel.add(self.left_panel, BorderLayout.WEST)
743 | self.main_panel.add(self.right_panel, BorderLayout.EAST)
744 |
745 | popup_frame.add(self.main_panel, BorderLayout.CENTER)
746 |
747 | popup_frame.setLocationRelativeTo(None)
748 | popup_frame.setVisible(True)
749 |
750 |
751 | def syncTables(self):
752 | global parsable_docs
753 | global parsable_docs_combobox
754 |
755 | model = self.table.getModel()
756 | model.setRowCount(0)
757 |
758 | parsable_docs_combobox.setModel(DefaultComboBoxModel([]))
759 |
760 | for doc_item in sorted(list(dict(parsable_docs).keys())):
761 | model.addRow([doc_item])
762 | parsable_docs_combobox.addItem(doc_item)
763 |
764 | def addToParcableDocsDict(self, new_item):
765 | global parsable_docs
766 |
767 | if new_item not in list(dict(parsable_docs).keys()):
768 | parsable_docs[new_item] = "" #TODO request will be generate
769 |
770 | self.sendHttpRequest(new_item)
771 |
772 | def addUrlToTable(self, event):
773 | global parsable_docs
774 |
775 | text = str(self.text_field.getText())
776 |
777 | if not isValidSwaggerDoc(text):
778 | return
779 |
780 | self.addToParcableDocsDict(text)
781 | self.syncTables()
782 |
783 | self.text_field.setText("")
784 | self.text_field.requestFocus()
785 |
786 | def removeSelectedItems(self, event):
787 | global parsable_docs
788 |
789 | selected_rows = list(self.table.getSelectedRows())
790 | table_model = self.table.getModel()
791 | table_changed = False
792 |
793 | for row in selected_rows:
794 | value = table_model.getValueAt(row, 0)
795 | if value in parsable_docs.keys():
796 | del parsable_docs[value]
797 | table_changed = True
798 |
799 |
800 | if table_changed:
801 | self.syncTables()
802 |
803 |
804 |
805 | def clearTable(self, event):
806 | global parsable_docs
807 | parsable_docs = {}
808 | self.syncTables()
809 | self.text_field.requestFocus()
810 |
811 |
812 | def getComboboxItems(self):
813 | global parsable_docs
814 | return sorted(list(dict(parsable_docs).keys()))
815 |
816 | def getSelectedComboboxItem(self, event):
817 | global parsable_docs_combobox
818 | global parsable_docs
819 |
820 | selected_item = parsable_docs_combobox.getSelectedItem()
821 |
822 | if selected_item != None:
823 | self.parent_self.startParseFromUI(parsable_docs[str(selected_item)])
824 |
825 | def getUiComponent(self):
826 | global header_text_editor
827 | global output_text_editor
828 | global history_table
829 | global output_scroll_pane
830 | global parsable_docs_combobox
831 | global request_detail_text_editor
832 | global tabbedPane
833 | global tabbedPane2
834 | global main_panel
835 |
836 | main_panel = JPanel()
837 | main_panel.addComponentListener(ResizeListener())
838 | layout = GroupLayout(main_panel)
839 | main_panel.setLayout(layout)
840 | layout.setAutoCreateGaps(True)
841 | layout.setAutoCreateContainerGaps(True)
842 |
843 | header_label = JLabel("Swagger Docs: ")
844 | header_text_editor = JTextPane()
845 | header_text_editor.setText("X-Forwarded-For: 127.0.0.1\nAuthorization: Bearer [TOKEN]")
846 | header_scroll_pane = JScrollPane(header_text_editor)
847 |
848 | request_detail_text_editor = JTextPane()
849 | request_detail_text_editor.setText("")
850 | request_detail_scroll_pane = JScrollPane(request_detail_text_editor)
851 |
852 |
853 |
854 | tabbedPane = JTabbedPane()
855 |
856 | tabbedPane.addTab("Custom Headers", header_scroll_pane)
857 | tabbedPane.addTab("Request Detail", request_detail_scroll_pane)
858 |
859 |
860 |
861 | output_label = JLabel("")
862 | output_text_editor = JTextPane()
863 | output_scroll_pane = JScrollPane(output_text_editor)
864 |
865 | tabbedPane2 = JTabbedPane()
866 |
867 | tabbedPane2.addTab("Output", output_scroll_pane)
868 |
869 | table_model = self.NonEditableTableModel()
870 | table_model.addColumn("Method")
871 | table_model.addColumn("URL")
872 | table_model.addColumn("Status Code")
873 | table_model.addColumn("Length")
874 |
875 | parsable_docs_combobox = JComboBox([])
876 | add_button = JButton("Start Parsing", actionPerformed=self.getSelectedComboboxItem)
877 | add_button2 = JButton("Add New Doc", actionPerformed=self.addNewUrl)
878 |
879 | history_table = JTable(table_model)
880 |
881 | column_model = history_table.getColumnModel()
882 | column_model.getColumn(0).setMinWidth(80)
883 | column_model.getColumn(0).setMaxWidth(80)
884 | column_model.getColumn(2).setMinWidth(120)
885 | column_model.getColumn(2).setMaxWidth(120)
886 | column_model.getColumn(3).setMinWidth(80)
887 | column_model.getColumn(3).setMaxWidth(80)
888 |
889 | history_scroll_pane = JScrollPane(history_table)
890 |
891 | history_table.addMouseListener(TableMenuClickListener(self, history_table))
892 |
893 | input_map = history_table.getInputMap()
894 | action_map = history_table.getActionMap()
895 |
896 | input_map.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "Up")
897 | action_map.put("Up", MoveAction(history_table, -1))
898 |
899 | input_map.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "Down")
900 | action_map.put("Down", MoveAction(history_table, 1))
901 |
902 | screen_size = Toolkit.getDefaultToolkit().getScreenSize()
903 | screen_height = screen_size.getHeight()
904 | screen_width = screen_size.getWidth()
905 |
906 | output_components_max_height = int(screen_height / 3)
907 | output_components_max_width = int(screen_width / 2)
908 |
909 | tabbedPane.setMinimumSize(
910 | Dimension(output_components_max_width - 10, output_components_max_height))
911 |
912 | tabbedPane.setMaximumSize(
913 | Dimension(output_components_max_width - 10, output_components_max_height))
914 |
915 | tabbedPane2.setMinimumSize(
916 | Dimension(output_components_max_width - 10, output_components_max_height - 2))
917 |
918 | tabbedPane2.setMaximumSize(
919 | Dimension(output_components_max_width - 10, output_components_max_height - 2))
920 |
921 |
922 | layout.setHorizontalGroup(
923 | layout.createParallelGroup()
924 | .addGroup(layout.createSequentialGroup()
925 | .addComponent(header_label)
926 | .addComponent(parsable_docs_combobox)
927 | .addComponent(add_button)
928 | .addComponent(add_button2)
929 | .addComponent(output_label))
930 | .addGroup(layout.createSequentialGroup()
931 | .addComponent(tabbedPane)
932 | .addComponent(tabbedPane2))
933 | .addComponent(history_scroll_pane)
934 | )
935 |
936 | layout.setVerticalGroup(
937 | layout.createSequentialGroup()
938 | .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
939 | .addComponent(header_label)
940 | .addComponent(parsable_docs_combobox)
941 | .addComponent(add_button)
942 | .addComponent(add_button2)
943 | .addComponent(output_label))
944 | .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
945 | .addComponent(tabbedPane)
946 | .addComponent(tabbedPane2))
947 | .addComponent(history_scroll_pane)
948 | )
949 |
950 | return main_panel
951 |
952 | class SequentialThread(threading.Thread):
953 | def __init__(self, func, args):
954 | super(SequentialThread, self).__init__()
955 | self.func = func
956 | self.args = args
957 |
958 | def run(self):
959 | self.func(*self.args)
960 |
961 | class BurpExtender(IBurpExtender, IContextMenuFactory, IProxyListener):
962 |
963 | def bytesToString(self, _bytes):
964 |
965 | char_arr = []
966 |
967 | for b in _bytes:
968 | if b < 257 and b > -1:
969 | char_arr.append(chr(b))
970 |
971 | return "".join(char_arr)
972 |
973 | def registerExtenderCallbacks(self, callbacks):
974 | global popup_menu
975 |
976 | self._callbacks = callbacks
977 | self._helpers = callbacks.getHelpers()
978 | callbacks.setExtensionName("Swagger Parser")
979 |
980 |
981 | popup_menu = JPopupMenu()
982 | menu_item = JMenuItem("Send to Repeater")
983 | menu_item.addActionListener(self.tableMenuItemClickedToRepeater)
984 | popup_menu.add(menu_item)
985 | menu_item = JMenuItem("Send to Intruder")
986 | menu_item.addActionListener(self.tableMenuItemClickedToIntruder)
987 | popup_menu.add(menu_item)
988 | menu_item = JMenuItem("Send to Scanner")
989 | menu_item.addActionListener(self.tableMenuItemClickedToScanner)
990 | popup_menu.add(menu_item)
991 |
992 |
993 | callbacks.registerContextMenuFactory(self)
994 |
995 | callbacks.registerProxyListener(self)
996 |
997 | tab = SwaggerParserTab(callbacks, self)
998 | callbacks.addSuiteTab(tab)
999 |
1000 | def createMenuItems(self, invocation):
1001 | self.invocation = invocation
1002 | menuList = ArrayList()
1003 |
1004 | menuItem = JMenuItem("Send to Swagger Parser")
1005 | menuItem.addMouseListener(MenuClickListener(self, invocation))
1006 |
1007 |
1008 | menuList.add(menuItem)
1009 |
1010 | history_table.addMouseListener(TableMenuClickListener(self, invocation))
1011 |
1012 |
1013 | return menuList
1014 |
1015 | def processProxyMessage(self, messageIsRequest, message):
1016 | global parsable_docs
1017 | global parsable_docs_combobox
1018 |
1019 | if not messageIsRequest:
1020 | message = message.getMessageInfo()
1021 |
1022 | request = message.getRequest()
1023 |
1024 | analyzedRequest = self._helpers.analyzeRequest(request)
1025 |
1026 | doc_url = str(message.getUrl().toString()).strip()
1027 |
1028 | response = message.getResponse()
1029 | analyzedResponse = self._helpers.analyzeResponse(response)
1030 | headers = analyzedResponse.getHeaders()
1031 | responseBody = response[analyzedResponse.getBodyOffset():].tostring()
1032 |
1033 | if ("swagger" in responseBody or "openapi" in responseBody) and "paths" in responseBody and "info" in responseBody and (doc_url.endswith("json") or "api-docs" in doc_url or doc_url.endswith("swagger-ui-init.js")):
1034 | parsable_docs[doc_url] = message
1035 |
1036 | parsable_docs_combobox.setModel(DefaultComboBoxModel([]))
1037 | for doc_item in sorted(list(dict(parsable_docs).keys())):
1038 | parsable_docs_combobox.addItem(doc_item)
1039 |
1040 |
1041 |
1042 |
1043 | def tableMenuItemClickedToScanner(self, event):
1044 | global history_table
1045 | global extracted_requests
1046 |
1047 | selected_rows = history_table.getSelectedRows()
1048 |
1049 | for selected_row in selected_rows:
1050 |
1051 | request_item = extracted_requests[selected_row]
1052 | http_service = request_item["http_service"]
1053 | http_request = request_item["http_request"]
1054 |
1055 | is_https = True
1056 |
1057 | if http_service.getProtocol() != "https":
1058 | is_https = False
1059 |
1060 | self._callbacks.doActiveScan(http_service.getHost(), http_service.getPort(), is_https,
1061 | http_request)
1062 |
1063 | def tableMenuItemClickedToIntruder(self, event):
1064 | global history_table
1065 | global extracted_requests
1066 |
1067 | selected_rows = history_table.getSelectedRows()
1068 |
1069 | for selected_row in selected_rows:
1070 |
1071 | request_item = extracted_requests[selected_row]
1072 | http_service = request_item["http_service"]
1073 | http_request = request_item["http_request"]
1074 |
1075 | is_https = True
1076 |
1077 | if http_service.getProtocol() != "https":
1078 | is_https = False
1079 |
1080 |
1081 | self._callbacks.sendToIntruder(http_service.getHost(), http_service.getPort(), is_https,
1082 | http_request, None)
1083 |
1084 |
1085 | def tableMenuItemClickedToRepeater(self, event):
1086 | global history_table
1087 | global extracted_requests
1088 |
1089 | selected_rows = history_table.getSelectedRows()
1090 |
1091 | for selected_row in selected_rows:
1092 |
1093 | request_item = extracted_requests[selected_row]
1094 | http_service = request_item["http_service"]
1095 | http_request = request_item["http_request"]
1096 |
1097 | is_https = True
1098 |
1099 | if http_service.getProtocol() != "https":
1100 | is_https = False
1101 |
1102 |
1103 | title_endpoint = str(request_item["request_url"]).split("?")[0].split("#")[0]
1104 |
1105 | tab_title = request_item["request_method"] + " " + title_endpoint
1106 |
1107 | self._callbacks.sendToRepeater(http_service.getHost(), http_service.getPort(), is_https,
1108 | http_request, tab_title)
1109 |
1110 |
1111 | def runParser(self, traffic, main_url):
1112 | global header_text_editor
1113 | global output_text_editor
1114 | global output_scroll_pane
1115 |
1116 | request_info = self._helpers.analyzeRequest(traffic)
1117 | request_headers = request_info.getHeaders()
1118 | request_body = traffic.getRequest()[request_info.getBodyOffset():]
1119 |
1120 | response = traffic.getResponse()
1121 | response_info = self._helpers.analyzeResponse(response)
1122 |
1123 | response_headers = response_info.getHeaders()
1124 | response_body_bytes = response[response_info.getBodyOffset():]
1125 |
1126 | http_version = str(request_headers[0]).split(" ")[-1]
1127 | basic_headers = request_headers[1:]
1128 |
1129 | response_body_str = self.bytesToString(response_body_bytes)
1130 |
1131 | swagger_parser = SwaggerParser(swagger_url=main_url, headers={})
1132 |
1133 | parsed_swagger = swagger_parser.parseResponse(main_url, response_body_str)
1134 |
1135 | endpoints = parsed_swagger.get("endpoints")
1136 |
1137 | self.resetTable()
1138 |
1139 | for endpoint in endpoints:
1140 |
1141 | temp_query_string_dict = endpoint.get("query_string")
1142 |
1143 | temp_path = parsed_swagger.get("base_path")
1144 | temp_path += endpoint.get("path")
1145 |
1146 | temp_headers_dict = {}
1147 |
1148 |
1149 | if temp_query_string_dict != None:
1150 | temp_path += "?" + swagger_parser.generateQueryString(temp_query_string_dict)
1151 |
1152 | temp_first_header = endpoint.get("method").upper() + " " + temp_path + " " + http_version
1153 | temp_headers = [temp_first_header]
1154 |
1155 |
1156 | #temp_headers.extend(basic_headers)
1157 |
1158 | for basic_header in basic_headers:
1159 | if ":" in basic_header:
1160 | basic_header_key_value = basic_header.split(":")
1161 | temp_headers_dict[basic_header_key_value[0]] = ":".join(basic_header_key_value[1:])
1162 |
1163 | temp_custom_headers_dict = endpoint.get("header") # from swagger
1164 |
1165 | if temp_custom_headers_dict != None:
1166 |
1167 | temp_custom_headers_dict = dict(endpoint.get("header"))
1168 |
1169 | for _key in temp_custom_headers_dict.keys():
1170 | temp_headers_dict[_key] = temp_custom_headers_dict.get(_key)
1171 |
1172 | custom_headers_from_ui = str(header_text_editor.getText()).split("\n") # from ui
1173 |
1174 | for custom_header_from_ui in custom_headers_from_ui:
1175 | clean_custom_header_from_ui = custom_header_from_ui.strip() # todo
1176 | if clean_custom_header_from_ui != "" and ":" in clean_custom_header_from_ui:
1177 | clean_custom_header_from_ui_key_value = clean_custom_header_from_ui.split(":")
1178 | temp_headers_dict[clean_custom_header_from_ui_key_value[0]] = ":".join(clean_custom_header_from_ui_key_value[1:])
1179 |
1180 | for header_key in temp_headers_dict.keys():
1181 | temp_headers.append(header_key + ":" + temp_headers_dict.get(header_key))
1182 |
1183 | temp_body = endpoint.get("body")
1184 |
1185 | if temp_body != None:
1186 | temp_body = json.dumps(temp_body)
1187 | else:
1188 | temp_body = endpoint.get("formData")
1189 |
1190 | if temp_body != None:
1191 | temp_body = swagger_parser.generateQueryString(temp_body)
1192 | else:
1193 | temp_body = ""
1194 |
1195 | get_swagger_request = self._helpers.buildHttpMessage(temp_headers, temp_body)
1196 |
1197 | threads = []
1198 |
1199 | temp_thread = SequentialThread(self.makeHttpRequest, (traffic.getHttpService(), get_swagger_request,
1200 | "/".join(main_url.split("/")[:3]) + temp_path,
1201 | endpoint.get("method").upper()))
1202 |
1203 | threads.append(temp_thread)
1204 |
1205 | for thread in threads:
1206 | thread.start()
1207 |
1208 | for thread in threads:
1209 | thread.join()
1210 |
1211 | markup_endpoints = parsed_swagger.get("markup_endpoints")
1212 |
1213 | output_markup_endpoints = main_url + "\n\n"
1214 |
1215 | for markup_endpoint in markup_endpoints:
1216 | output_markup_endpoints += markup_endpoint + "\n"
1217 |
1218 | output_markup_endpoints += "\nTotal: " + str(len(markup_endpoints))
1219 |
1220 | output_markup_endpoints += "\n\n"
1221 |
1222 | output_text_editor.setText(output_markup_endpoints)
1223 |
1224 | output_scroll_pane.revalidate()
1225 | output_scroll_pane.repaint()
1226 |
1227 | def resetTable(self):
1228 | global history_table
1229 | global extracted_requests
1230 |
1231 | table_model = history_table.getModel()
1232 | while table_model.getRowCount() > 0:
1233 | table_model.removeRow(0)
1234 |
1235 | extracted_requests = []
1236 |
1237 | def loadingOutputEditor(self):
1238 | global output_text_editor
1239 |
1240 | output_text_editor.setText("Loading...")
1241 |
1242 | def loadingTable(self):
1243 | global history_table
1244 |
1245 | table_model = history_table.getModel()
1246 | table_model.addRow(["Loading...", "Loading...", "Loading...", "Loading..."])
1247 |
1248 | def startParseFromUI(self, traffic):
1249 |
1250 | if traffic.getUrl() != None:
1251 | main_url = str(traffic.getUrl().toString())
1252 |
1253 | if isValidSwaggerDoc(main_url):
1254 | self.resetTable()
1255 | self.loadingTable()
1256 | self.loadingOutputEditor()
1257 |
1258 | threading.Thread(target=self.runParser,
1259 | args=(traffic, main_url)).start()
1260 |
1261 | def menuItemClicked(self, event):
1262 | global header_text_editor
1263 | global output_text_editor
1264 | global output_scroll_pane
1265 | global history_table
1266 |
1267 |
1268 | httpTraffic = self.invocation.getSelectedMessages()
1269 |
1270 | for traffic in httpTraffic:
1271 |
1272 | self.startParseFromUI(traffic)
1273 |
1274 |
1275 | def makeHttpRequest(self, target_service, post_request, request_url, request_method):
1276 | global history_table
1277 | global extracted_requests
1278 |
1279 | if str(request_url).strip() == "" or str(request_method).strip() == "":
1280 | return
1281 |
1282 | try:
1283 | response = self._callbacks.makeHttpRequest(target_service, post_request)
1284 |
1285 | if response:
1286 | response_info = self._helpers.analyzeResponse(response.getResponse())
1287 | response_headers = response_info.getHeaders()
1288 | response_body = response.getResponse()[response_info.getBodyOffset():]
1289 |
1290 | response_body_str = self.bytesToString(response_body)
1291 |
1292 | table_model = history_table.getModel()
1293 | row_data = [request_method, request_url, response_info.getStatusCode(), len(response_body_str)]
1294 | table_model.addRow(row_data)
1295 |
1296 | extracted_requests.append(
1297 | {"http_service": target_service, "http_request": post_request, "request_method": request_method, "request_url": request_url})
1298 |
1299 |
1300 | except Exception as e:
1301 | print("table_model add row err")
1302 | print(e)
1303 |
--------------------------------------------------------------------------------