137 lines
3.7 KiB
Python
137 lines
3.7 KiB
Python
{"""Parser module for curl commands."""
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
|
|
|
|
@dataclass
|
|
class ParsedCurl:
|
|
"""Represents a parsed curl command."""
|
|
url: str = ""
|
|
method: str = "GET"
|
|
headers: dict = field(default_factory=dict)
|
|
data: Optional[str] = None
|
|
auth: Optional[tuple] = None
|
|
cookies: Optional[str] = None
|
|
user_agent: Optional[str] = None
|
|
|
|
|
|
def tokenize_command(cmd: str) -> list:
|
|
"""Tokenize a curl command into arguments."""
|
|
tokens = []
|
|
current = ""
|
|
in_single_quote = False
|
|
in_double_quote = False
|
|
escape_next = False
|
|
|
|
for char in cmd:
|
|
if escape_next:
|
|
current += char
|
|
escape_next = False
|
|
continue
|
|
|
|
if char == '\\' and not in_single_quote:
|
|
escape_next = True
|
|
continue
|
|
|
|
if char == "'" and not in_double_quote:
|
|
in_single_quote = not in_single_quote
|
|
current += char
|
|
continue
|
|
|
|
if char == '"' and not in_single_quote:
|
|
in_double_quote = not in_double_quote
|
|
current += char
|
|
continue
|
|
|
|
if char == ' ' and not in_single_quote and not in_double_quote:
|
|
if current:
|
|
tokens.append(current)
|
|
current = ""
|
|
continue
|
|
|
|
current += char
|
|
|
|
if current:
|
|
tokens.append(current)
|
|
|
|
return tokens
|
|
|
|
|
|
def parse_curl(command: str) -> ParsedCurl:
|
|
"""Parse a curl command string into a ParsedCurl object."""
|
|
if not command:
|
|
raise ValueError("Empty curl command")
|
|
|
|
command = command.strip()
|
|
if command.startswith("curl "):
|
|
command = command[5:]
|
|
|
|
tokens = tokenize_command(command)
|
|
|
|
if not tokens:
|
|
raise ValueError("No URL found in curl command")
|
|
|
|
parsed = ParsedCurl()
|
|
|
|
i = 0
|
|
while i < len(tokens):
|
|
token = tokens[i]
|
|
|
|
if not token.startswith("-"):
|
|
if not parsed.url:
|
|
parsed.url = token
|
|
i += 1
|
|
continue
|
|
|
|
if token in ("-X", "--request"):
|
|
if i + 1 < len(tokens):
|
|
parsed.method = tokens[i + 1].upper()
|
|
i += 2
|
|
continue
|
|
|
|
if token in ("-H", "--header"):
|
|
if i + 1 < len(tokens):
|
|
header = tokens[i + 1]
|
|
if ":" in header:
|
|
key, value = header.split(":", 1)
|
|
parsed.headers[key.strip()] = value.strip()
|
|
i += 2
|
|
continue
|
|
|
|
if token in ("-d", "--data", "--data-raw", "--data-binary"):
|
|
if i + 1 < len(tokens):
|
|
parsed.data = tokens[i + 1]
|
|
if parsed.method == "GET":
|
|
parsed.method = "POST"
|
|
i += 2
|
|
continue
|
|
|
|
if token in ("-u", "--user"):
|
|
if i + 1 < len(tokens):
|
|
auth = tokens[i + 1]
|
|
if ":" in auth:
|
|
parsed.auth = auth.split(":", 1)
|
|
else:
|
|
parsed.auth = (auth, "")
|
|
i += 2
|
|
continue
|
|
|
|
if token in ("-b", "--cookie"):
|
|
if i + 1 < len(tokens):
|
|
parsed.cookies = tokens[i + 1]
|
|
i += 2
|
|
continue
|
|
|
|
if token in ("-A", "--user-agent"):
|
|
if i + 1 < len(tokens):
|
|
parsed.user_agent = tokens[i + 1]
|
|
i += 2
|
|
continue
|
|
|
|
i += 1
|
|
|
|
if not parsed.url:
|
|
raise ValueError("No URL found in curl command")
|
|
|
|
return parsed |