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

Generated on Tue Jan 25 21:18:21 2005 for Mango by doxygen 1.3.6