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