@@ -42,21 +42,10 @@ const grammars = String.raw`
4242 (quote === '"' || quote === "'") &&
4343 trimmed[trimmed.length - 1] === quote
4444 ) {
45- return trimmed.slice(1, -1).replace(/\\(["'\\])/g, "$1");
46- }
47-
48- return trimmed.replace(/\\(["'\\])/g, "$1");
49- }
50- function isEscaped(text, index) {
51- let count = 0;
52- let cursor = index - 1;
53-
54- while (cursor >= 0 && text[cursor] === "\\") {
55- count++;
56- cursor--;
45+ return trimmed.slice(1, -1);
5746 }
5847
59- return count % 2 === 1 ;
48+ return trimmed ;
6049 }
6150 function readQuotedHeaderKey(text, start) {
6251 const quote = text[start];
@@ -65,11 +54,6 @@ const grammars = String.raw`
6554
6655 while (index < text.length) {
6756 const char = text[index];
68- if (char === "\\" && index + 1 < text.length) {
69- hasKey = true;
70- index += 2;
71- continue;
72- }
7357 if (char === quote) {
7458 return hasKey ? index + 1 : -1;
7559 }
@@ -125,15 +109,35 @@ const grammars = String.raw`
125109 while (index < text.length && /\s/.test(text[index])) index++;
126110 return text[index] === ":";
127111 }
128- function isHeaderValueQuoteEnd(text, index) {
112+ function isOptionStart(text, start) {
113+ let index = start;
114+ while (index < text.length && /\s/.test(text[index])) index++;
115+
116+ const keyStart = index;
117+ while (index < text.length && /[0-9A-Za-z-]/.test(text[index])) index++;
118+ if (index === keyStart) return false;
119+
120+ while (index < text.length && /\s/.test(text[index])) index++;
121+ return text[index] === "=";
122+ }
123+ function isHeaderValueQuoteEnd(text, index, pairSeparator, allowCommaEnd, containerQuote) {
129124 let cursor = index + 1;
130125 while (cursor < text.length && /\s/.test(text[cursor])) cursor++;
131126
132- return (
133- cursor >= text.length ||
134- text[cursor] === "," ||
135- (text[cursor] === ";" && isHeaderKeyStart(text, cursor + 1))
136- );
127+ if (cursor >= text.length) return true;
128+ if (allowCommaEnd && text[cursor] === "," && isOptionStart(text, cursor + 1)) {
129+ return true;
130+ }
131+ if (text[cursor] === pairSeparator && isHeaderKeyStart(text, cursor + 1)) {
132+ return true;
133+ }
134+ if (containerQuote && text[cursor] === containerQuote) {
135+ let next = cursor + 1;
136+ while (next < text.length && /\s/.test(text[next])) next++;
137+ return next >= text.length || text[next] === ",";
138+ }
139+
140+ return false;
137141 }
138142 function findHeaderSeparator(pair) {
139143 let quote = "";
@@ -142,10 +146,6 @@ const grammars = String.raw`
142146 const char = pair[index];
143147
144148 if (quote) {
145- if (char === "\\" && index + 1 < pair.length) {
146- index++;
147- continue;
148- }
149149 if (char === quote) {
150150 quote = "";
151151 }
@@ -164,7 +164,7 @@ const grammars = String.raw`
164164
165165 return -1;
166166 }
167- function readUnquotedHeadersEnd(text, start) {
167+ function readUnquotedHeadersEnd(text, start, pairSeparator ) {
168168 let index = start;
169169 let quote = "";
170170 let quoteRole = "";
@@ -174,14 +174,10 @@ const grammars = String.raw`
174174 const char = text[index];
175175
176176 if (quote) {
177- if (char === "\\" && index + 1 < text.length) {
178- index += 2;
179- continue;
180- }
181177 if (char === quote) {
182178 if (
183179 quoteRole === "key" ||
184- isHeaderValueQuoteEnd(text, index)
180+ isHeaderValueQuoteEnd(text, index, pairSeparator, true )
185181 ) {
186182 quote = "";
187183 quoteRole = "";
@@ -204,7 +200,7 @@ const grammars = String.raw`
204200 continue;
205201 }
206202
207- if (char === ";" && isHeaderKeyStart(text, index + 1)) {
203+ if (char === pairSeparator && isHeaderKeyStart(text, index + 1)) {
208204 seenSeparator = false;
209205 index++;
210206 continue;
@@ -216,37 +212,75 @@ const grammars = String.raw`
216212
217213 return index;
218214 }
219- function readQuotedHeadersEnd(text, start) {
215+ function readQuotedHeadersEnd(text, start, pairSeparator ) {
220216 const quote = text[start];
221217 let index = start + 1;
218+ let innerQuote = "";
219+ let quoteRole = "";
220+ let seenSeparator = false;
222221
223222 while (index < text.length) {
224- if (text[index] === quote && !isEscaped(text, index)) {
223+ const char = text[index];
224+
225+ if (innerQuote) {
226+ if (char === innerQuote) {
227+ if (
228+ quoteRole === "key" ||
229+ isHeaderValueQuoteEnd(text, index, pairSeparator, false, quote)
230+ ) {
231+ innerQuote = "";
232+ quoteRole = "";
233+ }
234+ }
235+ index++;
236+ continue;
237+ }
238+
239+ if (char === quote) {
225240 let cursor = index + 1;
226241 while (cursor < text.length && /\s/.test(text[cursor])) cursor++;
227242 if (cursor >= text.length || text[cursor] === ",") {
228243 return index + 1;
229244 }
230245 }
246+
247+ if (char === '"' || char === "'") {
248+ innerQuote = char;
249+ quoteRole = seenSeparator ? "value" : "key";
250+ index++;
251+ continue;
252+ }
253+
254+ if (char === ":" && !seenSeparator) {
255+ seenSeparator = true;
256+ index++;
257+ continue;
258+ }
259+
260+ if (char === pairSeparator && isHeaderKeyStart(text, index + 1)) {
261+ seenSeparator = false;
262+ index++;
263+ continue;
264+ }
231265 index++;
232266 }
233267
234268 return text.length;
235269 }
236- function readHeadersEnd(text, start) {
270+ function readHeadersEnd(text, start, pairSeparator ) {
237271 let index = start;
238272 while (index < text.length && /\s/.test(text[index])) index++;
239273
240274 if (
241275 (text[index] === '"' || text[index] === "'") &&
242276 !startsWithQuotedHeaderKey(text.slice(index))
243277 ) {
244- return readQuotedHeadersEnd(text, index);
278+ return readQuotedHeadersEnd(text, index, pairSeparator );
245279 }
246280
247- return readUnquotedHeadersEnd(text, start);
281+ return readUnquotedHeadersEnd(text, start, pairSeparator );
248282 }
249- function splitHeaders(headers) {
283+ function splitHeaders(headers, pairSeparator ) {
250284 const result = [];
251285 let start = 0;
252286 let quote = "";
@@ -257,14 +291,10 @@ const grammars = String.raw`
257291 const char = headers[index];
258292
259293 if (quote) {
260- if (char === "\\" && index + 1 < headers.length) {
261- index++;
262- continue;
263- }
264294 if (char === quote) {
265295 if (
266296 quoteRole === "key" ||
267- isHeaderValueQuoteEnd(headers, index)
297+ isHeaderValueQuoteEnd(headers, index, pairSeparator, false )
268298 ) {
269299 quote = "";
270300 quoteRole = "";
@@ -284,7 +314,7 @@ const grammars = String.raw`
284314 continue;
285315 }
286316
287- if (char === ";" && isHeaderKeyStart(headers, index + 1)) {
317+ if (char === pairSeparator && isHeaderKeyStart(headers, index + 1)) {
288318 result.push(headers.slice(start, index));
289319 start = index + 1;
290320 seenSeparator = false;
@@ -294,9 +324,9 @@ const grammars = String.raw`
294324 result.push(headers.slice(start));
295325 return result;
296326 }
297- function parseHeaders(headers) {
327+ function parseHeaders(headers, pairSeparator ) {
298328 const result = {};
299- splitHeaders(stripOuterHeadersQuotes(headers)).forEach((pair) => {
329+ splitHeaders(stripOuterHeadersQuotes(headers), pairSeparator ).forEach((pair) => {
300330 const index = findHeaderSeparator(pair);
301331 if (index === -1) return;
302332
@@ -496,24 +526,23 @@ method = comma "encrypt-method" equals cipher:cipher {
496526cipher = ("aes-128-cfb"/"aes-128-ctr"/"aes-128-gcm"/"aes-192-cfb"/"aes-192-ctr"/"aes-192-gcm"/"aes-256-cfb"/"aes-256-ctr"/"aes-256-gcm"/"bf-cfb"/"camellia-128-cfb"/"camellia-192-cfb"/"camellia-256-cfb"/"cast5-cfb"/"chacha20-ietf-poly1305"/"chacha20-ietf"/"chacha20-poly1305"/"chacha20"/"des-cfb"/"idea-cfb"/"none"/"rc2-cfb"/"rc4-md5"/"rc4"/"salsa20"/"seed-cfb"/"xchacha20-ietf-poly1305"/"2022-blake3-aes-128-gcm"/"2022-blake3-aes-256-gcm");
497527
498528ws = comma "ws" equals flag:bool { obfs.type = "ws"; }
499- ws_headers = comma "ws-headers" equals headers:$[^,]+ {
500- const pairs = headers.split("|");
501- const result = {};
502- pairs.forEach(pair => {
503- const [key, value] = pair.trim().split(":");
504- result[key.trim()] = value.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1');
505- })
506- obfs["ws-headers"] = result;
507- }
529+ ws_headers = comma "ws-headers" equals & {
530+ const start = peg$currPos;
531+ const index = readHeadersEnd(input, start, "|");
532+
533+ $.headers = input.substring(start, index);
534+ peg$currPos = index;
535+ return $.headers.trim().length > 0;
536+ } { obfs["ws-headers"] = parseHeaders($.headers, "|"); }
508537ws_path = comma "ws-path" equals path:uri { obfs.path = path.trim().replace(/^"(.*?)"$/, '$1').replace(/^'(.*?)'$/, '$1'); }
509538headers = comma "headers" equals & {
510539 const start = peg$currPos;
511- const index = readHeadersEnd(input, start);
540+ const index = readHeadersEnd(input, start, ";" );
512541
513542 $.headers = input.substring(start, index);
514543 peg$currPos = index;
515544 return $.headers.trim().length > 0;
516- } { proxy.headers = parseHeaders($.headers); }
545+ } { proxy.headers = parseHeaders($.headers, ";" ); }
517546
518547obfs = comma "obfs" equals type:("http"/"tls") { obfs.type = type; }
519548obfs_host = comma "obfs-host" equals match:[^,]+ { obfs.host = match.join("").replace(/^"(.*)"$/, '$1'); };
0 commit comments