1 module hunt.http.codec.http.model.HttpField;
2 
3 import hunt.http.codec.http.model.HttpHeaderValue;
4 import hunt.http.codec.http.model.HttpHeader;
5 import hunt.http.codec.http.model.QuotedCSV;
6 
7 import hunt.container;
8 import hunt.string;
9 import hunt.lang.exception;
10 
11 import std.array;
12 import std.ascii;
13 import std.conv;
14 import std.uni;
15 import std.csv;
16 import std.string;
17 
18 import hunt.logging;
19 
20 class HttpField {
21 	private enum string __zeroquality = "q=0";
22 	private HttpHeader _header;
23 	private string _name;
24 	private string _value;
25 	// cached hashcode for case insensitive name
26 	private int hash = 0;
27 
28 	this(HttpHeader header, string name, string value) {
29 		_header = header;
30 		_name = name; 
31 		_value = value;
32 	}
33 
34 	this(HttpHeader header, string value) {
35 		this(header, header.asString(), value);
36 	}
37 
38 	this(HttpHeader header, HttpHeaderValue value) {
39 		this(header, header.asString(), value.asString());
40 	}
41 
42 	this(string name, string value) {
43         // version(HUNT_DEBUG)
44 		// 	tracef("Field: name=%s, value=%s", name, value);
45 		HttpHeader h = HttpHeader.get(name);
46 		this(h, name, value);
47 	}
48 
49 	HttpHeader getHeader() {
50 		return _header;
51 	}
52 
53 	string getName() {
54 		return _name;
55 	}
56 
57 	string getValue() {
58 		return _value;
59 	}
60 
61 	int getIntValue() {
62 		return std.conv.to!int(_value);
63 	}
64 
65 	long getLongValue() {
66 		try
67 		{
68 			return std.conv.to!long(_value);
69 		}
70 		catch(Exception ex)
71 		{
72 			throw new NumberFormatException(_value);
73 		}
74 	}
75 
76 	string[] getValues() {
77 		if (_value.empty)
78 			return null;
79 
80 		QuotedCSV list = new QuotedCSV(false, _value);
81 		return list.getValues(); //.toArray(new string[list.size()]);
82 		// return csvReader!(string)(_value).front().array;
83 	}
84 
85 	/**
86 	 * Look for a value in a possible multi valued field
87 	 * 
88 	 * @param search
89 	 *            Values to search for (case insensitive)
90 	 * @return True iff the value is contained in the field value entirely or as
91 	 *         an element of a quoted comma separated list. List element
92 	 *         parameters (eg qualities) are ignored, except if they are q=0, in
93 	 *         which case the item itself is ignored.
94 	 */
95 	bool contains(string search) {
96 		if (search == null)
97 			return _value == null;
98 		if (search.length == 0)
99 			return false;
100 		if (_value == null)
101 			return false;
102 		if (search == (_value))
103 			return true;
104 
105 		search = std.uni.toLower(search);
106 
107 		int state = 0;
108 		int match = 0;
109 		int param = 0;
110 
111 		for (int i = 0; i < _value.length; i++) {
112 			char c = _value[i];
113 			switch (state) {
114 			case 0: // initial white space
115 				switch (c) {
116 				case '"': // open quote
117 					match = 0;
118 					state = 2;
119 					break;
120 
121 				case ',': // ignore leading empty field
122 					break;
123 
124 				case ';': // ignore leading empty field parameter
125 					param = -1;
126 					match = -1;
127 					state = 5;
128 					break;
129 
130 				case ' ': // more white space
131 				case '\t':
132 					break;
133 
134 				default: // character
135 					match = std.ascii.toLower(c) == search[0] ? 1 : -1;
136 					state = 1;
137 					break;
138 				}
139 				break;
140 
141 			case 1: // In token
142 				switch (c) {
143 				case ',': // next field
144 					// Have we matched the token?
145 					if (match == search.length)
146 						return true;
147 					state = 0;
148 					break;
149 
150 				case ';':
151 					param = match >= 0 ? 0 : -1;
152 					state = 5; // parameter
153 					break;
154 
155 				default:
156 					if (match > 0) {
157 						if (match < search.length)
158 							match = std.ascii.toLower(c) == search[match] ? (match + 1) : -1;
159 						else if (c != ' ' && c != '\t')
160 							match = -1;
161 					}
162 					break;
163 
164 				}
165 				break;
166 
167 			case 2: // In Quoted token
168 				switch (c) {
169 				case '\\': // quoted character
170 					state = 3;
171 					break;
172 
173 				case '"': // end quote
174 					state = 4;
175 					break;
176 
177 				default:
178 					if (match >= 0) {
179 						if (match < search.length)
180 							match = std.ascii.toLower(c) == search[match] ? (match + 1) : -1;
181 						else
182 							match = -1;
183 					}
184 				}
185 				break;
186 
187 			case 3: // In Quoted character in quoted token
188 				if (match >= 0) {
189 					if (match < search.length)
190 						match = std.ascii.toLower(c) == search[match] ? (match + 1) : -1;
191 					else
192 						match = -1;
193 				}
194 				state = 2;
195 				break;
196 
197 			case 4: // WS after end quote
198 				switch (c) {
199 				case ' ': // white space
200 				case '\t': // white space
201 					break;
202 
203 				case ';':
204 					state = 5; // parameter
205 					break;
206 
207 				case ',': // end token
208 					// Have we matched the token?
209 					if (match == search.length)
210 						return true;
211 					state = 0;
212 					break;
213 
214 				default:
215 					// This is an illegal token, just ignore
216 					match = -1;
217 				}
218 				break;
219 
220 			case 5: // parameter
221 				switch (c) {
222 				case ',': // end token
223 					// Have we matched the token and not q=0?
224 					if (param != __zeroquality.length && match == search.length)
225 						return true;
226 					param = 0;
227 					state = 0;
228 					break;
229 
230 				case ' ': // white space
231 				case '\t': // white space
232 					break;
233 
234 				default:
235 					if (param >= 0) {
236 						if (param < __zeroquality.length)
237 							param = std.ascii.toLower(c) == __zeroquality[param] ? (param + 1) : -1;
238 						else if (c != '0' && c != '.')
239 							param = -1;
240 					}
241 
242 				}
243 				break;
244 
245 			default:
246 				throw new IllegalStateException("");
247 			}
248 		}
249 
250 		return param != __zeroquality.length && match == search.length;
251 	}
252 
253 	override
254 	string toString() {
255 		string v = getValue();
256 		return getName() ~ ": " ~ (v.empty ? "" : v);
257 	}
258 
259 	bool isSameName(HttpField field) {
260 		if (field is null)
261 			return false;
262 		if (field is this)
263 			return true;
264 		if (_header != HttpHeader.Null && _header == field.getHeader())
265 			return true;
266 		if (_name.equalsIgnoreCase(field.getName()))
267 			return true;
268 		return false;
269 	}
270 
271 	private int nameHashCode() nothrow {
272 		int h = this.hash;
273 		int len = cast(int)_name.length;
274 		if (h == 0 && len > 0) {
275 			for (int i = 0; i < len; i++) {
276 				// simple case insensitive hash
277 				char c = _name[i];
278 				// assuming us-ascii (per last paragraph on
279 				// http://tools.ietf.org/html/rfc7230#section-3.2.4)
280 				if ((c >= 'a' && c <= 'z'))
281 					c -= 0x20;
282 				h = 31 * h + c;
283 			}
284 			this.hash = h;
285 		}
286 		return h;
287 	}
288 
289 	override
290 	size_t toHash() @trusted nothrow {
291 		size_t vhc = hashOf(_value);
292 		if (_header ==  HttpHeader.Null)
293 			return vhc ^ nameHashCode();
294 		return vhc ^ hashOf(_header);
295 	}
296 
297 	override bool opEquals(Object o)
298 	{
299 		if(o is this) return true;
300     	if(o is null) return false;
301 		
302 		HttpField field = cast(HttpField) o;
303 		if(field is null)	return false;
304 
305 		// trace("xx=>", _header.toString());
306 		// trace("2222=>", field.getHeader().toString());
307 
308 		if (_header != field.getHeader())
309 			return false;
310 
311 		if (std..string.icmp(_name, field.getName()) != 0)
312 			return false;
313 
314 		string v = field.getValue();
315 		if (_value.empty && !v.empty)
316 			return false;
317 		return _value == v;
318 	}
319 
320 	static class IntValueHttpField :HttpField {
321 		private int _int;
322 
323 		this(HttpHeader header, string name, string value, int intValue) {
324 			super(header, name, value);
325 			_int = intValue;
326 		}
327 
328 		this(HttpHeader header, string name, string value) {
329 			tracef("name=%s, value=%s",name, value);
330 			this(header, name, value, std.conv.to!int(value));
331 		}
332 
333 		this(HttpHeader header, string name, int intValue) {
334 			this(header, name, std.conv.to!(string)(intValue), intValue);
335 		}
336 
337 		this(HttpHeader header, int value) {
338 			this(header, header.asString(), value);
339 		}
340 
341 		override
342 		int getIntValue() {
343 			return _int;
344 		}
345 
346 		override
347 		long getLongValue() {
348 			return _int;
349 		}
350 	}
351 
352 	static class LongValueHttpField :HttpField {
353 		private long _long;
354 
355 		this(HttpHeader header, string name, string value, long longValue) {
356 			super(header, name, value);
357 			_long = longValue;
358 		}
359 
360 		this(HttpHeader header, string name, string value) {
361 			this(header, name, value, std.conv.to!long(value));
362 		}
363 
364 		this(HttpHeader header, string name, long value) {
365 			this(header, name, std.conv.to!string(value), value);
366 		}
367 
368 		this(HttpHeader header, long value) {
369 			this(header, header.asString(), value);
370 		}
371 
372 		override
373 		int getIntValue() {
374 			return cast(int) _long;
375 		}
376 
377 		override
378 		long getLongValue() {
379 			return _long;
380 		}
381 	}
382 
383 	static bool isCaseInsensitive() {
384         return true;
385     }
386 }