1
2 /* Web Polygraph http://www.web-polygraph.org/
3 * Copyright 2003-2011 The Measurement Factory
4 * Licensed under the Apache License, Version 2.0 */
5
6 #include "base/polygraph.h"
7
8 #include "runtime/HttpCookies.h"
9 #include "runtime/HttpDate.h"
10 #include "runtime/LogComment.h"
11 #include "xstd/gadgets.h"
12 #include "xstd/Clock.h"
13 #include "xstd/PrefixIdentifier.h"
14
15
16 /* HttpCookie */
17
18 ObjFarm<HttpCookie> HttpCookie::TheCookieFarm;
19
HttpCookie()20 HttpCookie::HttpCookie() {
21 reset();
22 }
23
reset()24 void HttpCookie::reset() {
25 theName = theData = String();
26 theExpires = Time();
27 isDiscardable = false;
28 }
29
set(const String & aData,const int nameEnd)30 void HttpCookie::set(const String &aData, const int nameEnd) {
31 Assert(!theData && aData);
32 Assert(!theName);
33 Assert(nameEnd > 0 && nameEnd != String::npos);
34 theData = aData;
35 theName = theData(0, nameEnd);
36 }
37
maxAge(const int delta)38 void HttpCookie::maxAge(const int delta) {
39 theExpires = TheClock.time() + Time::Sec(delta);
40 }
41
expired() const42 bool HttpCookie::expired() const {
43 return theExpires != Time() && theExpires <= TheClock.time();
44 }
45
discardable() const46 bool HttpCookie::discardable() const {
47 return isDiscardable || theExpires == Time() || expired();
48 }
49
Configure()50 void HttpCookie::Configure() {
51 TheCookieFarm.limit(1024); // magic, no good way to estimate
52 }
53
Put(HttpCookie * const cookie)54 void HttpCookie::Put(HttpCookie *const cookie) {
55 TheCookieFarm.put(cookie);
56 }
57
Parse(const char * buf,const char * eoh)58 HttpCookie *HttpCookie::Parse(const char *buf, const char *eoh) {
59 HttpCookie *cookie(0);
60 // skip spaces and newlines at the end
61 while (buf < eoh && isspace(eoh[-1])) --eoh;
62 // loop through all name=value pairs
63 // the first pair is stored as opaque cookie data
64 while (buf < eoh) {
65 const char *end(eoh);
66 const char *const semicolon = StrBoundChr(buf, ';', end);
67 if (semicolon) {
68 const char *quote = StrBoundChr(buf, '"', end);
69 if (!quote || semicolon < quote)
70 end = semicolon;
71 else {
72 quote = StrBoundChr(quote + 1, '"', end);
73 if (quote)
74 end = quote + 1;
75 }
76 }
77
78 if (!cookie) {
79 const String data(buf, end - buf);
80 const int nameEnd(data.find('='));
81 if (Should(nameEnd != String::npos)) {
82 cookie = TheCookieFarm.get();
83 cookie->set(data, nameEnd);
84 } else
85 break;
86 } else
87 if (!ParseParameter(buf, end, *cookie)) {
88 Put(cookie);
89 cookie = 0;
90 break;
91 }
92
93 // skip until the next name=value pair begins
94 buf = end;
95 while (buf < eoh && (isspace(*buf) || *buf == ';')) ++buf;
96 }
97
98 return cookie;
99 }
100
ParseParameter(const char * const buf,const char * const end,HttpCookie & cookie)101 bool HttpCookie::ParseParameter(const char *const buf, const char *const end, HttpCookie &cookie) {
102 enum {
103 pUnknown,
104 pDiscard,
105 pExpires,
106 pMaxAge
107 };
108 static const String Discard("Discard");
109 static const String Expires("expires=");
110 static const String MaxAge("Max-Age=");
111 static PrefixIdentifier sidf;
112 if (!sidf.count()) {
113 sidf.add(Discard, pDiscard);
114 sidf.add(Expires, pExpires);
115 sidf.add(MaxAge, pMaxAge);
116 sidf.optimize();
117 }
118 bool result(true);
119 switch (sidf.lookup(buf, end - buf)) {
120 case pDiscard:
121 if (buf + sizeof(Discard) == end)
122 cookie.discard();
123 else
124 result = true;
125 break;
126 case pExpires: {
127 const Time date(HttpDateParse(buf + Expires.len(), end - buf - Expires.len()));
128 if (Should(date >= 0))
129 cookie.expires(date);
130 else
131 result = true;
132 break;
133 }
134 case pMaxAge: {
135 int delta;
136 const char *p;
137 if (Should(isInt(buf + MaxAge.len(), delta, &p, 10) &&
138 p == end))
139 cookie.maxAge(delta);
140 else
141 result = true;
142 break;
143 }
144 case pUnknown:
145 // ignore other parameters
146 break;
147 }
148 return result;
149 }
150
151
152 /* HttpCookies */
153
154 Counter HttpCookies::TheTotalCount(0);
155 Counter HttpCookies::ThePurgedFreshCount(0);
156 Counter HttpCookies::ThePurgedStaleCount(0);
157 Counter HttpCookies::TheUpdatedCount(0);
158
159
HttpCookies()160 HttpCookies::HttpCookies(): theLimit(0) {
161 }
162
~HttpCookies()163 HttpCookies::~HttpCookies() {
164 for (Cache::const_iterator i = theCookies.begin();
165 i != theCookies.end();
166 ++i)
167 HttpCookie::Put(*i);
168 }
169
keepLimit(const unsigned limit)170 void HttpCookies::keepLimit(const unsigned limit) {
171 Assert(!theLimit && limit);
172 theLimit = limit;
173 while (theCookies.size() > theLimit)
174 purgeAt(theCookies.end());
175 }
176
add(HttpCookie * const cookie)177 void HttpCookies::add(HttpCookie *const cookie) {
178 Assert(cookie);
179
180 const String name(cookie->name());
181 if (cookie->expired()) {
182 NameMap::iterator i(theNameMap.find(name));
183 if (i != theNameMap.end()) {
184 HttpCookie::Put(*i->second);
185 // so that purge stats use the new value
186 *i->second = cookie;
187 purgeAt(i->second, false);
188 theNameMap.erase(i);
189 }
190 } else {
191 theCookies.push_front(cookie);
192 std::pair<NameMap::iterator, bool> result =
193 theNameMap.insert(std::make_pair(name, theCookies.begin()));
194 if (result.second) {
195 // new cookie was added
196 if (theLimit && theCookies.size() > theLimit)
197 purgeAt(theCookies.end());
198 reportCached(*cookie);
199 } else {
200 // existing cookie updated
201 // result.first->second is the iterator pointing
202 // to the cookie being updated
203 // purge the old cookie from the cache
204 purgeAt(result.first->second, false, false);
205 // put reference to the new cookie into the name map
206 result.first->second = theCookies.begin();
207 reportUpdated(*cookie);
208 }
209 }
210 }
211
count() const212 int HttpCookies::count() const {
213 return theCookies.size();
214 }
215
first()216 const HttpCookie *HttpCookies::first() {
217 theIter = theCookies.begin();
218 return sync();
219 }
220
next()221 const HttpCookie *HttpCookies::next() {
222 Should(theIter != theCookies.end());
223 ++theIter;
224 return sync();
225 }
226
purgeDiscardable()227 void HttpCookies::purgeDiscardable() {
228 theIter = theCookies.begin();
229 while (theIter != theCookies.end()) {
230 if ((*theIter)->discardable())
231 purgeAt(theIter++);
232 else
233 ++theIter;
234 }
235 }
236
purgeAt(const Cache::iterator & i,const bool purgeName,const bool doReport)237 void HttpCookies::purgeAt(const Cache::iterator &i, const bool purgeName, const bool doReport) {
238 HttpCookie *const cookie(*i);
239 if (doReport)
240 reportPurged(*cookie);
241 if (purgeName)
242 theNameMap.erase(cookie->name());
243 theCookies.erase(i);
244 HttpCookie::Put(cookie);
245 }
246
sync()247 const HttpCookie *HttpCookies::sync() {
248 const HttpCookie *cookie(0);
249 while (!cookie && theIter != theCookies.end()) {
250 cookie = *theIter;
251 if (cookie->expired()) {
252 purgeAt(theIter++);
253 cookie = 0;
254 }
255 }
256 return cookie;
257 }
258
reportCached(const HttpCookie & cookie) const259 void HttpCookies::reportCached(const HttpCookie &cookie) const {
260 ++TheTotalCount;
261 static bool reported(false);
262 if (!reported) {
263 printMessage("the first cookie cached", cookie);
264 reported = true;
265 }
266 }
267
reportUpdated(const HttpCookie & cookie) const268 void HttpCookies::reportUpdated(const HttpCookie &cookie) const {
269 static bool reported(false);
270 if (!reported) {
271 printMessage("the first cookie updated", cookie);
272 reported = true;
273 }
274 ++TheUpdatedCount;
275 }
276
reportPurged(const HttpCookie & cookie) const277 void HttpCookies::reportPurged(const HttpCookie &cookie) const {
278 if (cookie.expired()) {
279 static bool reported(false);
280 if (!reported) {
281 printMessage("the first stale cookie evicted", cookie);
282 reported = true;
283 }
284 ++ThePurgedStaleCount;
285 } else {
286 static bool reported(false);
287 if (!reported) {
288 printMessage("the first fresh cookie evicted", cookie);
289 reported = true;
290 }
291 ++ThePurgedFreshCount;
292 }
293 if (Should(TheTotalCount > 0))
294 --TheTotalCount;
295 }
296
printMessage(const char * const message,const HttpCookie & cookie) const297 void HttpCookies::printMessage(const char *const message, const HttpCookie &cookie) const {
298 Comment(7)
299 << "fyi: " << message
300 << ", cookie: " << cookie.data()
301 << ", number of cookies: "
302 << theCookies.size() << '/' << TheTotalCount
303 << endl << endc
304 ;
305 }
306