Kaynağa Gözat

Editor: ss obfs, socks5 auth (upcoming)

bdbai 3 yıl önce
ebeveyn
işleme
e62d2ef022

+ 7 - 0
Maple.App/MonacoEditor/src/completion.ts

@@ -243,6 +243,13 @@ function completeProxy(
                 insertText: c,
                 range,
             }))
+        case facts.PROXY_PROPERTY_KEY_OBFS:
+            return facts.KNOWN_OBFS_METHODS.map(o => ({
+                label: o,
+                kind: monaco.languages.CompletionItemKind.EnumMember,
+                insertText: o,
+                range,
+            }))
         case facts.PROXY_PROPERTY_KEY_WS:
         case facts.PROXY_PROPERTY_KEY_TLS:
         case facts.PROXY_PROPERTY_KEY_AMUX:

+ 29 - 6
Maple.App/MonacoEditor/src/facts.ts

@@ -82,14 +82,18 @@ export const PROTOCOL_VMESS = 'vmess'
 export const KNOWN_AEAD_CIPHERS = ['chacha20-poly1305', 'chacha20-ietf-poly1305', 'aes-256-gcm', 'aes-128-gcm']
 export const KNOWN_AEAD_CIPHERS_SET = new Set(KNOWN_AEAD_CIPHERS)
 
+export const KNOWN_OBFS_METHOD_HTTP = 'http'
+export const KNOWN_OBFS_METHOD_TLS = 'tls'
+export const KNOWN_OBFS_METHODS = [KNOWN_OBFS_METHOD_HTTP, KNOWN_OBFS_METHOD_TLS]
+
 export const PROXY_PROTOCOLS: IProxyProtocolDef[] = [
     { name: PROTOCOL_DIRECT, desc: 'Forward requests directly without going through any proxies.', snippet: 'direct' },
     { name: PROTOCOL_REJECT, desc: 'Close connections immediately.', snippet: 'reject' },
     { name: PROTOCOL_REJECT_DROP, desc: 'Close connections immediately. Alias for `' + PROTOCOL_REJECT + '`.', snippet: 'drop' },
     { name: PROTOCOL_REDIRECT, desc: 'Rewrite connection destinations to a pre-defined address.', snippet: 'redirect, ${1:host}, ${2:port}' },
     { name: PROTOCOL_SOCKS, desc: 'A SOCKS5 proxy.', snippet: 'socks, ${1:host}, ${2:port}' },
-    { name: PROTOCOL_SHADOWSOCKS, desc: 'A Shadowsocks proxy.', snippet: `shadowsocks, \${1:host}, \${2:port}, encrypt-method=\${3|${KNOWN_AEAD_CIPHERS.join(',')}|}, password=\${4:password}` },
-    { name: PROTOCOL_SHADOWSOCKS_SS, desc: 'A Shadowsocks proxy. Alias for `' + PROTOCOL_SHADOWSOCKS + '`.', snippet: `ss, \${1:host}, \${2:port}, encrypt-method=\${3|${KNOWN_AEAD_CIPHERS.join(',')}|}, password=\${4:password}` },
+    { name: PROTOCOL_SHADOWSOCKS, desc: 'A Shadowsocks proxy with optional obfuscation.', snippet: `shadowsocks, \${1:host}, \${2:port}, encrypt-method=\${3|${KNOWN_AEAD_CIPHERS.join(',')}|}, password=\${4:password}` },
+    { name: PROTOCOL_SHADOWSOCKS_SS, desc: 'A Shadowsocks proxy with optional obfuscation. Alias for `' + PROTOCOL_SHADOWSOCKS + '`.', snippet: `ss, \${1:host}, \${2:port}, encrypt-method=\${3|${KNOWN_AEAD_CIPHERS.join(',')}|}, password=\${4:password}` },
     { name: PROTOCOL_TROJAN, desc: 'A Trojan proxy.\n\nCompatible with Trojan-GFW and Trojan-Go with `ws` enabled.', snippet: 'trojan, ${1:ip}, ${2:port}, password=${3:password}, sni=${4:hostname}' },
     { name: PROTOCOL_VMESS, desc: 'A VMess proxy with optional WebSocket and TLS transports.', snippet: 'vmess, ${1:host}, ${2:port}, username=${3:username}' },
 ]
@@ -112,6 +116,9 @@ export const PROXY_PROPERTY_KEY_WS_PATH = 'ws-path'
 export const PROXY_PROPERTY_KEY_WS_HOST = 'ws-host'
 export const PROXY_PROPERTY_KEY_TLS = 'tls'
 export const PROXY_PROPERTY_KEY_TLS_CERT = 'tls-cert'
