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("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> {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
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 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>
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue