3
* $Id: ESIVarState.cc,v 1.10 2007/05/29 13:31:37 amosjeffries Exp $
5
* DEBUG: section 86 ESI processing
6
* AUTHOR: Robert Collins
8
* SQUID Web Proxy Cache http://www.squid-cache.org/
9
* ----------------------------------------------------------
11
* Squid is the result of efforts by numerous individuals from
12
* the Internet community; see the CONTRIBUTORS file for full
13
* details. Many organizations have provided support for Squid's
14
* development; see the SPONSORS file for full details. Squid is
15
* Copyrighted (C) 2001 by the Regents of the University of
16
* California; see the COPYRIGHT file for full details. Squid
17
* incorporates software developed and/or copyrighted by other
18
* sources; see the CREDITS file for full details.
20
* This program is free software; you can redistribute it and/or modify
21
* it under the terms of the GNU General Public License as published by
22
* the Free Software Foundation; either version 2 of the License, or
23
* (at your option) any later version.
25
* This program is distributed in the hope that it will be useful,
26
; but WITHOUT ANY WARRANTY; without even the implied warranty of
27
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
* GNU General Public License for more details.
30
* You should have received a copy of the GNU General Public License
31
* along with this program; if not, write to the Free Software
32
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
34
* Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
38
#include "ESIVarState.h"
39
#include "HttpReply.h"
41
CBDATA_TYPE (ESIVarState);
44
char const *ESIVariableUserAgent::esiUserOs[]=
52
char const * esiBrowsers[]=
60
ESIVarState::Variable::eval (ESIVarState &state, char const *subref, char const *found_default) const
62
/* No-op. We swallow it */
65
ESISegment::ListAppend (state.getOutput(), found_default, strlen (found_default));
69
ESIVarState::hostUsed()
75
ESIVarState::cookieUsed()
81
ESIVarState::languageUsed()
87
ESIVarState::refererUsed()
93
ESIVarState::useragentUsed()
104
ESISegment::Pointer &
105
ESIVarState::getOutput()
111
ESIVariableQuery::queryString() const
116
struct _query_elem const *
117
ESIVariableQuery::queryVector() const
123
ESIVariableQuery::queryElements() const
125
return query_elements;
129
ESIVarState::feedData (const char *buf, size_t len)
131
/* TODO: if needed - tune to skip segment iteration */
132
debugs (86,6, "esiVarState::feedData: accepting " << len << " bytes");
133
ESISegment::ListAppend (input, buf, len);
137
ESIVarState::extractList()
140
ESISegment::Pointer rv = output;
142
debugs(86, 6, "ESIVarStateExtractList: Extracted list");
147
ESIVarState::extractChar ()
150
fatal ("Attempt to extract variable state with no data fed in \n");
154
char *rv = output->listToChar();
156
ESISegmentFreeList (output);
158
debugs(86, 6, "ESIVarStateExtractList: Extracted char");
165
esiVarStateFree (void *data)
167
ESIVarState *thisNode = (ESIVarState*)data;
168
thisNode->freeResources();
171
ESIVarState::~ESIVarState()
175
while (variablesForCleanup.size())
176
delete variablesForCleanup.pop_back();
178
delete defaultVariable;
182
ESIVarState::freeResources()
185
ESISegmentFreeList (output);
190
ESIVarState::operator new(size_t byteCount)
192
assert (byteCount == sizeof (ESIVarState));
194
CBDATA_INIT_TYPE_FREECB(ESIVarState, esiVarStateFree);
195
rv = (void *)cbdataAlloc (ESIVarState);
200
ESIVarState::operator delete (void *address)
202
cbdataFree (address);
206
ESIVariableUserAgent::getProductVersion (char const *s)
215
len = strcspn (t, " \r\n()<>@,;:\\\"/[]?={}");
217
return xstrndup (t, len + 1);
220
ESIVariableQuery::ESIVariableQuery(char const *uri) : query (NULL), query_sz (0), query_elements (0), query_string (NULL)
222
/* Count off the query elements */
223
char const *query_start = strchr (uri, '?');
225
if (query_start && query_start[1] != '\0' ) {
227
query_string = xstrdup (query_start + 1);
229
char const *query_pos = query_start + 1;
231
while ((query_pos = strchr (query_pos, '&'))) {
236
query = (_query_elem *)memReallocBuf(query, query_elements * sizeof (struct _query_elem),
238
query_pos = query_start + 1;
242
char const *next = strchr (query_pos, '&');
243
char const *div = strchr (query_pos, '=');
248
assert (n < query_elements);
253
if (!(div - query_pos + 1))
254
/* zero length between & and = or & and & */
257
query[n].var = xstrndup (query_pos, div - query_pos + 1) ;
260
query[n].val = xstrdup ("");
262
query[n].val = xstrndup (div + 1, next - div - 1);
269
query_string = xstrdup ("");
274
debugs(86, 6, "esiVarStateNew: Parsed Query string: '" << uri << "'");
276
while (n < query_elements) {
277
debugs(86, 6, "esiVarStateNew: Parsed Query element " << n + 1 << " '" << query[n].var << "'='" << query[n].val << "'");
283
ESIVariableQuery::~ESIVariableQuery()
288
for (i = 0; i < query_elements; ++i) {
289
safe_free(query[i].var);
290
safe_free(query[i].val);
293
memFreeBuf (query_sz, query);
296
safe_free (query_string);
299
ESIVarState::ESIVarState (HttpHeader const *aHeader, char const *uri)
300
: output (NULL), hdr(hoReply)
302
/* TODO: only grab the needed headers */
303
/* Note that as we pass these through to included requests, we
304
* cannot trim them */
307
/* populate our variables trie with the available variables.
308
* Additional ones can be added during the parsing.
309
* If there is a lazy evaluation approach to this, consider it!
311
defaultVariable = new Variable;
312
addVariable ("HTTP_ACCEPT_LANGUAGE", 20, new ESIVariableLanguage);
313
addVariable ("HTTP_COOKIE", 11, new ESIVariableCookie);
314
addVariable ("HTTP_HOST", 9, new ESIVariableHost);
315
addVariable ("HTTP_REFERER", 12, new ESIVariableReferer);
316
addVariable ("HTTP_USER_AGENT", 15, new ESIVariableUserAgent(*this));
317
addVariable ("QUERY_STRING", 12, new ESIVariableQuery(uri));
321
ESIVarState::removeVariable (String const &name)
323
Variable *candidate = static_cast <Variable *>(variables.find (name.buf(), name.size()));
327
/* Note - this involves:
328
* extend libTrie to have a remove() call.
329
* delete from the vector.
336
ESIVarState::addVariable(char const *name, size_t len, Variable *aVariable)
339
temp.limitInit (name, len);
340
removeVariable (temp);
341
variables.add(name, len, aVariable);
342
variablesForCleanup.push_back(aVariable);
345
ESIVariableUserAgent::~ESIVariableUserAgent()
347
safe_free (browserversion);
350
ESIVariableUserAgent::ESIVariableUserAgent(ESIVarState &state)
353
* User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705) */
354
/* Grr this Node is painful - RFC 2616 specifies that 'by convention' the tokens are in order of importance
355
* in identifying the product. According to the RFC the above should be interpreted as:
356
* Product - Mozilla version 4.0
357
* in comments - compatible; .... 3705
359
* Useing the RFC a more appropriate header would be
360
* User-Agent: MSIE/6.0 Mozilla/4.0 Windows-NT/5.1 .NET-CLR/1.0.3705
361
* or something similar.
363
* Because we can't parse under those rules and get real-world useful answers, we follow the following
365
* if the string Windows appears in the header, the OS is WIN.
366
* If the string Mac appears in the header, the OS is MAC.
367
* If the string nix, or BSD appears in the header, the OS is UNIX.
368
* If the string MSIE appears in the header, the BROWSER is MSIE, and the version is the string from
369
* MSIE<sp> to the first ;, or end of string.
370
* If the String MSIE does not appear in the header, and MOZILLA does, we use the version from the
372
* if MOZILLA doesn't appear, the browser is set to OTHER.
373
* In future, this may be better implemented as a regexp.
376
if (state.header().has(HDR_USER_AGENT)) {
377
char const *s = state.header().getStr(HDR_USER_AGENT);
378
UserOs = identifyOs(s);
381
/* Now the browser and version */
383
if ((t = strstr (s, "MSIE"))) {
384
browser = ESI_BROWSER_MSIE;
388
browserversion = xstrdup ("");
393
browserversion = xstrdup (t + 1);
395
browserversion = xstrndup (t + 1, t1-t);
397
} else if (strstr (s, "Mozilla")) {
398
browser = ESI_BROWSER_MOZILLA;
399
browserversion = getProductVersion(s);
401
browser = ESI_BROWSER_OTHER;
402
browserversion = getProductVersion(s);
405
UserOs = ESI_OS_OTHER;
406
browser = ESI_BROWSER_OTHER;
407
browserversion = xstrdup ("");
411
ESIVariableUserAgent::esiUserOs_t
412
ESIVariableUserAgent::identifyOs(char const *s) const
417
if (strstr (s, "Windows"))
419
else if (strstr (s, "Mac"))
421
else if (strstr (s, "nix") || strstr (s, "BSD"))
428
ESIVariableCookie::eval (ESIVarState &state, char const *subref, char const *found_default) const
430
const char *s = NULL;
433
if (state.header().has(HDR_COOKIE)) {
435
s = state.header().getStr (HDR_COOKIE);
437
String S = state.header().getListMember (HDR_COOKIE, subref, ';');
440
ESISegment::ListAppend (state.getOutput(), S.buf(), S.size());
441
else if (found_default)
442
ESISegment::ListAppend (state.getOutput(), found_default, strlen (found_default));
448
ESISegment::ListAppend (state.getOutput(), s, strlen (s));
452
ESIVariableHost::eval (ESIVarState &state, char const *subref, char const *found_default) const
454
const char *s = NULL;
457
if (!subref && state.header().has(HDR_HOST)) {
458
s = state.header().getStr (HDR_HOST);
462
ESISegment::ListAppend (state.getOutput(), s, strlen (s));
466
ESIVariableLanguage::eval (ESIVarState &state, char const *subref, char const *found_default) const
468
char const *s = NULL;
469
state.languageUsed();
471
if (state.header().has(HDR_ACCEPT_LANGUAGE)) {
473
String S (state.header().getList (HDR_ACCEPT_LANGUAGE));
474
ESISegment::ListAppend (state.getOutput(), S.buf(), S.size());
476
if (state.header().hasListMember (HDR_ACCEPT_LANGUAGE, subref, ',')) {
482
ESISegment::ListAppend (state.getOutput(), s, strlen (s));
486
ESISegment::ListAppend (state.getOutput(), s, strlen (s));
491
ESIVariableQuery::eval (ESIVarState &state, char const *subref, char const *found_default) const
493
char const *s = NULL;
500
while (i < queryElements() && !s) {
501
if (!strcmp (subref, queryVector()[i].var))
502
s = queryVector()[i].val;
511
ESISegment::ListAppend (state.getOutput(), s, strlen (s));
515
ESIVariableReferer::eval (ESIVarState &state, char const *subref, char const *found_default) const
517
const char *s = NULL;
520
if (!subref && state.header().has(HDR_REFERER))
521
s = state.header().getStr (HDR_REFERER);
525
ESISegment::ListAppend (state.getOutput(), s, strlen (s));
529
ESIVariableUserAgent::eval (ESIVarState &state, char const *subref, char const *found_default) const
531
char const *s = NULL;
532
state.useragentUsed();
534
if (state.header().has(HDR_USER_AGENT)) {
536
s = state.header().getStr (HDR_USER_AGENT);
538
if (!strcmp (subref, "os")) {
539
s = esiUserOs[UserOs];
540
} else if (!strcmp (subref, "browser")) {
541
s = esiBrowsers[browser];
542
} else if (!strcmp (subref, "version")) {
543
s = browserVersion();
550
ESISegment::ListAppend (state.getOutput(), s, strlen (s));
553
/* thoughts on long term:
556
* hand off to handler.
557
* one handler for variables.
558
* one handler for each function.
561
class ESIVariableProcessor;
567
static ESIFunction *GetFunction (char const *symbol, ESIVariableProcessor &);
568
ESIFunction(ESIVariableProcessor &);
572
ESIVariableProcessor &processor;
576
ESIFunction::ESIFunction(ESIVariableProcessor &aProcessor) : processor(aProcessor)
580
ESIFunction::GetFunction(char const *symbol, ESIVariableProcessor &aProcessor)
583
return new ESIFunction(aProcessor);
588
class ESIVariableProcessor
592
ESIVariableProcessor(char *, ESISegment::Pointer &, Trie &, ESIVarState *);
593
~ESIVariableProcessor();
597
bool validChar (char c);
598
void eval (ESIVarState::Variable *var, char const *subref, char const *found_default );
600
void identifyFunction();
602
ESISegment::Pointer &output;
604
ESIVarState *varState;
612
ESIVarState::Variable *vartype;
613
ESIFunction *currentFunction;
617
ESIVariableProcessor::eval (ESIVarState::Variable *var, char const *subref, char const *found_default )
624
var->eval (*varState, subref, found_default);
628
ESIVariableProcessor::validChar (char c)
630
if (('A' <= c && c <= 'Z') ||
631
('a' <= c && c <= 'z') ||
632
'_' == c || '-' == c)
638
ESIVarState::Variable *
639
ESIVarState::GetVar(char const *symbol, int len)
643
void *result = variables.find (symbol, len);
646
return static_cast<Variable *>(result);
648
return defaultVariable;
654
char *string = input->listToChar();
655
ESISegmentFreeList (input);
656
ESIVariableProcessor theProcessor(string, output, variables, this);
661
#define LOOKFORSTART 0
662
ESIVariableProcessor::ESIVariableProcessor(char *aString, ESISegment::Pointer &aSegment, Trie &aTrie, ESIVarState *aState) :
663
string(aString), output (aSegment), variables(aTrie), varState (aState),
664
state(LOOKFORSTART), pos(0), var_pos(0), done_pos(0), found_subref (NULL),
665
found_default (NULL), currentFunction(NULL)
667
len = strlen (string);
668
vartype = varState->GetVar("",0);
675
/* because we are only used to process:
679
* buffering is ok - we won't delay the start of async activity, or
680
* of output data preparation
682
/* Should make these an enum or something...
685
ESIVariableProcessor::doFunction()
687
if (!currentFunction)
690
/* stay in here whilst operating */
691
while (pos < len && state)
694
case 2: /* looking for variable name */
696
if (!validChar(string[pos])) {
697
/* not a variable name char */
700
vartype = varState->GetVar (string + var_pos, pos - var_pos);
710
case 3: /* looking for variable subref, end bracket or default indicator */
712
if (string[pos] == ')') {
714
eval(vartype, found_subref, found_default);
716
safe_free(found_subref);
717
safe_free(found_default);
718
state = LOOKFORSTART;
719
} else if (!found_subref && !found_default && string[pos] == '{') {
720
debugs(86, 6, "ESIVarStateDoIt: Subref of some sort");
721
/* subreference of some sort */
722
/* look for the entry name */
725
} else if (!found_default && string[pos] == '|') {
726
debugs(86, 6, "esiVarStateDoIt: Default present");
727
/* extract default value */
731
/* unexpected char, not a variable after all */
732
debugs(86, 6, "esiVarStateDoIt: unexpected char after varname");
733
state = LOOKFORSTART;
739
case 4: /* looking for variable subref */
741
if (string[pos] == '}') {
743
found_subref = xstrndup (&string[var_pos], pos - var_pos + 1);
744
debugs(86, 6, "esiVarStateDoIt: found end of variable subref '" << found_subref << "'");
747
} else if (!validChar (string[pos])) {
748
debugs(86, 6, "esiVarStateDoIt: found invalid char in variable subref");
749
/* not a valid subref */
750
safe_free(found_subref);
751
state = LOOKFORSTART;
759
case 5: /* looking for a default value */
761
if (string[pos] == '\'') {
762
/* begins with a quote */
763
debugs(86, 6, "esiVarStateDoIt: found quoted default");
768
debugs(86, 6, "esiVarStateDoIt: found unquoted default");
775
case 6: /* looking for a quote terminate default value */
777
if (string[pos] == '\'') {
779
found_default = xstrndup (&string[var_pos], pos - var_pos + 1);
780
debugs(86, 6, "esiVarStateDoIt: found end of quoted default '" << found_default << "'");
787
case 7: /* looking for } terminate default value */
789
if (string[pos] == ')') {
790
/* end of default - end of variable*/
791
found_default = xstrndup (&string[var_pos], pos - var_pos + 1);
792
debugs(86, 6, "esiVarStateDoIt: found end of variable (w/ unquoted default) '" << found_default << "'");
793
eval(vartype,found_subref, found_default);
795
safe_free(found_default);
796
safe_free(found_subref);
797
state = LOOKFORSTART;
804
fatal("esiVarStateDoIt: unexpected state\n");
809
ESIVariableProcessor::identifyFunction()
811
delete currentFunction;
812
currentFunction = ESIFunction::GetFunction (&string[pos], *this);
814
if (!currentFunction) {
815
state = LOOKFORSTART;
817
state = 2; /* process a function */
818
/* advance past function name */
824
ESIVariableProcessor::doIt()
826
assert (output == NULL);
829
/* skipping pre-variables */
831
if (string[pos] != '$') {
835
/* extract known plain text */
836
ESISegment::ListAppend (output, string + done_pos, pos - done_pos);
848
/* pos-done_pos chars are ready to copy */
850
ESISegment::ListAppend (output, string+done_pos, pos - done_pos);
852
safe_free (found_default);
854
safe_free (found_subref);
857
ESIVariableProcessor::~ESIVariableProcessor()
859
delete currentFunction;
863
/* XXX FIXME: this should be comma delimited, no? */
865
ESIVarState::buildVary (HttpReply *rep)
871
strcat (tempstr, "Accept-Language ");
874
strcat (tempstr, "Cookie ");
877
strcat (tempstr, "Host ");
880
strcat (tempstr, "Referer ");
883
strcat (tempstr, "User-Agent ");
888
String strVary (rep->header.getList (HDR_VARY));
890
if (!strVary.size() || strVary.buf()[0] != '*') {
891
rep->header.putStr (HDR_VARY, tempstr);