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 }