00001 /******************************************************************************* 00002 00003 @file HttpTokens.d 00004 00005 Copyright (c) 2004 Kris Bell 00006 00007 This software is provided 'as-is', without any express or implied 00008 warranty. In no event will the authors be held liable for damages 00009 of any kind arising from the use of this software. 00010 00011 Permission is hereby granted to anyone to use this software for any 00012 purpose, including commercial applications, and to alter it and/or 00013 redistribute it freely, subject to the following restrictions: 00014 00015 1. The origin of this software must not be misrepresented; you must 00016 not claim that you wrote the original software. If you use this 00017 software in a product, an acknowledgment within documentation of 00018 said product would be appreciated but is not required. 00019 00020 2. Altered source versions must be plainly marked as such, and must 00021 not be misrepresented as being the original software. 00022 00023 3. This notice may not be removed or altered from any distribution 00024 of the source. 00025 00026 4. Derivative works are permitted, but they must carry this notice 00027 in full and credit the original source. 00028 00029 00030 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 00031 00032 00033 @version Initial version, April 2004 00034 @author Kris 00035 00036 00037 *******************************************************************************/ 00038 00039 module mango.http.server.HttpTokens; 00040 00041 private import mango.text.Text; 00042 00043 private import mango.io.Token, 00044 mango.io.Buffer, 00045 mango.io.Tokenizer; 00046 00047 private import mango.convert.Integer, 00048 mango.convert.Rfc1123; 00049 00050 private import mango.io.model.IBuffer, 00051 mango.io.model.IWriter; 00052 00053 private import mango.http.utils.TokenStack; 00054 00055 /****************************************************************************** 00056 00057 Struct used to expose freachable HttpToken instances. 00058 00059 ******************************************************************************/ 00060 00061 struct HttpToken 00062 { 00063 char[] name, 00064 value; 00065 } 00066 00067 /****************************************************************************** 00068 00069 Maintains a set of HTTP tokens. These tokens include headers, query- 00070 parameters, and anything else vaguely similar. Both input and output 00071 are supported, though a subclass may choose to expose as read-only. 00072 00073 All tokens are mapped directly onto a buffer, so there is no memory 00074 allocation or copying involved. 00075 00076 Note that this class does not support deleting tokens. Supporting 00077 such operations require a different approach, such as mapping the 00078 tokens into a temporary buffer, and then setting token content in 00079 the stack to be null when it is deleted. This could be implemented 00080 as a wrapper upon the subclasses of HttpToken. 00081 00082 ******************************************************************************/ 00083 00084 class HttpTokens : IWritable 00085 { 00086 protected TokenStack stack; 00087 00088 private IBuffer input, 00089 output; 00090 private bool parsed; 00091 private bool inclusive; 00092 private char separator; 00093 private char[1] sepString; 00094 static private char[] emptyString; 00095 00096 /********************************************************************** 00097 00098 Setup an empty character array for later assignment. 00099 00100 **********************************************************************/ 00101 00102 static this () 00103 { 00104 emptyString = new char[0]; 00105 } 00106 00107 /********************************************************************** 00108 00109 Construct a set of tokens based upon the given delimeter, 00110 and an indication of whether said delimeter should be 00111 considered part of the left side (effectively the name). 00112 00113 The latter is useful with headers, since the seperating 00114 ':' character should really be considered part of the 00115 name for purposes of subsequent token matching. 00116 00117 **********************************************************************/ 00118 00119 this (char separator, bool inclusive = false) 00120 { 00121 stack = new TokenStack(); 00122 00123 this.inclusive = inclusive; 00124 this.separator = separator; 00125 00126 // convert separator into a string, for later use 00127 sepString[0] = separator; 00128 00129 // pre-construct an empty buffer for wrapping char[] parsing 00130 input = new Buffer; 00131 } 00132 00133 /********************************************************************** 00134 00135 Clone a source set of HttpTokens 00136 00137 **********************************************************************/ 00138 00139 this (HttpTokens source) 00140 { 00141 stack = source.stack.clone(); 00142 input = null; 00143 output = source.output; 00144 parsed = true; 00145 inclusive = source.inclusive; 00146 separator = source.separator; 00147 sepString[0] = source.sepString[0]; 00148 } 00149 00150 /********************************************************************** 00151 00152 Read all tokens. Everything is mapped rather than being 00153 allocated & copied 00154 00155 **********************************************************************/ 00156 00157 abstract void parse (IBuffer input); 00158 00159 /********************************************************************** 00160 00161 Parse an input string. 00162 00163 **********************************************************************/ 00164 00165 void parse (char[] content) 00166 { 00167 input.setValidContent (content); 00168 parse (input); 00169 } 00170 00171 /********************************************************************** 00172 00173 Reset this set of tokens. 00174 00175 **********************************************************************/ 00176 00177 void reset () 00178 { 00179 stack.reset(); 00180 parsed = false; 00181 00182 // reset output buffer, if it was configured 00183 if (output) 00184 output.clear(); 00185 } 00186 00187 /********************************************************************** 00188 00189 Have tokens been parsed yet? 00190 00191 **********************************************************************/ 00192 00193 bool isParsed () 00194 { 00195 return parsed; 00196 } 00197 00198 /********************************************************************** 00199 00200 Indicate whether tokens have been parsed or not. 00201 00202 **********************************************************************/ 00203 00204 void setParsed (bool parsed) 00205 { 00206 this.parsed = parsed; 00207 } 00208 00209 /********************************************************************** 00210 00211 Return the value of the provided header, or null if the 00212 header does not exist 00213 00214 **********************************************************************/ 00215 00216 char[] get (char[] name, char[] ret = null) 00217 { 00218 Token token = stack.findToken (name); 00219 if (token) 00220 { 00221 HttpToken element; 00222 00223 if (split (token, element)) 00224 ret = trim (element.value); 00225 } 00226 return ret; 00227 } 00228 00229 /********************************************************************** 00230 00231 Return the integer value of the provided header, or the 00232 provided default-vaule if the header does not exist 00233 00234 **********************************************************************/ 00235 00236 int getInt (char[] name, int ret = -1) 00237 { 00238 char[] value = get (name); 00239 00240 if (value.length) 00241 ret = cast(int) Integer.parse (value); 00242 00243 return ret; 00244 } 00245 00246 /********************************************************************** 00247 00248 Return the date value of the provided header, or the 00249 provided default-value if the header does not exist 00250 00251 **********************************************************************/ 00252 00253 ulong getDate (char[] name, ulong date = Rfc1123.InvalidEpoch) 00254 { 00255 char[] value = get (name); 00256 00257 if (value.length) 00258 date = Rfc1123.parse (value); 00259 00260 return date; 00261 } 00262 00263 /********************************************************************** 00264 00265 Iterate over the set of tokens 00266 00267 **********************************************************************/ 00268 00269 int opApply (int delegate(inout HttpToken) dg) 00270 { 00271 HttpToken element; 00272 int result = 0; 00273 00274 foreach (Token t; stack) 00275 if (split (t, element)) 00276 { 00277 result = dg (element); 00278 if (result) 00279 break; 00280 } 00281 return result; 00282 } 00283 00284 /********************************************************************** 00285 00286 Output the token list to the provided writer 00287 00288 **********************************************************************/ 00289 00290 void write (IWriter writer) 00291 { 00292 foreach (Token token; stack) 00293 { 00294 char[] content = token.toString; 00295 if (content.length) 00296 writer.put(content).cr(); 00297 } 00298 } 00299 00300 /********************************************************************** 00301 00302 overridable method to handle the case where a token does 00303 not have a separator. Apparently, this can happen in HTTP 00304 usage 00305 00306 **********************************************************************/ 00307 00308 protected bool handleMissingSeparator (char[] s, inout HttpToken element) 00309 { 00310 return false; 00311 } 00312 00313 /********************************************************************** 00314 00315 split basic token into an HttpToken 00316 00317 **********************************************************************/ 00318 00319 final private bool split (Token t, inout HttpToken element) 00320 { 00321 char[] s = t.toString(); 00322 00323 if (s.length) 00324 { 00325 int i = Text.indexOf (s, separator); 00326 00327 // we should always find the separator 00328 if (i > 0) 00329 { 00330 int j = (inclusive) ? i+1 : i; 00331 element.name = s[0..j]; 00332 element.value = (i < s.length) ? s[i+1..s.length] : emptyString; 00333 return true; 00334 } 00335 else 00336 // allow override to specialize this case 00337 return handleMissingSeparator (s, element); 00338 } 00339 return false; 00340 } 00341 00342 /********************************************************************** 00343 00344 Create a filter for iterating over the tokens matching 00345 a particular name. 00346 00347 **********************************************************************/ 00348 00349 FilteredTokens createFilter (char[] match) 00350 { 00351 return new FilteredTokens (this, match); 00352 } 00353 00354 /********************************************************************** 00355 00356 Implements a filter for iterating over tokens matching 00357 a particular name. We do it like this because there's no 00358 means of passing additional information to an opApply() 00359 method. 00360 00361 **********************************************************************/ 00362 00363 private static class FilteredTokens 00364 { 00365 private char[] match; 00366 private HttpTokens tokens; 00367 00368 /************************************************************** 00369 00370 Construct this filter upon the given tokens, and 00371 set the pattern to match against. 00372 00373 **************************************************************/ 00374 00375 this (HttpTokens tokens, char[] match) 00376 { 00377 this.match = match; 00378 this.tokens = tokens; 00379 } 00380 00381 /************************************************************** 00382 00383 Iterate over all tokens matching the given name 00384 00385 **************************************************************/ 00386 00387 int opApply (int delegate(inout HttpToken) dg) 00388 { 00389 HttpToken element; 00390 int result = 0; 00391 00392 foreach (Token token; tokens.stack) 00393 if (tokens.stack.isMatch (token, match)) 00394 if (tokens.split (token, element)) 00395 { 00396 result = dg (element); 00397 if (result) 00398 break; 00399 } 00400 return result; 00401 } 00402 00403 } 00404 00405 /********************************************************************** 00406 00407 Is the argument a whitespace character? 00408 00409 **********************************************************************/ 00410 00411 private bool isSpace (char c) 00412 { 00413 return cast(bool) (c is ' ' || c is '\t' || c is '\r' || c is '\n'); 00414 } 00415 00416 /********************************************************************** 00417 00418 Trim the provided string by stripping whitespace from 00419 both ends. Returns a slice of the original content. 00420 00421 **********************************************************************/ 00422 00423 private char[] trim (char[] source) 00424 { 00425 int front, 00426 back = source.length; 00427 00428 if (back) 00429 { 00430 while (front < back && isSpace(source[front])) 00431 ++front; 00432 00433 while (back > front && isSpace(source[back-1])) 00434 --back; 00435 } 00436 return source [front .. back]; 00437 } 00438 00439 00440 /********************************************************************** 00441 ****************** these should be exposed carefully ****************** 00442 **********************************************************************/ 00443 00444 00445 /********************************************************************** 00446 00447 Set the output buffer for adding tokens to. This is used 00448 by the various MutableXXXX classes. 00449 00450 **********************************************************************/ 00451 00452 protected void setOutputBuffer (IBuffer output) 00453 { 00454 this.output = output; 00455 } 00456 00457 /********************************************************************** 00458 00459 Return the buffer used for output. 00460 00461 **********************************************************************/ 00462 00463 protected IBuffer getOutputBuffer () 00464 { 00465 return output; 00466 } 00467 00468 /********************************************************************** 00469 00470 Return a char[] representing the output. An empty array 00471 is returned if output was not configured. 00472 00473 **********************************************************************/ 00474 00475 char[] toOutputString () 00476 { 00477 static const char[0] Null; 00478 00479 if (output) 00480 return output.toString; 00481 return Null; 00482 } 00483 00484 /********************************************************************** 00485 00486 Add a token with the given name. The content is provided 00487 via the specified delegate. We stuff this name & content 00488 into the output buffer, and map a new Token onto the 00489 appropriate buffer slice. 00490 00491 **********************************************************************/ 00492 00493 protected void add (char[] name, void delegate (IBuffer) dg) 00494 { 00495 // save the buffer write-position 00496 int prior = output.getLimit; 00497 00498 // add the name 00499 output.append (name); 00500 00501 // don't append separator if it's already part of the name 00502 if (! inclusive) 00503 output.append (sepString); 00504 00505 // add the value 00506 dg (output); 00507 00508 // map new token onto buffer slice 00509 int limit = output.getLimit; 00510 stack.push (output.toString[prior..limit]); 00511 } 00512 00513 /********************************************************************** 00514 00515 Add a simple name/value pair to the output 00516 00517 **********************************************************************/ 00518 00519 protected void add (char[] name, char[] value) 00520 { 00521 void addValue (IBuffer buffer) 00522 { 00523 buffer.append (value); 00524 } 00525 00526 add (name, &addValue); 00527 } 00528 00529 /********************************************************************** 00530 00531 Add a name/integer pair to the output 00532 00533 **********************************************************************/ 00534 00535 protected void addInt (char[] name, int value) 00536 { 00537 char[16] tmp; 00538 00539 add (name, Integer.format (tmp, value)); 00540 } 00541 00542 00543 /********************************************************************** 00544 00545 Add a name/date(long) pair to the output 00546 00547 **********************************************************************/ 00548 00549 protected void addDate (char[] name, ulong value) 00550 { 00551 char[40] tmp; 00552 00553 add (name, Rfc1123.format (tmp, value)); 00554 } 00555 }