Mercurial > projects > ldc
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 } |