├── BappDescription.html ├── BappManifest.bmf ├── CPH_Config.py ├── CPH_Help.py ├── CustomParamHandler.py ├── LICENSE ├── README.md ├── tinyweb.py └── utils └── CPH_Merger.py /BappDescription.html: -------------------------------------------------------------------------------- 1 |
2 | CPH provides a simple way to modify any part of an HTTP message, allowing manipulation with surgical precision even when using macros. Typically, macros only support HTTP parameters (name=value), but with this extension anything can be targeted. 3 |
4 |Features include:
5 | 6 |Source and bug reporting: github.com/elespike/burp-cph.
22 |Wiki: github.com/elespike/burp-cph/wiki.
23 | 24 |Requires Jython version 2.7.0
25 | -------------------------------------------------------------------------------- /BappManifest.bmf: -------------------------------------------------------------------------------- 1 | Uuid: a0c0cd68ab7c4928b3bf0a9ad48ec8c7 2 | ExtensionType: 2 3 | Name: Custom Parameter Handler 4 | RepoName: custom-parameter-handler 5 | ScreenVersion: 3.0 6 | SerialVersion: 3 7 | MinPlatformVersion: 0 8 | ProOnly: False 9 | Author: @elespike, @eth0up 10 | ShortDescription: Provides a simple way to automatically modify any part of an HTTP message. 11 | EntryPoint: CustomParamHandler_merged.py 12 | BuildCommand: python utils/CPH_Merger.py 13 | -------------------------------------------------------------------------------- /CPH_Config.py: -------------------------------------------------------------------------------- 1 | from logging import ( 2 | DEBUG , 3 | ERROR , 4 | INFO , 5 | WARNING , 6 | getLevelName, 7 | ) 8 | from SocketServer import ThreadingMixIn, TCPServer 9 | from collections import OrderedDict as odict, namedtuple 10 | from difflib import unified_diff 11 | from hashlib import sha256 12 | from itertools import product 13 | from json import dump, dumps, load, loads 14 | from re import escape as re_escape 15 | from socket import error as socket_error 16 | from thread import start_new_thread 17 | from threading import Thread 18 | from tinyweb import TinyHandler 19 | from webbrowser import open_new_tab as browser_open 20 | 21 | from burp import ITab 22 | from CPH_Help import CPH_Help 23 | 24 | from java.awt import ( 25 | CardLayout , 26 | Color , 27 | FlowLayout , 28 | Font , 29 | GridBagConstraints, 30 | GridBagLayout , 31 | Insets , 32 | ) 33 | from java.awt.event import ( 34 | ActionListener, 35 | KeyListener , 36 | MouseAdapter , 37 | ) 38 | from javax.swing import ( 39 | AbstractAction , 40 | BorderFactory , 41 | JButton , 42 | JCheckBox , 43 | JComboBox , 44 | JFileChooser , 45 | JFrame , 46 | JLabel , 47 | JOptionPane , 48 | JPanel , 49 | JScrollPane , 50 | JSeparator , 51 | JSpinner , 52 | JSplitPane , 53 | JTabbedPane , 54 | JTable , 55 | JTextArea , 56 | JTextField , 57 | KeyStroke , 58 | SpinnerNumberModel, 59 | ) 60 | from javax.swing.event import ChangeListener, ListSelectionListener 61 | from javax.swing.filechooser import FileNameExtensionFilter 62 | from javax.swing.table import AbstractTableModel 63 | from javax.swing.undo import UndoManager 64 | 65 | 66 | class MainTab(ITab, ChangeListener): 67 | mainpane = JTabbedPane() 68 | 69 | # These are set during __init__ 70 | _cph = None 71 | logger = None 72 | 73 | def __init__(self, cph): 74 | MainTab.mainpane.addChangeListener(self) 75 | MainTab._cph = cph 76 | MainTab.logger = cph.logger 77 | self.options_tab = OptionsTab() 78 | MainTab.mainpane.add('Options', self.options_tab) 79 | self._add_sign = unichr(0x002b) # addition sign 80 | MainTab.mainpane.add(self._add_sign, JPanel()) 81 | 82 | class Action(AbstractAction): 83 | def __init__(self, action): 84 | self.action = action 85 | def actionPerformed(self, e): 86 | if self.action: 87 | self.action() 88 | 89 | # Ctrl+N only on key released 90 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(78, 2, True), 'add_config_tab') 91 | MainTab.mainpane.getActionMap().put('add_config_tab', Action(lambda: ConfigTab())) 92 | 93 | # Ctrl+Shift+N only on key released 94 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(78, 3, True), 'clone_tab') 95 | MainTab.mainpane.getActionMap().put( 96 | 'clone_tab', 97 | Action( 98 | lambda: MainTab.mainpane.getSelectedComponent().clone_tab() 99 | if MainTab.mainpane.getSelectedIndex() > 0 100 | else None 101 | ) 102 | ) 103 | 104 | # Ctrl+W only on key released 105 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(87, 2, True), 'close_tab') 106 | MainTab.mainpane.getActionMap().put('close_tab', Action(MainTab.close_tab)) 107 | 108 | # Ctrl+E only on key released 109 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(69, 2, True), 'toggle_tab') 110 | MainTab.mainpane.getActionMap().put( 111 | 'toggle_tab', 112 | Action( 113 | lambda: MainTab.mainpane.getSelectedComponent().tabtitle_pane.enable_chkbox.setSelected( 114 | not MainTab.mainpane.getSelectedComponent().tabtitle_pane.enable_chkbox.isSelected() 115 | ) 116 | ) 117 | ) 118 | 119 | # Ctrl+, 120 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(44, 2), 'select_previous_tab') 121 | MainTab.mainpane.getActionMap().put( 122 | 'select_previous_tab', 123 | Action( 124 | lambda: MainTab.mainpane.setSelectedIndex(MainTab.mainpane.getSelectedIndex() - 1) 125 | if MainTab.mainpane.getSelectedIndex() > 0 126 | else MainTab.mainpane.setSelectedIndex(0) 127 | ) 128 | ) 129 | 130 | # Ctrl+. 131 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(46, 2), 'select_next_tab') 132 | MainTab.mainpane.getActionMap().put( 133 | 'select_next_tab', 134 | Action(lambda: MainTab.mainpane.setSelectedIndex(MainTab.mainpane.getSelectedIndex() + 1)) 135 | ) 136 | 137 | # Ctrl+Shift+, 138 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(44, 3), 'move_tab_back') 139 | MainTab.mainpane.getActionMap().put( 140 | 'move_tab_back', 141 | Action( 142 | lambda: MainTab.mainpane.getSelectedComponent().move_tab_back( 143 | MainTab.mainpane.getSelectedComponent() 144 | ) 145 | ) 146 | ) 147 | 148 | # Ctrl+Shift+. 149 | MainTab.mainpane.getInputMap(1).put(KeyStroke.getKeyStroke(46, 3), 'move_tab_fwd') 150 | MainTab.mainpane.getActionMap().put( 151 | 'move_tab_fwd', 152 | Action( 153 | lambda: MainTab.mainpane.getSelectedComponent().move_tab_fwd( 154 | MainTab.mainpane.getSelectedComponent() 155 | ) 156 | ) 157 | ) 158 | 159 | @staticmethod 160 | def getTabCaption(): 161 | return 'CPH Config' 162 | 163 | def getUiComponent(self): 164 | return MainTab.mainpane 165 | 166 | def add_config_tab(self, messages): 167 | for message in messages: 168 | ConfigTab(message) 169 | 170 | @staticmethod 171 | def get_options_tab(): 172 | return MainTab.mainpane.getComponentAt(0) 173 | 174 | @staticmethod 175 | def get_config_tabs(): 176 | components = MainTab.mainpane.getComponents() 177 | for i in range(len(components)): 178 | for tab in components: 179 | if isinstance(tab, ConfigTab) and i == MainTab.mainpane.indexOfComponent(tab): 180 | yield tab 181 | 182 | @staticmethod 183 | def get_config_tab_names(): 184 | for tab in MainTab.get_config_tabs(): 185 | yield tab.namepane_txtfield.getText() 186 | 187 | @staticmethod 188 | def get_config_tab_cache(tab_name): 189 | for tab in MainTab.get_config_tabs(): 190 | if tab.namepane_txtfield.getText() == tab_name: 191 | return tab.cached_request, tab.cached_response 192 | 193 | @staticmethod 194 | def check_configtab_names(): 195 | x = 0 196 | configtab_names = {} 197 | for name in MainTab.get_config_tab_names(): 198 | configtab_names[x] = name 199 | x += 1 200 | indices_to_rename = {} 201 | for tab_index_1, tab_name_1 in configtab_names.items(): 202 | for tab_index_2, tab_name_2 in configtab_names.items(): 203 | if tab_name_2 not in indices_to_rename: 204 | indices_to_rename[tab_name_2] = [] 205 | if tab_name_1 == tab_name_2 and tab_index_1 != tab_index_2: 206 | indices_to_rename[tab_name_2].append(tab_index_2 + 1) # +1 because the first tab is the Options tab 207 | for k, v in indices_to_rename.items(): 208 | indices_to_rename[k] = set(sorted(v)) 209 | for tab_name, indices in indices_to_rename.items(): 210 | x = 1 211 | for i in indices: 212 | MainTab.set_tab_name(MainTab.mainpane.getComponentAt(i), tab_name + ' (%s)' % x) 213 | x += 1 214 | 215 | @staticmethod 216 | def set_tab_name(tab, tab_name): 217 | tab.namepane_txtfield.tab_label.setText(tab_name) 218 | tab.namepane_txtfield.setText(tab_name) 219 | emv_tab_index = MainTab.mainpane.indexOfComponent(tab) - 1 220 | MainTab.get_options_tab().emv_tab_pane.setTitleAt(emv_tab_index, tab_name) 221 | 222 | @staticmethod 223 | def close_tab(tab_index=None): 224 | if tab_index is None: 225 | tab_index = MainTab.mainpane.getSelectedIndex() 226 | true_index = tab_index - 1 # because of the Options tab 227 | tab_count = MainTab.mainpane.getTabCount() 228 | if tab_index == 0 or tab_count == 2: 229 | return 230 | if tab_count == 3 or tab_index == tab_count - 2: 231 | MainTab.mainpane.setSelectedIndex(tab_count - 3) 232 | MainTab.mainpane.remove(tab_index) 233 | MainTab.get_options_tab().emv_tab_pane.remove(true_index) 234 | 235 | # If the closed tab was selected in subsequent tabs' combo_cached, remove selection. 236 | for i, subsequent_tab in enumerate(MainTab.get_config_tabs()): 237 | if i < true_index: 238 | continue 239 | if subsequent_tab.param_handl_combo_cached.getSelectedIndex() == true_index: 240 | subsequent_tab.param_handl_combo_cached.setSelectedItem(None) 241 | if subsequent_tab.param_handl_combo_extract.getSelectedItem() == ConfigTab.PARAM_HANDL_COMBO_EXTRACT_CACHED: 242 | MainTab.logger.warning( 243 | 'Selected cache no longer available for tab "{}"!'.format(subsequent_tab.namepane_txtfield.getText()) 244 | ) 245 | subsequent_tab.param_handl_combo_cached.removeItemAt(true_index) 246 | 247 | def stateChanged(self, e): 248 | if e.getSource() == MainTab.mainpane: 249 | index = MainTab.mainpane.getSelectedIndex() 250 | if hasattr(self, '_add_sign') and MainTab.mainpane.getTitleAt(index) == self._add_sign: 251 | MainTab.mainpane.setSelectedIndex(0) 252 | ConfigTab() 253 | 254 | 255 | class SubTab(JScrollPane, ActionListener): 256 | BTN_HELP = '?' 257 | DOCS_URL = 'https://github.com/elespike/burp-cph/wiki' 258 | INSETS = Insets(2, 4, 2, 4) 259 | # Expression pane component indices 260 | CHECKBOX_INDEX = 0 261 | TXT_FIELD_INDEX = 1 262 | # Socket pane component index tuples 263 | HTTPS_INDEX = 0 264 | HOST_INDEX = 1 265 | PORT_INDEX = 3 266 | 267 | CONFIG_MECHANISM = namedtuple('CONFIG_MECHANISM' , 'name, getter, setter') 268 | 269 | def __init__(self): 270 | self._main_tab_pane = JPanel(GridBagLayout()) 271 | self.setViewportView(self._main_tab_pane) 272 | self.getVerticalScrollBar().setUnitIncrement(16) 273 | 274 | @staticmethod 275 | def create_blank_space(): 276 | return JLabel(' ') 277 | 278 | @staticmethod 279 | def create_empty_button(button): 280 | button.setOpaque(False) 281 | button.setFocusable(False) 282 | button.setContentAreaFilled(False) 283 | button.setBorderPainted(False) 284 | 285 | @staticmethod 286 | def set_title_font(component): 287 | font = Font(Font.SANS_SERIF, Font.BOLD, 14) 288 | component.setFont(font) 289 | return component 290 | 291 | def initialize_constraints(self): 292 | constraints = GridBagConstraints() 293 | constraints.weightx = 1 294 | constraints.insets = self.INSETS 295 | constraints.fill = GridBagConstraints.HORIZONTAL 296 | constraints.anchor = GridBagConstraints.NORTHWEST 297 | constraints.gridx = 0 298 | constraints.gridy = 0 299 | return constraints 300 | 301 | @staticmethod 302 | def show_card(cardpanel, label): 303 | cl = cardpanel.getLayout() 304 | cl.show(cardpanel, label) 305 | 306 | 307 | class HelpButton(JButton): 308 | # From CPH_Help.py: 309 | # HelpPopup = namedtuple('HelpPopup', 'title, message, url') 310 | def __init__(self, help_popup=None): 311 | super(JButton, self).__init__() 312 | self.title, self.message = '', '' 313 | self.url = SubTab.DOCS_URL 314 | 315 | if help_popup is not None: 316 | self.title = help_popup.title 317 | self.message = JLabel(help_popup.message) 318 | self.message.setFont(Font(Font.MONOSPACED, Font.PLAIN, 14)) 319 | self.url = help_popup.url 320 | 321 | self.setText(SubTab.BTN_HELP) 322 | self.setFont(Font(Font.SANS_SERIF, Font.BOLD, 14)) 323 | 324 | def show_help(self): 325 | result = JOptionPane.showOptionDialog( 326 | self, 327 | self.message, 328 | self.title, 329 | JOptionPane.YES_NO_OPTION, 330 | JOptionPane.QUESTION_MESSAGE, 331 | None, 332 | ['Learn more', 'Close'], 333 | 'Close' 334 | ) 335 | if result == 0: 336 | browser_open(self.url) 337 | 338 | 339 | class ThreadedHTTPServer(ThreadingMixIn, TCPServer): 340 | pass 341 | 342 | 343 | class OptionsTab(SubTab, ChangeListener): 344 | VERBOSITY = 'Verbosity level:' 345 | BTN_QUICKSAVE = 'Quicksave' 346 | BTN_QUICKLOAD = 'Quickload' 347 | BTN_EXPORTCONFIG = 'Export Config' 348 | BTN_IMPORTCONFIG = 'Import Config' 349 | BTN_DOCS = 'Visit Wiki' 350 | BTN_EMV = 'Show EMV' 351 | DEMO_INACTIVE = 'Run the built-in httpd for interactive demos' 352 | DEMO_ACTIVE = 'Running built-in httpd on {}:{}' 353 | CHKBOX_PANE = 'Tool scope settings' 354 | QUICKSTART_PANE = 'Quickstart guide' 355 | 356 | FILEFILTER = FileNameExtensionFilter('JSON', ['json']) 357 | 358 | s = sha256() 359 | s.update('quick') 360 | CONFIGNAME_QUICK = s.hexdigest() 361 | s = sha256() 362 | s.update('options') 363 | CONFIGNAME_OPTIONS = s.hexdigest() 364 | del s 365 | 366 | def __init__(self): 367 | SubTab.__init__(self) 368 | 369 | btn_docs = JButton(self.BTN_DOCS) 370 | btn_docs.addActionListener(self) 371 | 372 | btn_emv = JButton(self.BTN_EMV) 373 | btn_emv.addActionListener(self) 374 | 375 | btn_quicksave = JButton(self.BTN_QUICKSAVE) 376 | btn_quicksave.addActionListener(self) 377 | 378 | btn_quickload = JButton(self.BTN_QUICKLOAD) 379 | btn_quickload.addActionListener(self) 380 | 381 | btn_exportconfig = JButton(self.BTN_EXPORTCONFIG) 382 | btn_exportconfig.addActionListener(self) 383 | 384 | btn_importconfig = JButton(self.BTN_IMPORTCONFIG) 385 | btn_importconfig.addActionListener(self) 386 | 387 | err, warn, info, dbg = 1, 2, 3, 4 388 | self.verbosity_translator = { 389 | err : ERROR , 390 | warn: WARNING, 391 | info: INFO , 392 | dbg : DEBUG , 393 | } 394 | 395 | # Just initializing 396 | self.httpd = None 397 | self.httpd_thread = None 398 | 399 | self.verbosity_level_lbl = JLabel(getLevelName(INFO)) 400 | self.verbosity_spinner = JSpinner(SpinnerNumberModel(info, err, dbg, 1)) 401 | self.verbosity_spinner.addChangeListener(self) 402 | 403 | verbosity_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 404 | verbosity_pane.add(JLabel(self.VERBOSITY)) 405 | verbosity_pane.add(self.verbosity_spinner) 406 | verbosity_pane.add(self.verbosity_level_lbl) 407 | 408 | self.chkbox_demo = JCheckBox(self.DEMO_INACTIVE, False) 409 | self.chkbox_demo.addActionListener(self) 410 | demo_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 411 | demo_pane.add(self.chkbox_demo) 412 | 413 | self.emv = JFrame('Effective Modification Viewer') 414 | self.emv_tab_pane = JTabbedPane() 415 | self.emv.add(self.emv_tab_pane) 416 | 417 | btn_pane = JPanel(GridBagLayout()) 418 | constraints = self.initialize_constraints() 419 | constraints.gridwidth = 2 420 | btn_pane.add(verbosity_pane, constraints) 421 | constraints.gridwidth = 1 422 | constraints.gridy = 1 423 | btn_pane.add(btn_quicksave, constraints) 424 | constraints.gridx = 1 425 | btn_pane.add(btn_exportconfig, constraints) 426 | constraints.gridy = 2 427 | constraints.gridx = 0 428 | btn_pane.add(btn_quickload, constraints) 429 | constraints.gridx = 1 430 | btn_pane.add(btn_importconfig, constraints) 431 | constraints.gridy = 3 432 | constraints.gridx = 0 433 | btn_pane.add(btn_docs, constraints) 434 | constraints.gridx = 1 435 | btn_pane.add(btn_emv, constraints) 436 | constraints.gridy = 4 437 | constraints.gridx = 0 438 | constraints.gridwidth = 2 439 | btn_pane.add(demo_pane, constraints) 440 | 441 | self.chkbox_proxy = JCheckBox('Proxy' , True ) 442 | self.chkbox_target = JCheckBox('Target' , False) 443 | self.chkbox_spider = JCheckBox('Spider' , False) 444 | self.chkbox_repeater = JCheckBox('Repeater' , True ) 445 | self.chkbox_sequencer = JCheckBox('Sequencer', False) 446 | self.chkbox_intruder = JCheckBox('Intruder' , False) 447 | self.chkbox_scanner = JCheckBox('Scanner' , False) 448 | self.chkbox_extender = JCheckBox('Extender' , False) 449 | 450 | chkbox_pane = JPanel(GridBagLayout()) 451 | chkbox_pane.setBorder(BorderFactory.createTitledBorder(self.CHKBOX_PANE)) 452 | chkbox_pane.getBorder().setTitleFont(Font(Font.SANS_SERIF, Font.ITALIC, 16)) 453 | constraints = self.initialize_constraints() 454 | chkbox_pane.add(self.chkbox_proxy, constraints) 455 | constraints.gridy = 1 456 | chkbox_pane.add(self.chkbox_target, constraints) 457 | constraints.gridy = 2 458 | chkbox_pane.add(self.chkbox_spider, constraints) 459 | constraints.gridy = 3 460 | chkbox_pane.add(self.chkbox_repeater, constraints) 461 | constraints.gridx = 1 462 | constraints.gridy = 0 463 | chkbox_pane.add(self.chkbox_sequencer, constraints) 464 | constraints.gridy = 1 465 | chkbox_pane.add(self.chkbox_intruder, constraints) 466 | constraints.gridy = 2 467 | chkbox_pane.add(self.chkbox_scanner, constraints) 468 | constraints.gridy = 3 469 | chkbox_pane.add(self.chkbox_extender, constraints) 470 | 471 | quickstart_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 472 | quickstart_pane.setBorder(BorderFactory.createTitledBorder(self.QUICKSTART_PANE)) 473 | quickstart_pane.getBorder().setTitleFont(Font(Font.SANS_SERIF, Font.ITALIC, 16)) 474 | quickstart_text_lbl = JLabel(CPH_Help.quickstart) 475 | quickstart_text_lbl.setFont(Font(Font.MONOSPACED, Font.PLAIN, 14)) 476 | quickstart_pane.add(quickstart_text_lbl) 477 | 478 | constraints = self.initialize_constraints() 479 | constraints.gridwidth = 3 480 | self._main_tab_pane.add(SubTab.create_blank_space(), constraints) 481 | constraints.gridwidth = 1 482 | constraints.gridy = 1 483 | self._main_tab_pane.add(btn_pane, constraints) 484 | constraints.gridx = 1 485 | self._main_tab_pane.add(SubTab.create_blank_space(), constraints) 486 | constraints.gridx = 2 487 | self._main_tab_pane.add(chkbox_pane, constraints) 488 | constraints.gridx = 3 489 | self._main_tab_pane.add(SubTab.create_blank_space(), constraints) 490 | constraints.gridx = 0 491 | constraints.gridy = 2 492 | constraints.gridwidth = 3 493 | self._main_tab_pane.add(SubTab.create_blank_space(), constraints) 494 | constraints.gridy = 3 495 | constraints.weighty = 1 496 | self._main_tab_pane.add(quickstart_pane, constraints) 497 | 498 | self.config_mechanisms = [ 499 | SubTab.CONFIG_MECHANISM( 500 | 'verbosity', 501 | self.verbosity_spinner.getValue, 502 | lambda cv: self.verbosity_spinner.setValue(cv) 503 | ), 504 | SubTab.CONFIG_MECHANISM( 505 | 'chkbox_proxy', 506 | self.chkbox_proxy.isSelected, 507 | lambda cv: self.chkbox_proxy.setSelected(cv) 508 | ), 509 | SubTab.CONFIG_MECHANISM( 510 | 'chkbox_target', 511 | self.chkbox_target.isSelected, 512 | lambda cv: self.chkbox_target.setSelected(cv) 513 | ), 514 | SubTab.CONFIG_MECHANISM( 515 | 'chkbox_spider', 516 | self.chkbox_spider.isSelected, 517 | lambda cv: self.chkbox_spider.setSelected(cv) 518 | ), 519 | SubTab.CONFIG_MECHANISM( 520 | 'chkbox_repeater', 521 | self.chkbox_repeater.isSelected, 522 | lambda cv: self.chkbox_repeater.setSelected(cv) 523 | ), 524 | SubTab.CONFIG_MECHANISM( 525 | 'chkbox_sequencer', 526 | self.chkbox_sequencer.isSelected, 527 | lambda cv: self.chkbox_sequencer.setSelected(cv) 528 | ), 529 | SubTab.CONFIG_MECHANISM( 530 | 'chkbox_intruder', 531 | self.chkbox_intruder.isSelected, 532 | lambda cv: self.chkbox_intruder.setSelected(cv) 533 | ), 534 | SubTab.CONFIG_MECHANISM( 535 | 'chkbox_scanner', 536 | self.chkbox_scanner.isSelected, 537 | lambda cv: self.chkbox_scanner.setSelected(cv) 538 | ), 539 | SubTab.CONFIG_MECHANISM( 540 | 'chkbox_extender', 541 | self.chkbox_extender.isSelected, 542 | lambda cv: self.chkbox_extender.setSelected(cv) 543 | ), 544 | ] 545 | 546 | def stateChanged(self, e): 547 | if e.getSource() == self.verbosity_spinner: 548 | level = self.verbosity_translator[self.verbosity_spinner.getValue()] 549 | MainTab.logger.setLevel(level) 550 | self.verbosity_level_lbl.setText(getLevelName(level)) 551 | 552 | def set_tab_values(self, config, tab, tab_name=''): 553 | warn = False 554 | for cm in tab.config_mechanisms: 555 | if cm.name in config: 556 | cm.setter(config[cm.name]) 557 | else: 558 | warn = True 559 | continue 560 | if warn: 561 | MainTab.logger.warning( 562 | 'Your configuration is corrupt or was generated by an old version of CPH. Expect the unexpected.' 563 | ) 564 | 565 | if isinstance(tab, OptionsTab): 566 | return 567 | 568 | MainTab.set_tab_name(tab, tab_name) 569 | # A couple hacks to avoid implementing an ItemListener just for this, 570 | # because ActionListener doesn't get triggered on setSelected() -_- 571 | # Reference: https://stackoverflow.com/questions/9882845 572 | try: 573 | tab.param_handl_forwarder_socket_pane.setVisible(config['enable_forwarder']) 574 | tab.param_handl_dynamic_pane .setVisible(config['dynamic_checkbox']) 575 | except KeyError: 576 | return 577 | 578 | def actionPerformed(self, e): 579 | c = e.getActionCommand() 580 | if c == self.BTN_QUICKLOAD or c == self.BTN_IMPORTCONFIG: 581 | replace_config_tabs = False 582 | result = 0 583 | tabcount = 0 584 | for tab in MainTab.get_config_tabs(): 585 | tabcount += 1 586 | break 587 | if tabcount > 0: 588 | result = JOptionPane.showOptionDialog( 589 | self, 590 | 'Would you like to Purge or Keep all existing tabs?', 591 | 'Existing Tabs Detected!', 592 | JOptionPane.YES_NO_CANCEL_OPTION, 593 | JOptionPane.QUESTION_MESSAGE, 594 | None, 595 | ['Purge', 'Keep', 'Cancel'], 596 | 'Cancel' 597 | ) 598 | # If purge... 599 | if result == 0: 600 | replace_config_tabs = True 601 | MainTab.logger.info('Replacing configuration...') 602 | # If not cancel or close dialog... 603 | # note: result can still be 0 here; do not use 'elif' 604 | if result != 2 and result != -1: 605 | if result != 0: 606 | MainTab.logger.info('Merging configuration...') 607 | 608 | if c == self.BTN_QUICKLOAD: 609 | try: 610 | config = loads( 611 | MainTab._cph.callbacks.loadExtensionSetting(OptionsTab.CONFIGNAME_QUICK), 612 | object_pairs_hook=odict 613 | ) 614 | self.load_config(config, replace_config_tabs) 615 | MainTab.logger.info('Configuration quickloaded.') 616 | except StandardError: 617 | MainTab.logger.exception('Error during quickload.') 618 | 619 | if c == self.BTN_IMPORTCONFIG: 620 | fc = JFileChooser() 621 | fc.setFileFilter(OptionsTab.FILEFILTER) 622 | result = fc.showOpenDialog(self) 623 | if result == JFileChooser.APPROVE_OPTION: 624 | fpath = fc.getSelectedFile().getPath() 625 | try: 626 | with open(fpath, 'r') as f: 627 | config = load(f, object_pairs_hook=odict) 628 | self.load_config(config, replace_config_tabs) 629 | MainTab.logger.info('Configuration imported from "{}".'.format(fpath)) 630 | except StandardError: 631 | MainTab.logger.exception('Error importing config from "{}".'.format(fpath)) 632 | if result == JFileChooser.CANCEL_OPTION: 633 | MainTab.logger.info('User canceled configuration import from file.') 634 | else: 635 | MainTab.logger.info('User canceled quickload/import.') 636 | 637 | if c == self.BTN_QUICKSAVE: 638 | try: 639 | full_config = self.prepare_to_save_all() 640 | MainTab._cph.callbacks.saveExtensionSetting(OptionsTab.CONFIGNAME_QUICK, dumps(full_config)) 641 | MainTab.logger.info('Configuration quicksaved.') 642 | except StandardError: 643 | MainTab.logger.exception('Error during quicksave.') 644 | 645 | if c == self.BTN_DOCS: 646 | browser_open(self.DOCS_URL) 647 | 648 | if c == self.BTN_EMV: 649 | if not self.emv.isVisible(): 650 | self.emv.pack() 651 | self.emv.setSize(800, 600) 652 | self.emv.show() 653 | # Un-minimize 654 | self.emv.setState(JFrame.NORMAL) 655 | self.emv.toFront() 656 | for emv_tab in self.emv_tab_pane.getComponents(): 657 | emv_tab.viewer.setDividerLocation(0.5) 658 | 659 | if c == self.BTN_EXPORTCONFIG: 660 | tabcount = 0 661 | for tab in MainTab.get_config_tabs(): 662 | tabcount += 1 663 | break 664 | if tabcount > 0: 665 | fc = JFileChooser() 666 | fc.setFileFilter(OptionsTab.FILEFILTER) 667 | result = fc.showSaveDialog(self) 668 | if result == JFileChooser.APPROVE_OPTION: 669 | fpath = fc.getSelectedFile().getPath() 670 | if not fpath.endswith('.json'): 671 | fpath += '.json' 672 | full_config = self.prepare_to_save_all() 673 | try: 674 | with open(fpath, 'w') as f: 675 | dump(full_config, f, indent=4, separators=(',', ': ')) 676 | MainTab.logger.info('Configuration exported to "{}".'.format(fpath)) 677 | except IOError: 678 | MainTab.logger.exception('Error exporting config to "{}".'.format(fpath)) 679 | if result == JFileChooser.CANCEL_OPTION: 680 | MainTab.logger.info('User canceled configuration export to file.') 681 | 682 | if c == self.DEMO_INACTIVE: 683 | # Start threaded HTTP server for interactive demos. 684 | try: 685 | self.httpd = ThreadedHTTPServer(('localhost', 9001), TinyHandler) 686 | except socket_error: 687 | # Port zero means any unused port. 688 | self.httpd = ThreadedHTTPServer(('localhost', 0), TinyHandler) 689 | self.httpd_thread = Thread(target=self.httpd.serve_forever) 690 | # Daemonize so that it terminates cleanly without needing to join(). 691 | self.httpd_thread.daemon = True 692 | self.httpd_thread.start() 693 | self.chkbox_demo.setText(self.DEMO_ACTIVE.format(*self.httpd.server_address)) 694 | 695 | if self.httpd is not None and c == self.DEMO_ACTIVE.format(*self.httpd.server_address): 696 | self.httpd.shutdown() 697 | self.httpd.server_close() 698 | self.httpd = None 699 | self.chkbox_demo.setText(self.DEMO_INACTIVE) 700 | 701 | 702 | def load_config(self, config, replace_config_tabs=False): 703 | loaded_tab_names = config.keys() 704 | 705 | if OptionsTab.CONFIGNAME_OPTIONS in loaded_tab_names: 706 | self.set_tab_values(config[OptionsTab.CONFIGNAME_OPTIONS], MainTab.get_options_tab()) 707 | loaded_tab_names.remove(OptionsTab.CONFIGNAME_OPTIONS) 708 | 709 | tabs_left_to_load = list(loaded_tab_names) 710 | tabs_to_remove = {} 711 | 712 | # Modify existing and mark for purge where applicable 713 | for tab_name, tab in product(loaded_tab_names, MainTab.get_config_tabs()): 714 | if tab_name == tab.namepane_txtfield.getText(): 715 | self.set_tab_values(config[tab_name], tab, tab_name) 716 | if tab_name in tabs_left_to_load: 717 | tabs_left_to_load.remove(tab_name) 718 | tabs_to_remove[tab] = False 719 | if tab not in tabs_to_remove: 720 | tabs_to_remove[tab] = True 721 | 722 | # Import and purge if applicable 723 | for tab, tab_marked in tabs_to_remove.items(): 724 | if tab_marked and replace_config_tabs: 725 | MainTab.get_options_tab().emv_tab_pane.remove(tab.emv_tab) 726 | MainTab.mainpane.remove(tab) 727 | for tab_name in tabs_left_to_load: 728 | self.set_tab_values(config[tab_name], ConfigTab(), tab_name) 729 | 730 | # No need to proceed if there's only 1 tab. 731 | # This is also the case when cloning a tab. 732 | if len(loaded_tab_names) <= 1: 733 | return 734 | 735 | # Restore tab order 736 | for tab in MainTab.get_config_tabs(): 737 | tab_name = tab.namepane_txtfield.getText() 738 | # Adding one because the Options tab is always the first tab. 739 | if tab_name in loaded_tab_names: 740 | ConfigTab.move_tab(tab, loaded_tab_names.index(tab_name) + 1) 741 | else: 742 | ConfigTab.move_tab(tab, len(loaded_tab_names) + 1) 743 | 744 | def prepare_to_save_all(self): 745 | MainTab.check_configtab_names() 746 | full_config = odict() 747 | for tab in MainTab.get_config_tabs(): 748 | full_config[tab.namepane_txtfield.getText()] = self.prepare_to_save_tab(tab) 749 | full_config[OptionsTab.CONFIGNAME_OPTIONS] = self.prepare_to_save_tab(MainTab.get_options_tab()) 750 | return full_config 751 | 752 | def prepare_to_save_tab(self, tab): 753 | config = {} 754 | for cm in tab.config_mechanisms: 755 | config[cm.name] = cm.getter() 756 | return config 757 | 758 | 759 | class EMVTab(JSplitPane, ListSelectionListener): 760 | MAX_ITEMS = 32 761 | def __init__(self): 762 | self.updating = False 763 | self.selected_index = -1 764 | 765 | self.table = JTable(self.EMVTableModel()) 766 | self.table_model = self.table.getModel() 767 | sm = self.table.getSelectionModel() 768 | sm.setSelectionMode(0) # Single selection 769 | sm.addListSelectionListener(self) 770 | 771 | table_pane = JScrollPane() 772 | table_pane.setViewportView(self.table) 773 | table_pane.getVerticalScrollBar().setUnitIncrement(16) 774 | 775 | self.diff_field = MainTab._cph.callbacks.createMessageEditor(None, False) 776 | self.original_field = MainTab._cph.callbacks.createMessageEditor(None, False) 777 | self.modified_field = MainTab._cph.callbacks.createMessageEditor(None, False) 778 | 779 | self.viewer = JSplitPane() 780 | self.viewer.setLeftComponent(self.original_field.getComponent()) 781 | self.viewer.setRightComponent(self.modified_field.getComponent()) 782 | 783 | self.diffpane = JSplitPane(JSplitPane.VERTICAL_SPLIT) 784 | # Top pane gets populated in value_changed(), below. 785 | self.diffpane.setTopComponent(JPanel()) 786 | self.diffpane.setBottomComponent(self.viewer) 787 | self.diffpane.setDividerLocation(100) 788 | 789 | viewer_pane = JPanel(GridBagLayout()) 790 | constraints = GridBagConstraints() 791 | constraints.weightx = 1 792 | constraints.weighty = 1 793 | constraints.fill = GridBagConstraints.BOTH 794 | constraints.anchor = GridBagConstraints.NORTH 795 | constraints.gridx = 0 796 | constraints.gridy = 0 797 | viewer_pane.add(self.diffpane, constraints) 798 | 799 | self.setOrientation(JSplitPane.VERTICAL_SPLIT) 800 | self.setTopComponent(table_pane) 801 | self.setBottomComponent(viewer_pane) 802 | self.setDividerLocation(100) 803 | 804 | def add_table_row(self, time, is_request, original_msg, modified_msg): 805 | if len(self.table_model.rows) == 0: 806 | self.viewer.setDividerLocation(0.5) 807 | 808 | message_type = 'Response' 809 | if is_request: 810 | message_type = 'Request' 811 | self.table_model.rows.insert( 812 | 0, 813 | [str(time)[:-3], message_type, len(modified_msg) - len(original_msg)] 814 | ) 815 | self.table_model.messages.insert( 816 | 0, 817 | self.table_model.MessagePair(original_msg, modified_msg) 818 | ) 819 | 820 | if len(self.table_model.rows) > self.MAX_ITEMS: 821 | self.table_model.rows.pop(-1) 822 | if len(self.table_model.messages) > self.MAX_ITEMS: 823 | self.table_model.messages.pop(-1) 824 | 825 | self.table_model.fireTableDataChanged() 826 | self.table.setRowSelectionInterval(0, 0) 827 | 828 | def valueChanged(self, e): 829 | # Jenky lock mechanism to prevent crash with many quickly-repeated triggers. 830 | if self.updating: 831 | return 832 | self.updating = True 833 | 834 | index = self.table.getSelectedRow() 835 | if self.selected_index == index: 836 | self.updating = False 837 | return 838 | self.selected_index = index 839 | original_msg = self.table_model.messages[index].original_msg 840 | modified_msg = self.table_model.messages[index].modified_msg 841 | 842 | diff = unified_diff(original_msg.splitlines(1), modified_msg.splitlines(1)) 843 | text = '' 844 | for line in diff: 845 | if '---' in line or '+++' in line: 846 | continue 847 | text += line 848 | if not text.endswith('\n'): 849 | text += '\n' 850 | 851 | dl = self.diffpane.getDividerLocation() 852 | is_request = self.table_model.rows[index][1] == 'Request' 853 | self.diff_field .setMessage(text , is_request) 854 | self.original_field.setMessage(original_msg, is_request) 855 | self.modified_field.setMessage(modified_msg, is_request) 856 | 857 | self.diffpane.setTopComponent(self.diff_field.getComponent().getComponentAt(0)) 858 | self.diffpane.setDividerLocation(dl) 859 | self.updating = False 860 | 861 | 862 | class EMVTableModel(AbstractTableModel): 863 | def __init__(self): 864 | super(EMVTab.EMVTableModel, self).__init__() 865 | self.MessagePair = namedtuple('MessagePair', 'original_msg, modified_msg') 866 | self.rows = [] 867 | self.messages = [] 868 | 869 | def getRowCount(self): 870 | return len(self.rows) 871 | 872 | def getColumnCount(self): 873 | return 3 874 | 875 | def getColumnName(self, columnIndex): 876 | if columnIndex == 0: 877 | return 'Time' 878 | if columnIndex == 1: 879 | return 'Type' 880 | if columnIndex == 2: 881 | return 'Length Difference' 882 | 883 | def getValueAt(self, rowIndex, columnIndex): 884 | return self.rows[rowIndex][columnIndex] 885 | 886 | def setValueAt(self, aValue, rowIndex, columnIndex): 887 | return 888 | 889 | def isCellEditable(self, rowIndex, columnIndex): 890 | return False 891 | 892 | 893 | class ConfigTabTitle(JPanel): 894 | def __init__(self): 895 | self.setBorder(BorderFactory.createEmptyBorder(-4, -5, -5, -5)) 896 | self.setOpaque(False) 897 | self.enable_chkbox = JCheckBox('', True) 898 | self.label = JLabel(ConfigTab.TAB_NEW_NAME) 899 | self.label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 4)) 900 | self.add(self.enable_chkbox) 901 | self.add(self.label) 902 | self.add(self.CloseButton()) 903 | 904 | class CloseButton(JButton, ActionListener): 905 | def __init__(self): 906 | self.setText(unichr(0x00d7)) # multiplication sign 907 | self.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 2)) 908 | SubTab.create_empty_button(self) 909 | self.addMouseListener(self.CloseButtonMouseListener()) 910 | self.addActionListener(self) 911 | 912 | def actionPerformed(self, e): 913 | MainTab.close_tab(MainTab.mainpane.indexOfTabComponent(self.getParent())) 914 | 915 | class CloseButtonMouseListener(MouseAdapter): 916 | def mouseEntered(self, e): 917 | button = e.getComponent() 918 | button.setForeground(Color.red) 919 | 920 | def mouseExited(self, e): 921 | button = e.getComponent() 922 | button.setForeground(Color.black) 923 | 924 | def mouseReleased(self, e): 925 | pass 926 | 927 | def mousePressed(self, e): 928 | pass 929 | 930 | 931 | class ConfigTabNameField(JTextField, KeyListener): 932 | def __init__(self, tab_label): 933 | self.setColumns(32) 934 | self.setText(ConfigTab.TAB_NEW_NAME) 935 | self.setFont(ConfigTab.FIELD_FONT) 936 | self.addKeyListener(self) 937 | self.tab_label = tab_label 938 | 939 | self.addKeyListener(UndoableKeyListener(self)) 940 | 941 | def keyReleased(self, e): 942 | self_index = MainTab.mainpane.getSelectedIndex() 943 | true_index = self_index - 1 # because of the Options tab 944 | self.tab_label.setText(self.getText()) 945 | MainTab.get_options_tab().emv_tab_pane.setTitleAt(true_index, self.getText()) 946 | for i, subsequent_tab in enumerate(MainTab.get_config_tabs()): 947 | if i <= true_index: 948 | continue 949 | subsequent_tab.param_handl_combo_cached.removeItemAt(true_index) 950 | subsequent_tab.param_handl_combo_cached.insertItemAt(self.getText(), true_index) 951 | 952 | def keyPressed(self, e): 953 | # Doing self._tab_label.setText() here is sub-optimal. Leave it above. 954 | pass 955 | 956 | def keyTyped(self, e): 957 | pass 958 | 959 | 960 | class UndoableKeyListener(KeyListener): 961 | REDO = 89 962 | UNDO = 90 963 | CTRL = 2 964 | def __init__(self, target): 965 | self.undomgr = UndoManager() 966 | target.getDocument().addUndoableEditListener(self.undomgr) 967 | 968 | def keyReleased(self, e): 969 | pass 970 | 971 | def keyPressed(self, e): 972 | if e.getModifiers() == self.CTRL: 973 | if e.getKeyCode() == self.UNDO and self.undomgr.canUndo(): 974 | self.undomgr.undo() 975 | if e.getKeyCode() == self.REDO and self.undomgr.canRedo(): 976 | self.undomgr.redo() 977 | 978 | def keyTyped(self, e): 979 | pass 980 | 981 | 982 | class ConfigTab(SubTab): 983 | TXT_FIELD_SIZE = 64 984 | FIELD_FONT = Font(Font.MONOSPACED, Font.PLAIN, 13) 985 | REGEX = 'RegEx' 986 | TAB_NEW_NAME = 'Unconfigured' 987 | 988 | BTN_BACK = '<' 989 | BTN_FWD = '>' 990 | BTN_CLONETAB = 'Clone' 991 | TAB_NAME = 'Friendly name:' 992 | 993 | # Scope pane 994 | MSG_MOD_GROUP = 'Scoping' 995 | MSG_MOD_SCOPE_BURP = ' Provided their URLs are within Burp Suite\'s scope,' 996 | MSG_MOD_TYPES_TO_MODIFY = 'this tab will work' 997 | 998 | MSG_MOD_COMBO_SCOPE_ALL = 'on all' 999 | MSG_MOD_COMBO_SCOPE_SOME = 'only on' 1000 | MSG_MOD_COMBO_SCOPE_CHOICES = [ 1001 | MSG_MOD_COMBO_SCOPE_ALL, 1002 | MSG_MOD_COMBO_SCOPE_SOME, 1003 | ] 1004 | MSG_MOD_COMBO_TYPE_REQ = 'requests' 1005 | MSG_MOD_COMBO_TYPE_RESP = 'responses' 1006 | MSG_MOD_COMBO_TYPE_BOTH = 'requests and responses' 1007 | MSG_MOD_COMBO_TYPE_CHOICES = [ 1008 | MSG_MOD_COMBO_TYPE_REQ , 1009 | MSG_MOD_COMBO_TYPE_RESP, 1010 | MSG_MOD_COMBO_TYPE_BOTH, 1011 | ] 1012 | MSG_MOD_SCOPE_SOME = ' containing this expression:' 1013 | 1014 | # Handling pane 1015 | PARAM_HANDL_GROUP = 'Parameter handling' 1016 | PARAM_HANDL_AUTO_ENCODE = 'Automatically URL-encode the first line of the request, if modified' 1017 | PARAM_HANDL_ENABLE_FORWARDER = 'Change the destination of the request' 1018 | PARAM_HANDL_MATCH_EXP = ' 1) Find matches to this expression:' 1019 | PARAM_HANDL_TARGET = '2) Target' 1020 | 1021 | PARAM_HANDL_COMBO_INDICES_FIRST = 'the first' 1022 | PARAM_HANDL_COMBO_INDICES_EACH = 'each' 1023 | PARAM_HANDL_COMBO_INDICES_SUBSET = 'a subset' 1024 | PARAM_HANDL_COMBO_INDICES_CHOICES = [ 1025 | PARAM_HANDL_COMBO_INDICES_FIRST , 1026 | PARAM_HANDL_COMBO_INDICES_EACH , 1027 | PARAM_HANDL_COMBO_INDICES_SUBSET, 1028 | ] 1029 | PARAM_HANDL_MATCH_RANGE = 'of the matches' 1030 | PARAM_HANDL_MATCH_SUBSET = 'Which subset?' 1031 | PARAM_HANDL_ACTION = ' 3) Replace each target with this expression:' 1032 | 1033 | PARAM_HANDL_DYNAMIC_CHECKBOX = 'The value I need is dynamic' 1034 | PARAM_HANDL_DYNAMIC_DESCRIPTION = '4) In the expression above, refer to the named RegEx groups you\'ll define below in order to insert:' 1035 | 1036 | PARAM_HANDL_COMBO_EXTRACT_SINGLE = 'values returned by issuing a single request' 1037 | PARAM_HANDL_COMBO_EXTRACT_MACRO = 'values returned by issuing a sequence of requests' 1038 | PARAM_HANDL_COMBO_EXTRACT_CACHED = 'values in the cached response of a previous CPH tab' 1039 | PARAM_HANDL_COMBO_EXTRACT_CHOICES = [ 1040 | PARAM_HANDL_COMBO_EXTRACT_SINGLE, 1041 | PARAM_HANDL_COMBO_EXTRACT_MACRO , 1042 | PARAM_HANDL_COMBO_EXTRACT_CACHED, 1043 | ] 1044 | PARAM_HANDL_BTN_ISSUE = 'Issue' 1045 | PARAM_HANDL_EXTRACT_STATIC = 'When "RegEx" is enabled here, backslash escape sequences and group references will be processed.' 1046 | PARAM_HANDL_EXTRACT_SINGLE = 'the request in the left pane, then extract the value from its response with this expression:' 1047 | PARAM_HANDL_EXTRACT_MACRO = 'When invoked from a Session Handling Rule, CPH will extract the value from the final macro response with this expression:' 1048 | PARAM_HANDL_EXTRACT_CACHED_PRE = 'Extract the value from' 1049 | PARAM_HANDL_EXTRACT_CACHED_POST = '\'s cached response with this expression:' 1050 | 1051 | EXPRESSION_CONFIG = namedtuple('EXPRESSION_CONFIG', 'is_regex, expression') 1052 | SOCKET_CONFIG = namedtuple('SOCKET_CONFIG' , 'https, host, port') 1053 | 1054 | def __init__(self, message=None): 1055 | SubTab.__init__(self) 1056 | 1057 | index = MainTab.mainpane.getTabCount() - 1 1058 | MainTab.mainpane.add(self, index) 1059 | self.tabtitle_pane = ConfigTabTitle() 1060 | MainTab.mainpane.setTabComponentAt(index, self.tabtitle_pane) 1061 | MainTab.mainpane.setSelectedIndex(index) 1062 | 1063 | btn_back = SubTab.set_title_font(JButton(self.BTN_BACK)) 1064 | btn_fwd = SubTab.set_title_font(JButton(self.BTN_FWD)) 1065 | btn_back.addActionListener(self) 1066 | btn_fwd .addActionListener(self) 1067 | 1068 | btn_clonetab = JButton(self.BTN_CLONETAB) 1069 | btn_clonetab.addActionListener(self) 1070 | 1071 | controlpane = JPanel(FlowLayout(FlowLayout.LEADING)) 1072 | controlpane.add(btn_back) 1073 | controlpane.add(btn_fwd) 1074 | controlpane.add(SubTab.create_blank_space()) 1075 | controlpane.add(btn_clonetab) 1076 | 1077 | namepane = JPanel(FlowLayout(FlowLayout.LEADING)) 1078 | namepane.add(SubTab.set_title_font(JLabel(self.TAB_NAME))) 1079 | self.namepane_txtfield = ConfigTabNameField(self.tabtitle_pane.label) 1080 | namepane.add(self.namepane_txtfield) 1081 | 1082 | msg_mod_layout_pane = JPanel(GridBagLayout()) 1083 | msg_mod_layout_pane.setBorder(BorderFactory.createTitledBorder(self.MSG_MOD_GROUP)) 1084 | msg_mod_layout_pane.getBorder().setTitleFont(Font(Font.SANS_SERIF, Font.ITALIC, 16)) 1085 | 1086 | param_handl_layout_pane = JPanel(GridBagLayout()) 1087 | param_handl_layout_pane.setBorder(BorderFactory.createTitledBorder(self.PARAM_HANDL_GROUP)) 1088 | param_handl_layout_pane.getBorder().setTitleFont(Font(Font.SANS_SERIF, Font.ITALIC, 16)) 1089 | 1090 | self.msg_mod_combo_scope = JComboBox(self.MSG_MOD_COMBO_SCOPE_CHOICES) 1091 | self.msg_mod_combo_type = JComboBox(self.MSG_MOD_COMBO_TYPE_CHOICES) 1092 | self.msg_mod_combo_scope.addActionListener(self) 1093 | self.msg_mod_combo_type .addActionListener(self) 1094 | 1095 | self.msg_mod_exp_pane_scope = self.create_expression_pane() 1096 | self.msg_mod_exp_pane_scope_lbl = JLabel(self.MSG_MOD_SCOPE_SOME) 1097 | self.msg_mod_exp_pane_scope .setVisible(False) 1098 | self.msg_mod_exp_pane_scope_lbl.setVisible(False) 1099 | 1100 | self.param_handl_auto_encode_chkbox = JCheckBox(self.PARAM_HANDL_AUTO_ENCODE , False) 1101 | self.param_handl_enable_forwarder_chkbox = JCheckBox(self.PARAM_HANDL_ENABLE_FORWARDER, False) 1102 | self.param_handl_enable_forwarder_chkbox.addActionListener(self) 1103 | 1104 | self.param_handl_forwarder_socket_pane = self.create_socket_pane() 1105 | self.param_handl_forwarder_socket_pane.setVisible(False) 1106 | 1107 | self.param_handl_exp_pane_target = self.create_expression_pane() 1108 | self.param_handl_combo_indices = JComboBox(self.PARAM_HANDL_COMBO_INDICES_CHOICES) 1109 | self.param_handl_combo_indices.addActionListener(self) 1110 | 1111 | self.param_handl_txtfield_match_indices = JTextField(12) 1112 | self.param_handl_txtfield_match_indices.addKeyListener( 1113 | UndoableKeyListener(self.param_handl_txtfield_match_indices) 1114 | ) 1115 | self.param_handl_txtfield_match_indices.setText('0') 1116 | self.param_handl_txtfield_match_indices.setEnabled(False) 1117 | 1118 | self.param_handl_button_indices_help = self.HelpButton(CPH_Help.indices) 1119 | self.param_handl_button_indices_help.addActionListener(self) 1120 | 1121 | self.param_handl_subset_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1122 | 1123 | self.param_handl_dynamic_chkbox = JCheckBox(self.PARAM_HANDL_DYNAMIC_CHECKBOX, False) 1124 | self.param_handl_dynamic_chkbox.addActionListener(self) 1125 | 1126 | self.param_handl_dynamic_pane = JPanel(GridBagLayout()) 1127 | self.param_handl_dynamic_pane.setVisible(False) 1128 | 1129 | self.param_handl_exp_pane_extract_static = self.create_expression_pane(label=self.PARAM_HANDL_EXTRACT_STATIC, multiline=False) 1130 | self.param_handl_exp_pane_extract_single = self.create_expression_pane(enabled=False) 1131 | self.param_handl_exp_pane_extract_macro = self.create_expression_pane(label=self.PARAM_HANDL_EXTRACT_MACRO, enabled=False) 1132 | self.param_handl_exp_pane_extract_cached = self.create_expression_pane(enabled=False) 1133 | 1134 | self.param_handl_issuer_socket_pane = self.create_socket_pane() 1135 | 1136 | self.request , self.response = self.initialize_req_resp() 1137 | self.cached_request, self.cached_response = self.initialize_req_resp() 1138 | 1139 | if message: # init argument, defaults to None, set when using 'Send to CPH' 1140 | self.request = message.getRequest() 1141 | resp = message.getResponse() 1142 | if resp: 1143 | self.response = resp 1144 | httpsvc = message.getHttpService() 1145 | self.get_socket_pane_component(self.param_handl_issuer_socket_pane, self.HOST_INDEX).setText(httpsvc.getHost()) 1146 | self.get_socket_pane_component(self.param_handl_issuer_socket_pane, self.PORT_INDEX).setValue(httpsvc.getPort()) 1147 | self.get_socket_pane_component(self.param_handl_issuer_socket_pane, self.HTTPS_INDEX).setSelected(httpsvc.getProtocol() == 'https') 1148 | 1149 | self.param_handl_request_editor = MainTab._cph.callbacks.createMessageEditor(None, True) 1150 | self.param_handl_response_editor = MainTab._cph.callbacks.createMessageEditor(None, False) 1151 | self.param_handl_request_editor .setMessage(self.request , True) 1152 | self.param_handl_response_editor.setMessage(self.response, False) 1153 | 1154 | self.param_handl_cached_req_viewer = MainTab._cph.callbacks.createMessageEditor(None, False) 1155 | self.param_handl_cached_resp_viewer = MainTab._cph.callbacks.createMessageEditor(None, False) 1156 | self.param_handl_cached_req_viewer .setMessage(self.cached_request , True) 1157 | self.param_handl_cached_resp_viewer.setMessage(self.cached_response, False) 1158 | 1159 | self.param_handl_cardpanel_static_or_extract = JPanel(FlexibleCardLayout()) 1160 | 1161 | self.param_handl_combo_extract = JComboBox(self.PARAM_HANDL_COMBO_EXTRACT_CHOICES) 1162 | self.param_handl_combo_extract.addActionListener(self) 1163 | 1164 | self.param_handl_button_named_groups_help = self.HelpButton(CPH_Help.named_groups) 1165 | self.param_handl_button_named_groups_help.addActionListener(self) 1166 | 1167 | # These ones don't need ActionListeners; see actionPerformed(). 1168 | self.param_handl_button_extract_single_help = self.HelpButton(CPH_Help.extract_single) 1169 | self.param_handl_button_extract_macro_help = self.HelpButton(CPH_Help.extract_macro ) 1170 | self.param_handl_button_extract_cached_help = self.HelpButton(CPH_Help.extract_cached) 1171 | 1172 | self.param_handl_combo_cached = JComboBox() 1173 | self.param_handl_combo_cached.addActionListener(self) 1174 | 1175 | self.build_msg_mod_pane(msg_mod_layout_pane) 1176 | self.build_param_handl_pane(param_handl_layout_pane) 1177 | 1178 | if self.request: 1179 | self.param_handl_combo_extract.setSelectedItem(self.PARAM_HANDL_COMBO_EXTRACT_SINGLE) 1180 | # Using doClick() since it's initially unchecked, which means it'll get checked *and* the ActionListener will trigger. 1181 | self.param_handl_dynamic_chkbox.doClick() 1182 | 1183 | for previous_tab in MainTab.get_config_tabs(): 1184 | if previous_tab == self: 1185 | break 1186 | self.param_handl_combo_cached.addItem(previous_tab.namepane_txtfield.getText()) 1187 | 1188 | constraints = self.initialize_constraints() 1189 | constraints.weighty = 0.05 1190 | self._main_tab_pane.add(controlpane, constraints) 1191 | constraints.gridy = 1 1192 | self._main_tab_pane.add(namepane, constraints) 1193 | constraints.gridy = 2 1194 | self._main_tab_pane.add(msg_mod_layout_pane, constraints) 1195 | constraints.gridy = 3 1196 | constraints.weighty = 1 1197 | self._main_tab_pane.add(param_handl_layout_pane, constraints) 1198 | 1199 | self.emv_tab = EMVTab() 1200 | MainTab.get_options_tab().emv_tab_pane.add(self.namepane_txtfield.getText(), self.emv_tab) 1201 | 1202 | self.config_mechanisms = [ 1203 | SubTab.CONFIG_MECHANISM( 1204 | 'enabled', 1205 | self.tabtitle_pane.enable_chkbox.isSelected, 1206 | lambda cv: self.tabtitle_pane.enable_chkbox.setSelected(cv) 1207 | ), 1208 | SubTab.CONFIG_MECHANISM( 1209 | 'modify_scope_choice_index', 1210 | self.msg_mod_combo_scope.getSelectedIndex, 1211 | lambda cv: self.msg_mod_combo_scope.setSelectedIndex(cv) 1212 | ), 1213 | SubTab.CONFIG_MECHANISM( 1214 | 'modify_type_choice_index', 1215 | self.msg_mod_combo_type.getSelectedIndex, 1216 | lambda cv: self.msg_mod_combo_type.setSelectedIndex(cv) 1217 | ), 1218 | SubTab.CONFIG_MECHANISM( 1219 | 'modify_expression', 1220 | lambda : self.get_exp_pane_config(self.msg_mod_exp_pane_scope ), 1221 | lambda cv: self.set_exp_pane_config(self.msg_mod_exp_pane_scope, cv) 1222 | ), 1223 | SubTab.CONFIG_MECHANISM( 1224 | 'auto_encode', 1225 | self.param_handl_auto_encode_chkbox.isSelected, 1226 | lambda cv: self.param_handl_auto_encode_chkbox.setSelected(cv) 1227 | ), 1228 | SubTab.CONFIG_MECHANISM( 1229 | 'enable_forwarder', 1230 | self.param_handl_enable_forwarder_chkbox.isSelected, 1231 | lambda cv: self.param_handl_enable_forwarder_chkbox.setSelected(cv) 1232 | ), 1233 | SubTab.CONFIG_MECHANISM( 1234 | 'forwarder', 1235 | lambda : self.get_socket_pane_config(self.param_handl_forwarder_socket_pane ), 1236 | lambda cv: self.set_socket_pane_config(self.param_handl_forwarder_socket_pane, cv) 1237 | ), 1238 | SubTab.CONFIG_MECHANISM( 1239 | 'match_expression', 1240 | lambda : self.get_exp_pane_config(self.param_handl_exp_pane_target ), 1241 | lambda cv: self.set_exp_pane_config(self.param_handl_exp_pane_target, cv) 1242 | ), 1243 | SubTab.CONFIG_MECHANISM( 1244 | 'indices_choice_index', 1245 | self.param_handl_combo_indices.getSelectedIndex, 1246 | lambda cv: self.param_handl_combo_indices.setSelectedIndex(cv) 1247 | ), 1248 | SubTab.CONFIG_MECHANISM( 1249 | 'extract_choice_index', 1250 | self.param_handl_combo_extract.getSelectedIndex, 1251 | lambda cv: self.param_handl_combo_extract.setSelectedIndex(cv) 1252 | ), 1253 | SubTab.CONFIG_MECHANISM( 1254 | 'match_indices', 1255 | self.param_handl_txtfield_match_indices.getText, 1256 | lambda cv: self.param_handl_txtfield_match_indices.setText(cv) 1257 | ), 1258 | SubTab.CONFIG_MECHANISM( 1259 | 'static_expression', 1260 | lambda : self.get_exp_pane_config(self.param_handl_exp_pane_extract_static ), 1261 | lambda cv: self.set_exp_pane_config(self.param_handl_exp_pane_extract_static, cv) 1262 | ), 1263 | SubTab.CONFIG_MECHANISM( 1264 | 'dynamic_checkbox', 1265 | self.param_handl_dynamic_chkbox.isSelected, 1266 | lambda cv: self.param_handl_dynamic_chkbox.setSelected(cv) 1267 | ), 1268 | SubTab.CONFIG_MECHANISM( 1269 | 'single_expression', 1270 | lambda : self.get_exp_pane_config(self.param_handl_exp_pane_extract_single ), 1271 | lambda cv: self.set_exp_pane_config(self.param_handl_exp_pane_extract_single, cv) 1272 | ), 1273 | SubTab.CONFIG_MECHANISM( 1274 | 'issuer', 1275 | lambda : self.get_socket_pane_config(self.param_handl_issuer_socket_pane ), 1276 | lambda cv: self.set_socket_pane_config(self.param_handl_issuer_socket_pane, cv) 1277 | ), 1278 | SubTab.CONFIG_MECHANISM( 1279 | 'single_request', 1280 | lambda : MainTab._cph.helpers.bytesToString(self.param_handl_request_editor.getMessage()), 1281 | lambda cv: self.param_handl_request_editor.setMessage(MainTab._cph.helpers.stringToBytes(cv), True) 1282 | ), 1283 | SubTab.CONFIG_MECHANISM( 1284 | 'single_response', 1285 | lambda : MainTab._cph.helpers.bytesToString(self.param_handl_response_editor.getMessage()), 1286 | lambda cv: self.param_handl_response_editor.setMessage(MainTab._cph.helpers.stringToBytes(cv), False) 1287 | ), 1288 | SubTab.CONFIG_MECHANISM( 1289 | 'macro_expression', 1290 | lambda : self.get_exp_pane_config(self.param_handl_exp_pane_extract_macro ), 1291 | lambda cv: self.set_exp_pane_config(self.param_handl_exp_pane_extract_macro, cv) 1292 | ), 1293 | SubTab.CONFIG_MECHANISM( 1294 | 'cached_expression', 1295 | lambda : self.get_exp_pane_config(self.param_handl_exp_pane_extract_cached ), 1296 | lambda cv: self.set_exp_pane_config(self.param_handl_exp_pane_extract_cached, cv) 1297 | ), 1298 | SubTab.CONFIG_MECHANISM( 1299 | 'cached_selection', 1300 | self.param_handl_combo_cached.getSelectedItem, 1301 | lambda cv: self.param_handl_combo_cached.setSelectedItem(cv) 1302 | ), 1303 | ] 1304 | 1305 | def initialize_req_resp(self): 1306 | return [], MainTab._cph.helpers.stringToBytes(''.join([' \r\n' for i in range(6)])) 1307 | 1308 | def create_expression_pane(self, label=None, multiline=True, checked=True, enabled=True): 1309 | field = JTextArea() 1310 | if not multiline: 1311 | field = JTextField() 1312 | field.setColumns(self.TXT_FIELD_SIZE) 1313 | field.setFont(self.FIELD_FONT) 1314 | field.addKeyListener(UndoableKeyListener(field)) 1315 | 1316 | box = JCheckBox(self.REGEX, checked) 1317 | if not enabled: 1318 | box.setEnabled(False) 1319 | 1320 | child_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1321 | child_pane.add(box) 1322 | child_pane.add(field) 1323 | 1324 | parent_pane = JPanel(GridBagLayout()) 1325 | constraints = self.initialize_constraints() 1326 | if label: 1327 | parent_pane.add(JLabel(label), constraints) 1328 | constraints.gridy += 1 1329 | parent_pane.add(child_pane, constraints) 1330 | 1331 | return parent_pane 1332 | 1333 | def create_socket_pane(self): 1334 | host_field = JTextField() 1335 | host_field.setColumns(self.TXT_FIELD_SIZE) 1336 | host_field.setText('host') 1337 | host_field.setFont(self.FIELD_FONT) 1338 | host_field.addKeyListener(UndoableKeyListener(host_field)) 1339 | 1340 | port_spinner = JSpinner(SpinnerNumberModel(80, 1, 65535, 1)) 1341 | port_spinner.setFont(self.FIELD_FONT) 1342 | port_spinner.setEditor(JSpinner.NumberEditor(port_spinner, '#')) 1343 | 1344 | socket_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1345 | https_box = JCheckBox('HTTPS') 1346 | https_box.setSelected(False) 1347 | socket_pane.add(https_box ) 1348 | socket_pane.add(host_field ) 1349 | socket_pane.add(JLabel(':') ) 1350 | socket_pane.add(port_spinner) 1351 | 1352 | return socket_pane 1353 | 1354 | def get_exp_pane_component(self, pane, component_index): 1355 | """ 1356 | component_index values: 1357 | 0: regex checkbox 1358 | 1: expression field 1359 | See create_expression_pane() for further details 1360 | """ 1361 | comp_count = pane.getComponentCount() 1362 | if comp_count == 1: 1363 | # then there's no label and child_pane is the only component 1364 | child_pane = pane.getComponent(0) 1365 | elif comp_count == 2: 1366 | # then there is a label and child_pane is the second component 1367 | child_pane = pane.getComponent(1) 1368 | return child_pane.getComponent(component_index) 1369 | 1370 | def get_exp_pane_expression(self, pane): 1371 | expression = self.get_exp_pane_component(pane, ConfigTab.TXT_FIELD_INDEX).getText() 1372 | # If the RegEx checkbox is unchecked, run re.escape() 1373 | # in order to treat it like a literal string. 1374 | if not self.get_exp_pane_component(pane, ConfigTab.CHECKBOX_INDEX).isSelected(): 1375 | expression = re_escape(expression) 1376 | return expression 1377 | 1378 | def get_exp_pane_config(self, pane): 1379 | config = self.EXPRESSION_CONFIG( 1380 | self.get_exp_pane_component(pane, ConfigTab.CHECKBOX_INDEX).isSelected(), 1381 | self.get_exp_pane_component(pane, ConfigTab.TXT_FIELD_INDEX).getText() 1382 | ) 1383 | return config 1384 | 1385 | def set_exp_pane_config(self, pane, config): 1386 | config = self.EXPRESSION_CONFIG(*config) 1387 | self.get_exp_pane_component(pane, ConfigTab.CHECKBOX_INDEX ).setSelected(config.is_regex ) 1388 | self.get_exp_pane_component(pane, ConfigTab.TXT_FIELD_INDEX).setText (config.expression) 1389 | 1390 | def get_socket_pane_component(self, pane, component_index): 1391 | """ 1392 | indices_tuple values: 1393 | 0: https checkbox 1394 | 1: host field 1395 | 3: port spinner (2 is the ':' JLabel) 1396 | See create_socket_pane() for further details 1397 | """ 1398 | return pane.getComponent(component_index) 1399 | 1400 | def get_socket_pane_config(self, pane): 1401 | config = self.SOCKET_CONFIG( 1402 | self.get_socket_pane_component(pane, ConfigTab.HTTPS_INDEX).isSelected(), 1403 | self.get_socket_pane_component(pane, ConfigTab.HOST_INDEX ).getText (), 1404 | self.get_socket_pane_component(pane, ConfigTab.PORT_INDEX ).getValue () 1405 | ) 1406 | return config 1407 | 1408 | def set_socket_pane_config(self, pane, config): 1409 | config = self.SOCKET_CONFIG(*config) 1410 | self.get_socket_pane_component(pane, ConfigTab.HTTPS_INDEX).setSelected(config.https) 1411 | self.get_socket_pane_component(pane, ConfigTab.HOST_INDEX ).setText (config.host ) 1412 | self.get_socket_pane_component(pane, ConfigTab.PORT_INDEX ).setValue (config.port ) 1413 | 1414 | def build_msg_mod_pane(self, msg_mod_pane): 1415 | msg_mod_req_or_resp_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1416 | msg_mod_req_or_resp_pane.add(JLabel(self.MSG_MOD_TYPES_TO_MODIFY)) 1417 | msg_mod_req_or_resp_pane.add(self.msg_mod_combo_scope) 1418 | msg_mod_req_or_resp_pane.add(self.msg_mod_combo_type) 1419 | msg_mod_req_or_resp_pane.add(self.msg_mod_exp_pane_scope_lbl) 1420 | 1421 | constraints = self.initialize_constraints() 1422 | msg_mod_pane.add(SubTab.set_title_font(JLabel(self.MSG_MOD_SCOPE_BURP)), constraints) 1423 | constraints.gridy = 1 1424 | msg_mod_pane.add(msg_mod_req_or_resp_pane, constraints) 1425 | constraints.gridy = 2 1426 | msg_mod_pane.add(self.msg_mod_exp_pane_scope, constraints) 1427 | 1428 | def build_param_handl_pane(self, param_derivation_pane): 1429 | target_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1430 | target_pane.add(SubTab.set_title_font(JLabel(self.PARAM_HANDL_TARGET))) 1431 | target_pane.add(self.param_handl_combo_indices) 1432 | target_pane.add(SubTab.set_title_font(JLabel(self.PARAM_HANDL_MATCH_RANGE))) 1433 | 1434 | self.param_handl_subset_pane.add(JLabel(self.PARAM_HANDL_MATCH_SUBSET)) 1435 | self.param_handl_subset_pane.add(self.param_handl_txtfield_match_indices) 1436 | self.param_handl_subset_pane.add(self.param_handl_button_indices_help) 1437 | self.param_handl_subset_pane.setVisible(False) 1438 | 1439 | derive_param_single_card = JPanel(GridBagLayout()) 1440 | constraints = self.initialize_constraints() 1441 | derive_param_single_card.add(self.param_handl_issuer_socket_pane, constraints) 1442 | constraints.gridy = 1 1443 | issue_request_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1444 | issue_request_button = JButton(self.PARAM_HANDL_BTN_ISSUE) 1445 | issue_request_button.addActionListener(self) 1446 | issue_request_pane.add(issue_request_button) 1447 | issue_request_pane.add(JLabel(self.PARAM_HANDL_EXTRACT_SINGLE)) 1448 | derive_param_single_card.add(issue_request_pane, constraints) 1449 | constraints.gridy = 2 1450 | derive_param_single_card.add(self.param_handl_exp_pane_extract_single, constraints) 1451 | constraints.gridy = 3 1452 | constraints.gridwidth = 2 1453 | splitpane = JSplitPane() 1454 | splitpane.setLeftComponent (self.param_handl_request_editor .getComponent()) 1455 | splitpane.setRightComponent(self.param_handl_response_editor.getComponent()) 1456 | derive_param_single_card.add(splitpane, constraints) 1457 | splitpane.setDividerLocation(0.5) 1458 | 1459 | derive_param_macro_card = JPanel(GridBagLayout()) 1460 | constraints = self.initialize_constraints() 1461 | derive_param_macro_card.add(self.param_handl_exp_pane_extract_macro, constraints) 1462 | 1463 | cached_param_card = JPanel(GridBagLayout()) 1464 | constraints = self.initialize_constraints() 1465 | tab_choice_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1466 | tab_choice_pane.add(JLabel(self.PARAM_HANDL_EXTRACT_CACHED_PRE)) 1467 | tab_choice_pane.add(self.param_handl_combo_cached) 1468 | tab_choice_pane.add(JLabel(self.PARAM_HANDL_EXTRACT_CACHED_POST)) 1469 | cached_param_card.add(tab_choice_pane, constraints) 1470 | constraints.gridy = 1 1471 | cached_param_card.add(self.param_handl_exp_pane_extract_cached, constraints) 1472 | constraints.gridy = 2 1473 | constraints.gridwidth = 2 1474 | splitpane = JSplitPane() 1475 | splitpane.setLeftComponent (self.param_handl_cached_req_viewer .getComponent()) 1476 | splitpane.setRightComponent(self.param_handl_cached_resp_viewer.getComponent()) 1477 | cached_param_card.add(splitpane, constraints) 1478 | splitpane.setDividerLocation(0.5) 1479 | 1480 | self.param_handl_cardpanel_static_or_extract.add(derive_param_single_card, self.PARAM_HANDL_COMBO_EXTRACT_SINGLE) 1481 | self.param_handl_cardpanel_static_or_extract.add(derive_param_macro_card , self.PARAM_HANDL_COMBO_EXTRACT_MACRO ) 1482 | self.param_handl_cardpanel_static_or_extract.add(cached_param_card , self.PARAM_HANDL_COMBO_EXTRACT_CACHED) 1483 | 1484 | # Making a FlowLayout panel here so the combo box doesn't stretch. 1485 | combo_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1486 | combo_pane.add(self.param_handl_combo_extract) 1487 | placeholder_btn = self.HelpButton() 1488 | placeholder_btn.addActionListener(self) 1489 | combo_pane.add(placeholder_btn) 1490 | combo_pane.add(SubTab.create_blank_space()) 1491 | constraints = self.initialize_constraints() 1492 | dyn_desc_pane = JPanel(FlowLayout(FlowLayout.LEADING)) 1493 | dyn_desc_pane.add(SubTab.set_title_font(JLabel(self.PARAM_HANDL_DYNAMIC_DESCRIPTION))) 1494 | dyn_desc_pane.add(self.param_handl_button_named_groups_help) 1495 | self.param_handl_dynamic_pane.add(dyn_desc_pane, constraints) 1496 | constraints.gridy = 1 1497 | self.param_handl_dynamic_pane.add(combo_pane, constraints) 1498 | constraints.gridy = 2 1499 | constraints.gridwidth = GridBagConstraints.REMAINDER - 1 1500 | self.param_handl_dynamic_pane.add(self.param_handl_cardpanel_static_or_extract, constraints) 1501 | 1502 | constraints = self.initialize_constraints() 1503 | param_derivation_pane.add(self.param_handl_auto_encode_chkbox, constraints) 1504 | constraints.gridy = 1 1505 | param_derivation_pane.add(self.param_handl_enable_forwarder_chkbox, constraints) 1506 | constraints.gridy = 2 1507 | param_derivation_pane.add(self.param_handl_forwarder_socket_pane, constraints) 1508 | constraints.gridy = 3 1509 | param_derivation_pane.add(SubTab.set_title_font(JLabel(self.PARAM_HANDL_MATCH_EXP)), constraints) 1510 | constraints.gridy = 4 1511 | param_derivation_pane.add(self.param_handl_exp_pane_target, constraints) 1512 | constraints.gridy = 5 1513 | param_derivation_pane.add(target_pane, constraints) 1514 | constraints.gridy = 6 1515 | param_derivation_pane.add(self.param_handl_subset_pane, constraints) 1516 | constraints.gridy = 7 1517 | param_derivation_pane.add(SubTab.set_title_font(JLabel(self.PARAM_HANDL_ACTION)), constraints) 1518 | constraints.gridy = 8 1519 | param_derivation_pane.add(self.param_handl_exp_pane_extract_static, constraints) 1520 | constraints.gridy = 9 1521 | param_derivation_pane.add(self.param_handl_dynamic_chkbox, constraints) 1522 | constraints.gridy = 10 1523 | param_derivation_pane.add(self.param_handl_dynamic_pane, constraints) 1524 | 1525 | @staticmethod 1526 | def restore_combo_cached_selection(tab, selected_item): 1527 | tab.param_handl_combo_cached.setSelectedItem(selected_item) 1528 | # If the item has been removed, remove selection. 1529 | if tab.param_handl_combo_cached.getSelectedItem() != selected_item: 1530 | tab.param_handl_combo_cached.setSelectedItem(None) 1531 | if tab.param_handl_combo_extract.getSelectedItem() == ConfigTab.PARAM_HANDL_COMBO_EXTRACT_CACHED: 1532 | MainTab.logger.warning( 1533 | 'Selected cache no longer available for tab "{}"!'.format(tab.namepane_txtfield.getText()) 1534 | ) 1535 | 1536 | @staticmethod 1537 | def move_tab(tab, desired_index): 1538 | # The Options tab is index 0, hence subtracting 1 in a number of lines below. 1539 | if desired_index <= 0 or desired_index >= MainTab.mainpane.getTabCount() - 1: 1540 | return 1541 | 1542 | MainTab.mainpane.setSelectedIndex(0) 1543 | emv_sel_tab = MainTab.get_options_tab().emv_tab_pane.getSelectedComponent() 1544 | current_index = MainTab.mainpane.indexOfComponent(tab) 1545 | combo_cached_item = tab.param_handl_combo_cached.getSelectedItem() 1546 | 1547 | if current_index > desired_index: 1548 | MainTab.mainpane.add(tab, desired_index) 1549 | MainTab.get_options_tab().emv_tab_pane.add(tab.emv_tab, desired_index - 1) 1550 | # Rearrange combo_cached appropriately. 1551 | for i, other_tab in enumerate(MainTab.get_config_tabs()): 1552 | if i < desired_index - 1: 1553 | continue 1554 | selected_item = other_tab.param_handl_combo_cached.getSelectedItem() 1555 | if i > desired_index - 1 and i <= current_index - 1: 1556 | tab.param_handl_combo_cached.removeItemAt(tab.param_handl_combo_cached.getItemCount() - 1) 1557 | other_tab.param_handl_combo_cached.insertItemAt(tab.namepane_txtfield.getText(), desired_index - 1) 1558 | if i > current_index - 1: 1559 | other_tab.param_handl_combo_cached.removeItemAt(current_index - 1) 1560 | other_tab.param_handl_combo_cached.insertItemAt(tab.namepane_txtfield.getText(), desired_index - 1) 1561 | ConfigTab.restore_combo_cached_selection(other_tab, selected_item) 1562 | 1563 | else: 1564 | # I've no idea why +1 is needed here. =) 1565 | MainTab.mainpane.add(tab, desired_index + 1) 1566 | MainTab.get_options_tab().emv_tab_pane.add(tab.emv_tab, desired_index) 1567 | # Rearrange combo_cached appropriately. 1568 | for i, other_tab in enumerate(MainTab.get_config_tabs()): 1569 | if i < current_index - 1: 1570 | continue 1571 | selected_item = other_tab.param_handl_combo_cached.getSelectedItem() 1572 | if i < desired_index - 1: 1573 | tab.param_handl_combo_cached.insertItemAt(other_tab.namepane_txtfield.getText(), i) 1574 | other_tab.param_handl_combo_cached.removeItemAt(current_index - 1) 1575 | if i > desired_index - 1: 1576 | other_tab.param_handl_combo_cached.removeItemAt(current_index - 1) 1577 | other_tab.param_handl_combo_cached.insertItemAt(tab.namepane_txtfield.getText(), desired_index - 1) 1578 | ConfigTab.restore_combo_cached_selection(other_tab, selected_item) 1579 | 1580 | MainTab.mainpane.setTabComponentAt(desired_index, tab.tabtitle_pane) 1581 | MainTab.mainpane.setSelectedIndex (desired_index) 1582 | MainTab.get_options_tab().emv_tab_pane.setTitleAt( 1583 | desired_index - 1, 1584 | tab.namepane_txtfield.getText() 1585 | ) 1586 | MainTab.get_options_tab().emv_tab_pane.setSelectedComponent(emv_sel_tab) 1587 | ConfigTab.restore_combo_cached_selection(tab, combo_cached_item) 1588 | 1589 | @staticmethod 1590 | def move_tab_back(tab): 1591 | desired_index = MainTab.mainpane.getSelectedIndex() - 1 1592 | ConfigTab.move_tab(tab, desired_index) 1593 | 1594 | @staticmethod 1595 | def move_tab_fwd(tab): 1596 | desired_index = MainTab.mainpane.getSelectedIndex() + 1 1597 | ConfigTab.move_tab(tab, desired_index) 1598 | 1599 | def clone_tab(self): 1600 | desired_index = MainTab.mainpane.getSelectedIndex() + 1 1601 | 1602 | newtab = ConfigTab() 1603 | MainTab.set_tab_name(newtab, self.namepane_txtfield.getText()) 1604 | config = MainTab.get_options_tab().prepare_to_save_tab(self) 1605 | MainTab.get_options_tab().load_config({self.namepane_txtfield.getText(): config}) 1606 | 1607 | ConfigTab.move_tab(newtab, desired_index) 1608 | 1609 | # def disable_cache_viewers(self): 1610 | # self.cached_request, self.cached_response = self.initialize_req_resp() 1611 | # self.param_handl_cached_req_viewer .setMessage(self.cached_request , False) 1612 | # self.param_handl_cached_resp_viewer.setMessage(self.cached_response, False) 1613 | 1614 | # @staticmethod 1615 | # def disable_all_cache_viewers(): 1616 | # for tab in MainTab.mainpane.getComponents(): 1617 | # if isinstance(tab, ConfigTab): 1618 | # tab.disable_cache_viewers() 1619 | 1620 | def actionPerformed(self, e): 1621 | c = e.getActionCommand() 1622 | 1623 | if c == self.BTN_HELP: 1624 | source = e.getSource() 1625 | if hasattr(source, 'title') and source.title: 1626 | source.show_help() 1627 | else: 1628 | # The dynamic help button (placeholder_btn) has no title, 1629 | # so use the selected combobox item to show the appropriate help message. 1630 | extract_combo_selection = self.param_handl_combo_extract.getSelectedItem() 1631 | if extract_combo_selection == self.PARAM_HANDL_COMBO_EXTRACT_SINGLE: 1632 | self.param_handl_button_extract_single_help.show_help() 1633 | if extract_combo_selection == self.PARAM_HANDL_COMBO_EXTRACT_MACRO: 1634 | self.param_handl_button_extract_macro_help.show_help() 1635 | if extract_combo_selection == self.PARAM_HANDL_COMBO_EXTRACT_CACHED: 1636 | self.param_handl_button_extract_cached_help.show_help() 1637 | 1638 | if c == 'comboBoxChanged': 1639 | c = e.getSource().getSelectedItem() 1640 | 1641 | if c == self.MSG_MOD_COMBO_TYPE_RESP: 1642 | self.param_handl_auto_encode_chkbox .setVisible(False) 1643 | self.param_handl_enable_forwarder_chkbox.setVisible(False) 1644 | self.param_handl_forwarder_socket_pane .setVisible(False) 1645 | elif c == self.MSG_MOD_COMBO_TYPE_REQ or c == self.MSG_MOD_COMBO_TYPE_BOTH: 1646 | self.param_handl_auto_encode_chkbox .setVisible(True) 1647 | self.param_handl_enable_forwarder_chkbox.setVisible(True) 1648 | self.param_handl_forwarder_socket_pane .setVisible(self.param_handl_enable_forwarder_chkbox.isSelected()) 1649 | 1650 | if c == self.MSG_MOD_COMBO_SCOPE_ALL: 1651 | self.msg_mod_exp_pane_scope_lbl.setVisible(False) 1652 | self.msg_mod_exp_pane_scope.setVisible(False) 1653 | if c == self.MSG_MOD_COMBO_SCOPE_SOME: 1654 | self.msg_mod_exp_pane_scope_lbl.setVisible(True) 1655 | self.msg_mod_exp_pane_scope.setVisible(True) 1656 | 1657 | if c == self.PARAM_HANDL_ENABLE_FORWARDER: 1658 | self.param_handl_forwarder_socket_pane.setVisible(self.param_handl_enable_forwarder_chkbox.isSelected()) 1659 | 1660 | if c == self.PARAM_HANDL_COMBO_INDICES_FIRST: 1661 | self.param_handl_txtfield_match_indices.setEnabled(False) 1662 | self.param_handl_txtfield_match_indices.setText('0') 1663 | self.param_handl_subset_pane.setVisible(False) 1664 | if c == self.PARAM_HANDL_COMBO_INDICES_EACH: 1665 | self.param_handl_txtfield_match_indices.setEnabled(False) 1666 | self.param_handl_txtfield_match_indices.setText('0:-1,-1') 1667 | self.param_handl_subset_pane.setVisible(False) 1668 | if c == self.PARAM_HANDL_COMBO_INDICES_SUBSET: 1669 | self.param_handl_txtfield_match_indices.setEnabled(True) 1670 | self.param_handl_subset_pane.setVisible(True) 1671 | 1672 | if c == self.PARAM_HANDL_DYNAMIC_CHECKBOX: 1673 | is_selected = self.param_handl_dynamic_chkbox.isSelected() 1674 | self.param_handl_dynamic_pane.setVisible(is_selected) 1675 | 1676 | if c in self.PARAM_HANDL_COMBO_EXTRACT_CHOICES: 1677 | SubTab.show_card(self.param_handl_cardpanel_static_or_extract, c) 1678 | 1679 | # Set the cached request/response viewers to the selected tab's cache 1680 | if e.getSource() == self.param_handl_combo_cached: 1681 | if c is None: 1682 | req, resp = self.initialize_req_resp() 1683 | if c in MainTab.get_config_tab_names(): 1684 | req, resp = MainTab.get_config_tab_cache(c) 1685 | self.param_handl_cached_req_viewer .setMessage(req , True) 1686 | self.param_handl_cached_resp_viewer.setMessage(resp, False) 1687 | 1688 | if c == self.PARAM_HANDL_BTN_ISSUE: 1689 | start_new_thread(MainTab._cph.issue_request, (self,)) 1690 | 1691 | if c == self.BTN_BACK: 1692 | ConfigTab.move_tab_back(self) 1693 | if c == self.BTN_FWD: 1694 | ConfigTab.move_tab_fwd(self) 1695 | if c == self.BTN_CLONETAB: 1696 | self.clone_tab() 1697 | 1698 | 1699 | class FlexibleCardLayout(CardLayout): 1700 | def __init__(self): 1701 | super(FlexibleCardLayout, self).__init__() 1702 | 1703 | def preferredLayoutSize(self, parent): 1704 | current = FlexibleCardLayout.find_current_component(parent) 1705 | if current: 1706 | insets = parent.getInsets() 1707 | pref = current.getPreferredSize() 1708 | pref.width += insets.left + insets.right 1709 | pref.height += insets.top + insets.bottom 1710 | return pref 1711 | return super.preferredLayoutSize(parent) 1712 | 1713 | @staticmethod 1714 | def find_current_component(parent): 1715 | for comp in parent.getComponents(): 1716 | if comp.isVisible(): 1717 | return comp 1718 | return None 1719 | 1720 | -------------------------------------------------------------------------------- /CPH_Help.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | 3 | 4 | class CPH_Help: 5 | quickstart = """ 6 | The quicksave and quickload functionality (see buttons above) persist through