22 * HTTP请求解析器 - Fetch格式解析模块
33 *
44 * 支持解析浏览器fetch API和Node.js fetch格式
5+ *
6+ * 特殊处理:
7+ * - JavaScript字符串中的转义引号 \"
8+ * - 嵌套的JSON对象
9+ * - 多行格式
510 */
611
712import { parseUrl } from '../urlParser'
@@ -11,39 +16,185 @@ import type { ParsedHttpRequest } from '../types'
1116 * 从fetch调用中提取URL
1217 */
1318function extractUrl ( input : string ) : string {
14- const urlMatch = input . match ( / f e t c h \s * \( \s * [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / i)
15- return urlMatch && urlMatch [ 1 ] ? urlMatch [ 1 ] : ''
19+ // 支持双引号和单引号
20+ const urlMatch = input . match ( / f e t c h \s * \( \s * ( [ ' " ] ) ( (?: (? ! \1) [ ^ \\ ] | \\ .) * ) \1/ i)
21+ return urlMatch && urlMatch [ 2 ] ? urlMatch [ 2 ] : ''
22+ }
23+
24+ /**
25+ * 匹配JavaScript字符串(处理转义引号)
26+ * 返回匹配的字符串内容(不含引号)
27+ */
28+ function matchJsString ( str : string , startIndex : number ) : { value : string ; endIndex : number } | null {
29+ if ( startIndex >= str . length ) return null
30+
31+ const quoteChar = str [ startIndex ]
32+ if ( quoteChar !== '"' && quoteChar !== "'" ) return null
33+
34+ let i = startIndex + 1
35+ let value = ''
36+
37+ while ( i < str . length ) {
38+ const char = str [ i ]
39+
40+ if ( char === '\\' && i + 1 < str . length ) {
41+ // 处理转义字符
42+ const nextChar = str [ i + 1 ]
43+ if ( nextChar === '"' || nextChar === "'" || nextChar === '\\' ) {
44+ value += nextChar
45+ i += 2
46+ continue
47+ } else if ( nextChar === 'n' ) {
48+ value += '\n'
49+ i += 2
50+ continue
51+ } else if ( nextChar === 'r' ) {
52+ value += '\r'
53+ i += 2
54+ continue
55+ } else if ( nextChar === 't' ) {
56+ value += '\t'
57+ i += 2
58+ continue
59+ }
60+ // 其他转义保持原样
61+ value += char
62+ i ++
63+ } else if ( char === quoteChar ) {
64+ // 找到结束引号
65+ return { value, endIndex : i }
66+ } else {
67+ value += char
68+ i ++
69+ }
70+ }
71+
72+ return null // 未找到结束引号
73+ }
74+
75+ /**
76+ * 查找匹配的大括号位置
77+ */
78+ function findMatchingBrace ( str : string , startIndex : number ) : number {
79+ let depth = 0
80+ let inString = false
81+ let stringChar = ''
82+
83+ for ( let i = startIndex ; i < str . length ; i ++ ) {
84+ const char = str [ i ]
85+
86+ if ( inString ) {
87+ if ( char === '\\' && i + 1 < str . length ) {
88+ i ++ // 跳过转义字符
89+ continue
90+ }
91+ if ( char === stringChar ) {
92+ inString = false
93+ }
94+ } else {
95+ if ( char === '"' || char === "'" ) {
96+ inString = true
97+ stringChar = char
98+ } else if ( char === '{' ) {
99+ depth ++
100+ } else if ( char === '}' ) {
101+ depth --
102+ if ( depth === 0 ) {
103+ return i
104+ }
105+ }
106+ }
107+ }
108+
109+ return - 1
16110}
17111
18112/**
19113 * 从fetch options对象中提取method
20114 */
21115function extractMethod ( optionsStr : string ) : string {
116+ // 匹配 "method": "POST" 或 method: "POST"
22117 const methodMatch = optionsStr . match ( / [ ' " ] ? m e t h o d [ ' " ] ? \s * : \s * [ ' " ] ( \w + ) [ ' " ] / i)
23118 return methodMatch && methodMatch [ 1 ] ? methodMatch [ 1 ] . toUpperCase ( ) : 'GET'
24119}
25120
26121/**
27122 * 从fetch options对象中提取headers
123+ * 正确处理包含转义引号的header值
28124 */
29125function extractHeaders ( optionsStr : string ) : Record < string , string > {
30126 const headers : Record < string , string > = { }
31127
32- const headersMatch = optionsStr . match ( / [ ' " ] ? h e a d e r s [ ' " ] ? \s * : \s * ( \{ [ ^ } ] + \} ) / i)
33- if ( ! headersMatch || ! headersMatch [ 1 ] ) {
128+ // 找到 headers: { 的位置
129+ const headersStartMatch = optionsStr . match ( / [ ' " ] ? h e a d e r s [ ' " ] ? \s * : \s * \{ / )
130+ if ( ! headersStartMatch || headersStartMatch . index === undefined ) {
131+ return headers
132+ }
133+
134+ const braceStart = headersStartMatch . index + headersStartMatch [ 0 ] . length - 1
135+ const braceEnd = findMatchingBrace ( optionsStr , braceStart )
136+
137+ if ( braceEnd === - 1 ) {
34138 return headers
35139 }
36140
37- const headersStr = headersMatch [ 1 ]
141+ // 提取headers对象内容
142+ const headersContent = optionsStr . substring ( braceStart + 1 , braceEnd )
38143
39- // 解析 "key": "value" 格式
40- const headerPairs = headersStr . match ( / [ ' " ] ( [ ^ ' " ] + ) [ ' " ] \s * : \s * [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / g)
41- if ( headerPairs ) {
42- for ( const pair of headerPairs ) {
43- const pairMatch = pair . match ( / [ ' " ] ( [ ^ ' " ] + ) [ ' " ] \s * : \s * [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / )
44- if ( pairMatch && pairMatch [ 1 ] && pairMatch [ 2 ] ) {
45- headers [ pairMatch [ 1 ] ] = pairMatch [ 2 ]
144+ // 解析每个 key: value 对
145+ let i = 0
146+ while ( i < headersContent . length ) {
147+ // 跳过空白和逗号
148+ let currentChar = headersContent . charAt ( i )
149+ while ( i < headersContent . length && / [ \s , ] / . test ( currentChar ) ) {
150+ i ++
151+ currentChar = headersContent . charAt ( i )
152+ }
153+
154+ if ( i >= headersContent . length ) break
155+
156+ // 查找key
157+ const keyResult = matchJsString ( headersContent , i )
158+ if ( ! keyResult ) {
159+ // 尝试匹配不带引号的key
160+ const unquotedKeyMatch = headersContent . substring ( i ) . match ( / ^ ( \w + ) \s * : / )
161+ if ( unquotedKeyMatch && unquotedKeyMatch [ 1 ] ) {
162+ const unquotedKey = unquotedKeyMatch [ 1 ]
163+ i += unquotedKeyMatch [ 0 ] . length
164+ // 跳过空白
165+ currentChar = headersContent . charAt ( i )
166+ while ( i < headersContent . length && / \s / . test ( currentChar ) ) {
167+ i ++
168+ currentChar = headersContent . charAt ( i )
169+ }
170+ const valueResult = matchJsString ( headersContent , i )
171+ if ( valueResult ) {
172+ headers [ unquotedKey ] = valueResult . value
173+ i = valueResult . endIndex + 1
174+ }
175+ } else {
176+ i ++
46177 }
178+ continue
179+ }
180+
181+ const key = keyResult . value
182+ i = keyResult . endIndex + 1
183+
184+ // 跳过 : 和空白
185+ currentChar = headersContent . charAt ( i )
186+ while ( i < headersContent . length && / [ \s : ] / . test ( currentChar ) ) {
187+ i ++
188+ currentChar = headersContent . charAt ( i )
189+ }
190+
191+ // 查找value
192+ const valueResult = matchJsString ( headersContent , i )
193+ if ( valueResult ) {
194+ headers [ key ] = valueResult . value
195+ i = valueResult . endIndex + 1
196+ } else {
197+ i ++
47198 }
48199 }
49200
@@ -52,21 +203,44 @@ function extractHeaders(optionsStr: string): Record<string, string> {
52203
53204/**
54205 * 从fetch options对象中提取body
206+ * 正确处理包含转义引号的body
55207 */
56208function extractBody ( optionsStr : string ) : string {
57- // 尝试匹配直接字符串body
58- const stringBodyMatch = optionsStr . match ( / [ ' " ] ? b o d y [ ' " ] ? \s * : \s * [ ' " ] ( [ ^ ' " ] + ) [ ' " ] / i )
59- if ( stringBodyMatch && stringBodyMatch [ 1 ] ) {
60- return stringBodyMatch [ 1 ]
209+ // 找到 body: 的位置
210+ const bodyMatch = optionsStr . match ( / [ ' " ] ? b o d y [ ' " ] ? \s * : \s * / )
211+ if ( ! bodyMatch || bodyMatch . index === undefined ) {
212+ return ''
61213 }
62214
63- // 尝试匹配JSON.stringify格式
64- const jsonBodyMatch = optionsStr . match ( / [ ' " ] ? b o d y [ ' " ] ? \s * : \s * J S O N \. s t r i n g i f y \s * \( ( [ ^ ) ] + ) \) / i)
65- if ( jsonBodyMatch && jsonBodyMatch [ 1 ] ) {
66- let body = jsonBodyMatch [ 1 ]
67- // 简单清理:将单引号转换为双引号
68- body = body . replace ( / ' / g, '"' )
69- return body
215+ const valueStart = bodyMatch . index + bodyMatch [ 0 ] . length
216+
217+ // 检查是否是 JSON.stringify
218+ const jsonStringifyMatch = optionsStr . substring ( valueStart ) . match ( / ^ J S O N \. s t r i n g i f y \s * \( / )
219+ if ( jsonStringifyMatch ) {
220+ // 找到匹配的括号
221+ const parenStart = valueStart + jsonStringifyMatch [ 0 ] . length - 1
222+ let depth = 1
223+ let i = parenStart + 1
224+
225+ while ( i < optionsStr . length && depth > 0 ) {
226+ const char = optionsStr [ i ]
227+ if ( char === '(' ) depth ++
228+ else if ( char === ')' ) depth --
229+ i ++
230+ }
231+
232+ if ( depth === 0 ) {
233+ let content = optionsStr . substring ( parenStart + 1 , i - 1 ) . trim ( )
234+ // 将单引号转换为双引号
235+ content = content . replace ( / ' / g, '"' )
236+ return content
237+ }
238+ }
239+
240+ // 尝试匹配字符串值
241+ const valueResult = matchJsString ( optionsStr , valueStart )
242+ if ( valueResult ) {
243+ return valueResult . value
70244 }
71245
72246 return ''
@@ -76,23 +250,44 @@ function extractBody(optionsStr: string): string {
76250 * 从fetch调用中提取options对象
77251 */
78252function extractOptions ( input : string ) : string | null {
79- const optionsMatch = input . match ( / f e t c h \s * \( [ ^ , ] + , \s * ( \{ [ \s \S ] * ?\} ) \s * \) / )
80- return optionsMatch && optionsMatch [ 1 ] ? optionsMatch [ 1 ] : null
253+ // 找到第一个参数后的逗号位置
254+ const fetchMatch = input . match ( / f e t c h \s * \( \s * ( [ ' " ] ) ( (?: (? ! \1) [ ^ \\ ] | \\ .) * ) \1\s * , \s * / )
255+ if ( ! fetchMatch || fetchMatch . index === undefined ) {
256+ return null
257+ }
258+
259+ const optionsStart = fetchMatch . index + fetchMatch [ 0 ] . length
260+
261+ // 找到 { 的位置
262+ const bracePos = input . indexOf ( '{' , optionsStart )
263+ if ( bracePos === - 1 ) {
264+ return null
265+ }
266+
267+ const braceEnd = findMatchingBrace ( input , bracePos )
268+ if ( braceEnd === - 1 ) {
269+ return null
270+ }
271+
272+ return input . substring ( bracePos , braceEnd + 1 )
81273}
82274
83275/**
84276 * 解析 fetch (JavaScript) 格式
85277 *
86- * 支持浏览器fetch API格式
278+ * 支持浏览器fetch API格式,正确处理转义引号
87279 *
88280 * @param input - fetch调用代码字符串
89281 * @returns 解析后的HTTP请求,失败返回null
90282 *
91283 * @example
92- * parseFetch(`fetch('https://api.example.com/users', {
93- * method: 'POST',
94- * headers: { 'Content-Type': 'application/json' },
95- * body: JSON.stringify({ name: 'test' })
284+ * parseFetch(`fetch("https://api.example.com/users", {
285+ * "headers": {
286+ * "content-type": "application/json",
287+ * "sec-ch-ua": "\"Chromium\";v=\"138\""
288+ * },
289+ * "body": "{\"name\":\"test\"}",
290+ * "method": "POST"
96291 * })`)
97292 */
98293export function parseFetch ( input : string ) : ParsedHttpRequest | null {
0 commit comments