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