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

Generated on Sat Apr 9 20:11:26 2005 for Mango by doxygen 1.3.6