1 /** 2 * Internal parsing utilities for the server's HTTP request processing. 3 */ 4 module handy_httpd.parse_utils; 5 6 import std.typecons; 7 import std.conv; 8 import std.array; 9 import std.stdio; 10 import std.string; 11 import std.algorithm; 12 import std.uri; 13 import httparsed; 14 import handy_httpd.request : HttpRequest; 15 16 /** 17 * The header struct to use when parsing data. 18 */ 19 public struct Header { 20 const(char)[] name; 21 const(char)[] value; 22 } 23 24 /** 25 * The message struct to use when parsing HTTP requests, using the httparsed library. 26 */ 27 public struct Msg { 28 @safe pure nothrow @nogc: 29 void onMethod(const(char)[] method) { this.method = method; } 30 31 void onUri(const(char)[] uri) { this.uri = uri; } 32 33 int onVersion(const(char)[] ver) { 34 minorVer = parseHttpVersion(ver); 35 return minorVer >= 0 ? 0 : minorVer; 36 } 37 38 void onHeader(const(char)[] name, const(char)[] value) { 39 this.m_headers[m_headersLength].name = name; 40 this.m_headers[m_headersLength++].value = value; 41 } 42 43 void onStatus(int status) { this.status = status; } 44 45 void onStatusMsg(const(char)[] statusMsg) { this.statusMsg = statusMsg; } 46 47 void reset() { 48 this.m_headersLength = 0; 49 } 50 51 public const(char)[] method; 52 public const(char)[] uri; 53 public int minorVer; 54 public int status; 55 public const(char)[] statusMsg; 56 57 private Header[64] m_headers; 58 private size_t m_headersLength; 59 60 Header[] headers() return { return m_headers[0..m_headersLength]; } 61 } 62 63 /** 64 * Parses an HTTP request from a string. 65 * Params: 66 * s = The raw HTTP request string. 67 * Returns: An HttpRequest object which can be passed to a handler. 68 */ 69 public HttpRequest parseRequest(MsgParser!Msg requestParser, string s) { 70 // requestParser.msg.m_headersLength = 0; // Reset the parser headers. 71 int result = requestParser.parseRequest(s); 72 if (result != s.length) { 73 throw new Exception("Error! parse result doesn't match length. " ~ result.to!string); 74 } 75 string[string] headers; 76 foreach (h; requestParser.headers) { 77 headers[h.name] = cast(string) h.value; 78 } 79 string rawUrl = decode(cast(string) requestParser.uri); 80 auto urlAndParams = parseUrlAndParams(rawUrl); 81 82 return HttpRequest( 83 cast(string) requestParser.method, 84 urlAndParams[0], 85 requestParser.minorVer, 86 headers, 87 urlAndParams[1] 88 ); 89 } 90 91 /** 92 * Parses a path and set of query parameters from a raw URL string. 93 * Params: 94 * rawUrl = The raw url containing both path and query params. 95 * Returns: A tuple containing the path and parsed query params. 96 */ 97 private Tuple!(string, string[string]) parseUrlAndParams(string rawUrl) { 98 Tuple!(string, string[string]) result; 99 auto p = rawUrl.indexOf('?'); 100 if (p == -1) { 101 result[0] = rawUrl; 102 result[1] = null; 103 } else { 104 result[0] = rawUrl[0..p]; 105 result[1] = parseQueryString(rawUrl[p..$]); 106 } 107 // Strip away a trailing slash if there is one. This makes path matching easier. 108 if (result[0][$ - 1] == '/') { 109 result[0] = result[0][0 .. $ - 1]; 110 } 111 return result; 112 } 113 114 /** 115 * Parses a set of query parameters from a query string. 116 * Params: 117 * queryString = The raw query string to parse, including the preceding '?' character. 118 * Returns: An associative array containing parsed params. 119 */ 120 private string[string] parseQueryString(string queryString) { 121 string[string] params; 122 if (queryString.length > 1) { 123 string[] paramSections = queryString[1..$].split("&").filter!(s => s.length > 0).array; 124 foreach (paramSection; paramSections) { 125 string paramName; 126 string paramValue; 127 auto p = paramSection.indexOf('='); 128 if (p == -1 || p + 1 == paramSection.length) { 129 paramName = paramSection; 130 paramValue = "true"; 131 } else { 132 paramName = paramSection[0..p]; 133 paramValue = paramSection[p+1..$]; 134 } 135 params[paramName] = paramValue; 136 } 137 } 138 return params; 139 }