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 }