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 }