scan = import("scan.noja"); newScanner = scan.newScanner; Scanner = scan.Scanner; isDigit = scan.isDigit; ord = string.ord; cat = string.cat; fun isAlpha(char: String) return (ord(char) >= ord('a') and ord(char) <= ord('z')) or (ord(char) >= ord('A') and ord(char) <= ord('Z')); fun isUpper(char: String) return ord(char) >= ord('A') and ord(char) <= ord('Z'); fun isUnreserved(char: String) return isAlpha(char) or isDigit(char) or char == "-" or char == "." or char == "_" or char == "~"; fun isSubdelim(char: String) return char == "!" or char == "$" or char == "&" or char == "'" or char == "(" or char == ")" or char == "*" or char == "+" or char == "," or char == ";" or char == "="; fun isPChar(char: String) return isUnreserved(char) or isSubdelim(char) or char == ":" or char == "@"; fun parseMethod(scan: Scanner) { char = scan->current(); if char == none or not isUpper(char): return none, "Missing method"; method = ""; do { method = cat(method, char); scan->consume(); char = scan->current(); } while char != none and isUpper(char); return method, none; } fun parsePort(scan: Scanner) { port = none; char = scan->current(); if char == ":" and isDigit(scan->hint(1)): { scan->consume(); # Skip the ":" char = scan->current(); port = 0; do { digit = ord(char) - ord("0"); port = port * 10 + digit; scan->consume(); char = scan->current(); } while char != none and isDigit(char); } return port; } fun isHostname(char: String) return isUnreserved(char) or isSubdelim(char); fun parseHost(scan: Scanner) { char = scan->current(); if char == none: return none, "Missing host"; if not isHostname(char): return none, "Invalid character in place of host"; name = ""; do { name = cat(name, char); scan->consume(); char = scan->current(); } while char != none and isHostname(char); port = parsePort(scan); return {name: name, port: port}; } fun parsePath(scan: Scanner) { path = ""; char = scan->current(); if char != none and char == "/": { path = cat(path, char); scan->consume(); char = scan->current(); } else { if char == none or not isPChar(char): return none, "Invalid character in place of path"; } while char != none and isPChar(char): { do { path = cat(path, char); scan->consume(); char = scan->current(); } while char != none and isPChar(char); if char == none or char != "/": break; path = cat(path, char); scan->consume(); char = scan->current(); } return path; } fun isQuery(char: ?String) { if char == none: return false; return isPChar(char) or char == "/" or char == "?"; } fun parseQuery(scan: Scanner) { char = scan->current(); if char != none and char == "?": { scan->consume(); # Consume the "?" query = ""; while isQuery(char = scan->current()): { query = cat(query, char); scan->consume(); } } else query = none; return query; } fun isFragment(char: ?String) { if char == none: return false; return isPChar(char) or char == "/"; } fun parseFragment(scan: Scanner) { char = scan->current(); if char != none and char == "#": { scan->consume(); # Consume the "#" fragment = ""; while isFragment(char = scan->current()): { fragment = cat(fragment, char); scan->consume(); } } else fragment = none; return fragment; } Host = { name: String, port: ?int }; URL = { schema: ?String, host: ?Host, path: String, query: ?String, fragment: ?String }; fun isSchemaFirst(char: ?String) { return char != none and isAlpha(char); } fun isSchema(char: ?String) return char != none and (isAlpha(char) or isDigit(char) or char == "+" or char == "-" or char == "."); fun parseSchema(scan: Scanner) { char = scan->current(); schema = none; if isSchemaFirst(char): { start = scan.i; schema = ""; do { schema = cat(schema, char); scan->consume(); char = scan->current(); } while isSchema(char); if char == ":": scan->consume(); else { schema = none; scan.i = start; } } return schema; } fun followsAuthority(scan: Scanner) return scan->current() == "/" and scan->hint(1) == "/"; fun parseURL(scan: Scanner) { schema = parseSchema(scan); if followsAuthority(scan): { consume(scan, 2); host, error = parseHost(scan); if error != none: return none, error; path = none; if scan->current() == "/": path = parsePath(scan); } else { host = none; char = scan->current(); if char == none or char == "?" or char == "#": return none, "Missing path"; path, error = parsePath(scan); if error != none: return none, error; } query = parseQuery(scan); fragment = parseFragment(scan); return { host : host, path : path, query : query, schema : schema, fragment: fragment }; } fun parseVersion(scan: Scanner) { if scan->hint(0) != "H" or scan->hint(1) != "T" or scan->hint(2) != "T" or scan->hint(3) != "P" or scan->hint(4) != "/" or not isDigit(scan->hint(5)): return none, "Invalid version token"; scan->consume(5); major = ord(scan->current()) - ord("0"); if scan->hint(1) != "." and isDigit(scan->hint(2)): { scan->consume(2); minor = ord(scan->current()) - ord("0"); } else minor = 0; scan->consume(); return {major: major, minor: minor}; } Version = { major: int, minor: int }; Request = { method : String, url : URL, version: Version }; fun newRequest(method: String, url: URL, version: Version, headers: Map = {}) return { method: method, url: url, version: version, headers: headers }; fun parse(src: String) { scan = newScanner(src); method, error = parseMethod(scan); if error != none: return none, error; char = scan->current(); if char == none or char != " ": return none, "Missing space after method"; scan->consume(); url, error = parseURL(scan); if error != none: return none, error; char = scan->current(); if char != " ": return none, "Missing space after URL"; scan->consume(); version, error = parseVersion(scan); if error != none: return none, error; if scan->hint(0) != "\r" or scan->hint(1) != "\n": # Request with no headers or body return newRequest(method, url, version); scan->consume(2); # Consume the "\r\n" headers = {}; while (char = scan->hint(0)) != none and (char != "\r" or scan->hint(1) != "\n"): { # Header name until the next ":" name = ""; do { name = cat(name, char); scan->consume(); char = scan->current(); } while char != ":" and char != none; if char == none: return none, "Invalid header (missing \":\" after name)"; scan->consume(); scan->consumeSpaces(); char = scan->current(); # Header body until \r body = ""; do { body = cat(body, char); scan->consume(); char = scan->current(); } while char != "\r" and char != none; headers[name] = body; } if char != none: scan->consume(2); # Consume the final "\r\n" return newRequest(method, url, version, headers); } sample = cat( "GET /search?client=firefox-b-d&q=http+request+dataset HTTP/2\r\n", "Host: www.google.com\r\n", "user-agent: curl/7.84.0\r\n", "accept: */*\r\n", "\r\n"); request, error = parse(sample); print("request=", request, "\n"); print("error=", error, "\n");