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