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