1 module hunt.http.HttpMethod; 2 3 import hunt.io.ByteBuffer; 4 import hunt.text.Common; 5 import hunt.util.ObjectUtils; 6 7 import std.string; 8 9 10 /** 11 * 12 */ 13 struct HttpMethod { 14 enum HttpMethod Null = HttpMethod("Null"); 15 enum HttpMethod GET = HttpMethod("GET"); 16 enum HttpMethod POST = HttpMethod("POST"); 17 enum HttpMethod HEAD = HttpMethod("HEAD"); 18 enum HttpMethod PUT = HttpMethod("PUT"); 19 enum HttpMethod PATCH = HttpMethod("PATCH"); 20 enum HttpMethod OPTIONS = HttpMethod("OPTIONS"); 21 enum HttpMethod DELETE = HttpMethod("DELETE"); 22 enum HttpMethod TRACE = HttpMethod("TRACE"); 23 enum HttpMethod CONNECT = HttpMethod("CONNECT"); 24 enum HttpMethod MOVE = HttpMethod("MOVE"); 25 enum HttpMethod PROXY = HttpMethod("PROXY"); 26 enum HttpMethod PRI = HttpMethod("PRI"); 27 enum HttpMethod COPY = HttpMethod("COPY"); 28 enum HttpMethod LINK = HttpMethod("LINK"); 29 enum HttpMethod UNLINK = HttpMethod("UNLINK"); 30 enum HttpMethod PURGE = HttpMethod("PURGE"); 31 enum HttpMethod LOCK = HttpMethod("LOCK"); 32 enum HttpMethod UNLOCK = HttpMethod("UNLOCK"); 33 enum HttpMethod VIEW = HttpMethod("VIEW"); 34 35 /* ------------------------------------------------------------ */ 36 37 /** 38 * Optimized lookup to find a method name and trailing space in a byte array. 39 * 40 * @param bytes Array containing ISO-8859-1 characters 41 * @param position The first valid index 42 * @param limit The first non valid index 43 * @return A HttpMethod if a match or null if no easy match. 44 */ 45 static HttpMethod lookAheadGet(byte[] bytes, int position, int limit) { 46 int length = limit - position; 47 if (length < 4) 48 return HttpMethod.Null; 49 switch (bytes[position]) { 50 case 'G': 51 if (bytes[position + 1] == 'E' && bytes[position + 2] == 'T' && bytes[position + 3] == ' ') 52 return GET; 53 break; 54 case 'P': 55 if (bytes[position + 1] == 'O' && bytes[position + 2] == 'S' && bytes[position + 3] == 'T' && length >= 5 && bytes[position + 4] == ' ') 56 return POST; 57 if (bytes[position + 1] == 'U' && bytes[position + 2] == 'T' && bytes[position + 3] == ' ') 58 return PUT; 59 if (bytes[position + 1] == 'R' && bytes[position + 2] == 'O' && bytes[position + 3] == 'X' && length >= 6 && bytes[position + 4] == 'Y' && bytes[position + 5] == ' ') 60 return PROXY; 61 if (bytes[position + 1] == 'A' && bytes[position + 2] == 'T' && bytes[position + 3] == 'C' && length >= 6 && bytes[position + 4] == 'H' && bytes[position + 5] == ' ') 62 return PATCH; 63 if (bytes[position + 1] == 'U' && bytes[position + 2] == 'R' && bytes[position + 3] == 'G' && length >= 6 && bytes[position + 4] == 'E' && bytes[position + 5] == ' ') 64 return PURGE; 65 if (bytes[position + 1] == 'R' && bytes[position + 2] == 'I' && bytes[position + 3] == ' ') 66 return PRI; 67 break; 68 case 'H': 69 if (bytes[position + 1] == 'E' && bytes[position + 2] == 'A' && bytes[position + 3] == 'D' && length >= 5 && bytes[position + 4] == ' ') 70 return HEAD; 71 break; 72 case 'L': 73 if (bytes[position + 1] == 'I' && bytes[position + 2] == 'N' && bytes[position + 3] == 'K' && length >= 5 && bytes[position + 4] == ' ') 74 return LINK; 75 if (bytes[position + 1] == 'O' && bytes[position + 2] == 'C' && bytes[position + 3] == 'K' && length >= 5 && bytes[position + 4] == ' ') 76 return LOCK; 77 break; 78 case 'O': 79 if (bytes[position + 1] == 'P' && bytes[position + 2] == 'T' && bytes[position + 3] == 'I' && length >= 8 && 80 bytes[position + 4] == 'O' && bytes[position + 5] == 'N' && bytes[position + 6] == 'S' && bytes[position + 7] == ' ') 81 return OPTIONS; 82 break; 83 case 'D': 84 if (bytes[position + 1] == 'E' && bytes[position + 2] == 'L' && bytes[position + 3] == 'E' && length >= 7 && 85 bytes[position + 4] == 'T' && bytes[position + 5] == 'E' && bytes[position + 6] == ' ') 86 return DELETE; 87 break; 88 case 'T': 89 if (bytes[position + 1] == 'R' && bytes[position + 2] == 'A' && bytes[position + 3] == 'C' && length >= 6 && 90 bytes[position + 4] == 'E' && bytes[position + 5] == ' ') 91 return TRACE; 92 break; 93 94 case 'C': 95 if (bytes[position + 1] == 'O' && bytes[position + 2] == 'N' && bytes[position + 3] == 'N' && length >= 8 && 96 bytes[position + 4] == 'E' && bytes[position + 5] == 'C' && bytes[position + 6] == 'T' && bytes[position + 7] == ' ') 97 return CONNECT; 98 if (bytes[position + 1] == 'O' && bytes[position + 2] == 'P' && bytes[position + 3] == 'Y' && length >= 5 && bytes[position + 4] == ' ') 99 return COPY; 100 break; 101 102 case 'M': 103 if (bytes[position + 1] == 'O' && bytes[position + 2] == 'V' && bytes[position + 3] == 'E' && length >= 5 && bytes[position + 4] == ' ') 104 return MOVE; 105 break; 106 107 case 'U': 108 if (bytes[position + 1] == 'N' && bytes[position + 2] == 'L' && bytes[position + 3] == 'I' && length >= 8 && 109 bytes[position + 4] == 'N' && bytes[position + 5] == 'K' && bytes[position + 6] == ' ') 110 return UNLINK; 111 if (bytes[position + 1] == 'N' && bytes[position + 2] == 'L' && bytes[position + 3] == 'O' && length >= 8 && 112 bytes[position + 4] == 'C' && bytes[position + 5] == 'K' && bytes[position + 6] == ' ') 113 return UNLOCK; 114 break; 115 116 case 'V': 117 if (bytes[position + 1] == 'I' && bytes[position + 2] == 'E' && bytes[position + 3] == 'W' && length >= 5 && bytes[position + 4] == ' ') 118 return VIEW; 119 break; 120 121 default: 122 break; 123 } 124 return HttpMethod.Null; 125 } 126 127 /* ------------------------------------------------------------ */ 128 129 /** 130 * Optimized lookup to find a method name and trailing space in a byte array. 131 * 132 * @param buffer buffer containing ISO-8859-1 characters, it is not modified. 133 * @return A HttpMethod if a match or null if no easy match. 134 */ 135 static HttpMethod lookAheadGet(ByteBuffer buffer) { 136 if (buffer.hasArray()) 137 return lookAheadGet(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.arrayOffset() + buffer.limit()); 138 139 int l = buffer.remaining(); 140 if (l >= 4) { 141 string key = cast(string)buffer.peek(0, l); 142 HttpMethod m = CACHE[key]; 143 if (m != HttpMethod.Null) { 144 int ml = cast(int)m.asString().length; 145 if (l > ml && buffer.get(buffer.position() + ml) == ' ') 146 return m; 147 } 148 } 149 return HttpMethod.Null; 150 } 151 152 /* ------------------------------------------------------------ */ 153 __gshared static HttpMethod[string] INSENSITIVE_CACHE; 154 __gshared static HttpMethod[string] CACHE; 155 156 static HttpMethod get(string name) 157 { 158 return CACHE.get(name, HttpMethod.Null); 159 } 160 161 static HttpMethod getInsensitive(string name) 162 { 163 return INSENSITIVE_CACHE.get(name.toLower(), HttpMethod.Null); 164 } 165 166 167 shared static this() { 168 foreach (HttpMethod method ; HttpMethod.values()) 169 { 170 INSENSITIVE_CACHE[method.toString().toLower()] = method; 171 CACHE[method.toString()] = method; 172 } 173 } 174 175 176 mixin ValuesMemberTempate!(HttpMethod); 177 178 /* ------------------------------------------------------------ */ 179 private string _string; 180 // private ByteBuffer _buffer; 181 private byte[] _bytes; 182 183 /* ------------------------------------------------------------ */ 184 this(string s) { 185 _string = s; 186 _bytes = cast(byte[]) s.dup; // StringUtils.getBytes(s); 187 // _bytesColonSpace = cast(byte[])(s ~ ": ").dup; 188 } 189 190 191 bool isSame(string s) { 192 return s.length != 0 && std..string.icmp(_string, s) == 0; 193 } 194 195 string asString() { 196 return _string; 197 } 198 199 string toString() { 200 return _string; 201 } 202 203 /* ------------------------------------------------------------ */ 204 byte[] getBytes() { 205 return _bytes; 206 } 207 208 /* ------------------------------------------------------------ */ 209 // ByteBuffer asBuffer() { 210 // return _buffer.asReadOnlyBuffer(); 211 // } 212 213 /* ------------------------------------------------------------ */ 214 215 /** 216 * Converts the given string parameter to an HttpMethod 217 * 218 * @param method the string to get the equivalent HttpMethod from 219 * @return the HttpMethod or null if the parameter method is unknown 220 */ 221 static HttpMethod fromString(string method) { 222 string m = method.toUpper(); 223 if(m in CACHE) 224 return CACHE[m]; 225 else 226 return HttpMethod.Null; 227 } 228 229 static bool invalidatesCache(string method) { 230 return method == "POST" 231 || method == "PATCH" 232 || method == "PUT" 233 || method == "DELETE" 234 || method == "MOVE"; // WebDAV 235 } 236 237 static bool requiresRequestBody(string method) { 238 return method == "POST" 239 || method == "PUT" 240 || method == "PATCH" 241 || method == "PROPPATCH" // WebDAV 242 || method == "REPORT"; // CalDAV/CardDAV (defined in WebDAV Versioning) 243 } 244 245 static bool permitsRequestBody(string method) { 246 return (method != "GET" && method != "HEAD"); 247 } 248 249 static bool redirectsWithBody(string method) { 250 return method == "PROPFIND"; // (WebDAV) redirects should also maintain the request body 251 } 252 253 static bool redirectsToGet(string method) { 254 // All requests but PROPFIND should redirect to a GET request. 255 return method != "PROPFIND"; 256 } 257 }