mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-06 12:27:58 +02:00
add support for rewrite header engine
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
+71
-4
@@ -953,7 +953,11 @@ func (m *ProxyHandler) applyCustomResponseHeaderReplacementsWithVariables(resp *
|
||||
for _, replacement := range hCfg.Rewrite {
|
||||
if replacement.From == "response_header" || replacement.From == "any" {
|
||||
if m.rewriteRuleMatchesRequest(replacement, resp.Request) {
|
||||
headers = m.applyReplacementWithVariables(headers, replacement, session.ID, varCtx, "")
|
||||
if replacement.Engine == "header" {
|
||||
m.applyHeaderRewriteToHeaders(resp.Header, replacement, session.ID, varCtx)
|
||||
} else {
|
||||
headers = m.applyReplacementWithVariables(headers, replacement, session.ID, varCtx, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -965,7 +969,11 @@ func (m *ProxyHandler) applyCustomResponseHeaderReplacementsWithVariables(resp *
|
||||
for _, replacement := range proxyConfig.Global.Rewrite {
|
||||
if replacement.From == "response_header" || replacement.From == "any" {
|
||||
if m.rewriteRuleMatchesRequest(replacement, resp.Request) {
|
||||
headers = m.applyReplacementWithVariables(headers, replacement, session.ID, varCtx, "")
|
||||
if replacement.Engine == "header" {
|
||||
m.applyHeaderRewriteToHeaders(resp.Header, replacement, session.ID, varCtx)
|
||||
} else {
|
||||
headers = m.applyReplacementWithVariables(headers, replacement, session.ID, varCtx, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2427,7 +2435,11 @@ func (m *ProxyHandler) applyCustomResponseHeaderReplacementsWithoutSession(resp
|
||||
for _, replacement := range hostConfig.Rewrite {
|
||||
if replacement.From == "response_header" || replacement.From == "any" {
|
||||
if m.rewriteRuleMatchesRequest(replacement, resp.Request) {
|
||||
headers = m.applyReplacement(headers, replacement, "no-session", "")
|
||||
if replacement.Engine == "header" {
|
||||
m.applyHeaderRewriteToHeaders(resp.Header, replacement, "no-session", nil)
|
||||
} else {
|
||||
headers = m.applyReplacement(headers, replacement, "no-session", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2439,7 +2451,11 @@ func (m *ProxyHandler) applyCustomResponseHeaderReplacementsWithoutSession(resp
|
||||
for _, replacement := range proxyConfig.Global.Rewrite {
|
||||
if replacement.From == "response_header" || replacement.From == "any" {
|
||||
if m.rewriteRuleMatchesRequest(replacement, resp.Request) {
|
||||
headers = m.applyReplacement(headers, replacement, "no-session", "")
|
||||
if replacement.Engine == "header" {
|
||||
m.applyHeaderRewriteToHeaders(resp.Header, replacement, "no-session", nil)
|
||||
} else {
|
||||
headers = m.applyReplacement(headers, replacement, "no-session", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2519,6 +2535,46 @@ func (m *ProxyHandler) applyReplacement(body []byte, replacement service.ProxySe
|
||||
return m.applyReplacementWithVariables(body, replacement, sessionID, nil, contentType)
|
||||
}
|
||||
|
||||
// applyHeaderRewriteToHeaders applies a header-engine rewrite rule directly to an http.Header map.
|
||||
// find = header name (canonical form), action = "set" | "add" | "remove", replace = new value.
|
||||
// This operates directly on the header map so no serialize/parse round-trip is needed.
|
||||
func (m *ProxyHandler) applyHeaderRewriteToHeaders(headers http.Header, rule service.ProxyServiceReplaceRule, sessionID string, varCtx *VariablesContext) {
|
||||
if rule.Find == "" {
|
||||
m.logger.Errorw("header rewrite rule has empty find (header name)", "sessionID", sessionID, "rule", rule.Name)
|
||||
return
|
||||
}
|
||||
|
||||
// canonicalize the header name the same way Go's net/http does
|
||||
canonicalName := http.CanonicalHeaderKey(rule.Find)
|
||||
|
||||
action := rule.Action
|
||||
if action == "" {
|
||||
action = "set"
|
||||
}
|
||||
|
||||
switch action {
|
||||
case "remove":
|
||||
headers.Del(canonicalName)
|
||||
|
||||
case "add":
|
||||
value := rule.Replace
|
||||
if varCtx != nil && varCtx.Enabled && varCtx.Data != nil {
|
||||
value = m.interpolateVariables(value, varCtx)
|
||||
}
|
||||
headers.Add(canonicalName, value)
|
||||
|
||||
case "set":
|
||||
value := rule.Replace
|
||||
if varCtx != nil && varCtx.Enabled && varCtx.Data != nil {
|
||||
value = m.interpolateVariables(value, varCtx)
|
||||
}
|
||||
headers.Set(canonicalName, value)
|
||||
|
||||
default:
|
||||
m.logger.Errorw("unsupported header rewrite action", "action", action, "sessionID", sessionID, "rule", rule.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// applyReplacementWithVariables applies replacement with optional template variable interpolation
|
||||
func (m *ProxyHandler) applyReplacementWithVariables(body []byte, replacement service.ProxyServiceReplaceRule, sessionID string, varCtx *VariablesContext, contentType string) []byte {
|
||||
// interpolate variables in the replacement value if enabled
|
||||
@@ -2542,6 +2598,13 @@ func (m *ProxyHandler) applyReplacementWithVariables(body []byte, replacement se
|
||||
return m.applyRegexReplacement(body, interpolatedReplacement, sessionID)
|
||||
case "dom":
|
||||
return m.applyDomReplacement(body, interpolatedReplacement, sessionID, contentType)
|
||||
case "header":
|
||||
// header engine operates directly on http.Header — it cannot be used in a body rewrite context.
|
||||
// callers that handle headers (applyCustomResponseHeaderReplacementsWithVariables,
|
||||
// applyCustomResponseHeaderReplacementsWithoutSession, applyEarlyRequestHeaderReplacements)
|
||||
// intercept this engine before reaching here.
|
||||
m.logger.Errorw("header engine used in a non-header rewrite context — use from: request_header or response_header", "sessionID", sessionID, "rule", replacement.Name)
|
||||
return body
|
||||
default:
|
||||
m.logger.Errorw("unsupported replacement engine", "engine", engine, "sessionID", sessionID)
|
||||
return body
|
||||
@@ -2950,6 +3013,10 @@ func (m *ProxyHandler) applyEarlyRequestHeaderReplacements(req *http.Request, re
|
||||
if engine == "" {
|
||||
engine = "regex"
|
||||
}
|
||||
if engine == "header" {
|
||||
m.applyHeaderRewriteToHeaders(req.Header, replacement, "early-request", nil)
|
||||
continue
|
||||
}
|
||||
if engine == "regex" {
|
||||
re, err := regexp.Compile(replacement.Find)
|
||||
if err != nil {
|
||||
|
||||
+62
-11
@@ -309,15 +309,15 @@ func (c *ProxyServiceCaptureRule) GetFindAsString() string {
|
||||
// ProxyServiceReplaceRule represents a replacement rule
|
||||
type ProxyServiceReplaceRule struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Engine string `yaml:"engine,omitempty"` // "regex" (default) or "dom"
|
||||
Find string `yaml:"find,omitempty"` // regex pattern (regex engine) or css selector (dom engine)
|
||||
Replace string `yaml:"replace,omitempty"` // replacement value for both engines
|
||||
Action string `yaml:"action,omitempty"` // dom action: setText, setHtml, setAttr, removeAttr, addClass, removeClass, remove
|
||||
Target string `yaml:"target,omitempty"` // target matching: "first", "last", "all" (default), "1,3,5", "2-4"
|
||||
From string `yaml:"from,omitempty"`
|
||||
Path string `yaml:"path,omitempty"` // regex pattern to restrict rule to matching request paths
|
||||
Method string `yaml:"method,omitempty"` // restrict rule to this HTTP method (e.g. GET, POST)
|
||||
PathRe *regexp.Regexp `yaml:"-"` // compiled regex for path matching
|
||||
Engine string `yaml:"engine,omitempty"` // "regex" (default), "dom", or "header"
|
||||
Find string `yaml:"find,omitempty"` // regex pattern (regex engine), css selector (dom engine), or header name (header engine)
|
||||
Replace string `yaml:"replace,omitempty"` // replacement value (regex/dom), or new header value (header engine set/add actions)
|
||||
Action string `yaml:"action,omitempty"` // dom: setText, setHtml, setAttr, removeAttr, addClass, removeClass, remove — header: set, add, remove
|
||||
Target string `yaml:"target,omitempty"` // target matching for dom engine: "first", "last", "all" (default), "1,3,5", "2-4"
|
||||
From string `yaml:"from,omitempty"` // request_header, request_body, response_header, response_body, any
|
||||
Path string `yaml:"path,omitempty"` // regex pattern to restrict rule to matching request paths
|
||||
Method string `yaml:"method,omitempty"` // restrict rule to this HTTP method (e.g. GET, POST)
|
||||
PathRe *regexp.Regexp `yaml:"-"` // compiled regex for path matching
|
||||
}
|
||||
|
||||
// ProxyServiceURLRewriteRule represents a URL rewrite rule for anti-detection
|
||||
@@ -1355,6 +1355,17 @@ func (m *Proxy) cleanupRewriteRule(rule *ProxyServiceReplaceRule) {
|
||||
}
|
||||
// dom engine always uses response_body, force it
|
||||
rule.From = "response_body"
|
||||
} else if rule.Engine == "header" {
|
||||
// for header engine, remove dom/regex-specific fields
|
||||
rule.Target = ""
|
||||
// set default action to 'set' if not specified
|
||||
if rule.Action == "" {
|
||||
rule.Action = "set"
|
||||
}
|
||||
// set default 'from' to 'response_header' if not specified
|
||||
if rule.From == "" {
|
||||
rule.From = "response_header"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1368,9 +1379,9 @@ func (m *Proxy) validateReplaceRules(replaceRules []ProxyServiceReplaceRule) err
|
||||
}
|
||||
|
||||
// validate engine type
|
||||
if engine != "regex" && engine != "dom" {
|
||||
if engine != "regex" && engine != "dom" && engine != "header" {
|
||||
return validate.WrapErrorWithField(
|
||||
errors.New("invalid 'engine' value in replace rule, must be 'regex' or 'dom'"),
|
||||
errors.New("invalid 'engine' value in replace rule, must be one of: regex, dom, header"),
|
||||
"proxyConfig",
|
||||
)
|
||||
}
|
||||
@@ -1447,6 +1458,46 @@ func (m *Proxy) validateReplaceRules(replaceRules []ProxyServiceReplaceRule) err
|
||||
|
||||
// dom engine doesn't use 'from' field - it always works on response_body
|
||||
// if 'from' is specified for dom engine, it's ignored (will be forced to response_body)
|
||||
} else if engine == "header" {
|
||||
if replace.Find == "" {
|
||||
return validate.WrapErrorWithField(errors.New("replace rule 'find' (header name) is required for header engine"), "proxyConfig")
|
||||
}
|
||||
|
||||
// validate header action
|
||||
validActions := []string{"set", "add", "remove"}
|
||||
validAction := false
|
||||
for _, action := range validActions {
|
||||
if replace.Action == action || replace.Action == "" {
|
||||
validAction = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validAction {
|
||||
return validate.WrapErrorWithField(
|
||||
errors.New("invalid 'action' value for header engine, must be one of: "+strings.Join(validActions, ", ")),
|
||||
"proxyConfig",
|
||||
)
|
||||
}
|
||||
|
||||
// 'replace' (new value) is required for set and add, not for remove
|
||||
effectiveAction := replace.Action
|
||||
if effectiveAction == "" {
|
||||
effectiveAction = "set"
|
||||
}
|
||||
if (effectiveAction == "set" || effectiveAction == "add") && replace.Replace == "" {
|
||||
return validate.WrapErrorWithField(
|
||||
errors.New("replace rule 'replace' (new value) is required for header engine action '"+effectiveAction+"'"),
|
||||
"proxyConfig",
|
||||
)
|
||||
}
|
||||
|
||||
// validate that 'from' is a header context when explicitly set
|
||||
if replace.From != "" && replace.From != "request_header" && replace.From != "response_header" && replace.From != "any" {
|
||||
return validate.WrapErrorWithField(
|
||||
errors.New("header engine 'from' must be one of: request_header, response_header, any"),
|
||||
"proxyConfig",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if replace.From != "" {
|
||||
|
||||
@@ -525,6 +525,7 @@
|
||||
find: '',
|
||||
replace: '',
|
||||
from: 'response_body',
|
||||
action: '',
|
||||
path: '',
|
||||
method: ''
|
||||
}
|
||||
@@ -592,6 +593,7 @@
|
||||
find: '',
|
||||
replace: '',
|
||||
from: 'response_body',
|
||||
action: '',
|
||||
path: '',
|
||||
method: ''
|
||||
}
|
||||
@@ -727,8 +729,40 @@
|
||||
];
|
||||
const engines = [
|
||||
{ value: 'regex', label: 'Regex' },
|
||||
{ value: 'dom', label: 'DOM' }
|
||||
{ value: 'dom', label: 'DOM' },
|
||||
{ value: 'header', label: 'Header' }
|
||||
];
|
||||
|
||||
const headerActions = [
|
||||
{ value: 'set', label: 'Set' },
|
||||
{ value: 'add', label: 'Add' },
|
||||
{ value: 'remove', label: 'Remove' }
|
||||
];
|
||||
|
||||
const rewriteHeaderFromOptions = [
|
||||
{ value: 'request_header', label: 'Request Header' },
|
||||
{ value: 'response_header', label: 'Response Header' },
|
||||
{ value: 'any', label: 'Any' }
|
||||
];
|
||||
|
||||
function getFromOptionsForRewriteEngine(engine) {
|
||||
if (engine === 'header') return rewriteHeaderFromOptions;
|
||||
return fromOptions;
|
||||
}
|
||||
|
||||
function getDefaultFromForRewriteEngine(engine) {
|
||||
if (engine === 'header') return 'response_header';
|
||||
return 'response_body';
|
||||
}
|
||||
|
||||
function handleRewriteEngineChange(rule, newEngine) {
|
||||
rule.engine = newEngine;
|
||||
rule.from = getDefaultFromForRewriteEngine(newEngine);
|
||||
if (newEngine !== 'dom') {
|
||||
rule.action = newEngine === 'header' ? 'set' : '';
|
||||
}
|
||||
configData = configData;
|
||||
}
|
||||
const domActions = [
|
||||
{ value: 'setText', label: 'Set Text' },
|
||||
{ value: 'setHtml', label: 'Set HTML' },
|
||||
@@ -1910,6 +1944,7 @@
|
||||
bind:value={rule.engine}
|
||||
options={engines}
|
||||
size="normal"
|
||||
on:change={() => handleRewriteEngineChange(rule, rule.engine)}
|
||||
>
|
||||
Engine
|
||||
</TextFieldSelect>
|
||||
@@ -1966,31 +2001,45 @@
|
||||
>Also supports numeric list (1,3,5) or range (2-4)</span
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
{:else if rule.engine === 'header'}
|
||||
<div class="field-wrapper">
|
||||
<TextFieldSelect
|
||||
id={`host-${expandedHostIndex}-rewrite-${ruleIndex}-from`}
|
||||
bind:value={rule.from}
|
||||
options={fromOptions}
|
||||
id={`host-${expandedHostIndex}-rewrite-${ruleIndex}-action`}
|
||||
bind:value={rule.action}
|
||||
options={headerActions}
|
||||
size="normal"
|
||||
>
|
||||
From
|
||||
Action
|
||||
</TextFieldSelect>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="field-wrapper">
|
||||
<TextFieldSelect
|
||||
id={`host-${expandedHostIndex}-rewrite-${ruleIndex}-from`}
|
||||
bind:value={rule.from}
|
||||
options={getFromOptionsForRewriteEngine(rule.engine)}
|
||||
size="normal"
|
||||
>
|
||||
From
|
||||
</TextFieldSelect>
|
||||
</div>
|
||||
<div class="field-wrapper full">
|
||||
<TextField
|
||||
width="full"
|
||||
bind:value={rule.find}
|
||||
placeholder={rule.engine === 'dom'
|
||||
? 'div.logo, #header img'
|
||||
: 'target\\.com'}
|
||||
: rule.engine === 'header'
|
||||
? 'Server'
|
||||
: 'target\\.com'}
|
||||
error={hasError(
|
||||
`hosts.${expandedHostIndex}.rewrite.${ruleIndex}.find`
|
||||
)}
|
||||
>
|
||||
{#if rule.engine === 'dom'}
|
||||
Selector (CSS)
|
||||
{:else if rule.engine === 'header'}
|
||||
Header Name
|
||||
{:else}
|
||||
Find (regex)
|
||||
{/if}
|
||||
@@ -2005,59 +2054,69 @@
|
||||
<span class="form-hint">
|
||||
{#if rule.engine === 'dom'}
|
||||
CSS selector to find HTML elements
|
||||
{:else if rule.engine === 'header'}
|
||||
Exact header name (e.g. Server, X-Powered-By)
|
||||
{:else}
|
||||
Regex pattern to search for in content
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="field-wrapper full">
|
||||
<TextareaField
|
||||
fullWidth
|
||||
bind:value={rule.replace}
|
||||
placeholder={rule.engine === 'dom'
|
||||
? rule.action === 'setAttr'
|
||||
? 'href:https://example.com'
|
||||
: rule.action === 'remove'
|
||||
? ''
|
||||
: 'New content'
|
||||
: 'phishing.com'}
|
||||
height="medium"
|
||||
>
|
||||
{#if rule.engine === 'dom' && rule.action === 'setAttr'}
|
||||
Value (attr:value)
|
||||
{:else if rule.engine === 'dom' && rule.action === 'remove'}
|
||||
Value (not required)
|
||||
{:else}
|
||||
Replace
|
||||
{/if}
|
||||
</TextareaField>
|
||||
{#if hasError(`hosts.${expandedHostIndex}.rewrite.${ruleIndex}.replace`)}
|
||||
<span class="field-error"
|
||||
>{getError(
|
||||
`hosts.${expandedHostIndex}.rewrite.${ruleIndex}.replace`
|
||||
)}</span
|
||||
{#if rule.engine !== 'header' || rule.action !== 'remove'}
|
||||
<div class="field-wrapper full">
|
||||
<TextareaField
|
||||
fullWidth
|
||||
bind:value={rule.replace}
|
||||
placeholder={rule.engine === 'dom'
|
||||
? rule.action === 'setAttr'
|
||||
? 'href:https://example.com'
|
||||
: rule.action === 'remove'
|
||||
? ''
|
||||
: 'New content'
|
||||
: rule.engine === 'header'
|
||||
? 'new-value'
|
||||
: 'phishing.com'}
|
||||
height="medium"
|
||||
>
|
||||
{:else}
|
||||
<span class="form-hint">
|
||||
{#if rule.engine === 'dom'}
|
||||
{#if rule.action === 'setAttr'}
|
||||
Format: attribute:value (e.g. href:https://example.com)
|
||||
{:else if rule.action === 'remove'}
|
||||
Not required for remove action
|
||||
{:else if rule.action === 'removeAttr'}
|
||||
Attribute name to remove
|
||||
{:else if rule.action === 'addClass' || rule.action === 'removeClass'}
|
||||
CSS class name
|
||||
{:else}
|
||||
New content for matched elements
|
||||
{/if}
|
||||
{#if rule.engine === 'dom' && rule.action === 'setAttr'}
|
||||
Value (attr:value)
|
||||
{:else if rule.engine === 'dom' && rule.action === 'remove'}
|
||||
Value (not required)
|
||||
{:else if rule.engine === 'header'}
|
||||
New Value
|
||||
{:else}
|
||||
Replacement text (use $1, $2 for capture groups)
|
||||
Replace
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</TextareaField>
|
||||
{#if hasError(`hosts.${expandedHostIndex}.rewrite.${ruleIndex}.replace`)}
|
||||
<span class="field-error"
|
||||
>{getError(
|
||||
`hosts.${expandedHostIndex}.rewrite.${ruleIndex}.replace`
|
||||
)}</span
|
||||
>
|
||||
{:else}
|
||||
<span class="form-hint">
|
||||
{#if rule.engine === 'dom'}
|
||||
{#if rule.action === 'setAttr'}
|
||||
Format: attribute:value (e.g. href:https://example.com)
|
||||
{:else if rule.action === 'remove'}
|
||||
Not required for remove action
|
||||
{:else if rule.action === 'removeAttr'}
|
||||
Attribute name to remove
|
||||
{:else if rule.action === 'addClass' || rule.action === 'removeClass'}
|
||||
CSS class name
|
||||
{:else}
|
||||
New content for matched elements
|
||||
{/if}
|
||||
{:else if rule.engine === 'header'}
|
||||
New value for the header
|
||||
{:else}
|
||||
Replacement text (use $1, $2 for capture groups)
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
@@ -2724,6 +2783,7 @@
|
||||
bind:value={rule.engine}
|
||||
options={engines}
|
||||
size="normal"
|
||||
on:change={() => handleRewriteEngineChange(rule, rule.engine)}
|
||||
>
|
||||
Engine
|
||||
</TextFieldSelect>
|
||||
@@ -2778,29 +2838,43 @@
|
||||
>Also supports numeric list (1,3,5) or range (2-4)</span
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
{:else if rule.engine === 'header'}
|
||||
<div class="field-wrapper">
|
||||
<TextFieldSelect
|
||||
id={`global-rewrite-${i}-from`}
|
||||
bind:value={rule.from}
|
||||
options={fromOptions}
|
||||
id={`global-rewrite-${i}-action`}
|
||||
bind:value={rule.action}
|
||||
options={headerActions}
|
||||
size="normal"
|
||||
>
|
||||
From
|
||||
Action
|
||||
</TextFieldSelect>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="field-wrapper">
|
||||
<TextFieldSelect
|
||||
id={`global-rewrite-${i}-from`}
|
||||
bind:value={rule.from}
|
||||
options={getFromOptionsForRewriteEngine(rule.engine)}
|
||||
size="normal"
|
||||
>
|
||||
From
|
||||
</TextFieldSelect>
|
||||
</div>
|
||||
<div class="field-wrapper full">
|
||||
<TextField
|
||||
width="full"
|
||||
bind:value={rule.find}
|
||||
placeholder={rule.engine === 'dom'
|
||||
? 'div.logo, #header img'
|
||||
: 'target\\.com'}
|
||||
: rule.engine === 'header'
|
||||
? 'Server'
|
||||
: 'target\\.com'}
|
||||
error={hasError(`global.rewrite.${i}.find`)}
|
||||
>
|
||||
{#if rule.engine === 'dom'}
|
||||
Selector (CSS)
|
||||
{:else if rule.engine === 'header'}
|
||||
Header Name
|
||||
{:else}
|
||||
Find (regex)
|
||||
{/if}
|
||||
@@ -2811,57 +2885,67 @@
|
||||
<span class="form-hint">
|
||||
{#if rule.engine === 'dom'}
|
||||
CSS selector to find HTML elements
|
||||
{:else if rule.engine === 'header'}
|
||||
Exact header name (e.g. Server, X-Powered-By)
|
||||
{:else}
|
||||
Regex pattern to search for in content
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="field-wrapper full">
|
||||
<TextareaField
|
||||
fullWidth
|
||||
bind:value={rule.replace}
|
||||
placeholder={rule.engine === 'dom'
|
||||
? rule.action === 'setAttr'
|
||||
? 'href:https://example.com'
|
||||
: rule.action === 'remove'
|
||||
? ''
|
||||
: 'New content'
|
||||
: 'phishing.com'}
|
||||
height="medium"
|
||||
>
|
||||
{#if rule.engine === 'dom' && rule.action === 'setAttr'}
|
||||
Value (attr:value)
|
||||
{:else if rule.engine === 'dom' && rule.action === 'remove'}
|
||||
Value (not required)
|
||||
{:else}
|
||||
Replace
|
||||
{/if}
|
||||
</TextareaField>
|
||||
{#if hasError(`global.rewrite.${i}.replace`)}
|
||||
<span class="field-error"
|
||||
>{getError(`global.rewrite.${i}.replace`)}</span
|
||||
{#if rule.engine !== 'header' || rule.action !== 'remove'}
|
||||
<div class="field-wrapper full">
|
||||
<TextareaField
|
||||
fullWidth
|
||||
bind:value={rule.replace}
|
||||
placeholder={rule.engine === 'dom'
|
||||
? rule.action === 'setAttr'
|
||||
? 'href:https://example.com'
|
||||
: rule.action === 'remove'
|
||||
? ''
|
||||
: 'New content'
|
||||
: rule.engine === 'header'
|
||||
? 'new-value'
|
||||
: 'phishing.com'}
|
||||
height="medium"
|
||||
>
|
||||
{:else}
|
||||
<span class="form-hint">
|
||||
{#if rule.engine === 'dom'}
|
||||
{#if rule.action === 'setAttr'}
|
||||
Format: attribute:value (e.g. href:https://example.com)
|
||||
{:else if rule.action === 'remove'}
|
||||
Not required for remove action
|
||||
{:else if rule.action === 'removeAttr'}
|
||||
Attribute name to remove
|
||||
{:else if rule.action === 'addClass' || rule.action === 'removeClass'}
|
||||
CSS class name
|
||||
{:else}
|
||||
New content for matched elements
|
||||
{/if}
|
||||
{#if rule.engine === 'dom' && rule.action === 'setAttr'}
|
||||
Value (attr:value)
|
||||
{:else if rule.engine === 'dom' && rule.action === 'remove'}
|
||||
Value (not required)
|
||||
{:else if rule.engine === 'header'}
|
||||
New Value
|
||||
{:else}
|
||||
Replacement text (use $1, $2 for capture groups)
|
||||
Replace
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</TextareaField>
|
||||
{#if hasError(`global.rewrite.${i}.replace`)}
|
||||
<span class="field-error"
|
||||
>{getError(`global.rewrite.${i}.replace`)}</span
|
||||
>
|
||||
{:else}
|
||||
<span class="form-hint">
|
||||
{#if rule.engine === 'dom'}
|
||||
{#if rule.action === 'setAttr'}
|
||||
Format: attribute:value (e.g. href:https://example.com)
|
||||
{:else if rule.action === 'remove'}
|
||||
Not required for remove action
|
||||
{:else if rule.action === 'removeAttr'}
|
||||
Attribute name to remove
|
||||
{:else if rule.action === 'addClass' || rule.action === 'removeClass'}
|
||||
CSS class name
|
||||
{:else}
|
||||
New content for matched elements
|
||||
{/if}
|
||||
{:else if rule.engine === 'header'}
|
||||
New value for the header
|
||||
{:else}
|
||||
Replacement text (use $1, $2 for capture groups)
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
Reference in New Issue
Block a user