1   package org.mortbay.jetty;
2   import javax.servlet.http.Cookie;
3   
4   import org.mortbay.log.Log;
5   import org.mortbay.util.LazyList;
6   import org.mortbay.util.URIUtil;
7   
8   
9   /* ------------------------------------------------------------ */
10  /** Cookie parser
11   * <p>Optimized stateful cookie parser.  Cookies fields are added with the
12   * {@link #addCookieField(String)} method and parsed on the next subsequent
13   * call to {@link #getCookies()}.
14   * If the added fields are identical to those last added (as strings), then the 
15   * cookies are not re parsed.
16   * @author gregw
17   *
18   */
19  public class CookieCutter
20  {
21      private static final byte STATE_DELIMITER = 1;
22      private static final byte STATE_NAME = 2;
23      private static final byte STATE_VALUE = 4;
24      private static final byte STATE_QUOTED_VALUE = 8;
25      private static final byte STATE_UNQUOTED_VALUE = 16;
26  
27      private Cookie[] _cookies;
28      private String[] _fields;
29      int _added=0;
30      boolean _dirty;
31   
32      
33      public Cookie[] getCookies()
34      {
35          if (_added>0) 
36          {
37              if (!_dirty && _added==_fields.length)
38              {
39                  // same cookies as last time!
40                  _added=0;
41                  return _cookies;
42              }
43              
44              parseFields();
45          }
46          return _cookies;
47      }
48      
49      public void setCookies(Cookie[] cookies)
50      {
51          _dirty=false;
52          _added=0;
53          _cookies=cookies;
54      }
55      
56      public void reset()
57      {
58          _fields=null;
59          _cookies=null;
60      }
61      
62      public void addCookieField(String f)
63      {
64          if (!_dirty &&
65              _fields!=null && 
66              _fields.length>_added &&
67              _fields[_added].equals(f))
68          {
69              _added++;
70              return;
71          }
72          
73          if (_dirty)
74          {
75              _added++;
76              _fields=(String[])LazyList.addToArray(_fields,f,String.class);
77          }
78          else
79          {
80              _dirty=true;
81              if (_added>0)
82              {
83                  String[] fields=new String[_added+1];
84                  System.arraycopy(_fields,0,fields,0,_added);
85                  fields[_added++]=f;
86                  _fields=fields;
87              }
88              else
89              {
90                  _fields = new String[]{f};
91                  _added=1;
92              }
93              
94          }
95      }
96      
97      protected void parseFields()
98      {
99          Object cookies = null;
100 
101         int version = 0;
102 
103         // For each cookie field
104         for (int f=0;f<_added;f++)
105         {
106             String hdr = _fields[f];
107             
108             // Parse the header
109             String name = null;
110             String value = null;
111 
112             Cookie cookie = null;
113 
114             byte state = STATE_NAME;
115             for (int i = 0, tokenstart = 0, length = hdr.length(); i < length; i++)
116             {
117                 char c = hdr.charAt(i);
118                 switch (c)
119                 {
120                     case ',':
121                     case ';':
122                         switch (state)
123                         {
124                             case STATE_DELIMITER:
125                                 state = STATE_NAME;
126                                 tokenstart = i + 1;
127                                 break;
128                             case STATE_UNQUOTED_VALUE:
129                                 state = STATE_NAME;
130                                 // TODO remove this old style jetty cookie support (encoding)
131                                 value = URIUtil.decodePath(hdr.substring(tokenstart, i).trim());
132                                 tokenstart = i + 1;
133                                 break;
134                             case STATE_NAME:
135                                 name = hdr.substring(tokenstart, i);
136                                 value = "";
137                                 tokenstart = i + 1;
138                                 break;
139                             case STATE_VALUE:
140                                 state = STATE_NAME;
141                                 value = "";
142                                 tokenstart = i + 1;
143                                 break;
144                         }
145                         break;
146                     case '=':
147                         switch (state)
148                         {
149                             case STATE_NAME:
150                                 state = STATE_VALUE;
151                                 name = hdr.substring(tokenstart, i);
152                                 tokenstart = i + 1;
153                                 break;
154                             case STATE_VALUE:
155                                 state = STATE_UNQUOTED_VALUE;
156                                 tokenstart = i;
157                                 break;
158                         }
159                         break;
160                     case '"':
161                         switch (state)
162                         {
163                             case STATE_VALUE:
164                                 state = STATE_QUOTED_VALUE;
165                                 tokenstart = i + 1;
166                                 break;
167                             case STATE_QUOTED_VALUE:
168                                 state = STATE_DELIMITER;
169                                 value = hdr.substring(tokenstart, i);
170                                 break;
171                         }
172                         break;
173                     case ' ':
174                     case '\t':
175                         break;
176                     default:
177                         switch (state)
178                         {
179                             case STATE_VALUE:
180                                 state = STATE_UNQUOTED_VALUE;
181                                 tokenstart = i;
182                                 break;
183                             case STATE_DELIMITER:
184                                 state = STATE_NAME;
185                                 tokenstart = i;
186                                 break;
187                         }
188                 }
189 
190                 if (i + 1 == length)
191                 {
192                     switch (state)
193                     {
194                         case STATE_UNQUOTED_VALUE:
195                             // TODO remove this old style jetty cookie support (encoding)
196                             value = URIUtil.decodePath(hdr.substring(tokenstart).trim());
197                             break;
198                         case STATE_NAME:
199                             name = hdr.substring(tokenstart);
200                             value = "";
201                             break;
202                         case STATE_VALUE:
203                             value = "";
204                             break;
205                     }
206                 }
207 
208                 if (name != null && value != null)
209                 {
210                     name = name.trim();
211 
212                     try
213                     {
214                         if (name.startsWith("$"))
215                         {
216                             String lowercaseName = name.toLowerCase();
217                             if ("$path".equals(lowercaseName))
218                             {
219                                 cookie.setPath(value);
220                             }
221                             else if ("$domain".equals(lowercaseName))
222                             {
223                                 cookie.setDomain(value);
224                             }
225                             else if ("$version".equals(lowercaseName))
226                             {
227                                 version = Integer.parseInt(value);
228                             }
229                         }
230                         else
231                         {
232                             cookie = new Cookie(name, value);
233 
234                             if (version > 0)
235                             {
236                                 cookie.setVersion(version);
237                             }
238 
239                             cookies = LazyList.add(cookies, cookie);
240                         }
241                     }
242                     catch (Exception e)
243                     {
244                         Log.ignore(e);
245                     }
246 
247                     name = null;
248                     value = null;
249                 }
250             }
251         }
252 
253         int l = LazyList.size(cookies);
254         if (l>0)
255         {
256             if (_cookies != null && _cookies.length == l) 
257             {
258                 for (int i = 0; i < l; i++)
259                     _cookies[i] = (Cookie) LazyList.get(cookies, i);
260             }
261             else
262                 _cookies = (Cookie[]) LazyList.toArray(cookies,Cookie.class);
263         }
264         
265         _added=0;
266         _dirty=false;
267         
268     }
269     
270 }