1 /*
2 ** SIMPLE HTTP COOKIE FILTER HANDLER
3 **
4 ** (c) COPYRIGHT MIT 1999.
5 ** Please first read the full copyright statement in the file COPYRIGH.
6 ** @(#) $Id$
7 **
8 ** Written based on Jim Raven's code sent to the <www-lib@w3.org> list
9 **
10 ** History:
11 ** JR: Sent code to the list
12 ** HFN: Made a libwww module
13 **
14 */
15
16 /* Library include files */
17 #include "wwwsys.h"
18 #include "WWWUtil.h"
19 #include "WWWCore.h"
20 #include "WWWHTTP.h"
21 #include "WWWMIME.h"
22 #include "HTCookie.h" /* Implemented here */
23
24 /* Interface to persistent cookie jar */
25 PRIVATE HTCookieSetCallback * SetCookie = NULL;
26 PRIVATE void * SetCookieContext = NULL;
27
28 PRIVATE HTCookieFindCallback * FindCookie = NULL;
29 PRIVATE void * FindCookieContext = NULL;
30
31 /* Are cookies enabled */
32 PRIVATE BOOL baking_cookies = NO;
33
34 /* Our cookies */
35 struct _HTCookie {
36 char * name;
37 char * value;
38 char * domain;
39 char * path;
40 time_t expiration;
41 BOOL secure;
42 };
43
44 /* Hold all cookies found for a single request */
45 typedef struct _HTCookieHolder {
46 HTRequest * request;
47 HTList * cookies;
48 } HTCookieHolder;
49
50 /* List of current cookie holders */
51 PRIVATE HTList * cookie_holder = NULL;
52
53 /* What should we do with cookies? */
54 PRIVATE HTCookieMode CookieMode = HT_COOKIE_PROMPT | HT_COOKIE_ACCEPT | HT_COOKIE_SEND;
55
56 /* ------------------------------------------------------------------------- */
57
HTCookie_new(void)58 PRIVATE HTCookie * HTCookie_new (void)
59 {
60 HTCookie * me = NULL;
61 if ((me = (HTCookie *) HT_CALLOC(1, sizeof(HTCookie))) == NULL)
62 HT_OUTOFMEM("HTCookie_new");
63 return me;
64 }
65
HTCookie_delete(HTCookie * me)66 PRIVATE BOOL HTCookie_delete (HTCookie * me)
67 {
68 if (me) {
69 HT_FREE(me->name);
70 HT_FREE(me->value);
71 HT_FREE(me->domain);
72 HT_FREE(me->path);
73 HT_FREE(me);
74 return YES;
75 }
76 return NO;
77 }
78
HTCookie_setName(HTCookie * me,const char * name)79 PUBLIC BOOL HTCookie_setName (HTCookie * me, const char * name)
80 {
81 if (me && name) {
82 me->name = StrAllocCopy(me->name, name);
83 return YES;
84 }
85 return NO;
86 }
87
HTCookie_name(HTCookie * me)88 PUBLIC char * HTCookie_name (HTCookie * me)
89 {
90 return me ? me->name : NULL;
91 }
92
HTCookie_setValue(HTCookie * me,const char * value)93 PUBLIC BOOL HTCookie_setValue (HTCookie * me, const char * value)
94 {
95 if (me && value) {
96 me->value = StrAllocCopy(me->value, value);
97 return YES;
98 }
99 return NO;
100 }
101
HTCookie_value(HTCookie * me)102 PUBLIC char * HTCookie_value (HTCookie * me)
103 {
104 return me ? me->value : NULL;
105 }
106
HTCookie_setDomain(HTCookie * me,const char * domain)107 PUBLIC BOOL HTCookie_setDomain (HTCookie * me, const char * domain)
108 {
109 if (me && domain) {
110 me->domain = StrAllocCopy(me->domain, domain);
111 return YES;
112 }
113 return NO;
114 }
115
HTCookie_domain(HTCookie * me)116 PUBLIC char * HTCookie_domain (HTCookie * me)
117 {
118 return me ? me->domain : NULL;
119 }
120
HTCookie_setPath(HTCookie * me,const char * path)121 PUBLIC BOOL HTCookie_setPath (HTCookie * me, const char * path)
122 {
123 if (me && path) {
124 me->path = StrAllocCopy(me->path, path);
125 return YES;
126 }
127 return NO;
128 }
129
HTCookie_path(HTCookie * me)130 PUBLIC char * HTCookie_path (HTCookie * me)
131 {
132 return me ? me->path : NULL;
133 }
134
HTCookie_setExpiration(HTCookie * me,time_t expiration)135 PUBLIC time_t HTCookie_setExpiration (HTCookie * me, time_t expiration)
136 {
137 if (me) {
138 me->expiration = expiration;
139 return YES;
140 }
141 return NO;
142 }
143
HTCookie_expiration(HTCookie * me)144 PUBLIC time_t HTCookie_expiration (HTCookie * me)
145 {
146 return me ? me->expiration : -1;
147 }
148
HTCookie_setSecure(HTCookie * me,BOOL secure)149 PUBLIC time_t HTCookie_setSecure (HTCookie * me, BOOL secure)
150 {
151 if (me) {
152 me->secure = secure;
153 return YES;
154 }
155 return NO;
156 }
157
HTCookie_isSecure(HTCookie * me)158 PUBLIC BOOL HTCookie_isSecure (HTCookie * me)
159 {
160 return me ? me->secure : NO;
161 }
162
163 /* ------------------------------------------------------------------------- */
164
HTCookieHolder_addCookie(HTRequest * request,HTCookie * cookie)165 PRIVATE BOOL HTCookieHolder_addCookie (HTRequest * request, HTCookie * cookie)
166 {
167 if (request && cookie) {
168 HTList * cur = cookie_holder;
169 HTCookieHolder * pres = NULL;
170
171 /* Make sure that we have a cookie holder list */
172 if (!cookie_holder) cookie_holder = HTList_new();
173
174 /* See if we already have a cookie holder for this request */
175 while ((pres = (HTCookieHolder *) HTList_nextObject(cur))) {
176 if (pres->request == request) break;
177 }
178
179 /* If found then use existing cookie holder, otherwise create new one */
180 if (!pres) {
181 if ((pres = (HTCookieHolder *) HT_CALLOC(1, sizeof(HTCookieHolder))) == NULL)
182 HT_OUTOFMEM("HTCookieHolder_newCookie");
183 pres->request = request;
184 pres->cookies = HTList_new();
185
186 /* Add to cookie holder list */
187 HTList_addObject(cookie_holder, pres);
188 }
189
190 /* Now add the cookie */
191 HTList_addObject(pres->cookies, cookie);
192
193 return YES;
194 }
195 return NO;
196 }
197
HTCookieHolder_find(HTRequest * request)198 PRIVATE HTCookieHolder * HTCookieHolder_find (HTRequest * request)
199 {
200 if (request) {
201 HTList * cur = cookie_holder;
202 HTCookieHolder * pres = NULL;
203 while ((pres = (HTCookieHolder *) HTList_nextObject(cur))) {
204 if (pres->request == request) return pres;
205 }
206 }
207 return NULL;
208 }
209
HTCookieHolder_delete(HTCookieHolder * me)210 PRIVATE BOOL HTCookieHolder_delete (HTCookieHolder * me)
211 {
212 if (me) {
213 if (me->cookies) {
214 HTList * cookies = me->cookies;
215 HTCookie * cookie;
216 while ((cookie = (HTCookie *) HTList_nextObject(cookies)))
217 HTCookie_delete(cookie);
218 HTList_delete(me->cookies);
219 }
220 HTList_removeObject(cookie_holder, me);
221 HT_FREE(me);
222 return YES;
223 }
224 return NO;
225 }
226
HTCookieHolder_deleteAll(void)227 PRIVATE BOOL HTCookieHolder_deleteAll (void)
228 {
229 if (cookie_holder) {
230 HTList * cur = cookie_holder;
231 HTCookieHolder * pres = NULL;
232 while ((pres = (HTCookieHolder *) HTList_nextObject(cur))) {
233 HTCookieHolder_delete(pres);
234 }
235 HTList_delete(cookie_holder);
236 cookie_holder = NULL;
237 return YES;
238 }
239 return NO;
240 }
241
242 /* ------------------------------------------------------------------------- */
243
244 /*
245 ** Added By Jesse Morgan <jesse@jesterpm.net> on 2006-05-22
246 ** Splits a KEY=VALUE pair into a KEY and VALUE
247 */
HTCookie_splitPair(char * pair,char ** key,char ** value)248 PRIVATE int HTCookie_splitPair (char * pair, char ** key, char ** value)
249 {
250 char * index = strchr(pair, '=');
251
252 if (index == NULL) {
253 return HT_ERROR;
254 }
255
256 *key = pair;
257 *index = '\0';
258 *value = ++index;
259
260 return HT_OK;
261 }
262
263 /*
264 ** MIME header parser for the Set-Cookie header field. We parse the cookies
265 ** and create HTCookie objects and store them in the cookie holder so that
266 ** the cookie after filter can deal with them accordingly.
267 */
HTCookie_parseSetCookie(HTRequest * request,HTResponse * response,char * token,char * value)268 PRIVATE int HTCookie_parseSetCookie (HTRequest * request, HTResponse * response,
269 char * token, char * value)
270
271 {
272 char * cookie_name = NULL;
273 char * cookie_value = NULL;
274
275 if (HTCookie_splitPair(HTNextParam(&value), &cookie_name, &cookie_value) != HT_OK) {
276 return HT_ERROR; /* Malformed Cookie */
277 }
278
279 if (cookie_name && *cookie_name && cookie_value) {
280 HTCookie * cookie = HTCookie_new();
281 char * param_pair;
282
283 HTCookie_setName(cookie, cookie_name);
284 HTCookie_setValue(cookie, cookie_value);
285
286 /* Add the cookie to our holder */
287 HTCookieHolder_addCookie(request, cookie);
288
289 /* Parse cookie parameters */
290 while ((param_pair = HTNextParam(&value))) {
291 char * tok = NULL;
292 char * val = NULL;
293
294 if (HTCookie_splitPair(param_pair, &tok, &val) != HT_OK) {
295 return HT_ERROR; /* Malformed Cookie */
296 }
297
298 if (tok) {
299 if (!strcasecomp(tok, "expires") && val && *val) {
300 HTTRACE(STREAM_TRACE, "Cookie...... Expires `%s\'\n" _ val);
301 HTCookie_setExpiration(cookie, HTParseTime(val, NULL, YES));
302 } else if (!strcasecomp(tok, "domain") && val && *val) {
303 HTTRACE(STREAM_TRACE, "Cookie...... Domain `%s\'\n" _ val);
304 HTCookie_setDomain(cookie, val);
305 } else if (!strcasecomp(tok, "path") && val && *val) {
306 HTTRACE(STREAM_TRACE, "Cookie...... Path `%s\'\n" _ val);
307 HTCookie_setPath(cookie, val);
308 } else if (!strcasecomp(tok, "secure")) {
309 HTTRACE(STREAM_TRACE, "Cookie...... Secure `%s\'\n" _ val);
310 HTCookie_setSecure(cookie, YES);
311 } else
312 HTTRACE(STREAM_TRACE, "Cookie...... Unknown `%s\' with value `%s\'\n" _
313 tok _ val ? val : "<null>");
314 }
315 }
316 }
317 return HT_OK;
318 }
319
320 /*
321 ** Check whether the application provides us with a cookie or more.
322 */
HTCookie_beforeFilter(HTRequest * request,void * param,int mode)323 PRIVATE int HTCookie_beforeFilter (HTRequest * request, void * param, int mode)
324 {
325 if ((CookieMode & HT_COOKIE_SEND) && FindCookie) {
326 HTAssocList * cookies = (*FindCookie)(request, FindCookieContext);
327 if (cookies) {
328 HTChunk * cookie_header = HTChunk_new(64);
329 HTAssocList * cur = cookies;
330 HTAssoc * pres;
331 BOOL first=YES;
332 while ((pres = (HTAssoc *) HTAssocList_nextObject(cur))) {
333 if (!first) HTChunk_putc(cookie_header, ';');
334 HTChunk_puts(cookie_header, HTAssoc_name(pres));
335 HTChunk_putc(cookie_header, '=');
336 HTChunk_puts(cookie_header, HTAssoc_value(pres));
337 first = NO;
338 }
339 HTRequest_addExtraHeader(request, "Cookie", HTChunk_data(cookie_header));
340 HTChunk_delete(cookie_header);
341
342 /* Also delete the association list */
343 HTAssocList_delete(cookies);
344 }
345 }
346 return HT_OK;
347 }
348
349 /*
350 ** Check the response to see if we got a cookie or more.
351 ** If so then figure out what to do with it (prompt user, store, etc.)
352 */
HTCookie_afterFilter(HTRequest * request,HTResponse * response,void * param,int status)353 PRIVATE int HTCookie_afterFilter (HTRequest * request, HTResponse * response,
354 void * param, int status)
355 {
356 if ((CookieMode & HT_COOKIE_ACCEPT) && SetCookie) {
357 HTCookieHolder * holder = HTCookieHolder_find(request);
358 if (holder) {
359 HTList * cookies = holder->cookies;
360 HTCookie * pres;
361 while ((pres = (HTCookie *) HTAssocList_nextObject(cookies))) {
362
363 /* Should we check to see if hosts match? */
364 if (CookieMode & (HT_COOKIE_SAME_HOST|HT_COOKIE_SAME_DOMAIN)) {
365 char * cookie_host = HTCookie_domain(pres);
366 if (cookie_host) {
367 int res;
368 char * addr = HTAnchor_address((HTAnchor *) HTRequest_anchor(request));
369 char * host = HTParse(addr, "", PARSE_HOST);
370
371 if (CookieMode & HT_COOKIE_SAME_DOMAIN)
372 res = tailcasecomp(cookie_host, host);
373 else
374 res = strcasecomp(cookie_host, host);
375
376 if (res != 0) {
377 HTTRACE(APP_TRACE, "Cookie...... Host `%s\' doesn't match what is sent in cookie `%s\'\n" _ host _ cookie_host);
378 HT_FREE(addr);
379 continue;
380 }
381 HT_FREE(addr);
382 }
383 }
384
385 /* Should we prompt the user? */
386 if (CookieMode & HT_COOKIE_PROMPT) {
387 HTAlertCallback * prompt = HTAlert_find(HT_A_CONFIRM);
388 if (prompt) {
389 if ((*prompt)(request, HT_A_CONFIRM, HT_MSG_ACCEPT_COOKIE,
390 NULL, NULL, NULL) != YES)
391 continue;
392 } else
393 continue;
394 }
395
396 /* Call the application with our new cookie */
397 (*SetCookie)(request, pres, SetCookieContext);
398 }
399
400 /* Delete cookie holder */
401 HTCookieHolder_delete(holder);
402 }
403 }
404 return HT_OK;
405 }
406
407 /* ------------------------------------------------------------------------- */
408
409 /*
410 ** Start and stop the cookie engine
411 */
HTCookie_init(void)412 PUBLIC BOOL HTCookie_init (void)
413 {
414 if (!baking_cookies) {
415
416 /* Register the SetCookie header parser */
417 HTHeader_addParser("Set-Cookie", NO, HTCookie_parseSetCookie);
418
419 /* Register the cookie before and after filters */
420 HTNet_addBefore(HTCookie_beforeFilter, "http://*", NULL, HT_FILTER_MIDDLE);
421 HTNet_addAfter(HTCookie_afterFilter, "http://*", NULL, HT_ALL, HT_FILTER_MIDDLE);
422
423 baking_cookies = YES;
424 return YES;
425 }
426 return NO;
427 }
428
HTCookie_terminate(void)429 PUBLIC BOOL HTCookie_terminate (void)
430 {
431 /* Unregister Set-Cookie header parser */
432 HTHeader_deleteParser("Set-Cookie");
433
434 /* Unregister the cookie before and after filters */
435 HTNet_deleteBefore(HTCookie_beforeFilter);
436 HTNet_deleteAfter(HTCookie_afterFilter);
437
438 /* Delete all pending cookies */
439 HTCookieHolder_deleteAll();
440
441 baking_cookies = NO;
442 return YES;
443 }
444
HTCookie_setCookieMode(HTCookieMode mode)445 PUBLIC BOOL HTCookie_setCookieMode (HTCookieMode mode)
446 {
447 CookieMode = mode;
448 return YES;
449 }
450
HTCookie_cookieMode(void)451 PUBLIC HTCookieMode HTCookie_cookieMode (void)
452 {
453 return CookieMode;
454 }
455
456 /*
457 ** Callbacks can be used by the application to set and retrieve cookies
458 ** from a persistent cookie jar as libwww doesn't provide a persistent
459 ** storage for this kind of thing and they probably should be secured
460 ** anyway.
461 */
HTCookie_setCallbacks(HTCookieSetCallback * setCookie,void * setCookieContext,HTCookieFindCallback * findCookie,void * findCookieContext)462 PUBLIC BOOL HTCookie_setCallbacks (HTCookieSetCallback * setCookie,
463 void * setCookieContext,
464 HTCookieFindCallback * findCookie,
465 void * findCookieContext)
466 {
467 if (setCookie && findCookie) {
468 HTTRACE(APP_TRACE, "Cookie...... Registering cookie callbacks\n");
469 SetCookie = setCookie;
470 SetCookieContext = setCookieContext;
471
472 FindCookie = findCookie;
473 FindCookieContext = findCookieContext;
474 return YES;
475 }
476 return NO;
477 }
478
HTCookie_deleteCallbacks(void)479 PUBLIC BOOL HTCookie_deleteCallbacks (void)
480 {
481 HTTRACE(APP_TRACE, "Cookie...... Unregistering cookie callbacks\n");
482 SetCookie = NULL;
483 SetCookieContext = NULL;
484 FindCookie = NULL;
485 FindCookieContext = NULL;
486 return YES;
487 }
488