Main Page | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Class Members | File Members | Related Pages

HttpCookies.d

Go to the documentation of this file.
00001 /*******************************************************************************
00002 
00003         @file HttpCookies.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 
00035         @author         Kris
00036 
00037 
00038 *******************************************************************************/
00039 
00040 module mango.http.server.HttpCookies;
00041 
00042 private import  mango.text.Text;
00043 
00044 private import  mango.io.Token,
00045                 mango.io.Buffer,
00046                 mango.io.Tokenizer;
00047 
00048 private import  mango.io.model.IWriter,
00049                 mango.io.model.IBuffer,
00050                 mango.io.model.IConduit;
00051 
00052 private import  mango.convert.Integer;
00053 
00054 private import  mango.http.HttpWriter;
00055 
00056 private import  mango.http.server.HttpHeaders;
00057 
00058 
00059 version (Ares)
00060          private import  std.c.ctype;
00061       else
00062          private import  std.ctype;
00063 
00064 
00065 /*******************************************************************************
00066 
00067         Defines the Cookie class, and the means for reading & writing them.
00068         Cookie implementation conforms with RFC 2109, but supports parsing 
00069         of server-side cookies only. Client-side cookies are supported in
00070         terms of output, but response parsing is not yet implemented ...
00071 
00072         See over <A HREF="http://www.faqs.org/rfcs/rfc2109.html">here</A>
00073         for the RFC document.        
00074 
00075 *******************************************************************************/
00076 
00077 class Cookie : IWritable
00078 {
00079         private char[]  name,
00080                         path,
00081                         value,
00082                         domain,
00083                         comment;
00084         private uint    vrsn=1;              // 'version' is a reserved word
00085         private long    maxAge;
00086         private bool    secure;
00087 
00088         /***********************************************************************
00089                 
00090                 Construct an empty client-side cookie. You add these
00091                 to an output request using HttpClient.addCookie(), or
00092                 the equivalent.
00093 
00094         ***********************************************************************/
00095 
00096         this ()
00097         {
00098         }
00099 
00100         /***********************************************************************
00101         
00102                 Construct a cookie with the provided attributes. You add 
00103                 these to an output request using HttpClient.addCookie(), 
00104                 or the equivalent.
00105 
00106         ***********************************************************************/
00107 
00108         this (char[] name, char[] value)
00109         {
00110                 setName (name);
00111                 setValue (value);
00112         }
00113 
00114         /***********************************************************************
00115         
00116                 Set the name of this cookie
00117 
00118         ***********************************************************************/
00119 
00120         void setName (char[] name)
00121         {
00122                 this.name = name;
00123         }
00124 
00125         /***********************************************************************
00126         
00127                 Set the value of this cookie
00128 
00129         ***********************************************************************/
00130 
00131         void setValue (char[] value)
00132         {
00133                 this.value = value;
00134         }
00135 
00136         /***********************************************************************
00137                 
00138                 Set the version of this cookie
00139 
00140         ***********************************************************************/
00141 
00142         void setVersion (uint vrsn)
00143         {
00144                 this.vrsn = vrsn;
00145         }
00146 
00147         /***********************************************************************
00148         
00149                 Set the path of this cookie
00150 
00151         ***********************************************************************/
00152 
00153         void setPath (char[] path)
00154         {
00155                 this.path = path;
00156         }
00157 
00158         /***********************************************************************
00159         
00160                 Set the domain of this cookie
00161 
00162         ***********************************************************************/
00163 
00164         void setDomain (char[] domain)
00165         {
00166                 this.domain = domain;
00167         }
00168 
00169         /***********************************************************************
00170         
00171                 Set the comment associated with this cookie
00172 
00173         ***********************************************************************/
00174 
00175         void setComment (char[] comment)
00176         {
00177                 this.comment = comment;
00178         }
00179 
00180         /***********************************************************************
00181         
00182                 Set the maximum duration of this cookie
00183 
00184         ***********************************************************************/
00185 
00186         void setMaxAge (long maxAge)
00187         {
00188                 this.maxAge = maxAge;
00189         }
00190 
00191         /***********************************************************************
00192         
00193                 Indicate wether this cookie should be considered secure or not
00194 
00195         ***********************************************************************/
00196 
00197         void setSecure (bool secure)
00198         {
00199                 this.secure = secure;
00200         }
00201 
00202         /***********************************************************************
00203         
00204                 Output the cookie as a text stream, via the provided IWriter.
00205 
00206         ***********************************************************************/
00207 
00208         void write (IWriter writer)
00209         {
00210                 writer.put (name);
00211 
00212                 if (value.length)
00213                     writer.put ('=').put(value);
00214 
00215                 if (path.length)
00216                     writer.put (";Path="c).put(path);
00217 
00218                 if (domain.length)
00219                     writer.put (";Domain="c).put(domain);
00220 
00221                 if (vrsn)
00222                    {
00223                    char[32] tmp;
00224 
00225                    writer.put (";Version="c).put(Integer.format(tmp, vrsn));
00226 
00227                    if (comment.length)
00228                        writer.put (";Comment=\""c).put(comment).put('"');
00229 
00230                    if (secure)
00231                        writer.put (";Secure"c);
00232 
00233                    if (maxAge >= 0)
00234                        writer.put (";Max-Age="c).put(Integer.format(tmp, maxAge));
00235                    }
00236         }
00237 
00238         /***********************************************************************
00239         
00240                 Reset this cookie
00241 
00242         ***********************************************************************/
00243 
00244         void clear ()
00245         {
00246                 maxAge = 0;
00247                 vrsn = 1;
00248                 secure = false;
00249                 name = path = domain = comment = null;
00250         }
00251 }
00252 
00253 
00254 
00255 /*******************************************************************************
00256 
00257         Implements a stack of cookies. Each cookie is pushed onto the
00258         stack by a parser, which takes its input from HttpHeaders. The
00259         stack can be populated for both client and server side cookies.
00260 
00261 *******************************************************************************/
00262 
00263 class CookieStack
00264 {
00265         private int             depth;
00266         private Cookie[]        cookies;
00267 
00268         /**********************************************************************
00269 
00270                 Construct a cookie stack with the specified initial extent.
00271                 The stack will grow as necessary over time.
00272 
00273         **********************************************************************/
00274 
00275         this (int size)
00276         {
00277                 cookies = new Cookie[0];
00278                 resize (cookies, size);
00279         }
00280 
00281         /**********************************************************************
00282 
00283                 Pop the stack all the way to zero
00284 
00285         **********************************************************************/
00286 
00287         final void reset ()
00288         {
00289                 depth = 0;
00290         }
00291 
00292         /**********************************************************************
00293 
00294                 Return a fresh cookie from the stack
00295 
00296         **********************************************************************/
00297 
00298         final Cookie push ()
00299         {
00300                 if (depth == cookies.length)
00301                     resize (cookies, depth * 2);
00302                 return cookies [depth++];
00303         }
00304         
00305         /**********************************************************************
00306 
00307                 Resize the stack such that is has more room.
00308 
00309         **********************************************************************/
00310 
00311         private final static void resize (inout Cookie[] cookies, int size)
00312         {
00313                 int i = cookies.length;
00314                 
00315                 for (cookies.length=size; i < cookies.length; ++i)
00316                      cookies[i] = new Cookie();
00317         }
00318 
00319         /**********************************************************************
00320 
00321                 Iterate over all cookies in stack
00322 
00323         **********************************************************************/
00324 
00325         int opApply (int delegate(inout Cookie) dg)
00326         {
00327                 int result = 0;
00328 
00329                 for (int i=0; i < depth; ++i)
00330                      if ((result = dg (cookies[i])) != 0)
00331                           break;
00332                 return result;
00333         }
00334 }
00335 
00336 
00337 
00338 /*******************************************************************************
00339 
00340         This is the support point for server-side cookies. It wraps a
00341         CookieStack together with a set of HttpHeaders, along with the
00342         appropriate cookie parser. One would do something very similar
00343         for client side cookie parsing also.
00344 
00345 *******************************************************************************/
00346 
00347 class HttpCookies : IWritable
00348 {
00349         private bool                    parsed;
00350         private Cookie                  cookie;
00351         private IBuffer                 buffer;
00352         private CookieStack             stack;
00353         private HttpHeaders             headers;
00354 
00355         private static CookieParser     parser;
00356 
00357         /**********************************************************************
00358 
00359                 Setup the parser for server-side cookies
00360 
00361         **********************************************************************/
00362 
00363         static this ()
00364         {
00365                 parser = new CookieParser ();
00366         }
00367 
00368         /**********************************************************************
00369 
00370                 Construct cookie wrapper with the provided headers.
00371 
00372         **********************************************************************/
00373 
00374         this (HttpHeaders headers)
00375         {
00376                 this.headers = headers;
00377 
00378                 // setup an empty buffer for later use
00379                 buffer = new Buffer;
00380 
00381                 // create a stack for parsed cookies
00382                 stack = new CookieStack (10);
00383         }
00384 
00385         /**********************************************************************
00386 
00387                 Output each of the cookies parsed to the provided IWriter.
00388 
00389         **********************************************************************/
00390 
00391         void write (IWriter writer)
00392         {
00393                 foreach (Cookie cookie; parse())
00394                          writer.put (cookie).cr();
00395         }
00396 
00397         /**********************************************************************
00398 
00399                 Reset these cookies for another parse
00400 
00401         **********************************************************************/
00402 
00403         void reset ()
00404         {
00405                 stack.reset();
00406                 parsed = false;
00407         }
00408 
00409         /**********************************************************************
00410 
00411                 Parse all cookies from our HttpHeaders, pushing each onto
00412                 the CookieStack as we go.
00413 
00414         **********************************************************************/
00415 
00416         CookieStack parse ()
00417         {
00418                 if (! parsed)
00419                    {
00420                    parsed = true;
00421 
00422                    foreach (HeaderElement header; headers)
00423                             if (header.name.value == HttpHeader.Cookie.value)
00424                                {
00425                                buffer.setValidContent (header.value);
00426                                parser.next (buffer, stack);
00427                                }
00428                    }
00429                 return stack;
00430         }
00431 }
00432 
00433 
00434 
00435 /*******************************************************************************
00436 
00437         Handles a set of output cookies by writing them into the list of
00438         output headers.
00439 
00440 *******************************************************************************/
00441 
00442 class HttpMutableCookies
00443 {
00444         private HttpWriter              writer;
00445         private HttpMutableHeaders      headers;
00446 
00447         /**********************************************************************
00448 
00449                 Construct an output cookie wrapper upon the provided 
00450                 output headers. Each cookie added is converted to an
00451                 addition to those headers.
00452 
00453         **********************************************************************/
00454 
00455         this (HttpMutableHeaders headers)
00456         {
00457                 this.headers = headers;
00458                 writer = new HttpWriter (headers.getOutputBuffer());
00459         }
00460 
00461         /**********************************************************************
00462 
00463                 Add a cookie to our output headers.
00464 
00465         **********************************************************************/
00466 
00467         void add (Cookie cookie)
00468         {
00469                 // nested function to actually perform the output
00470                 void writeCookie (IBuffer buf)
00471                 {
00472                         cookie.write (writer);
00473                 }
00474 
00475                 // add the cookie header via our callback
00476                 headers.add (HttpHeader.SetCookie, &writeCookie);        
00477         }
00478 }
00479 
00480 
00481 
00482 /*******************************************************************************
00483 
00484         Server-side cookie parser. See RFC 2109 for details.
00485 
00486 *******************************************************************************/
00487 
00488 class CookieParser : Scanner
00489 {
00490         private enum State {Begin, LValue, Equals, RValue, Token, SQuote, DQuote};
00491 
00492         /***********************************************************************
00493         
00494                 Locate the next token from the provided buffer, and map a
00495                 buffer reference into token. Returns true if a token was 
00496                 located, false otherwise. 
00497 
00498                 Note that the buffer content is not duplicated. Instead, a
00499                 slice of the buffer is referenced by the token. You can use
00500                 Token.clone() or Token.toString().dup() to copy content per
00501                 your application needs.
00502 
00503                 Note also that there may still be one token left in a buffer 
00504                 that was not terminated correctly (as in eof conditions). In 
00505                 such cases, tokens are mapped onto remaining content and the 
00506                 buffer will have no more readable content.
00507 
00508         ***********************************************************************/
00509 
00510         bool next (IBuffer buffer, CookieStack stack)
00511         {
00512                 /***************************************************************
00513 
00514                         callback from Scanner.next(). We scan for name-value
00515                         pairs, populating Cookie instances along the way.
00516 
00517                 ***************************************************************/
00518 
00519                 uint scan (char[] content)
00520                 {      
00521                         char    c;
00522                         int     mark,
00523                                 vrsn;
00524                         char[]  name,
00525                                 token;
00526                         Cookie  cookie;
00527                         State   state = State.Begin;
00528                         
00529                         /*******************************************************
00530 
00531                                 Found a value; set that also
00532 
00533                         *******************************************************/
00534 
00535                         void setValue (int i)
00536                         {   
00537                                 token = content [mark..i];
00538                                 //printf ("::name '%.*s'\n", name);
00539                                 //printf ("::value '%.*s'\n", token);
00540 
00541                                 if (name[0] != '$')
00542                                    {
00543                                    cookie = stack.push();
00544                                    cookie.setName (name);
00545                                    cookie.setValue (token);
00546                                    cookie.setVersion (vrsn);
00547                                    }
00548                                 else
00549                                    switch (toLower (name))
00550                                           {
00551                                           case "$path":
00552                                                 if (cookie)
00553                                                     cookie.setPath (token); 
00554                                                 break;
00555 
00556                                           case "$domain":
00557                                                 if (cookie)
00558                                                     cookie.setDomain (token); 
00559                                                 break;
00560 
00561                                           case "$version":
00562                                                 vrsn = cast(int) Integer.parse (token); 
00563                                                 break;
00564 
00565                                           default:
00566                                                break;
00567                                           }
00568                                 state = State.Begin;
00569                         }
00570 
00571                         /*******************************************************
00572 
00573                                 Scan content looking for cookie fields
00574 
00575                         *******************************************************/
00576 
00577                         for (int i; i < content.length; ++i)
00578                             {
00579                             c = content [i];
00580                             switch (state)
00581                                    {
00582                                    // look for an lValue
00583                                    case State.Begin:
00584                                         mark = i;
00585                                         if (isalpha (c) || c == '$')
00586                                             state = State.LValue;
00587                                         continue;
00588 
00589                                    // scan until we have all lValue chars
00590                                    case State.LValue:
00591                                         if (! isalnum (c))
00592                                            {
00593                                            state = State.Equals;
00594                                            name = content [mark..i];
00595                                            --i;
00596                                            }
00597                                         continue;
00598 
00599                                    // should now have either a '=', ';', or ','
00600                                    case State.Equals:
00601                                         if (c == '=')
00602                                             state = State.RValue;
00603                                         else
00604                                            if (c == ',' || c == ';')
00605                                                // get next NVPair
00606                                                state = State.Begin;
00607                                         continue;
00608 
00609                                    // look for a quoted token, or a plain one
00610                                    case State.RValue:
00611                                         mark = i;
00612                                         if (c == '\'')
00613                                             state = State.SQuote;
00614                                         else
00615                                            if (c == '"')
00616                                                state = State.DQuote;
00617                                            else
00618                                               if (isalpha (c))
00619                                                   state = State.Token;
00620                                         continue;
00621 
00622                                    // scan for all plain token chars
00623                                    case State.Token:
00624                                         if (! isalnum (c))
00625                                            {
00626                                            setValue (i);
00627                                            --i;
00628                                            }
00629                                         continue;
00630 
00631                                    // scan until the next '
00632                                    case State.SQuote:
00633                                         if (c == '\'')
00634                                             ++mark, setValue (i);
00635                                         continue;
00636 
00637                                    // scan until the next "
00638                                    case State.DQuote:
00639                                         if (c == '"')
00640                                             ++mark, setValue (i);
00641                                         continue;
00642 
00643                                    default:
00644                                         continue;
00645                                    }
00646                             }
00647 
00648                         // we ran out of content; patch partial cookie values 
00649                         if (state == State.Token)
00650                             setValue (content.length);
00651                         
00652                         // go home
00653                         return IConduit.Eof;
00654                 }
00655 
00656                 return super.next (buffer, &scan);
00657         }
00658 
00659 
00660         /**********************************************************************
00661 
00662                 in-place conversion to lowercase 
00663 
00664         **********************************************************************/
00665 
00666         final static char[] toLower (inout char[] src)
00667         {
00668                 foreach (int i, char c; src)
00669                          if (c >= 'A' && c <= 'Z')
00670                              src[i] = c + ('a' - 'A');
00671                 return src;
00672         }
00673 }
00674    
00675      

Generated on Sat Dec 24 17:28:32 2005 for Mango by  doxygen 1.4.0