1 /******************************************************************************* 2 3 Copyright: 4 Copyright (c) 2004 Kris Bell. 5 Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH. 6 All rights reserved. 7 8 License: 9 Tango Dual License: 3-Clause BSD License / Academic Free License v3.0. 10 See LICENSE_TANGO.txt for details. 11 12 Version: Initial release: April 2004 13 14 Authors: Kris 15 16 *******************************************************************************/ 17 18 module ocean.net.http.HttpTokens; 19 20 import ocean.meta.types.Qualifiers; 21 22 import ocean.time.Time; 23 24 import ocean.io.device.Array; 25 26 import ocean.io.stream.Buffered; 27 28 import ocean.net.http.HttpStack, 29 ocean.net.http.HttpConst; 30 31 import Text = ocean.text.Util; 32 33 import Integer = ocean.text.convert.Integer_tango; 34 35 import TimeStamp = ocean.text.convert.TimeStamp; 36 37 /****************************************************************************** 38 39 Struct used to expose reachable HttpToken instances. 40 41 ******************************************************************************/ 42 43 struct HttpToken 44 { 45 cstring name, 46 value; 47 } 48 49 /****************************************************************************** 50 51 Maintains a set of HTTP tokens. These tokens include headers, query- 52 parameters, and anything else vaguely related. Both input and output 53 are supported, though a subclass may choose to expose as read-only. 54 55 All tokens are mapped directly onto a buffer, so there is no memory 56 allocation or copying involved. 57 58 Note that this class does not support deleting tokens, per se. Instead 59 it marks tokens as being 'unused' by setting content to null, avoiding 60 unwarranted reshaping of the token stack. The token stack is reused as 61 time goes on, so there's only minor runtime overhead. 62 63 ******************************************************************************/ 64 65 class HttpTokens 66 { 67 protected HttpStack stack; 68 private Array input; 69 private Array output; 70 private bool parsed; 71 private bool inclusive; 72 private char separator; 73 private char[1] sepString; 74 75 /********************************************************************** 76 77 Construct a set of tokens based upon the given delimiter, 78 and an indication of whether said delimiter should be 79 considered part of the left side (effectively the name). 80 81 The latter is useful with headers, since the separating 82 ':' character should really be considered part of the 83 name for purposes of subsequent token matching. 84 85 **********************************************************************/ 86 87 this (char separator, bool inclusive = false) 88 { 89 stack = new HttpStack; 90 91 this.inclusive = inclusive; 92 this.separator = separator; 93 94 // convert separator into a string, for later use 95 sepString[0] = separator; 96 97 // pre-construct an empty buffer for wrapping char[] parsing 98 input = new Array (0); 99 100 // construct an array for containing stack tokens 101 output = new Array (4096, 1024); 102 } 103 104 /********************************************************************** 105 106 Clone a source set of HttpTokens 107 108 **********************************************************************/ 109 110 this (HttpTokens source) 111 { 112 stack = source.stack.clone; 113 input = null; 114 output = source.output; 115 parsed = true; 116 inclusive = source.inclusive; 117 separator = source.separator; 118 sepString[0] = source.sepString[0]; 119 } 120 121 /********************************************************************** 122 123 Read all tokens. Everything is mapped rather than being 124 allocated & copied 125 126 **********************************************************************/ 127 128 abstract void parse (InputBuffer input); 129 130 /********************************************************************** 131 132 Parse an input string. 133 134 **********************************************************************/ 135 136 void parse (char[] content) 137 { 138 input.assign (content); 139 parse (input); 140 } 141 142 /********************************************************************** 143 144 Reset this set of tokens. 145 146 **********************************************************************/ 147 148 HttpTokens reset () 149 { 150 stack.reset; 151 parsed = false; 152 153 // reset output buffer 154 output.clear; 155 return this; 156 } 157 158 /********************************************************************** 159 160 Have tokens been parsed yet? 161 162 **********************************************************************/ 163 164 bool isParsed () 165 { 166 return parsed; 167 } 168 169 /********************************************************************** 170 171 Indicate whether tokens have been parsed or not. 172 173 **********************************************************************/ 174 175 void setParsed (bool parsed) 176 { 177 this.parsed = parsed; 178 } 179 180 /********************************************************************** 181 182 Return the value of the provided header, or null if the 183 header does not exist 184 185 **********************************************************************/ 186 187 cstring get (cstring name, cstring ret = null) 188 { 189 Token token = stack.findToken (name); 190 if (token) 191 { 192 HttpToken element; 193 194 if (split (token, element)) 195 ret = trim (element.value); 196 } 197 return ret; 198 } 199 200 /********************************************************************** 201 202 Return the integer value of the provided header, or the 203 provided default-vaule if the header does not exist 204 205 **********************************************************************/ 206 207 int getInt (cstring name, int ret = -1) 208 { 209 auto value = get (name); 210 211 if (value.length) 212 ret = cast(int) Integer.parse (value); 213 214 return ret; 215 } 216 217 /********************************************************************** 218 219 Return the date value of the provided header, or the 220 provided default-value if the header does not exist 221 222 **********************************************************************/ 223 224 Time getDate (cstring name, Time date = Time.epoch) 225 { 226 auto value = get (name); 227 228 if (value.length) 229 date = TimeStamp.parse (value); 230 231 return date; 232 } 233 234 /********************************************************************** 235 236 Iterate over the set of tokens 237 238 **********************************************************************/ 239 240 int opApply (scope int delegate(ref HttpToken) dg) 241 { 242 HttpToken element; 243 int result = 0; 244 245 foreach (Token t; stack) 246 if (split (t, element)) 247 { 248 result = dg (element); 249 if (result) 250 break; 251 } 252 return result; 253 } 254 255 /********************************************************************** 256 257 Output the token list to the provided consumer 258 259 **********************************************************************/ 260 261 void produce (scope size_t delegate(const(void)[]) consume, cstring eol = null) 262 { 263 foreach (Token token; stack) 264 { 265 auto content = token.toString; 266 if (content.length) 267 { 268 consume (content); 269 if (eol.length) 270 consume (eol); 271 } 272 } 273 } 274 275 /********************************************************************** 276 277 overridable method to handle the case where a token does 278 not have a separator. Apparently, this can happen in HTTP 279 usage 280 281 **********************************************************************/ 282 283 protected bool handleMissingSeparator (cstring s, ref HttpToken element) 284 { 285 return false; 286 } 287 288 /********************************************************************** 289 290 split basic token into an HttpToken 291 292 **********************************************************************/ 293 294 final private bool split (Token t, ref HttpToken element) 295 { 296 auto s = t.get(); 297 298 if (s.length) 299 { 300 auto i = Text.locate (s, separator); 301 302 // we should always find the separator 303 if (i < s.length) 304 { 305 auto j = (inclusive) ? i+1 : i; 306 element.name = s[0 .. j]; 307 element.value = (++i < s.length) ? s[i .. $] : null; 308 return true; 309 } 310 else 311 // allow override to specialize this case 312 return handleMissingSeparator (s, element); 313 } 314 return false; 315 } 316 317 /********************************************************************** 318 319 Create a filter for iterating over the tokens matching 320 a particular name. 321 322 **********************************************************************/ 323 324 FilteredTokens createFilter (char[] match) 325 { 326 return new FilteredTokens (this, match); 327 } 328 329 /********************************************************************** 330 331 Implements a filter for iterating over tokens matching 332 a particular name. We do it like this because there's no 333 means of passing additional information to an opApply() 334 method. 335 336 **********************************************************************/ 337 338 protected static class FilteredTokens 339 { 340 private cstring match; 341 private HttpTokens tokens; 342 343 /************************************************************** 344 345 Construct this filter upon the given tokens, and 346 set the pattern to match against. 347 348 **************************************************************/ 349 350 this (HttpTokens tokens, cstring match) 351 { 352 this.match = match; 353 this.tokens = tokens; 354 } 355 356 /************************************************************** 357 358 Iterate over all tokens matching the given name 359 360 **************************************************************/ 361 362 int opApply (scope int delegate(ref HttpToken) dg) 363 { 364 HttpToken element; 365 int result = 0; 366 367 foreach (Token token; tokens.stack) 368 if (tokens.stack.isMatch (token, match)) 369 if (tokens.split (token, element)) 370 { 371 result = dg (element); 372 if (result) 373 break; 374 } 375 return result; 376 } 377 378 } 379 380 /********************************************************************** 381 382 Is the argument a whitespace character? 383 384 **********************************************************************/ 385 386 private bool isSpace (char c) 387 { 388 return cast(bool) (c is ' ' || c is '\t' || c is '\r' || c is '\n'); 389 } 390 391 /********************************************************************** 392 393 Trim the provided string by stripping whitespace from 394 both ends. Returns a slice of the original content. 395 396 **********************************************************************/ 397 398 private cstring trim (cstring source) 399 { 400 int front, 401 back = cast(int) source.length; 402 403 if (back) 404 { 405 while (front < back && isSpace(source[front])) 406 ++front; 407 408 while (back > front && isSpace(source[back-1])) 409 --back; 410 } 411 return source [front .. back]; 412 } 413 414 415 /********************************************************************** 416 ****************** these should be exposed carefully ****************** 417 **********************************************************************/ 418 419 420 /********************************************************************** 421 422 Return a char[] representing the output. An empty array 423 is returned if output was not configured. This perhaps 424 could just return our 'output' buffer content, but that 425 would not reflect deletes, or separators. Better to do 426 it like this instead, for a small cost. 427 428 **********************************************************************/ 429 430 char[] formatTokens (OutputBuffer dst, cstring delim) 431 { 432 bool first = true; 433 434 foreach (Token token; stack) 435 { 436 cstring content = token.get(); 437 if (content.length) 438 { 439 if (first) 440 first = false; 441 else 442 dst.write (delim); 443 dst.write (content); 444 } 445 } 446 return cast(mstring) dst.slice; 447 } 448 449 /********************************************************************** 450 451 Add a token with the given name. The content is provided 452 via the specified delegate. We stuff this name & content 453 into the output buffer, and map a new Token onto the 454 appropriate buffer slice. 455 456 **********************************************************************/ 457 458 protected void add (cstring name, scope void delegate(OutputBuffer) value) 459 { 460 // save the buffer write-position 461 //int prior = output.limit; 462 auto prior = output.slice.length; 463 464 // add the name 465 output.append (name); 466 467 // don't append separator if it's already part of the name 468 if (! inclusive) 469 output.append (sepString); 470 471 // add the value 472 value (output); 473 474 // map new token onto buffer slice 475 stack.push (cast(char[]) output.slice [prior .. $]); 476 } 477 478 /********************************************************************** 479 480 Add a simple name/value pair to the output 481 482 **********************************************************************/ 483 484 protected void add (cstring name, cstring value) 485 { 486 void addValue (OutputBuffer buffer) 487 { 488 buffer.write (value); 489 } 490 491 add (name, &addValue); 492 } 493 494 /********************************************************************** 495 496 Add a name/integer pair to the output 497 498 **********************************************************************/ 499 500 protected void addInt (cstring name, int value) 501 { 502 char[16] tmp = void; 503 504 add (name, Integer.format (tmp, cast(long) value)); 505 } 506 507 /********************************************************************** 508 509 Add a name/date(long) pair to the output 510 511 **********************************************************************/ 512 513 protected void addDate (cstring name, Time value) 514 { 515 char[40] tmp = void; 516 517 add (name, TimeStamp.format (tmp, value)); 518 } 519 520 /********************************************************************** 521 522 remove a token from our list. Returns false if the named 523 token is not found. 524 525 **********************************************************************/ 526 527 protected bool remove (cstring name) 528 { 529 return stack.removeToken (name); 530 } 531 }