initial commit for transformers

This commit is contained in:
Rasmus Moorats 2022-10-06 13:59:29 +03:00
parent 0bb5e00209
commit 2383f2a3dd
Signed by: xx
GPG key ID: FE14255A6AE7241C
6 changed files with 381 additions and 41 deletions

View file

@ -19,7 +19,7 @@ dependencies {
implementation("com.miglayout:miglayout-swing:11.0") implementation("com.miglayout:miglayout-swing:11.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
implementation("com.google.re2j:re2j:1.7") implementation("com.google.re2j:re2j:1.7")
api("net.portswigger.burp.extender:montoya-api:0.9.5.1") api("net.portswigger.burp.extender:montoya-api:0.9.25")
} }
tasks.withType<KotlinCompile> { tasks.withType<KotlinCompile> {

View file

@ -2,12 +2,12 @@ package burp
import burp.api.montoya.BurpExtension import burp.api.montoya.BurpExtension
import burp.api.montoya.MontoyaApi import burp.api.montoya.MontoyaApi
import burp.api.montoya.core.Annotations
import burp.api.montoya.core.HighlightColor import burp.api.montoya.core.HighlightColor
import burp.api.montoya.core.MessageAnnotations
import burp.api.montoya.core.ToolSource import burp.api.montoya.core.ToolSource
import burp.api.montoya.http.HttpHandler import burp.api.montoya.http.HttpHandler
import burp.api.montoya.http.RequestHandlerResult import burp.api.montoya.http.RequestResult
import burp.api.montoya.http.ResponseHandlerResult import burp.api.montoya.http.ResponseResult
import burp.api.montoya.http.message.requests.HttpRequest import burp.api.montoya.http.message.requests.HttpRequest
import burp.api.montoya.http.message.responses.HttpResponse import burp.api.montoya.http.message.responses.HttpResponse
@ -16,6 +16,7 @@ const val EXTENSION_NAME = "Value Autoupdater"
class BurpExtender : BurpExtension { class BurpExtender : BurpExtension {
private lateinit var ui: UI private lateinit var ui: UI
private lateinit var items: ItemStore private lateinit var items: ItemStore
private lateinit var transformers: TransformerStore
private lateinit var replacer: Replacer private lateinit var replacer: Replacer
private lateinit var api: MontoyaApi private lateinit var api: MontoyaApi
@ -32,8 +33,9 @@ class BurpExtender : BurpExtension {
api.misc().setExtensionName(EXTENSION_NAME) api.misc().setExtensionName(EXTENSION_NAME)
items = ItemStore(api.persistence().userContext()) items = ItemStore(api.persistence().userContext())
ui = UI(api, items) transformers = TransformerStore(api.persistence().userContext())
replacer = Replacer(api, items) ui = UI(api, items, transformers)
replacer = Replacer(api, items, transformers)
api.http().registerHttpHandler(ExtHttpHandler()) api.http().registerHttpHandler(ExtHttpHandler())
@ -42,30 +44,33 @@ class BurpExtender : BurpExtension {
inner class ExtHttpHandler : HttpHandler { inner class ExtHttpHandler : HttpHandler {
override fun handleHttpRequest( override fun handleHttpRequest(
request: HttpRequest, annotations: MessageAnnotations, toolSource: ToolSource request: HttpRequest, annotations: Annotations, toolSource: ToolSource
): RequestHandlerResult { ): RequestResult {
if (!ui.isEnabled(toolSource.toolType())) return RequestHandlerResult.from(request, MessageAnnotations.NONE) if (!ui.isEnabled(toolSource.toolType())) return RequestResult.requestResult(
request,
Annotations.annotations()
)
val result = replacer.handleRequest(request.toString()) val result = replacer.handleRequest(request.toString())
return RequestHandlerResult.from( return RequestResult.requestResult(
api.http().createRequest(request.httpService(), result.contents), constructAnnotations(result) HttpRequest.httpRequest(request.httpService(), result.contents), constructAnnotations(result)
) )
} }
override fun handleHttpResponse( override fun handleHttpResponse(
request: HttpRequest, response: HttpResponse, annotations: MessageAnnotations, toolSource: ToolSource request: HttpRequest, response: HttpResponse, annotations: Annotations, toolSource: ToolSource
): ResponseHandlerResult { ): ResponseResult {
if (!ui.isEnabled(toolSource.toolType())) return ResponseHandlerResult.from( if (!ui.isEnabled(toolSource.toolType())) return ResponseResult.responseResult(
response, MessageAnnotations.NONE response, Annotations.annotations()
) )
val result = replacer.handleResponse(response.toString()) val result = replacer.handleResponse(response.toString())
return ResponseHandlerResult.from(response, constructAnnotations(result)) return ResponseResult.responseResult(response, constructAnnotations(result))
} }
} }
fun constructAnnotations(result: ReplaceResult): MessageAnnotations { fun constructAnnotations(result: ReplaceResult): Annotations {
if (!result.matched) return MessageAnnotations.NONE if (!result.matched) return Annotations.annotations()
// sometimes newlines are removed from comments, so accommodate for that as well // sometimes newlines are removed from comments, so accommodate for that as well
var comment = when (result.type) { var comment = when (result.type) {
@ -77,6 +82,6 @@ class BurpExtender : BurpExtension {
comment += "(${it.old}: ${it.new}) \n" comment += "(${it.old}: ${it.new}) \n"
} }
return MessageAnnotations.from(comment, HighlightColor.NONE) return Annotations.annotations(comment, HighlightColor.NONE)
} }
} }

View file

@ -20,6 +20,7 @@ data class Item(
var enabled: Boolean, var enabled: Boolean,
var matchCount: Int, var matchCount: Int,
var replaceCount: Int, var replaceCount: Int,
var transformer: String,
) )
typealias Items = MutableMap<String, Item> // name -> item typealias Items = MutableMap<String, Item> // name -> item

View file

@ -18,11 +18,17 @@ val regexCache = mutableMapOf<String, Pattern>()
interface ReplaceStrategy { interface ReplaceStrategy {
fun updateValue(request: String, match: String): Response fun updateValue(request: String, match: String): Response
fun matchAndReplace(request: String, key: String, value: String): Response { fun matchAndReplace(request: String, key: String, item: Item, transformerStore: TransformerStore): Response {
val res = Response(false, "") val res = Response(false, "")
if (request.contains("\$$key\$")) { if (request.contains("\$$key\$")) {
res.matched = true res.matched = true
var value = item.lastMatch
if (item.transformer != "") {
transformerStore.transformers[item.transformer]?.let {
value = evalTransformer(value, it)
}
}
res.contents = request.replace("\$$key\$", value) res.contents = request.replace("\$$key\$", value)
} }
@ -65,8 +71,9 @@ class HeaderStrategy : ReplaceStrategy {
} }
} }
class Replacer(api: MontoyaApi, itemStore: ItemStore) { class Replacer(api: MontoyaApi, itemStore: ItemStore, transformerStore: TransformerStore) {
private val itemStore: ItemStore private val itemStore: ItemStore
private val transformerStore: TransformerStore
private val api: MontoyaApi private val api: MontoyaApi
private val strategies: Map<ItemType, ReplaceStrategy> = mapOf( private val strategies: Map<ItemType, ReplaceStrategy> = mapOf(
@ -76,6 +83,7 @@ class Replacer(api: MontoyaApi, itemStore: ItemStore) {
init { init {
this.itemStore = itemStore this.itemStore = itemStore
this.transformerStore = transformerStore
this.api = api this.api = api
} }
@ -85,7 +93,7 @@ class Replacer(api: MontoyaApi, itemStore: ItemStore) {
itemStore.items.forEach { itemStore.items.forEach {
if (!it.value.enabled) return@forEach if (!it.value.enabled) return@forEach
val resp = strategies[it.value.type]?.matchAndReplace(result.contents, it.key, it.value.lastMatch)!! val resp = strategies[it.value.type]?.matchAndReplace(result.contents, it.key, it.value, transformerStore)!!
if (resp.matched) { if (resp.matched) {
it.value.replaceCount += 1 it.value.replaceCount += 1

View file

@ -0,0 +1,76 @@
package burp
import burp.api.montoya.persistence.PersistenceContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.graalvm.polyglot.Context
import org.graalvm.polyglot.Source
import org.graalvm.polyglot.Value
val LIB = initLib()
const val TRANSFORMER_STORE = "transformerStore"
fun initLib(): Value? {
val context = Context.newBuilder("js").allowIO(true).build()
val src = "import * as Lib from \"/home/xx/Projects/transformerHook/bundle.mjs\";" + "Lib;"
val source = Source.newBuilder("js", src, "lib.mjs").build()
return context.eval(source)
}
fun evalTransformer(value: String, transformer: Transformer): String {
val context = Context.create()
val bindings = context.getBindings("js")
var res = value
bindings.putMember("value", value)
bindings.putMember("Lib", LIB)
try {
res = context.eval("js", transformer.code).asString()
} catch (e: Exception) {
var error = "Transformer exception!"
e.message?.let {
error += "\n${it}"
transformer.error = it
}
log.error(error)
}
context.close()
return res
}
@Serializable
data class Transformer(
var code: String,
var error: String,
)
typealias Transformers = MutableMap<String, Transformer> // name -> transformer
class TransformerStore(ctx: PersistenceContext) {
// todo: unify stores somehow
private val ctx: PersistenceContext
var transformers: Transformers
init {
this.ctx = ctx
this.transformers = load()
}
private fun load(): Transformers {
// loads and returns transformers from persistent storage
var jsonStr = ctx.getString(TRANSFORMER_STORE) ?: "{}"
return Json.decodeFromString(jsonStr)
}
fun save() {
// saves items in memory to disk
ctx.setString(TRANSFORMER_STORE, Json.encodeToString(transformers))
}
fun nuke() {
ctx.delete(TRANSFORMER_STORE)
}
}

View file

@ -6,7 +6,6 @@ import burp.api.montoya.core.ToolType.*
import burp.api.montoya.persistence.PersistenceContext import burp.api.montoya.persistence.PersistenceContext
import net.miginfocom.swing.MigLayout import net.miginfocom.swing.MigLayout
import java.awt.Color import java.awt.Color
import java.awt.Dimension
import java.awt.Font import java.awt.Font
import java.awt.Window import java.awt.Window
import java.awt.event.* import java.awt.event.*
@ -17,11 +16,14 @@ import javax.swing.SwingUtilities.getWindowAncestor
import javax.swing.event.TableModelEvent import javax.swing.event.TableModelEvent
import javax.swing.table.DefaultTableModel import javax.swing.table.DefaultTableModel
import javax.swing.table.TableModel import javax.swing.table.TableModel
import kotlin.text.Charsets.UTF_8
val VALUES_TABLE = JTable() val VALUES_TABLE = JTable()
val TRANSFORMER_TABLE = JTable()
const val TAB_NAME = "Value updater" const val TAB_NAME = "Value updater"
var TAB_VISIBLE = false var TAB_VISIBLE = false
var namedRows = mapOf<String, Int>() var namedRows = mapOf<String, Int>()
var namedTRows = mapOf<String, Int>()
fun updated(name: String, value: String, count: Int) { fun updated(name: String, value: String, count: Int) {
if (!TAB_VISIBLE) return if (!TAB_VISIBLE) return
@ -46,7 +48,13 @@ private fun reloadValuesTable(items: Items) {
items.forEach { items.forEach {
dtm.addRow( dtm.addRow(
arrayOf( arrayOf(
it.value.enabled, it.key, it.value.match, it.value.lastMatch, it.value.matchCount, it.value.replaceCount it.value.enabled,
it.key,
it.value.match,
it.value.lastMatch,
it.value.matchCount,
it.value.replaceCount,
it.value.transformer
) )
) )
tmpMap[it.key] = dtm.rowCount - 1 tmpMap[it.key] = dtm.rowCount - 1
@ -56,16 +64,41 @@ private fun reloadValuesTable(items: Items) {
namedRows = tmpMap namedRows = tmpMap
} }
private fun reloadTransformersTable(transformers: Transformers) {
val dtm = TRANSFORMER_TABLE.model as DefaultTableModel
val tmpMap = mutableMapOf<String, Int>()
dtm.dataVector.removeAllElements()
transformers.forEach {
dtm.addRow(
arrayOf(
it.key
)
)
tmpMap[it.key] = dtm.rowCount - 1
}
dtm.fireTableDataChanged()
namedTRows = tmpMap
}
private fun rowToName(row: Int): String { private fun rowToName(row: Int): String {
val dtm = VALUES_TABLE.model val dtm = VALUES_TABLE.model
return dtm.getValueAt(row, 1).toString() return dtm.getValueAt(row, 1).toString()
} }
class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() { private fun rowToTName(row: Int): String {
val dtm = TRANSFORMER_TABLE.model
return dtm.getValueAt(row, 0).toString()
}
class UI(api: MontoyaApi, itemStore: ItemStore, transformerStore: TransformerStore) : JPanel() {
private val api: MontoyaApi private val api: MontoyaApi
private val ctx: PersistenceContext private val ctx: PersistenceContext
private val itemStore: ItemStore private val itemStore: ItemStore
private val transformerStore: TransformerStore
private var extEnabled = true private var extEnabled = true
private var enabledTools = mutableMapOf( private var enabledTools = mutableMapOf(
PROXY to true, PROXY to true,
@ -77,6 +110,8 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
) )
private val headerPanel = JPanel() private val headerPanel = JPanel()
private val leftPanel = JPanel()
private val rightPanel = JPanel()
private val mainLabel = JLabel() private val mainLabel = JLabel()
private val headerNestedPanel = JPanel() private val headerNestedPanel = JPanel()
private val enabledToggle = JCheckBox() private val enabledToggle = JCheckBox()
@ -100,11 +135,21 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
private val repeaterSel = JCheckBox() private val repeaterSel = JCheckBox()
private val sequencerSel = JCheckBox() private val sequencerSel = JCheckBox()
private val extenderSel = JCheckBox() private val extenderSel = JCheckBox()
private val transformerLabel = JLabel()
private val transformerSelectorPanel = JPanel()
private val transformerButtons = JPanel()
private val transformerAdd = JButton()
private val transformerRemove = JButton()
private val transformerTablePanel = JScrollPane()
private val transformerTable = TRANSFORMER_TABLE
private val transformerEditor = api.userInterface().createRawEditor()
private val transformerEditorSave = JButton()
init { init {
this.api = api this.api = api
this.ctx = api.persistence().userContext() this.ctx = api.persistence().userContext()
this.itemStore = itemStore this.itemStore = itemStore
this.transformerStore = transformerStore
initComponents() initComponents()
loadValuesFromStore() loadValuesFromStore()
api.userInterface().registerSuiteTab(TAB_NAME, this) api.userInterface().registerSuiteTab(TAB_NAME, this)
@ -118,6 +163,7 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
TAB_VISIBLE = false TAB_VISIBLE = false
} }
}) })
} }
private fun loadValuesFromStore() { private fun loadValuesFromStore() {
@ -138,6 +184,7 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
extenderSel.isSelected = enabledTools[EXTENDER]!! extenderSel.isSelected = enabledTools[EXTENDER]!!
reloadValuesTable(itemStore.items) reloadValuesTable(itemStore.items)
reloadTransformersTable(transformerStore.transformers)
} }
@ -146,11 +193,17 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
} }
private fun valueAdd() { private fun valueAdd() {
val window = AddEditDialog(getWindowAncestor(this), -1, itemStore) val window = AddEditDialog(getWindowAncestor(this), -1, itemStore, transformerStore)
window.title = "Add value" window.title = "Add value"
window.isVisible = true window.isVisible = true
} }
private fun transformerAdd() {
val window = TransformerAddDialog(getWindowAncestor(this), transformerStore)
window.title = "Add transformer"
window.isVisible = true
}
private fun valueEdit() { private fun valueEdit() {
val selected = valuesTable.selectedRowCount val selected = valuesTable.selectedRowCount
if (selected < 1) { if (selected < 1) {
@ -163,7 +216,7 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
val index = valuesTable.selectedRows[0] val index = valuesTable.selectedRows[0]
val window = AddEditDialog(getWindowAncestor(this), index, itemStore) val window = AddEditDialog(getWindowAncestor(this), index, itemStore, transformerStore)
window.title = "Edit value" window.title = "Edit value"
window.isVisible = true window.isVisible = true
} }
@ -181,6 +234,27 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
reloadValuesTable(itemStore.items) reloadValuesTable(itemStore.items)
} }
private fun transformerRemove() {
val selectedRows = transformerTable.selectedRows
TRANSFORMER_TABLE.clearSelection() // visual bug work around
if (selectedRows.isEmpty()) return
transformerStore.transformers.remove(rowToTName(selectedRows[0]))
transformerStore.save()
transformerEditor.contents = "".toByteArray(UTF_8)
reloadTransformersTable(transformerStore.transformers)
}
private fun transformerSave() {
transformerStore.transformers[rowToTName(transformerTable.selectedRow)]?.code =
transformerEditor.contents.toString(UTF_8)
transformerStore.save()
}
private fun enabledToggle(e: ItemEvent) { private fun enabledToggle(e: ItemEvent) {
extEnabled = e.stateChange == SELECTED extEnabled = e.stateChange == SELECTED
ctx.setBoolean("extEnabled", extEnabled) ctx.setBoolean("extEnabled", extEnabled)
@ -246,9 +320,27 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
} }
} }
private fun transformerSelected() {
when (transformerTable.selectedRowCount) {
0 -> {
transformerRemove.isEnabled = false
transformerEditorSave.isEnabled = false
}
else -> {
transformerRemove.isEnabled = true
transformerEditorSave.isEnabled = true
transformerEditor.contents =
transformerStore.transformers[rowToTName(transformerTable.selectedRow)]!!.code.toByteArray(UTF_8)
}
}
}
//<editor-fold desc="UI layout cruft"> //<editor-fold desc="UI layout cruft">
private fun initComponents() { private fun initComponents() {
layout = MigLayout("hidemode 3", "[fill][fill]", "[][][][][]") layout = MigLayout("fill,hidemode 3,align center top", "fill")
leftPanel.layout = MigLayout("fill,hidemode 3,align left top", "[fill]", "[][][][][]")
rightPanel.layout = MigLayout("fill,hidemode 3,align left top", "[fill]", "[][][]")
headerPanel.layout = MigLayout("hidemode 3", "[fill]", "[][][]") headerPanel.layout = MigLayout("hidemode 3", "[fill]", "[][][]")
@ -262,8 +354,8 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
enabledToggle.addItemListener { e: ItemEvent -> enabledToggle(e) } enabledToggle.addItemListener { e: ItemEvent -> enabledToggle(e) }
headerNestedPanel.add(enabledToggle, "cell 0 0") headerNestedPanel.add(enabledToggle, "cell 0 0")
headerPanel.add(headerNestedPanel, "cell 0 1") headerPanel.add(headerNestedPanel, "cell 0 1")
add(headerPanel, "cell 0 0") leftPanel.add(headerPanel, "cell 0 0")
add(separator2, "cell 0 1") leftPanel.add(separator2, "cell 0 1")
valuesPanel.layout = MigLayout("hidemode 3", "[fill]", "[][][][]") valuesPanel.layout = MigLayout("hidemode 3", "[fill]", "[][][][]")
@ -294,7 +386,7 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
valuesTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) valuesTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
valuesTable.model = object : DefaultTableModel( valuesTable.model = object : DefaultTableModel(
arrayOf(), arrayOf( arrayOf(), arrayOf(
"", "Name", "Match", "Last value", "Times updated", "Times replaced" "", "Name", "Match", "Last value", "Times updated", "Times replaced", "Transformer"
) )
) { ) {
override fun getColumnClass(columnIndex: Int): Class<*> { override fun getColumnClass(columnIndex: Int): Class<*> {
@ -309,16 +401,15 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
} }
val cm = valuesTable.columnModel val cm = valuesTable.columnModel
cm.getColumn(0).resizable = false cm.getColumn(0).resizable = false
cm.getColumn(0).preferredWidth = 25 cm.getColumn(0).width = 25
valuesTable.preferredScrollableViewportSize = Dimension(600, 300)
(valuesTable.model as DefaultTableModel).addTableModelListener { e: TableModelEvent -> tableEdit(e) } (valuesTable.model as DefaultTableModel).addTableModelListener { e: TableModelEvent -> tableEdit(e) }
valuesTable.selectionModel.addListSelectionListener { tableSelected() } valuesTable.selectionModel.addListSelectionListener { tableSelected() }
valuesTablePanel.setViewportView(valuesTable) valuesTablePanel.setViewportView(valuesTable)
valueSelectorPanel.add(valuesTablePanel, "cell 1 0") valueSelectorPanel.add(valuesTablePanel, "cell 1 0,grow,push,span")
valuesPanel.add(valueSelectorPanel, "cell 0 2") valuesPanel.add(valueSelectorPanel, "cell 0 2,grow,push,span")
add(valuesPanel, "cell 0 2") leftPanel.add(valuesPanel, "cell 0 2")
add(separator1, "cell 0 3") leftPanel.add(separator1, "cell 0 3")
toolsPanel.layout = MigLayout("hidemode 3", "[fill]", "[][]") toolsPanel.layout = MigLayout("hidemode 3", "[fill]", "[][]")
@ -353,17 +444,58 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
toolSelectionPanel.add(extenderSel, "cell 2 1") toolSelectionPanel.add(extenderSel, "cell 2 1")
toolsPanel.add(toolSelectionPanel, "cell 0 1") toolsPanel.add(toolSelectionPanel, "cell 0 1")
add(toolsPanel, "cell 0 4") leftPanel.add(toolsPanel, "cell 0 4")
add(leftPanel, "w 50%,aligny top,growy 0,growx")
transformerLabel.text = "Value Transformers"
transformerLabel.font = transformerLabel.font.deriveFont(transformerLabel.font.style or Font.BOLD)
rightPanel.add(transformerLabel, "cell 0 0")
transformerSelectorPanel.layout = MigLayout("hidemode 3", "[fill][fill]", "[]")
transformerButtons.layout = MigLayout("hidemode 3", "[fill]", "[][][]")
transformerAdd.text = "Add"
transformerAdd.addActionListener { transformerAdd() }
transformerButtons.add(transformerAdd, "cell 0 0")
transformerRemove.text = "Remove"
transformerRemove.addActionListener { transformerRemove() }
transformerRemove.isEnabled = false
transformerButtons.add(transformerRemove, "cell 0 1")
transformerEditorSave.text = "Save"
transformerEditorSave.addActionListener { transformerSave() }
transformerEditorSave.isEnabled = true
transformerButtons.add(transformerEditorSave, "dock south")
transformerSelectorPanel.add(transformerButtons, "cell 0 0,aligny top,growy 0")
transformerTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
transformerTable.model = object : DefaultTableModel(
arrayOf(), arrayOf(
"Name"
)
) {}
transformerTable.selectionModel.addListSelectionListener { transformerSelected() }
transformerTablePanel.setViewportView(transformerTable)
transformerSelectorPanel.add(transformerTablePanel, "cell 1 0,grow,push,span")
rightPanel.add(transformerSelectorPanel, "cell 0 1")
rightPanel.add(transformerEditor.uiComponent(), "cell 0 2,grow,push,span")
add(rightPanel, "w 50%,aligny top,growy 0,grow,push,span")
} }
//</editor-fold> //</editor-fold>
} }
class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(owner) { class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore, transformerStore: TransformerStore) :
JDialog(owner) {
private val headerTypeHint = "Matches header names and replaces values " private val headerTypeHint = "Matches header names and replaces values "
private val regexTypeHint = "Uses regex for matches (named group: val)" private val regexTypeHint = "Uses regex for matches (named group: val)"
private var index: Int private var index: Int
private val itemStore: ItemStore private val itemStore: ItemStore
private val transformerStore: TransformerStore
private val panel1 = JPanel() private val panel1 = JPanel()
private val nameLabel = JLabel() private val nameLabel = JLabel()
@ -374,6 +506,8 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
private val headerButton = JRadioButton() private val headerButton = JRadioButton()
private val regexButton = JRadioButton() private val regexButton = JRadioButton()
private val typeDescription = JLabel() private val typeDescription = JLabel()
private val transformerLabel = JLabel()
private val transformerComboBox = JComboBox<String>()
private val errorLabel = JLabel() private val errorLabel = JLabel()
private val panel3 = JPanel() private val panel3 = JPanel()
private val okButton = JButton() private val okButton = JButton()
@ -383,13 +517,24 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
init { init {
this.index = index this.index = index
this.itemStore = itemStore this.itemStore = itemStore
this.transformerStore = transformerStore
initComponents() initComponents()
loadTransformers()
if (index != -1) { if (index != -1) {
loadStoredValues() loadStoredValues()
} }
this.defaultCloseOperation = DISPOSE_ON_CLOSE this.defaultCloseOperation = DISPOSE_ON_CLOSE
} }
private fun loadTransformers() {
transformerComboBox.addItem("")
transformerStore.transformers.forEach {
transformerComboBox.addItem(it.key)
}
transformerComboBox.selectedIndex = 0
}
private fun loadStoredValues() { private fun loadStoredValues() {
val name = rowToName(index) val name = rowToName(index)
val item = itemStore.items[name] val item = itemStore.items[name]
@ -404,6 +549,8 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
ItemType.REGEX -> regexButton.isSelected = true ItemType.REGEX -> regexButton.isSelected = true
} }
transformerComboBox.selectedItem = item.transformer
nameField.isEnabled = false nameField.isEnabled = false
} }
@ -439,6 +586,7 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
val name = nameField.text val name = nameField.text
val match = matchField.text val match = matchField.text
val type = if (regexButton.isSelected) ItemType.REGEX else ItemType.HEADER val type = if (regexButton.isSelected) ItemType.REGEX else ItemType.HEADER
val transformer = transformerComboBox.selectedItem as String
if (name == "") { if (name == "") {
showError("Name field cannot be left blank!") showError("Name field cannot be left blank!")
@ -459,7 +607,7 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
} }
if (index == -1) { if (index == -1) {
val item = Item(match, type, "", true, 0, 0) val item = Item(match, type, "", true, 0, 0, transformer)
if (itemStore.items[name] != null) { if (itemStore.items[name] != null) {
showError("Entry named \"$name\" already exists!") showError("Entry named \"$name\" already exists!")
return false return false
@ -475,6 +623,7 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
} }
item.match = match item.match = match
item.type = type item.type = type
item.transformer = transformer
log.debug("Edited item at index $index: $item") log.debug("Edited item at index $index: $item")
} }
@ -496,7 +645,6 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
//<editor-fold desc="UI layout cruft"> //<editor-fold desc="UI layout cruft">
private fun initComponents() { private fun initComponents() {
val contentPane = contentPane
contentPane.layout = MigLayout("hidemode 3", "[fill][fill][fill][fill][fill]", "[][][][][][][]") contentPane.layout = MigLayout("hidemode 3", "[fill][fill][fill][fill][fill]", "[][][][][][][]")
panel1.layout = MigLayout("hidemode 3", "[fill][fill]", "[][][][]") panel1.layout = MigLayout("hidemode 3", "[fill][fill]", "[][][][]")
@ -536,6 +684,11 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
typeDescription.text = headerTypeHint typeDescription.text = headerTypeHint
panel1.add(typeDescription, "cell 1 3") panel1.add(typeDescription, "cell 1 3")
transformerLabel.text = "Transformer"
panel1.add(transformerLabel, "cell 0 4")
panel1.add(transformerComboBox, "cell 1 4")
contentPane.add(panel1, "cell 0 0") contentPane.add(panel1, "cell 0 0")
errorLabel.foreground = Color.RED errorLabel.foreground = Color.RED
@ -571,3 +724,100 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
} }
//</editor-fold> //</editor-fold>
} }
class TransformerAddDialog(owner: Window?, transformerStore: TransformerStore) : JDialog(owner) {
private val transformerStore: TransformerStore
private val panel1 = JPanel()
private val nameLabel = JLabel()
private val nameField = JTextField()
private val errorLabel = JLabel()
private val panel3 = JPanel()
private val okButton = JButton()
private val cancelButton = JButton()
init {
this.transformerStore = transformerStore
initComponents()
this.defaultCloseOperation = DISPOSE_ON_CLOSE
}
private fun keyTyped() {
showError(" ")
}
private fun ok() {
if (apply()) this.dispatchEvent(WindowEvent(this, WindowEvent.WINDOW_CLOSING))
}
private fun apply(): Boolean {
val name = nameField.text
if (name == "") {
showError("Name field cannot be left blank!")
return false
}
val transformer = Transformer("", "")
if (transformerStore.transformers[name] != null) {
showError("Transformer named \"$name\" already exists!")
return false
}
transformerStore.transformers[name] = transformer
transformerStore.save()
reloadTransformersTable(transformerStore.transformers)
return true
}
private fun cancel() {
this.dispatchEvent(WindowEvent(this, WindowEvent.WINDOW_CLOSING))
}
private fun showError(err: String) {
errorLabel.text = err
}
//<editor-fold desc="UI layout cruft">
private fun initComponents() {
contentPane.layout = MigLayout("hidemode 3", "[fill][fill][fill][fill][fill]", "[][][][][][][]")
panel1.layout = MigLayout("hidemode 3", "[fill][fill]", "[][][][]")
nameLabel.text = "Name"
panel1.add(nameLabel, "cell 0 0")
nameField.addKeyListener(object : KeyAdapter() {
override fun keyTyped(e: KeyEvent) {
this@TransformerAddDialog.keyTyped()
}
})
panel1.add(nameField, "cell 1 0,wmin 270,grow 0")
contentPane.add(panel1, "cell 0 0")
errorLabel.foreground = Color.RED
showError(" ")
contentPane.add(errorLabel, "cell 0 4")
panel3.layout = MigLayout("fillx,hidemode 3", "[fill][fill][fill][fill][fill]", "[fill]")
okButton.text = "OK"
okButton.background = UIManager.getColor("Button.background")
okButton.font = okButton.font.deriveFont(okButton.font.style or Font.BOLD)
okButton.addActionListener { ok() }
panel3.add(okButton, "west,gapx null 10")
cancelButton.text = "Cancel"
cancelButton.addActionListener { cancel() }
panel3.add(cancelButton, "EAST")
contentPane.add(panel3, "cell 0 5")
setSize(250, 100)
isResizable = false
pack()
setLocationRelativeTo(owner)
}
//</editor-fold>
}