comparison tango/tango/net/http/HttpTokens.d @ 132:1700239cab2e trunk

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