Mercurial > projects > ldc
comparison tango/tango/net/http/HttpCookies.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.HttpCookies; | |
14 | |
15 private import tango.io.Buffer; | |
16 | |
17 private import tango.stdc.ctype; | |
18 | |
19 private import tango.net.http.HttpHeaders; | |
20 | |
21 private import tango.io.protocol.model.IWriter; | |
22 | |
23 private import tango.text.stream.StreamIterator; | |
24 | |
25 private import Integer = tango.text.convert.Integer; | |
26 | |
27 /******************************************************************************* | |
28 | |
29 Defines the Cookie class, and the means for reading & writing them. | |
30 Cookie implementation conforms with RFC 2109, but supports parsing | |
31 of server-side cookies only. Client-side cookies are supported in | |
32 terms of output, but response parsing is not yet implemented ... | |
33 | |
34 See over <A HREF="http://www.faqs.org/rfcs/rfc2109.html">here</A> | |
35 for the RFC document. | |
36 | |
37 *******************************************************************************/ | |
38 | |
39 class Cookie : IWritable | |
40 { | |
41 private char[] name, | |
42 path, | |
43 value, | |
44 domain, | |
45 comment; | |
46 private uint vrsn=1; // 'version' is a reserved word | |
47 private long maxAge; | |
48 private bool secure; | |
49 | |
50 /*********************************************************************** | |
51 | |
52 Construct an empty client-side cookie. You add these | |
53 to an output request using HttpClient.addCookie(), or | |
54 the equivalent. | |
55 | |
56 ***********************************************************************/ | |
57 | |
58 this () {} | |
59 | |
60 /*********************************************************************** | |
61 | |
62 Construct a cookie with the provided attributes. You add | |
63 these to an output request using HttpClient.addCookie(), | |
64 or the equivalent. | |
65 | |
66 ***********************************************************************/ | |
67 | |
68 this (char[] name, char[] value) | |
69 { | |
70 setName (name); | |
71 setValue (value); | |
72 } | |
73 | |
74 /*********************************************************************** | |
75 | |
76 Set the name of this cookie | |
77 | |
78 ***********************************************************************/ | |
79 | |
80 void setName (char[] name) | |
81 { | |
82 this.name = name; | |
83 } | |
84 | |
85 /*********************************************************************** | |
86 | |
87 Set the value of this cookie | |
88 | |
89 ***********************************************************************/ | |
90 | |
91 void setValue (char[] value) | |
92 { | |
93 this.value = value; | |
94 } | |
95 | |
96 /*********************************************************************** | |
97 | |
98 Set the version of this cookie | |
99 | |
100 ***********************************************************************/ | |
101 | |
102 void setVersion (uint vrsn) | |
103 { | |
104 this.vrsn = vrsn; | |
105 } | |
106 | |
107 /*********************************************************************** | |
108 | |
109 Set the path of this cookie | |
110 | |
111 ***********************************************************************/ | |
112 | |
113 void setPath (char[] path) | |
114 { | |
115 this.path = path; | |
116 } | |
117 | |
118 /*********************************************************************** | |
119 | |
120 Set the domain of this cookie | |
121 | |
122 ***********************************************************************/ | |
123 | |
124 void setDomain (char[] domain) | |
125 { | |
126 this.domain = domain; | |
127 } | |
128 | |
129 /*********************************************************************** | |
130 | |
131 Set the comment associated with this cookie | |
132 | |
133 ***********************************************************************/ | |
134 | |
135 void setComment (char[] comment) | |
136 { | |
137 this.comment = comment; | |
138 } | |
139 | |
140 /*********************************************************************** | |
141 | |
142 Set the maximum duration of this cookie | |
143 | |
144 ***********************************************************************/ | |
145 | |
146 void setMaxAge (long maxAge) | |
147 { | |
148 this.maxAge = maxAge; | |
149 } | |
150 | |
151 /*********************************************************************** | |
152 | |
153 Indicate wether this cookie should be considered secure or not | |
154 | |
155 ***********************************************************************/ | |
156 | |
157 void setSecure (bool secure) | |
158 { | |
159 this.secure = secure; | |
160 } | |
161 | |
162 /*********************************************************************** | |
163 | |
164 Output the cookie as a text stream, via the provided IWriter | |
165 | |
166 ***********************************************************************/ | |
167 | |
168 void write (IWriter writer) | |
169 { | |
170 produce (&writer.buffer.consume); | |
171 } | |
172 | |
173 /*********************************************************************** | |
174 | |
175 Output the cookie as a text stream, via the provided consumer | |
176 | |
177 ***********************************************************************/ | |
178 | |
179 void produce (void delegate(void[]) consume) | |
180 { | |
181 consume (name); | |
182 | |
183 if (value.length) | |
184 consume ("="), consume (value); | |
185 | |
186 if (path.length) | |
187 consume (";Path="), consume (path); | |
188 | |
189 if (domain.length) | |
190 consume (";Domain="), consume (domain); | |
191 | |
192 if (vrsn) | |
193 { | |
194 char[16] tmp = void; | |
195 | |
196 consume (";Version="); | |
197 consume (Integer.format (tmp, vrsn)); | |
198 | |
199 if (comment.length) | |
200 consume (";Comment=\""), consume(comment), consume("\""); | |
201 | |
202 if (secure) | |
203 consume (";Secure"); | |
204 | |
205 if (maxAge >= 0) | |
206 consume (";Max-Age="c), consume (Integer.format (tmp, maxAge)); | |
207 } | |
208 } | |
209 | |
210 /*********************************************************************** | |
211 | |
212 Reset this cookie | |
213 | |
214 ***********************************************************************/ | |
215 | |
216 void clear () | |
217 { | |
218 vrsn = 1; | |
219 maxAge = 0; | |
220 secure = false; | |
221 name = path = domain = comment = null; | |
222 } | |
223 } | |
224 | |
225 | |
226 | |
227 /******************************************************************************* | |
228 | |
229 Implements a stack of cookies. Each cookie is pushed onto the | |
230 stack by a parser, which takes its input from HttpHeaders. The | |
231 stack can be populated for both client and server side cookies. | |
232 | |
233 *******************************************************************************/ | |
234 | |
235 class CookieStack | |
236 { | |
237 private int depth; | |
238 private Cookie[] cookies; | |
239 | |
240 /********************************************************************** | |
241 | |
242 Construct a cookie stack with the specified initial extent. | |
243 The stack will grow as necessary over time. | |
244 | |
245 **********************************************************************/ | |
246 | |
247 this (int size) | |
248 { | |
249 cookies = new Cookie[0]; | |
250 resize (cookies, size); | |
251 } | |
252 | |
253 /********************************************************************** | |
254 | |
255 Pop the stack all the way to zero | |
256 | |
257 **********************************************************************/ | |
258 | |
259 final void reset () | |
260 { | |
261 depth = 0; | |
262 } | |
263 | |
264 /********************************************************************** | |
265 | |
266 Return a fresh cookie from the stack | |
267 | |
268 **********************************************************************/ | |
269 | |
270 final Cookie push () | |
271 { | |
272 if (depth == cookies.length) | |
273 resize (cookies, depth * 2); | |
274 return cookies [depth++]; | |
275 } | |
276 | |
277 /********************************************************************** | |
278 | |
279 Resize the stack such that it has more room. | |
280 | |
281 **********************************************************************/ | |
282 | |
283 private final static void resize (inout Cookie[] cookies, int size) | |
284 { | |
285 int i = cookies.length; | |
286 | |
287 for (cookies.length=size; i < cookies.length; ++i) | |
288 cookies[i] = new Cookie(); | |
289 } | |
290 | |
291 /********************************************************************** | |
292 | |
293 Iterate over all cookies in stack | |
294 | |
295 **********************************************************************/ | |
296 | |
297 int opApply (int delegate(inout Cookie) dg) | |
298 { | |
299 int result = 0; | |
300 | |
301 for (int i=0; i < depth; ++i) | |
302 if ((result = dg (cookies[i])) != 0) | |
303 break; | |
304 return result; | |
305 } | |
306 } | |
307 | |
308 | |
309 | |
310 /******************************************************************************* | |
311 | |
312 This is the support point for server-side cookies. It wraps a | |
313 CookieStack together with a set of HttpHeaders, along with the | |
314 appropriate cookie parser. One would do something very similar | |
315 for client side cookie parsing also. | |
316 | |
317 *******************************************************************************/ | |
318 | |
319 class HttpCookiesView : IWritable | |
320 { | |
321 private bool parsed; | |
322 private CookieStack stack; | |
323 private CookieParser parser; | |
324 private HttpHeadersView headers; | |
325 | |
326 /********************************************************************** | |
327 | |
328 Construct cookie wrapper with the provided headers. | |
329 | |
330 **********************************************************************/ | |
331 | |
332 this (HttpHeadersView headers) | |
333 { | |
334 this.headers = headers; | |
335 | |
336 // create a stack for parsed cookies | |
337 stack = new CookieStack (10); | |
338 | |
339 // create a parser | |
340 parser = new CookieParser (stack); | |
341 } | |
342 | |
343 /********************************************************************** | |
344 | |
345 Output each of the cookies parsed to the provided IWriter. | |
346 | |
347 **********************************************************************/ | |
348 | |
349 void write (IWriter writer) | |
350 { | |
351 produce (&writer.buffer.consume, HttpConst.Eol); | |
352 } | |
353 | |
354 /********************************************************************** | |
355 | |
356 Output the token list to the provided consumer | |
357 | |
358 **********************************************************************/ | |
359 | |
360 void produce (void delegate (void[]) consume, char[] eol = HttpConst.Eol) | |
361 { | |
362 foreach (cookie; parse) | |
363 cookie.produce (consume), consume (eol); | |
364 } | |
365 | |
366 /********************************************************************** | |
367 | |
368 Reset these cookies for another parse | |
369 | |
370 **********************************************************************/ | |
371 | |
372 void reset () | |
373 { | |
374 stack.reset; | |
375 parsed = false; | |
376 } | |
377 | |
378 /********************************************************************** | |
379 | |
380 Parse all cookies from our HttpHeaders, pushing each onto | |
381 the CookieStack as we go. | |
382 | |
383 **********************************************************************/ | |
384 | |
385 CookieStack parse () | |
386 { | |
387 if (! parsed) | |
388 { | |
389 parsed = true; | |
390 | |
391 foreach (HeaderElement header; headers) | |
392 if (header.name.value == HttpHeader.Cookie.value) | |
393 parser.parse (header.value); | |
394 } | |
395 return stack; | |
396 } | |
397 } | |
398 | |
399 | |
400 | |
401 /******************************************************************************* | |
402 | |
403 Handles a set of output cookies by writing them into the list of | |
404 output headers. | |
405 | |
406 *******************************************************************************/ | |
407 | |
408 class HttpCookies | |
409 { | |
410 private HttpHeaders headers; | |
411 | |
412 /********************************************************************** | |
413 | |
414 Construct an output cookie wrapper upon the provided | |
415 output headers. Each cookie added is converted to an | |
416 addition to those headers. | |
417 | |
418 **********************************************************************/ | |
419 | |
420 this (HttpHeaders headers) | |
421 { | |
422 this.headers = headers; | |
423 } | |
424 | |
425 /********************************************************************** | |
426 | |
427 Add a cookie to our output headers. | |
428 | |
429 **********************************************************************/ | |
430 | |
431 void add (Cookie cookie) | |
432 { | |
433 // add the cookie header via our callback | |
434 headers.add (HttpHeader.SetCookie, (IBuffer buf){cookie.produce (&buf.consume);}); | |
435 } | |
436 } | |
437 | |
438 | |
439 | |
440 /******************************************************************************* | |
441 | |
442 Server-side cookie parser. See RFC 2109 for details. | |
443 | |
444 *******************************************************************************/ | |
445 | |
446 class CookieParser : StreamIterator!(char) | |
447 { | |
448 private enum State {Begin, LValue, Equals, RValue, Token, SQuote, DQuote}; | |
449 | |
450 private CookieStack stack; | |
451 private Buffer buffer; | |
452 | |
453 /*********************************************************************** | |
454 | |
455 ***********************************************************************/ | |
456 | |
457 this (CookieStack stack) | |
458 { | |
459 super(); | |
460 this.stack = stack; | |
461 buffer = new Buffer; | |
462 } | |
463 | |
464 /*********************************************************************** | |
465 | |
466 Callback for iterator.next(). We scan for name-value | |
467 pairs, populating Cookie instances along the way. | |
468 | |
469 ***********************************************************************/ | |
470 | |
471 protected uint scan (void[] data) | |
472 { | |
473 char c; | |
474 int mark, | |
475 vrsn; | |
476 char[] name, | |
477 token; | |
478 Cookie cookie; | |
479 | |
480 State state = State.Begin; | |
481 char[] content = cast(char[]) data; | |
482 | |
483 /*************************************************************** | |
484 | |
485 Found a value; set that also | |
486 | |
487 ***************************************************************/ | |
488 | |
489 void setValue (int i) | |
490 { | |
491 token = content [mark..i]; | |
492 //Print ("::name '%.*s'\n", name); | |
493 //Print ("::value '%.*s'\n", token); | |
494 | |
495 if (name[0] != '$') | |
496 { | |
497 cookie = stack.push(); | |
498 cookie.setName (name); | |
499 cookie.setValue (token); | |
500 cookie.setVersion (vrsn); | |
501 } | |
502 else | |
503 switch (toLower (name)) | |
504 { | |
505 case "$path": | |
506 if (cookie) | |
507 cookie.setPath (token); | |
508 break; | |
509 | |
510 case "$domain": | |
511 if (cookie) | |
512 cookie.setDomain (token); | |
513 break; | |
514 | |
515 case "$version": | |
516 vrsn = cast(int) Integer.parse (token); | |
517 break; | |
518 | |
519 default: | |
520 break; | |
521 } | |
522 state = State.Begin; | |
523 } | |
524 | |
525 /*************************************************************** | |
526 | |
527 Scan content looking for cookie fields | |
528 | |
529 ***************************************************************/ | |
530 | |
531 for (int i; i < content.length; ++i) | |
532 { | |
533 c = content [i]; | |
534 switch (state) | |
535 { | |
536 // look for an lValue | |
537 case State.Begin: | |
538 mark = i; | |
539 if (isalpha (c) || c is '$') | |
540 state = State.LValue; | |
541 continue; | |
542 | |
543 // scan until we have all lValue chars | |
544 case State.LValue: | |
545 if (! isalnum (c)) | |
546 { | |
547 state = State.Equals; | |
548 name = content [mark..i]; | |
549 --i; | |
550 } | |
551 continue; | |
552 | |
553 // should now have either a '=', ';', or ',' | |
554 case State.Equals: | |
555 if (c is '=') | |
556 state = State.RValue; | |
557 else | |
558 if (c is ',' || c is ';') | |
559 // get next NVPair | |
560 state = State.Begin; | |
561 continue; | |
562 | |
563 // look for a quoted token, or a plain one | |
564 case State.RValue: | |
565 mark = i; | |
566 if (c is '\'') | |
567 state = State.SQuote; | |
568 else | |
569 if (c is '"') | |
570 state = State.DQuote; | |
571 else | |
572 if (isalpha (c)) | |
573 state = State.Token; | |
574 continue; | |
575 | |
576 // scan for all plain token chars | |
577 case State.Token: | |
578 if (! isalnum (c)) | |
579 { | |
580 setValue (i); | |
581 --i; | |
582 } | |
583 continue; | |
584 | |
585 // scan until the next ' | |
586 case State.SQuote: | |
587 if (c is '\'') | |
588 ++mark, setValue (i); | |
589 continue; | |
590 | |
591 // scan until the next " | |
592 case State.DQuote: | |
593 if (c is '"') | |
594 ++mark, setValue (i); | |
595 continue; | |
596 | |
597 default: | |
598 continue; | |
599 } | |
600 } | |
601 | |
602 // we ran out of content; patch partial cookie values | |
603 if (state is State.Token) | |
604 setValue (content.length); | |
605 | |
606 // go home | |
607 return IConduit.Eof; | |
608 } | |
609 | |
610 /*********************************************************************** | |
611 | |
612 Locate the next token from the provided buffer, and map a | |
613 buffer reference into token. Returns true if a token was | |
614 located, false otherwise. | |
615 | |
616 Note that the buffer content is not duplicated. Instead, a | |
617 slice of the buffer is referenced by the token. You can use | |
618 Token.clone() or Token.toString().dup() to copy content per | |
619 your application needs. | |
620 | |
621 Note also that there may still be one token left in a buffer | |
622 that was not terminated correctly (as in eof conditions). In | |
623 such cases, tokens are mapped onto remaining content and the | |
624 buffer will have no more readable content. | |
625 | |
626 ***********************************************************************/ | |
627 | |
628 bool parse (char[] header) | |
629 { | |
630 super.set (buffer.setContent (header)); | |
631 return next.ptr > null; | |
632 } | |
633 | |
634 /********************************************************************** | |
635 | |
636 in-place conversion to lowercase | |
637 | |
638 **********************************************************************/ | |
639 | |
640 final static char[] toLower (inout char[] src) | |
641 { | |
642 foreach (int i, char c; src) | |
643 if (c >= 'A' && c <= 'Z') | |
644 src[i] = c + ('a' - 'A'); | |
645 return src; | |
646 } | |
647 } | |
648 | |
649 |