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

Generated on Sun Nov 7 19:06:51 2004 for Mango by doxygen 1.3.6