+export const PROXY_PROPERTY_KEY_OBFS = 'obfs'
+export const PROXY_PROPERTY_KEY_OBFS_HOST = 'obfs-host'
+export const PROXY_PROPERTY_KEY_OBFS_PATH = 'obfs-path'
 export const PROXY_PROPERTY_KEY_SNI = 'sni'
 export const PROXY_PROPERTY_KEY_QUIC = 'quic'
 export const PROXY_PROPERTY_KEY_AMUX = 'amux'
@@ -121,13 +128,16 @@ export const PROXY_PROPERTY_KEY_INTERFACE = 'interface'
 
 export const PROXY_PROPERTY_KEYS_DESC_MAP = new Map([
     [PROXY_PROPERTY_KEY_METHOD, `Encryption method for Shadowsocks and VMess. Possible values are \`${KNOWN_AEAD_CIPHERS.join('`, `')}\`.\n\nDefaults to \`chacha20-ietf-poly1305\`.`],
-    [PROXY_PROPERTY_KEY_USERNAME, 'User name for VMess.'],
+    [PROXY_PROPERTY_KEY_USERNAME, 'User name for VMess and SOCKS5.'],
     [PROXY_PROPERTY_KEY_PASSWORD, 'Password of the proxy server.'],
     [PROXY_PROPERTY_KEY_WS, 'Specify whether WebSocket transport should be enabled.\n\nDefaults to \`false\`.'],
     [PROXY_PROPERTY_KEY_WS_PATH, 'Path for WebSocket transport.'],
     [PROXY_PROPERTY_KEY_WS_HOST, 'Host for WebSocket transport.'],
     [PROXY_PROPERTY_KEY_TLS, 'Specify whether TLS transport should be enabled.\n\nDefaults to \`false\`.'],
     [PROXY_PROPERTY_KEY_TLS_CERT, 'Certificate file for TLS transport.'],
+    [PROXY_PROPERTY_KEY_OBFS, `Specify which obfuscation method to use. Possible values are \`${KNOWN_OBFS_METHODS.join('`, `')}\`.\n\nDefaults to \`http\`.`],
+    [PROXY_PROPERTY_KEY_OBFS_HOST, 'Specify the \`host\` parameter for obfuscation.'],
+    [PROXY_PROPERTY_KEY_OBFS_PATH, 'Specify the \`path\` parameter for `http` obfuscation.'],
     [PROXY_PROPERTY_KEY_SNI, 'Server name (SNI), or host name for TLS transport.\n\nIf omitted, the host name of the proxy server will be used.'],
     [PROXY_PROPERTY_KEY_QUIC, 'Specify whether QUIC transport should be enabled.\n\nDefaults to \`false\`.'],
     [PROXY_PROPERTY_KEY_AMUX, 'Specify whether AMUX transport should be enabled.\n\nDefaults to \`false\`.'],
@@ -146,20 +156,33 @@ export const PROXY_PROTOCOL_PROPERTY_KEY_MAP: Record<string, IProxyPropertyKeyDe
     [PROTOCOL_REJECT]: { required: new Set(), allowed: new Set() },
     [PROTOCOL_REJECT_DROP]: { required: new Set(), allowed: new Set() },
     [PROTOCOL_REDIRECT]: { required: new Set(), allowed: new Set() },
-    [PROTOCOL_SOCKS]: { required: new Set(), allowed: new Set() },
+    [PROTOCOL_SOCKS]: {
+        required: new Set(), allowed: new Set([
+            PROXY_PROPERTY_KEY_USERNAME,
+            PROXY_PROPERTY_KEY_PASSWORD,
+        ])
+    },
     [PROTOCOL_SHADOWSOCKS]: {
         required: new Set([
             PROXY_PROPERTY_KEY_METHOD,
             PROXY_PROPERTY_KEY_PASSWORD,
         ]),
-        allowed: new Set(),
+        allowed: new Set([
+            PROXY_PROPERTY_KEY_OBFS,
+            PROXY_PROPERTY_KEY_OBFS_HOST,
+            PROXY_PROPERTY_KEY_OBFS_PATH,
+        ]),
     },
     [PROTOCOL_SHADOWSOCKS_SS]: {
         required: new Set([
             PROXY_PROPERTY_KEY_METHOD,
             PROXY_PROPERTY_KEY_PASSWORD,
         ]),
-        allowed: new Set(),
+        allowed: new Set([
+            PROXY_PROPERTY_KEY_OBFS,
+            PROXY_PROPERTY_KEY_OBFS_HOST,
+            PROXY_PROPERTY_KEY_OBFS_PATH,
+        ]),
     },
     [PROTOCOL_TROJAN]: {
         required: new Set([PROXY_PROPERTY_KEY_PASSWORD]),

+ 61 - 9
Maple.App/MonacoEditor/src/validate.ts

@@ -359,6 +359,12 @@ function validateProxyItem(
     const argsWithKv = args.slice(firstKvArgId)
     const visitedKvs: Map<string, ILeafConfKvItem> = new Map()
     const requiredVisited = new Map([...protocolKeyDef.required].map(k => [k, false]))
+    const obfsSettings = {
+        allowed: protocolKeyDef.allowed.has(facts.PROXY_PROPERTY_KEY_OBFS),
+        methodKv: undefined as ILeafConfKvItem | undefined,
+        hostKvs: [] as ILeafConfKvItem[],
+        pathKvs: [] as ILeafConfKvItem[],
+    }
     for (const arg of argsWithKv) {
         const kv = parseKvLine(arg.text, item.lineId, arg.startCol)
         if (kv === undefined) {
@@ -433,6 +439,27 @@ function validateProxyItem(
             case facts.PROXY_PROPERTY_KEY_INTERFACE:
                 validateNonEmpty(kv.value, item.lineId, kv.valueStartCol, errors)
                 continue
+            case facts.PROXY_PROPERTY_KEY_OBFS:
+                obfsSettings.methodKv = kv
+                if (!facts.KNOWN_OBFS_METHODS.includes(kv.value)) {
+                    const allowedMethods = facts.KNOWN_OBFS_METHODS.join('", "')
+                    errors.push({
+                        severity: monaco.MarkerSeverity.Error,
+                        startLineNumber: item.lineId,
+                        startColumn: kv.valueStartCol,
+                        endLineNumber: item.lineId,
+                        endColumn: kv.valueStartCol + kv.value.length,
+                        message: `Unknown obfs method "${kv.value}". `
+                            + `Allowed values are "${allowedMethods}".`,
+                    })
+                }
+                break
+            case facts.PROXY_PROPERTY_KEY_OBFS_HOST:
+                obfsSettings.hostKvs.push(kv)
+                break
+            case facts.PROXY_PROPERTY_KEY_OBFS_PATH:
+                obfsSettings.pathKvs.push(kv)
+                break
             default:
                 isUnknownKey = true
                 errors.push({
@@ -456,6 +483,31 @@ function validateProxyItem(
             })
         }
     }
+    if (obfsSettings.allowed && obfsSettings.methodKv !== undefined) {
+        const { methodKv, hostKvs, pathKvs } = obfsSettings
+        if (hostKvs.length === 0) {
+            errors.push({
+                severity: monaco.MarkerSeverity.Warning,
+                startLineNumber: item.lineId,
+                startColumn: methodKv.valueStartCol,
+                endLineNumber: item.lineId,
+                endColumn: methodKv.valueStartCol + methodKv.value.length,
+                message: `"${facts.PROXY_PROPERTY_KEY_OBFS_HOST}" is required for obfuscation.`,
+            })
+        }
+        if (methodKv.value === facts.KNOWN_OBFS_METHOD_TLS) {
+            for (const pathKv of pathKvs) {
+                errors.push({
+                    severity: monaco.MarkerSeverity.Warning,
+                    startLineNumber: item.lineId,
+                    startColumn: pathKv.keyStartCol,
+                    endLineNumber: item.lineId,
+                    endColumn: pathKv.keyStartCol + pathKv.key.length,
+                    message: `"${pathKv.key}" is ignored for "${facts.KNOWN_OBFS_METHOD_TLS}" obfuscation method.`,
+                })
+            }
+        }
+    }
     for (const [key, visited] of requiredVisited) {
         if (!visited) {
             errors.push({
@@ -1016,15 +1068,15 @@ function validateRules(
                                 if (segs.length > 2 && segs[1] === '') {
                                     groupSegId = 2
                                     if (segs[1] === '') {
-                                    errors.push({
-                                        severity: monaco.MarkerSeverity.Error,
-                                        startLineNumber: currLineId,
-                                        startColumn: ruleItem.startCol,
-                                        endLineNumber: currLineId,
-                                        endColumn: ruleItem.startCol + ruleItem.text.length,
-                                        message: `An external site rule must have a non-empty database file name.`,
-                                    })
-                                }
+                                        errors.push({
+                                            severity: monaco.MarkerSeverity.Error,
+                                            startLineNumber: currLineId,
+                                            startColumn: ruleItem.startCol,
+                                            endLineNumber: currLineId,
+                                            endColumn: ruleItem.startCol + ruleItem.text.length,
+                                            message: `An external site rule must have a non-empty database file name.`,
+                                        })
+                                    }
                                 }
                                 if (segs[groupSegId] === '') {
                                     errors.push({