1 /******************************************************************************
2 
3     HTTP version identifier constants and enumerator
4 
5     Copyright:
6         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
7         All rights reserved.
8 
9     License:
10         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
11         Alternatively, this file may be distributed under the terms of the Tango
12         3-Clause BSD License (see LICENSE_BSD.txt for details).
13 
14  ******************************************************************************/
15 
16 module ocean.net.http.consts.HttpVersion;
17 
18 
19 import ocean.meta.types.Qualifiers;
20 import ocean.core.Verify;
21 import core.stdc.ctype: isdigit;
22 
23 version (unittest) import ocean.core.Test;
24 
25 /******************************************************************************
26 
27     HTTP version enumerator
28 
29  ******************************************************************************/
30 
31 enum HttpVersion : ubyte
32 {
33     Undefined = 0,
34     v1_1,
35     v1_0
36 }
37 
38 /******************************************************************************
39 
40     HTTP version identifier string constants and enumerator value association
41 
42  ******************************************************************************/
43 
44 struct HttpVersionIds
45 {
46     /**************************************************************************
47 
48         HTTP version identifier string constants
49 
50         @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.1
51 
52      **************************************************************************/
53 
54     enum istring[HttpVersion.max + 1] list =
55     [
56         HttpVersion.v1_1: "HTTP/1.1",
57         HttpVersion.v1_0: "HTTP/1.0"
58     ];
59 
60     /**************************************************************************
61 
62         Obtains the HTTP identifier string by version enumerator value. ver must
63         be a HttpVersion value different from HttpVersion.Undefined.
64 
65         Params:
66             ver = HTTP version enumerator value
67 
68          Returns:
69              HTTP version identifier string corresponding to val
70 
71      **************************************************************************/
72 
73     static istring opIndex ( HttpVersion ver )
74     {
75         verify(ver != 0, "no version id for HttpVersion.Undefined");
76         verify(ver <= ver.max, "invalid HttpVersion enumerator value");
77 
78         return list[ver];
79     }
80 
81     /**************************************************************************
82 
83         Obtains the HTTP version enumerator value by identifier string.
84 
85         Params:
86             id = HTTP version identifier string
87 
88          Returns:
89              Pointer to the HTTP version enumerator value corresponding to
90              identifier string or null if the name identifier does not match any
91              known HTTP version identifier string.
92 
93      **************************************************************************/
94 
95     static HttpVersion* opBinaryRight (string op : "in") ( cstring id )
96     {
97         return id.length? id in codes : null;
98     }
99 
100     /**************************************************************************
101 
102         Obtains the HTTP version enumerator value by identifier string. Does not
103         throw an exception.
104 
105         Params:
106             id = HTTP version identifier string
107 
108          Returns:
109              HTTP version enumerator value corresponding to identifier string or
110              HttpVersion.Undefined if the name string is unknown.
111 
112      **************************************************************************/
113 
114     static HttpVersion opIndex ( cstring id )
115     {
116         HttpVersion* code = opBinaryRight!("in")(id);
117 
118         return code? *code : (*code).Undefined;
119     }
120 
121     /**************************************************************************
122 
123         Checks whether id has a valid syntax for a HTTP version identifier
124         string:
125 
126         "HTTP" "/" 1*DIGIT "." 1*DIGIT
127 
128         @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.1
129 
130         Params:
131             id = HTTP version identifier string
132 
133          Returns:
134              true if d has a valid syntax for a HTTP version identifier string
135              or false otherwise.
136 
137      **************************************************************************/
138 
139     static bool validSyntax ( cstring id )
140     {
141         enum prefix = "HTTP/";
142 
143         bool valid = id.length > prefix.length;
144 
145         if (valid)
146         {
147             valid = id[0 .. prefix.length] == prefix;
148         }
149 
150         if (valid)
151         {
152             size_t n_before_dot = 0;
153 
154             foreach (i, c; id[prefix.length .. $])
155             {
156                 if (!isdigit(c))
157                 {
158                     if (c == '.')
159                     {
160                         n_before_dot = i;
161                     }
162                     else
163                     {
164                         valid = false;
165                     }
166 
167                     break;
168                 }
169             }
170 
171             valid &= n_before_dot != 0;
172 
173             if (valid)
174             {
175                 size_t after_dot = n_before_dot + prefix.length + 1;
176 
177                 valid &= id.length > after_dot;
178 
179                 if (valid) foreach (i, c; id[after_dot .. $])
180                 {
181                     if (!isdigit(c))
182                     {
183                         valid = false;
184                         break;
185                     }
186                 }
187             }
188         }
189 
190         return valid;
191     }
192 
193     /**************************************************************************
194 
195         Unittest for validSyntax()
196 
197      **************************************************************************/
198 
199     unittest
200     {
201         test (validSyntax("HTTP/1.1"));
202         test (validSyntax("HTTP/1.23"));
203         test (validSyntax("HTTP/123.456"));
204         test (!validSyntax("HTTP/123456"));
205         test (!validSyntax("HTTP/.123456"));
206         test (!validSyntax("HTTP/1,1"));
207         test (!validSyntax("HTTP/1."));
208         test (!validSyntax("HTTP/.1"));
209         test (!validSyntax("HTTP/."));
210         test (!validSyntax("HTTP/"));
211         test (!validSyntax(""));
212     }
213 
214     /**************************************************************************
215 
216         HTTP version code enumerator value by name string
217 
218      **************************************************************************/
219 
220     private static HttpVersion[istring] codes;
221 
222     /**************************************************************************
223 
224         Static constructor; populates this.codes
225 
226      **************************************************************************/
227 
228     static this ( )
229     {
230         foreach (i, str; list)
231         {
232             codes[str] = cast (HttpVersion) i;
233         }
234 
235         codes.rehash;
236     }
237 }
238 
239 
240 unittest
241 {
242     static assert(HttpVersionIds.list[HttpVersion.v1_1]     == "HTTP/1.1");
243     static assert(HttpVersionIds.list[HttpVersion.v1_0]     == "HTTP/1.0");
244 
245     test(!HttpVersionIds.list[HttpVersion.Undefined].length);
246 
247     test(HttpVersionIds.list[HttpVersion.v1_1]     == "HTTP/1.1");
248     test(HttpVersionIds.list[HttpVersion.v1_0]     == "HTTP/1.0");
249 
250     test(HttpVersionIds["HTTP/1.1"]     == HttpVersion.v1_1);
251     test(HttpVersionIds["HTTP/1.0"]     == HttpVersion.v1_0);
252     test(HttpVersionIds["SPAM"]         == HttpVersion.Undefined);
253     test(HttpVersionIds[""]             == HttpVersion.Undefined);
254     test(HttpVersionIds[null]           == HttpVersion.Undefined);
255 
256     HttpVersion* v = "HTTP/1.1" in HttpVersionIds;
257     test(v);
258     test(*v == (*v).v1_1);
259 
260     v = "HTTP/1.0" in HttpVersionIds;
261     test(v);
262     test(*v == (*v).v1_0);
263 
264     test(!("SPAM" in HttpVersionIds));
265     test(!(""     in HttpVersionIds));
266     test(!(null   in HttpVersionIds));
267 }