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 }