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

Generated on Sun Mar 6 00:30:57 2005 for Mango by doxygen 1.3.6