├── BappManifest.bmf
├── LICENSE
├── BappDescription.html
├── utils
└── CPH_Merger.py
├── README.md
├── tinyweb.py
├── CPH_Help.py
├── CustomParamHandler.py
└── CPH_Config.py
/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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 elespike
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 |
--------------------------------------------------------------------------------
/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 |
7 | - Automatic modification of requests and/or responses based on scope and pattern matching.
8 | - Find and replace with full RegEx support.
9 | - Scoping to specific Burp Suite tools.
10 | - Targeting specific matches (only first, all matches, or hand-picked).
11 | - Chaining and ordering of modifications, and the ability to base next-in-line modifications off previous responses.
12 | - Save/load Config:
13 |
14 | - Quicksave/quickload (persists between Burp/Extender sessions).
15 | - Import/export from and to JSON files.
16 |
17 | - After running a macro, extract a replacement value from its final response.
18 | - Modifying each request/response within a macro.
19 |
20 |
21 | 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 |
--------------------------------------------------------------------------------
/utils/CPH_Merger.py:
--------------------------------------------------------------------------------
1 | from os import path, chdir, SEEK_CUR
2 | from sys import argv
3 |
4 |
5 | chdir(path.dirname(path.abspath(argv[0])))
6 | cph_help = open('../CPH_Help.py' , 'r')
7 | cph_config = open('../CPH_Config.py' , 'r')
8 | cph_main = open('../CustomParamHandler.py', 'r')
9 | tinyweb = open('../tinyweb.py' , 'r')
10 |
11 |
12 | def write_imports(opened_file):
13 | line = opened_file.readline()
14 | while line:
15 | if line.startswith('class'):
16 | opened_file.seek(len(line) * -1, SEEK_CUR)
17 | merged_file.write('\n')
18 | break
19 | if line.strip().startswith('from CPH_Config')\
20 | or line.strip().startswith('from CPH_Help' )\
21 | or line.strip().startswith('from tinyweb' )\
22 | or line.strip().startswith('#' )\
23 | or not line.strip():
24 | line = opened_file.readline()
25 | continue
26 | merged_file.write(line)
27 | line = opened_file.readline()
28 |
29 | with open('../CustomParamHandler_merged.py', 'w') as merged_file:
30 | write_imports(cph_help)
31 | write_imports(tinyweb)
32 | write_imports(cph_config)
33 | write_imports(cph_main)
34 |
35 | for line in cph_help.readlines():
36 | merged_file.write(line)
37 | for line in tinyweb.readlines():
38 | merged_file.write(line)
39 | for line in cph_config.readlines():
40 | merged_file.write(line)
41 | for line in cph_main.readlines():
42 | merged_file.write(line)
43 |
44 |
45 | cph_help .close()
46 | cph_config.close()
47 | cph_main .close()
48 | tinyweb .close()
49 |
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Custom Parameter Handler extension for Burp Suite: a power tool for modifying HTTP messages with surgical precision, even when using macros.
2 |
3 | #### The quicksave and quickload functionality persists through reloading not only the extension, but Burp Suite entirely. All values of each existing configuration tab will be saved, along with the order of all tabs.
4 |
5 | #### Use the Export/Import Config buttons to save/load your current configuration to/from a JSON file.
6 |
7 | ### Manual installation
8 |
9 | 1. Download the [latest release](https://github.com/elespike/burp-cph/releases) file; e.g., `CustomParameterHandler_3.0.py`
10 | 2. Under _Extender_ > _Options_ > _Python Environment_, point Burp to the location of your copy of Jython's standalone .jar file.
11 | a. If you don't already have Jython's standalone .jar file, [download it here](http://www.jython.org/downloads.html).
12 | 3. Finally, add `CustomParamHandler_3.0.py` in _Extender_ > _Extensions_.
13 |
14 | ### Installation from the BApp store
15 |
16 | 1. Under _Extender_ > _Options_ > _Python Environment_, point Burp to the location of your copy of Jython's standalone .jar file.
17 | a. If you don't already have Jython's standalone .jar file, [download it here](http://www.jython.org/downloads.html).
18 | 2. Find and select _Custom Parameter Handler_ within the _Extender_ > _BApp Store_ tab, then click the _Install_ button.
19 |
20 | ### Adding configuration tabs
21 |
22 | - Click '+' to add an empty tab; or
23 | - Select one or many requests from anywhere in Burp, right-click, and choose _Send to CPH_.
24 | This will create as many tabs as the number of selected requests, and populate each tab with each selected request to be issued for parameter extraction.
25 |
26 | ### Enabling/Disabling configuration tabs
27 |
28 | Simply click the checkbox next to the tab's name.
29 | New tabs are enabled by default but require a valid configuration in order to have any effect.
30 |
31 | ### Reordering configuration tabs
32 |
33 | - Click the '<' and '>' buttons to swap the currently selected tab with its preceding or succeeding neighbor.
34 |
35 | Leftmost tabs will be processed first; therefore, tab order may be important, especially when extracting values from cached responses.
36 |
37 | Visit the Wiki to learn more about [utilizing cached responses](https://github.com/elespike/burp-cph/wiki/08.-Utilizing-cached-responses).
38 |
39 | ### Tab configuration at a glance
40 |
41 | #### _Scoping_
42 | Depending on the selected option, this tab will take action on either:
43 | - Requests only, Responses only, or both Requests and Responses; then either
44 | - All requests coming through Burp which are also in Burp's scope; or
45 | - Requests coming through Burp, in Burp's scope, and also matching the given expression.
46 |
47 | #### _Parameter handling_
48 | The supplied expression will be used to find the value that will be replaced with the replacement value.
49 |
50 | RegEx features may be used to fine-tune modifications. Visit the Wiki to learn more.
51 |
52 | When targeting a subset of matches, enter comma-separated, zero-based indices or slices (following Python's slice syntax).
53 | E.g.: `1,3,6:9` would act on the 2nd, 4th, 7th, 8th and 9th matches.
54 |
55 | If not using a static value, the supplied expression will be used to find the desired replacement value.
56 |
57 | ### Please [visit the Wiki](https://github.com/elespike/burp-cph/wiki) for explanations on the remaining options.
58 |
--------------------------------------------------------------------------------
/tinyweb.py:
--------------------------------------------------------------------------------
1 | from SimpleHTTPServer import SimpleHTTPRequestHandler
2 | from json import loads
3 | from random import randint
4 | from re import search as re_search
5 | from urllib import unquote
6 |
7 |
8 | class TinyHandler(SimpleHTTPRequestHandler, object):
9 | the_number = randint(1, 99999)
10 | def __init__(self, *args, **kwargs):
11 | self.protocol_version = 'HTTP/1.1'
12 | super(TinyHandler, self).__init__(*args, **kwargs)
13 |
14 | @staticmethod
15 | def normalize(number):
16 | try:
17 | number = int(number)
18 | except ValueError:
19 | return randint(1, 99999)
20 | if number == 0:
21 | return 1
22 | if number < 0:
23 | number = abs(number)
24 | while number > 99999:
25 | number = number / 10
26 | return number
27 |
28 | def do_GET(self):
29 | headers = {}
30 | response_body = 'https://github.com/elespike/burp-cph/wiki/00.-Interactive-demos'
31 |
32 | if self.path == '/':
33 | headers['Content-Type'] = 'text/html'
34 | response_body = 'Welcome!
Please visit the Wiki for instructions.'
35 |
36 | if self.path.startswith('/number'):
37 | response_body = str(TinyHandler.the_number)
38 |
39 | if self.path.startswith('/indices'):
40 | response_body = '[0][ ]1st [1][ ]2nd [2][ ]3rd\n\n[3][ ]4th [4][ ]5th [5][ ]6th\n\n[6][ ]7th [7][ ]8th [8][ ]9th'
41 |
42 | # E.g., /1/12345
43 | s = re_search('^/[123]/?.*?(\d{1,5})$', self.path)
44 | if s is not None:
45 | number = TinyHandler.normalize(s.group(1))
46 | if number == TinyHandler.the_number:
47 | response_body = '{} was correct!'.format(number)
48 | else:
49 | response_body = 'Try again!'
50 | TinyHandler.the_number = randint(1, 99999)
51 | response_body += '\nNew number: {}'.format(TinyHandler.the_number)
52 |
53 | if self.path.startswith('/echo/'):
54 | response_body = self.path.replace('/echo/', '')
55 | response_body = unquote(response_body)
56 |
57 | if self.path.startswith('/check'):
58 | number = 0
59 | s = re_search('number=(\d{1,5})', self.headers.get('cookie', ''))
60 | if s is not None and s.groups():
61 | number = TinyHandler.normalize(s.group(1))
62 | if not number:
63 | # Search again in the path/querystring.
64 | s = re_search('\d{1,5}', self.path)
65 | if s is not None:
66 | number = TinyHandler.normalize(s.group(0))
67 | if number == TinyHandler.the_number:
68 | response_body = '{} was correct!'.format(number)
69 | else:
70 | response_body = 'Try again!'
71 |
72 | self.respond(response_body, headers)
73 |
74 | def do_POST(self):
75 | headers = {}
76 | response_body = 'Try again!'
77 |
78 | content_length = int(self.headers.get('content-length', 0))
79 | body = self.rfile.read(size=content_length)
80 |
81 | if self.path.startswith('/cookie'):
82 | number = 0
83 | # Accept both JSON and url-encoded form data.
84 | try:
85 | number = TinyHandler.normalize(loads(body)['number'])
86 | except:
87 | s = re_search('number=(\d{1,5})', body)
88 | if s is not None and s.groups():
89 | number = TinyHandler.normalize(s.group(1))
90 | if number == TinyHandler.the_number:
91 | headers['Set-Cookie'] = 'number={}'.format(TinyHandler.the_number)
92 | response_body = '"number" cookie set to {}!'.format(TinyHandler.the_number)
93 |
94 | if self.path.startswith('/number'):
95 | s = re_search('number=(\d{1,5})', self.headers.get('cookie', ''))
96 | number_cookie = 0
97 | if s is not None and s.groups():
98 | number_cookie = int(s.group(1))
99 | if number_cookie == TinyHandler.the_number:
100 | number = randint(1, 99999)
101 | # Accept both JSON and url-encoded form data.
102 | try:
103 | number = TinyHandler.normalize(loads(body)['number'])
104 | except:
105 | s = re_search('number=(\d{1,5})', body)
106 | if s is not None and s.groups():
107 | number = TinyHandler.normalize(s.group(1))
108 | TinyHandler.the_number = number
109 | response_body = 'Number set to {}!'.format(TinyHandler.the_number)
110 |
111 | self.respond(response_body, headers)
112 |
113 | def respond(self, response_body, headers=dict()):
114 | self.send_response(200, 'OK')
115 | self.send_header('Content-Length', len(response_body))
116 | for h, v in headers.items():
117 | self.send_header(h, v)
118 | self.end_headers()
119 | self.wfile.write(response_body)
120 |
121 |
--------------------------------------------------------------------------------
/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
7 | reloading not only the extension, but Burp Suite entirely. All values of each existing
8 | configuration tab will be saved, along with the order of all tabs.
9 |
10 | Use the Export/Import Config buttons to save/load your current configuration to/from a JSON file.
11 |
12 | Adding configuration tabs
13 | - Click '+' to add an empty tab; or
14 | - Select one or many requests from anywhere in Burp, right-click, and choose 'Send to CPH'.
15 | This will create as many tabs as the number of selected requests, and populate each tab
16 | with each selected request to be issued for parameter extraction from its response.
17 |
18 | Enabling/Disabling configuration tabs
19 | Simply click the checkbox next to the tab's name.
20 | New tabs are enabled by default but require a valid configuration in order to have any effect.
21 |
22 | Tab order
23 | Leftmost tabs will be processed first; therefore, tab order may be important,
24 | especially when extracting values from cached responses.
25 | Please visit the Wiki to learn more about utilizing cached responses.
26 | """
27 |
28 | HelpPopup = namedtuple('HelpPopup', 'title, message, url')
29 |
30 | indices = HelpPopup(
31 | 'Targeting a subset of matches',
32 | """
33 | To target a specific subset of matches,
34 | enter comma-separated indices and/or slices, such as:
35 | 0,3,5,7 - targets the 1st, 4th, 6th and 8th matches
36 | 0:7 - targets the first 7 matches but not the 8th match
37 | 0:7,9 - targets the first 7 matches and the 10th match
38 | -1,-2 - targets the last and penultimate matches
39 | 0:-1 - targets all but the last match
40 | """,
41 | 'https://github.com/elespike/burp-cph/wiki/04.-Targeting-matches'
42 | )
43 |
44 | named_groups = HelpPopup(
45 | 'Inserting a dynamic value using named groups',
46 | """
47 | In the expression field shown in step 4,
48 | define named groups for values you wish to extract
49 | from the appropriate response.
50 |
51 | For example, (?P<mygroup>[Ss]ome.[Rr]eg[Ee]x)
52 |
53 | Then, in the expression field shown in step 3,
54 | ensure that the RegEx box is selected,
55 | and use named group references to access your extracted values.
56 |
57 | In line with the above example, \\g<mygroup>
58 | """,
59 | 'https://github.com/elespike/burp-cph/wiki/05.-Issuing-a-separate-request-to-use-a-dynamic-value-from-its-response'
60 | )
61 |
62 | extract_single = HelpPopup(
63 | 'Extracting a value after issuing a request',
64 | """
65 | To replace your target match(es) with a value
66 | or append a value to your target match(es) when
67 | that value depends on another request to be issued,
68 | set up the request on the left pane and craft a RegEx
69 | to extract the desired value from its response.
70 |
71 | The Issue button may be used to test the request,
72 | helping ensure a proper response.
73 | """,
74 | 'https://github.com/elespike/burp-cph/wiki/05.-Issuing-a-separate-request-to-use-a-dynamic-value-from-its-response'
75 | )
76 |
77 | extract_macro = HelpPopup(
78 | 'Extracting a value after issuing sequential requests',
79 | """
80 | To replace your target match(es) with a value
81 | or append a value to your target match(es) when
82 | that value depends on sequential requests to be issued,
83 | set up a Burp Suite Macro and invoke the CPH handler
84 | from the Macro's associated Session Handling Rule.
85 |
86 | Finally, craft a RegEx to extract the desired value
87 | from the final Macro response.
88 | """,
89 | 'https://github.com/elespike/burp-cph/wiki/07.-Extracting-replace-value-from-final-macro-response'
90 | )
91 |
92 | extract_cached = HelpPopup(
93 | 'Extracting a value from a previous tab',
94 | """
95 | To replace your target match(es) with a value
96 | or append a value to your target match(es) when
97 | that value has been cached by a previous CPH tab,
98 | simply select the desired tab from the dynamic drop-down.
99 | NOTE: If the desired tab is not in the drop-down, ensure
100 | that the tab has seen its request at least once.
101 |
102 | Then, craft a RegEx to extract the desired value
103 | from the selected tab's cached response.
104 | Note that disabled tabs will still cache HTTP messages
105 | and therefore can be used as a mechanism for value extraction.
106 | """,
107 | 'https://github.com/elespike/burp-cph/wiki/08.-Utilizing-cached-responses'
108 | )
109 |
110 | def __init__(self):
111 | pass
112 |
113 |
--------------------------------------------------------------------------------
/CustomParamHandler.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from datetime import datetime as dt
4 | from sys import stdout
5 | from urllib import quote
6 |
7 | from logging import (
8 | Formatter ,
9 | INFO ,
10 | StreamHandler,
11 | getLogger ,
12 | )
13 |
14 | from CPH_Config import MainTab
15 |
16 | from burp import IBurpExtender
17 | from burp import IContextMenuFactory
18 | from burp import IExtensionStateListener
19 | from burp import IHttpListener
20 | from burp import ISessionHandlingAction
21 |
22 | from javax.swing import JMenuItem
23 |
24 |
25 | class BurpExtender(IBurpExtender, IContextMenuFactory, IExtensionStateListener, IHttpListener, ISessionHandlingAction):
26 | def __init__(self):
27 | self.messages_to_send = []
28 | self.final_macro_resp = ''
29 |
30 | self.logger = getLogger(__name__)
31 | self.initialize_logger()
32 |
33 | self.maintab = MainTab(self)
34 |
35 | def initialize_logger(self):
36 | fmt = '\n%(asctime)s:%(msecs)03d [%(levelname)s] %(message)s'
37 | datefmt = '%H:%M:%S'
38 | formatter = Formatter(fmt=fmt, datefmt=datefmt)
39 |
40 | handler = StreamHandler(stream=stdout)
41 | handler.setFormatter(formatter)
42 |
43 | self.logger.addHandler(handler)
44 | self.logger.setLevel(INFO)
45 |
46 | def registerExtenderCallbacks(self, callbacks):
47 | self.callbacks = callbacks
48 | self.helpers = callbacks.getHelpers()
49 | callbacks.setExtensionName('Custom Parameter Handler')
50 | callbacks.registerContextMenuFactory(self)
51 | callbacks.registerExtensionStateListener(self)
52 | callbacks.registerHttpListener(self)
53 | callbacks.registerSessionHandlingAction(self)
54 | callbacks.addSuiteTab(self.maintab)
55 |
56 | def getActionName(self):
57 | return 'CPH: extract replace value from the final macro response'
58 |
59 | def performAction(self, currentRequest, macroItems):
60 | if not macroItems:
61 | self.logger.error('No macro found, or macro is empty!')
62 | return
63 | self.final_macro_resp = self.helpers.bytesToString(macroItems[-1].getResponse())
64 |
65 | def createMenuItems(self, invocation):
66 | context = invocation.getInvocationContext()
67 | if context == invocation.CONTEXT_MESSAGE_EDITOR_REQUEST \
68 | or context == invocation.CONTEXT_MESSAGE_VIEWER_REQUEST \
69 | or context == invocation.CONTEXT_PROXY_HISTORY \
70 | or context == invocation.CONTEXT_TARGET_SITE_MAP_TABLE \
71 | or context == invocation.CONTEXT_SEARCH_RESULTS:
72 | self.messages_to_send = invocation.getSelectedMessages()
73 | if len(self.messages_to_send):
74 | return [JMenuItem('Send to CPH', actionPerformed=self.send_to_cph)]
75 | else:
76 | return None
77 |
78 | def send_to_cph(self, e):
79 | self.maintab.add_config_tab(self.messages_to_send)
80 |
81 | def extensionUnloaded(self):
82 | if self.maintab.options_tab.httpd is not None:
83 | self.maintab.options_tab.httpd.shutdown()
84 | self.maintab.options_tab.httpd.server_close()
85 | try:
86 | while self.maintab.options_tab.emv_tab_pane.getTabCount():
87 | self.maintab.options_tab.emv_tab_pane.remove(
88 | self.maintab.options_tab.emv_tab_pane.getTabCount() - 1
89 | )
90 | self.maintab.options_tab.emv.dispose()
91 | except AttributeError:
92 | self.logger.warning(
93 | 'Effective Modification Viewer not found! You may be using an outdated version of CPH!'
94 | )
95 |
96 | while self.maintab.mainpane.getTabCount():
97 | # For some reason, the last tab isn't removed until the next loop,
98 | # hence the try/except block with just a continue. Thx, Java.
99 | try:
100 | self.maintab.mainpane.remove(
101 | self.maintab.mainpane.getTabCount() - 1
102 | )
103 | except:
104 | continue
105 |
106 | def issue_request(self, tab):
107 | tab.request = tab.param_handl_request_editor.getMessage()
108 |
109 | issuer_config = tab.get_socket_pane_config(tab.param_handl_issuer_socket_pane)
110 | host = issuer_config.host
111 | port = issuer_config.port
112 | https = issuer_config.https
113 |
114 | tab.request = self.update_content_length(tab.request, True)
115 | tab.param_handl_request_editor.setMessage(tab.request, True)
116 |
117 | try:
118 | httpsvc = self.helpers.buildHttpService(host, port, https)
119 | response_bytes = self.callbacks.makeHttpRequest(httpsvc, tab.request).getResponse()
120 | self.logger.debug('Issued configured request from tab "{}" to host "{}:{}"'.format(
121 | tab.namepane_txtfield.getText(),
122 | httpsvc.getHost(),
123 | httpsvc.getPort()
124 | ))
125 | if response_bytes:
126 | tab.param_handl_response_editor.setMessage(response_bytes, False)
127 | tab.response = response_bytes
128 | self.logger.debug('Got response!')
129 | # Generic except because misc. Java exceptions might occur.
130 | except:
131 | self.logger.exception('Error issuing configured request from tab "{}" to host "{}:{}"'.format(
132 | tab.namepane_txtfield.getText(),
133 | host,
134 | port
135 | ))
136 | tab.response = self.helpers.stringToBytes('Error! See extension output for details.')
137 | tab.param_handl_response_editor.setMessage(tab.response, False)
138 |
139 | def update_content_length(self, message_bytes, is_request):
140 | if is_request:
141 | message_info = self.helpers.analyzeRequest(message_bytes)
142 | else:
143 | message_info = self.helpers.analyzeResponse(message_bytes)
144 |
145 | content_length = len(message_bytes) - message_info.getBodyOffset()
146 | msg_as_string = self.helpers.bytesToString(message_bytes)
147 | msg_as_string = re.sub(
148 | 'Content-Length: \d+\r\n',
149 | 'Content-Length: {}\r\n'.format(content_length),
150 | msg_as_string,
151 | 1
152 | )
153 | return self.helpers.stringToBytes(msg_as_string)
154 |
155 | def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
156 | dbg_skip_tool = 'Skipping message received from {} on account of global tool scope options.'
157 | if toolFlag == self.callbacks.TOOL_PROXY:
158 | if not self.maintab.options_tab.chkbox_proxy.isSelected():
159 | self.logger.debug(dbg_skip_tool.format('Proxy'))
160 | return
161 | elif toolFlag == self.callbacks.TOOL_TARGET:
162 | if not self.maintab.options_tab.chkbox_target.isSelected():
163 | self.logger.debug(dbg_skip_tool.format('Target'))
164 | return
165 | elif toolFlag == self.callbacks.TOOL_SPIDER:
166 | if not self.maintab.options_tab.chkbox_spider.isSelected():
167 | self.logger.debug(dbg_skip_tool.format('Spider'))
168 | return
169 | elif toolFlag == self.callbacks.TOOL_REPEATER:
170 | if not self.maintab.options_tab.chkbox_repeater.isSelected():
171 | self.logger.debug(dbg_skip_tool.format('Repeater'))
172 | return
173 | elif toolFlag == self.callbacks.TOOL_SEQUENCER:
174 | if not self.maintab.options_tab.chkbox_sequencer.isSelected():
175 | self.logger.debug(dbg_skip_tool.format('Sequencer'))
176 | return
177 | elif toolFlag == self.callbacks.TOOL_INTRUDER:
178 | if not self.maintab.options_tab.chkbox_intruder.isSelected():
179 | self.logger.debug(dbg_skip_tool.format('Intruder'))
180 | return
181 | elif toolFlag == self.callbacks.TOOL_SCANNER:
182 | if not self.maintab.options_tab.chkbox_scanner.isSelected():
183 | self.logger.debug(dbg_skip_tool.format('Scanner'))
184 | return
185 | elif toolFlag == self.callbacks.TOOL_EXTENDER:
186 | if not self.maintab.options_tab.chkbox_extender.isSelected():
187 | self.logger.debug(dbg_skip_tool.format('Extender'))
188 | return
189 | else:
190 | self.logger.debug('Skipping message received from unsupported Burp tool.')
191 | return
192 |
193 | requestinfo = self.helpers.analyzeRequest(messageInfo)
194 | requesturl = requestinfo.getUrl()
195 |
196 | if not self.callbacks.isInScope(requesturl):
197 | return
198 |
199 | # Leave these out of the 'if' statement; the 'else' needs req_as_string.
200 | request_bytes = messageInfo.getRequest()
201 | req_as_string = self.helpers.bytesToString(request_bytes)
202 | if messageIsRequest:
203 | original_req = req_as_string
204 | for tab in self.maintab.get_config_tabs():
205 | if request_bytes == tab.request:
206 | continue
207 |
208 | if tab.tabtitle_pane.enable_chkbox.isSelected() \
209 | and self.is_in_cph_scope(req_as_string, messageIsRequest, tab):
210 |
211 | self.logger.info('Sending request to tab "{}" for modification'.format(
212 | tab.namepane_txtfield.getText()
213 | ))
214 |
215 | req_as_string = self.modify_message(tab, req_as_string)
216 | if req_as_string != original_req:
217 | if tab.param_handl_auto_encode_chkbox.isSelected():
218 | # URL-encode the first line of the request, since it was modified
219 | first_req_line_old = req_as_string.split('\r\n')[0]
220 | self.logger.debug('first_req_line_old:\n{}'.format(first_req_line_old))
221 | first_req_line_old = first_req_line_old.split(' ')
222 | first_req_line_new = '{} {} {}'.format(
223 | first_req_line_old[0],
224 | ''.join([quote(char, safe='/%+=?&') for char in '%20'.join(first_req_line_old[1:-1])]),
225 | first_req_line_old[-1]
226 | )
227 | self.logger.debug('first_req_line_new:\n{}'.format(first_req_line_new))
228 | req_as_string = req_as_string.replace(
229 | ' '.join(first_req_line_old),
230 | first_req_line_new
231 | )
232 | self.logger.debug('Resulting first line of request:\n{}'.format(
233 | req_as_string.split('\r\n')[0]
234 | ))
235 |
236 | request_bytes = self.helpers.stringToBytes(req_as_string)
237 |
238 | forwarder_config = tab.get_socket_pane_config(tab.param_handl_forwarder_socket_pane)
239 | host = forwarder_config.host
240 | port = forwarder_config.port
241 | https = forwarder_config.https
242 |
243 | # Need to update content-length.
244 | request_bytes = self.update_content_length(request_bytes, messageIsRequest)
245 | req_as_string = self.helpers.bytesToString(request_bytes)
246 |
247 | if req_as_string != original_req:
248 | tab.emv_tab.add_table_row(dt.now().time(), True, original_req, req_as_string)
249 |
250 | if tab.param_handl_enable_forwarder_chkbox.isSelected():
251 | try:
252 | messageInfo.setHttpService(self.helpers.buildHttpService(host, int(port), https))
253 | httpsvc = messageInfo.getHttpService()
254 | self.logger.info('Tab "{}" is re-routing its request to "{}:{}"'.format(
255 | tab.namepane_txtfield.getText(),
256 | httpsvc.getHost(),
257 | httpsvc.getPort()
258 | ))
259 | # Generic except because misc. Java exceptions might occur.
260 | except:
261 | self.logger.exception('Error re-routing request:')
262 |
263 | messageInfo.setRequest(request_bytes)
264 |
265 | if not messageIsRequest:
266 | response_bytes = messageInfo.getResponse()
267 | resp_as_string = self.helpers.bytesToString(response_bytes)
268 | original_resp = resp_as_string
269 |
270 | for tab in self.maintab.get_config_tabs():
271 | if tab.tabtitle_pane.enable_chkbox.isSelected() \
272 | and self.is_in_cph_scope(resp_as_string, messageIsRequest, tab):
273 |
274 | self.logger.info('Sending response to tab "{}" for modification'.format(
275 | tab.namepane_txtfield.getText()
276 | ))
277 |
278 | resp_as_string = self.modify_message(tab, resp_as_string)
279 | response_bytes = self.helpers.stringToBytes(resp_as_string)
280 | response_bytes = self.update_content_length(response_bytes, messageIsRequest)
281 | resp_as_string = self.helpers.bytesToString(response_bytes)
282 |
283 | if resp_as_string != original_resp:
284 | tab.emv_tab.add_table_row(dt.now().time(), False, original_resp, resp_as_string)
285 |
286 | messageInfo.setResponse(response_bytes)
287 |
288 | for working_tab in self.maintab.get_config_tabs():
289 | selected_item = working_tab.param_handl_combo_cached.getSelectedItem()
290 | if self.is_in_cph_scope(req_as_string , True , working_tab)\
291 | or self.is_in_cph_scope(resp_as_string, False, working_tab):
292 | working_tab.cached_request = request_bytes
293 | working_tab.cached_response = response_bytes
294 | self.logger.debug('Messages cached for tab {}!'.format(
295 | working_tab.namepane_txtfield.getText()
296 | ))
297 | # If this tab is set to extract a value from one of the previous tabs,
298 | # update its cached message panes with that tab's cached messages.
299 | for previous_tab in self.maintab.get_config_tabs():
300 | if previous_tab == working_tab:
301 | break
302 | item = previous_tab.namepane_txtfield.getText()
303 | if item == selected_item:
304 | working_tab.param_handl_cached_req_viewer .setMessage(previous_tab.cached_request , True)
305 | working_tab.param_handl_cached_resp_viewer.setMessage(previous_tab.cached_response, False)
306 |
307 | def is_in_cph_scope(self, msg_as_string, is_request, tab):
308 | rms_scope_all = tab.msg_mod_combo_scope.getSelectedItem() == tab.MSG_MOD_COMBO_SCOPE_ALL
309 | rms_scope_some = tab.msg_mod_combo_scope.getSelectedItem() == tab.MSG_MOD_COMBO_SCOPE_SOME
310 |
311 | rms_type_requests = tab.msg_mod_combo_type.getSelectedItem() == tab.MSG_MOD_COMBO_TYPE_REQ
312 | rms_type_responses = tab.msg_mod_combo_type.getSelectedItem() == tab.MSG_MOD_COMBO_TYPE_RESP
313 | rms_type_both = tab.msg_mod_combo_type.getSelectedItem() == tab.MSG_MOD_COMBO_TYPE_BOTH
314 |
315 | rms_scope_exp = tab.get_exp_pane_expression(tab.msg_mod_exp_pane_scope)
316 |
317 | if is_request and (rms_type_requests or rms_type_both):
318 | pass
319 | elif not is_request and (rms_type_responses or rms_type_both):
320 | pass
321 | else:
322 | self.logger.debug('Preliminary scope check negative!')
323 | return False
324 |
325 | if rms_scope_all:
326 | return True
327 | elif rms_scope_some and rms_scope_exp:
328 | regexp = re.compile(rms_scope_exp)
329 | if regexp.search(msg_as_string):
330 | return True
331 | else:
332 | self.logger.warning('Scope restriction is active but no expression was specified. Skipping tab "{}".'.format(
333 | tab.namepane_txtfield.getText()
334 | ))
335 | return False
336 |
337 | def modify_message(self, tab, msg_as_string):
338 | ph_matchnum_txt = tab.param_handl_txtfield_match_indices.getText()
339 |
340 | ph_target_exp = tab.get_exp_pane_expression(tab.param_handl_exp_pane_target )
341 | ph_extract_static_exp = tab.get_exp_pane_expression(tab.param_handl_exp_pane_extract_static)
342 | ph_extract_single_exp = tab.get_exp_pane_expression(tab.param_handl_exp_pane_extract_single)
343 | ph_extract_macro_exp = tab.get_exp_pane_expression(tab.param_handl_exp_pane_extract_macro )
344 | ph_extract_cached_exp = tab.get_exp_pane_expression(tab.param_handl_exp_pane_extract_cached)
345 |
346 | if not ph_target_exp:
347 | self.logger.warning(
348 | 'No match expression specified! Skipping tab "{}".'.format(
349 | tab.namepane_txtfield.getText()
350 | )
351 | )
352 | return msg_as_string
353 |
354 | exc_invalid_regex = 'Skipping tab "{}" due to error in expression {{}}: {{}}'.format(
355 | tab.namepane_txtfield.getText()
356 | )
357 |
358 | try:
359 | match_exp = re.compile(ph_target_exp)
360 | except re.error as e:
361 | self.logger.error(exc_invalid_regex.format(ph_target_exp, e))
362 | return msg_as_string
363 |
364 | # The following code does not remove support for groups,
365 | # as the original expression will be used for actual replacements.
366 | # We simply need an expression without capturing groups to feed into re.findall(),
367 | # which enables the logic for granular control over which match indices to target.
368 |
369 | # Removing named groups to normalize capturing groups.
370 | findall_exp = re.sub('\?P<.+?>', '', ph_target_exp)
371 | # Removing capturing groups to search for full matches only.
372 | findall_exp = re.sub(r'(?', findall_exp)
373 | findall_exp = re.compile(findall_exp)
374 | self.logger.debug('findall_exp: {}'.format(findall_exp.pattern))
375 |
376 | all_matches = re.findall(findall_exp, msg_as_string)
377 | self.logger.debug('all_matches: {}'.format(all_matches))
378 |
379 | match_count = len(all_matches)
380 | if not match_count:
381 | self.logger.warning(
382 | 'Skipping tab "{}" because this expression found no matches: {}'.format(
383 | tab.namepane_txtfield.getText(),
384 | ph_target_exp
385 | )
386 | )
387 | return msg_as_string
388 |
389 | matches = list()
390 | dyn_values = ''
391 | replace_exp = ph_extract_static_exp
392 |
393 | if tab.param_handl_dynamic_chkbox.isSelected():
394 | find_exp, target_txt = '', ''
395 | selected_item = tab.param_handl_combo_extract.getSelectedItem()
396 |
397 | if selected_item == tab.PARAM_HANDL_COMBO_EXTRACT_CACHED:
398 | find_exp, target_txt = ph_extract_cached_exp, tab.param_handl_cached_resp_viewer.getMessage()
399 | target_txt = self.helpers.bytesToString(target_txt)
400 |
401 | elif selected_item == tab.PARAM_HANDL_COMBO_EXTRACT_SINGLE:
402 | self.issue_request(tab)
403 | find_exp, target_txt = ph_extract_single_exp, self.helpers.bytesToString(tab.response)
404 |
405 | elif selected_item == tab.PARAM_HANDL_COMBO_EXTRACT_MACRO:
406 | find_exp, target_txt = ph_extract_macro_exp, self.final_macro_resp
407 |
408 | if not find_exp:
409 | self.logger.warning(
410 | 'No dynamic value extraction expression specified! Skipping tab "{}".'.format(
411 | tab.namepane_txtfield.getText()
412 | )
413 | )
414 | return msg_as_string
415 |
416 | try:
417 | # Making a list to enable multiple iterations.
418 | matches = list(re.finditer(find_exp, target_txt))
419 | except re.error as e:
420 | self.logger.error(exc_invalid_regex.format(ph_extract_macro_exp, e))
421 | return msg_as_string
422 |
423 | if not matches:
424 | self.logger.warning('Skipping tab "{}" because this expression found no matches: {}'.format(
425 | tab.namepane_txtfield.getText(),
426 | find_exp
427 | ))
428 | return msg_as_string
429 |
430 | groups = {}
431 | groups_keys = groups.viewkeys()
432 | for match in matches:
433 | gd = match.groupdict()
434 | # The given expression should have unique group matches.
435 | for k in gd.keys():
436 | if k in groups_keys:
437 | self.logger.warning('Skipping tab "{}" because this expression found ambiguous matches: {}'.format(
438 | tab.namepane_txtfield.getText(),
439 | find_exp
440 | ))
441 | return msg_as_string
442 | groups.update(gd)
443 |
444 | # Remove '$' not preceded by '\'
445 | exp = re.sub(r'(?{})'.format(group_name, re.escape(group_match))
452 | for group_name, group_match in groups.items()
453 | ])
454 | dyn_values = ''.join(groups.values())
455 |
456 | # No need for another try/except around this re.compile(),
457 | # as ph_target_exp was already checked when compiling match_exp earlier.
458 | match_exp = re.compile(exp + groups_exp)
459 | self.logger.debug('match_exp adjusted to:\n{}'.format(match_exp.pattern))
460 |
461 | subsets = ph_matchnum_txt.replace(' ', '').split(',')
462 | match_indices = []
463 | for subset in subsets:
464 | try:
465 | if ':' in subset:
466 | sliceindex = subset.index(':')
467 | start = int(subset[:sliceindex ])
468 | end = int(subset[ sliceindex+1:])
469 | if start < 0:
470 | start = match_count + start
471 | if end < 0:
472 | end = match_count + end
473 | for match_index in range(start, end):
474 | match_indices.append(match_index)
475 | else:
476 | match_index = int(subset)
477 | if match_index < 0:
478 | match_index = match_count + match_index
479 | match_indices.append(match_index)
480 | except ValueError as e:
481 | self.logger.error(
482 | 'Ignoring invalid match index or slice on tab "{}" due to {}'.format(
483 | tab.namepane_txtfield.getText(),
484 | e
485 | )
486 | )
487 | continue
488 |
489 | match_indices = set(sorted([m for m in match_indices if m < match_count]))
490 | self.logger.debug('match_indices: {}'.format(match_indices))
491 |
492 | # Using findall_exp to avoid including capture groups in the result.
493 | message_parts = re.split(findall_exp, msg_as_string)
494 | self.logger.debug('message_parts: {}'.format(message_parts))
495 |
496 | # The above strategy to use re.split() in order to enable the usage of match_indices
497 | # ends up breaking non-capturing groups. At this point, however, we can safely remove
498 | # all non-capturing groups and everything will be peachy.
499 | ncg_exp = re.compile('\(\?[^P].+?\)')
500 | if re.search(ncg_exp, match_exp.pattern) is not None:
501 | match_exp = re.compile(ncg_exp.sub('', match_exp.pattern))
502 | if flags is not None:
503 | match_exp = re.compile(flags.group(0) + match_exp.pattern)
504 | self.logger.debug('match_exp adjusted to:\n{}'.format(match_exp.pattern))
505 |
506 | modified_message = ''
507 | remaining_indices = list(match_indices)
508 | for part_index, message_part in enumerate(message_parts):
509 | if remaining_indices and part_index == remaining_indices[0]:
510 | try:
511 | final_value = match_exp.sub(replace_exp, all_matches[part_index] + dyn_values)
512 | except (re.error, IndexError) as e:
513 | self.logger.error(exc_invalid_regex.format(match_exp.pattern + ' or expression ' + replace_exp, e))
514 | return msg_as_string
515 | self.logger.debug('Found:\n{}\nreplaced using:\n{}\nin string:\n{}'.format(
516 | match_exp.pattern,
517 | replace_exp,
518 | all_matches[part_index] + dyn_values
519 | ))
520 | final_value = message_part + final_value
521 | modified_message += final_value
522 | remaining_indices.pop(0)
523 | elif part_index < match_count:
524 | modified_message += message_part + all_matches[part_index]
525 | else:
526 | modified_message += message_part
527 |
528 | return modified_message
529 |
530 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------