initial commit for transformers
This commit is contained in:
parent
0bb5e00209
commit
2383f2a3dd
6 changed files with 381 additions and 41 deletions
|
@ -19,7 +19,7 @@ dependencies {
|
|||
implementation("com.miglayout:miglayout-swing:11.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
|
||||
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> {
|
||||
|
|
|
@ -2,12 +2,12 @@ package burp
|
|||
|
||||
import burp.api.montoya.BurpExtension
|
||||
import burp.api.montoya.MontoyaApi
|
||||
import burp.api.montoya.core.Annotations
|
||||
import burp.api.montoya.core.HighlightColor
|
||||
import burp.api.montoya.core.MessageAnnotations
|
||||
import burp.api.montoya.core.ToolSource
|
||||
import burp.api.montoya.http.HttpHandler
|
||||
import burp.api.montoya.http.RequestHandlerResult
|
||||
import burp.api.montoya.http.ResponseHandlerResult
|
||||
import burp.api.montoya.http.RequestResult
|
||||
import burp.api.montoya.http.ResponseResult
|
||||
import burp.api.montoya.http.message.requests.HttpRequest
|
||||
import burp.api.montoya.http.message.responses.HttpResponse
|
||||
|
||||
|
@ -16,6 +16,7 @@ const val EXTENSION_NAME = "Value Autoupdater"
|
|||
class BurpExtender : BurpExtension {
|
||||
private lateinit var ui: UI
|
||||
private lateinit var items: ItemStore
|
||||
private lateinit var transformers: TransformerStore
|
||||
private lateinit var replacer: Replacer
|
||||
private lateinit var api: MontoyaApi
|
||||
|
||||
|
@ -32,8 +33,9 @@ class BurpExtender : BurpExtension {
|
|||
|
||||
api.misc().setExtensionName(EXTENSION_NAME)
|
||||
items = ItemStore(api.persistence().userContext())
|
||||
ui = UI(api, items)
|
||||
replacer = Replacer(api, items)
|
||||
transformers = TransformerStore(api.persistence().userContext())
|
||||
ui = UI(api, items, transformers)
|
||||
replacer = Replacer(api, items, transformers)
|
||||
|
||||
api.http().registerHttpHandler(ExtHttpHandler())
|
||||
|
||||
|
@ -42,30 +44,33 @@ class BurpExtender : BurpExtension {
|
|||
|
||||
inner class ExtHttpHandler : HttpHandler {
|
||||
override fun handleHttpRequest(
|
||||
request: HttpRequest, annotations: MessageAnnotations, toolSource: ToolSource
|
||||
): RequestHandlerResult {
|
||||
if (!ui.isEnabled(toolSource.toolType())) return RequestHandlerResult.from(request, MessageAnnotations.NONE)
|
||||
request: HttpRequest, annotations: Annotations, toolSource: ToolSource
|
||||
): RequestResult {
|
||||
if (!ui.isEnabled(toolSource.toolType())) return RequestResult.requestResult(
|
||||
request,
|
||||
Annotations.annotations()
|
||||
)
|
||||
val result = replacer.handleRequest(request.toString())
|
||||
return RequestHandlerResult.from(
|
||||
api.http().createRequest(request.httpService(), result.contents), constructAnnotations(result)
|
||||
return RequestResult.requestResult(
|
||||
HttpRequest.httpRequest(request.httpService(), result.contents), constructAnnotations(result)
|
||||
)
|
||||
}
|
||||
|
||||
override fun handleHttpResponse(
|
||||
request: HttpRequest, response: HttpResponse, annotations: MessageAnnotations, toolSource: ToolSource
|
||||
): ResponseHandlerResult {
|
||||
if (!ui.isEnabled(toolSource.toolType())) return ResponseHandlerResult.from(
|
||||
response, MessageAnnotations.NONE
|
||||
request: HttpRequest, response: HttpResponse, annotations: Annotations, toolSource: ToolSource
|
||||
): ResponseResult {
|
||||
if (!ui.isEnabled(toolSource.toolType())) return ResponseResult.responseResult(
|
||||
response, Annotations.annotations()
|
||||
)
|
||||
val result = replacer.handleResponse(response.toString())
|
||||
return ResponseHandlerResult.from(response, constructAnnotations(result))
|
||||
return ResponseResult.responseResult(response, constructAnnotations(result))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun constructAnnotations(result: ReplaceResult): MessageAnnotations {
|
||||
if (!result.matched) return MessageAnnotations.NONE
|
||||
fun constructAnnotations(result: ReplaceResult): Annotations {
|
||||
if (!result.matched) return Annotations.annotations()
|
||||
|
||||
// sometimes newlines are removed from comments, so accommodate for that as well
|
||||
var comment = when (result.type) {
|
||||
|
@ -77,6 +82,6 @@ class BurpExtender : BurpExtension {
|
|||
comment += "(${it.old}: ${it.new}) \n"
|
||||
}
|
||||
|
||||
return MessageAnnotations.from(comment, HighlightColor.NONE)
|
||||
return Annotations.annotations(comment, HighlightColor.NONE)
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ data class Item(
|
|||
var enabled: Boolean,
|
||||
var matchCount: Int,
|
||||
var replaceCount: Int,
|
||||
var transformer: String,
|
||||
)
|
||||
|
||||
typealias Items = MutableMap<String, Item> // name -> item
|
||||
|
|
|
@ -18,11 +18,17 @@ val regexCache = mutableMapOf<String, Pattern>()
|
|||
|
||||
interface ReplaceStrategy {
|
||||
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, "")
|
||||
|
||||
if (request.contains("\$$key\$")) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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 transformerStore: TransformerStore
|
||||
private val api: MontoyaApi
|
||||
|
||||
private val strategies: Map<ItemType, ReplaceStrategy> = mapOf(
|
||||
|
@ -76,6 +83,7 @@ class Replacer(api: MontoyaApi, itemStore: ItemStore) {
|
|||
|
||||
init {
|
||||
this.itemStore = itemStore
|
||||
this.transformerStore = transformerStore
|
||||
this.api = api
|
||||
}
|
||||
|
||||
|
@ -85,7 +93,7 @@ class Replacer(api: MontoyaApi, itemStore: ItemStore) {
|
|||
|
||||
itemStore.items.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) {
|
||||
it.value.replaceCount += 1
|
||||
|
|
76
src/main/kotlin/Transformer.kt
Normal file
76
src/main/kotlin/Transformer.kt
Normal 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)
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ import burp.api.montoya.core.ToolType.*
|
|||
import burp.api.montoya.persistence.PersistenceContext
|
||||
import net.miginfocom.swing.MigLayout
|
||||
import java.awt.Color
|
||||
import java.awt.Dimension
|
||||
import java.awt.Font
|
||||
import java.awt.Window
|
||||
import java.awt.event.*
|
||||
|
@ -17,11 +16,14 @@ import javax.swing.SwingUtilities.getWindowAncestor
|
|||
import javax.swing.event.TableModelEvent
|
||||
import javax.swing.table.DefaultTableModel
|
||||
import javax.swing.table.TableModel
|
||||
import kotlin.text.Charsets.UTF_8
|
||||
|
||||
val VALUES_TABLE = JTable()
|
||||
val TRANSFORMER_TABLE = JTable()
|
||||
const val TAB_NAME = "Value updater"
|
||||
var TAB_VISIBLE = false
|
||||
var namedRows = mapOf<String, Int>()
|
||||
var namedTRows = mapOf<String, Int>()
|
||||
|
||||
fun updated(name: String, value: String, count: Int) {
|
||||
if (!TAB_VISIBLE) return
|
||||
|
@ -46,7 +48,13 @@ private fun reloadValuesTable(items: Items) {
|
|||
items.forEach {
|
||||
dtm.addRow(
|
||||
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
|
||||
|
@ -56,16 +64,41 @@ private fun reloadValuesTable(items: Items) {
|
|||
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 {
|
||||
val dtm = VALUES_TABLE.model
|
||||
|
||||
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 ctx: PersistenceContext
|
||||
private val itemStore: ItemStore
|
||||
private val transformerStore: TransformerStore
|
||||
private var extEnabled = true
|
||||
private var enabledTools = mutableMapOf(
|
||||
PROXY to true,
|
||||
|
@ -77,6 +110,8 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
|
|||
)
|
||||
|
||||
private val headerPanel = JPanel()
|
||||
private val leftPanel = JPanel()
|
||||
private val rightPanel = JPanel()
|
||||
private val mainLabel = JLabel()
|
||||
private val headerNestedPanel = JPanel()
|
||||
private val enabledToggle = JCheckBox()
|
||||
|
@ -100,11 +135,21 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
|
|||
private val repeaterSel = JCheckBox()
|
||||
private val sequencerSel = 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 {
|
||||
this.api = api
|
||||
this.ctx = api.persistence().userContext()
|
||||
this.itemStore = itemStore
|
||||
this.transformerStore = transformerStore
|
||||
initComponents()
|
||||
loadValuesFromStore()
|
||||
api.userInterface().registerSuiteTab(TAB_NAME, this)
|
||||
|
@ -118,6 +163,7 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
|
|||
TAB_VISIBLE = false
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun loadValuesFromStore() {
|
||||
|
@ -138,6 +184,7 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
|
|||
extenderSel.isSelected = enabledTools[EXTENDER]!!
|
||||
|
||||
reloadValuesTable(itemStore.items)
|
||||
reloadTransformersTable(transformerStore.transformers)
|
||||
}
|
||||
|
||||
|
||||
|
@ -146,11 +193,17 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
|
|||
}
|
||||
|
||||
private fun valueAdd() {
|
||||
val window = AddEditDialog(getWindowAncestor(this), -1, itemStore)
|
||||
val window = AddEditDialog(getWindowAncestor(this), -1, itemStore, transformerStore)
|
||||
window.title = "Add value"
|
||||
window.isVisible = true
|
||||
}
|
||||
|
||||
private fun transformerAdd() {
|
||||
val window = TransformerAddDialog(getWindowAncestor(this), transformerStore)
|
||||
window.title = "Add transformer"
|
||||
window.isVisible = true
|
||||
}
|
||||
|
||||
private fun valueEdit() {
|
||||
val selected = valuesTable.selectedRowCount
|
||||
if (selected < 1) {
|
||||
|
@ -163,7 +216,7 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
|
|||
|
||||
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.isVisible = true
|
||||
}
|
||||
|
@ -181,6 +234,27 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
|
|||
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) {
|
||||
extEnabled = e.stateChange == SELECTED
|
||||
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">
|
||||
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]", "[][][]")
|
||||
|
||||
|
@ -262,8 +354,8 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
|
|||
enabledToggle.addItemListener { e: ItemEvent -> enabledToggle(e) }
|
||||
headerNestedPanel.add(enabledToggle, "cell 0 0")
|
||||
headerPanel.add(headerNestedPanel, "cell 0 1")
|
||||
add(headerPanel, "cell 0 0")
|
||||
add(separator2, "cell 0 1")
|
||||
leftPanel.add(headerPanel, "cell 0 0")
|
||||
leftPanel.add(separator2, "cell 0 1")
|
||||
|
||||
valuesPanel.layout = MigLayout("hidemode 3", "[fill]", "[][][][]")
|
||||
|
||||
|
@ -294,7 +386,7 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
|
|||
valuesTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
|
||||
valuesTable.model = object : DefaultTableModel(
|
||||
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<*> {
|
||||
|
@ -309,16 +401,15 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
|
|||
}
|
||||
val cm = valuesTable.columnModel
|
||||
cm.getColumn(0).resizable = false
|
||||
cm.getColumn(0).preferredWidth = 25
|
||||
valuesTable.preferredScrollableViewportSize = Dimension(600, 300)
|
||||
cm.getColumn(0).width = 25
|
||||
(valuesTable.model as DefaultTableModel).addTableModelListener { e: TableModelEvent -> tableEdit(e) }
|
||||
valuesTable.selectionModel.addListSelectionListener { tableSelected() }
|
||||
|
||||
valuesTablePanel.setViewportView(valuesTable)
|
||||
valueSelectorPanel.add(valuesTablePanel, "cell 1 0")
|
||||
valuesPanel.add(valueSelectorPanel, "cell 0 2")
|
||||
add(valuesPanel, "cell 0 2")
|
||||
add(separator1, "cell 0 3")
|
||||
valueSelectorPanel.add(valuesTablePanel, "cell 1 0,grow,push,span")
|
||||
valuesPanel.add(valueSelectorPanel, "cell 0 2,grow,push,span")
|
||||
leftPanel.add(valuesPanel, "cell 0 2")
|
||||
leftPanel.add(separator1, "cell 0 3")
|
||||
|
||||
toolsPanel.layout = MigLayout("hidemode 3", "[fill]", "[][]")
|
||||
|
||||
|
@ -353,17 +444,58 @@ class UI(api: MontoyaApi, itemStore: ItemStore) : JPanel() {
|
|||
toolSelectionPanel.add(extenderSel, "cell 2 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>
|
||||
}
|
||||
|
||||
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 regexTypeHint = "Uses regex for matches (named group: val)"
|
||||
private var index: Int
|
||||
private val itemStore: ItemStore
|
||||
private val transformerStore: TransformerStore
|
||||
|
||||
private val panel1 = JPanel()
|
||||
private val nameLabel = JLabel()
|
||||
|
@ -374,6 +506,8 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
|
|||
private val headerButton = JRadioButton()
|
||||
private val regexButton = JRadioButton()
|
||||
private val typeDescription = JLabel()
|
||||
private val transformerLabel = JLabel()
|
||||
private val transformerComboBox = JComboBox<String>()
|
||||
private val errorLabel = JLabel()
|
||||
private val panel3 = JPanel()
|
||||
private val okButton = JButton()
|
||||
|
@ -383,13 +517,24 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
|
|||
init {
|
||||
this.index = index
|
||||
this.itemStore = itemStore
|
||||
this.transformerStore = transformerStore
|
||||
initComponents()
|
||||
loadTransformers()
|
||||
if (index != -1) {
|
||||
loadStoredValues()
|
||||
}
|
||||
this.defaultCloseOperation = DISPOSE_ON_CLOSE
|
||||
}
|
||||
|
||||
|
||||
private fun loadTransformers() {
|
||||
transformerComboBox.addItem("")
|
||||
transformerStore.transformers.forEach {
|
||||
transformerComboBox.addItem(it.key)
|
||||
}
|
||||
transformerComboBox.selectedIndex = 0
|
||||
}
|
||||
|
||||
private fun loadStoredValues() {
|
||||
val name = rowToName(index)
|
||||
val item = itemStore.items[name]
|
||||
|
@ -404,6 +549,8 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
|
|||
ItemType.REGEX -> regexButton.isSelected = true
|
||||
}
|
||||
|
||||
transformerComboBox.selectedItem = item.transformer
|
||||
|
||||
nameField.isEnabled = false
|
||||
}
|
||||
|
||||
|
@ -439,6 +586,7 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
|
|||
val name = nameField.text
|
||||
val match = matchField.text
|
||||
val type = if (regexButton.isSelected) ItemType.REGEX else ItemType.HEADER
|
||||
val transformer = transformerComboBox.selectedItem as String
|
||||
|
||||
if (name == "") {
|
||||
showError("Name field cannot be left blank!")
|
||||
|
@ -459,7 +607,7 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
|
|||
}
|
||||
|
||||
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) {
|
||||
showError("Entry named \"$name\" already exists!")
|
||||
return false
|
||||
|
@ -475,6 +623,7 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
|
|||
}
|
||||
item.match = match
|
||||
item.type = type
|
||||
item.transformer = transformer
|
||||
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">
|
||||
private fun initComponents() {
|
||||
val contentPane = contentPane
|
||||
contentPane.layout = MigLayout("hidemode 3", "[fill][fill][fill][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
|
||||
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")
|
||||
|
||||
errorLabel.foreground = Color.RED
|
||||
|
@ -571,3 +724,100 @@ class AddEditDialog(owner: Window?, index: Int, itemStore: ItemStore) : JDialog(
|
|||
}
|
||||
//</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>
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue