]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/esi/VarState.cc
2 * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 86 ESI processing */
12 #include "esi/VarState.h"
13 #include "HttpReply.h"
16 CBDATA_TYPE (ESIVarState
);
19 char const *ESIVariableUserAgent::esiUserOs
[]= {
26 char const * esiBrowsers
[]= {"MSIE",
32 ESIVarState::Variable::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
34 /* No-op. We swallow it */
37 ESISegment::ListAppend (state
.getOutput(), found_default
, strlen (found_default
));
41 ESIVarState::hostUsed()
47 ESIVarState::cookieUsed()
53 ESIVarState::languageUsed()
59 ESIVarState::refererUsed()
65 ESIVarState::useragentUsed()
77 ESIVarState::getOutput()
83 ESIVariableQuery::queryString() const
88 struct _query_elem
const *
89 ESIVariableQuery::queryVector() const {
94 ESIVariableQuery::queryElements() const
96 return query_elements
;
100 ESIVarState::feedData (const char *buf
, size_t len
)
102 /* TODO: if needed - tune to skip segment iteration */
103 debugs (86,6, "esiVarState::feedData: accepting " << len
<< " bytes");
104 ESISegment::ListAppend (input
, buf
, len
);
108 ESIVarState::extractList()
111 ESISegment::Pointer rv
= output
;
113 debugs(86, 6, "ESIVarStateExtractList: Extracted list");
118 ESIVarState::extractChar ()
121 fatal ("Attempt to extract variable state with no data fed in \n");
125 char *rv
= output
->listToChar();
127 ESISegmentFreeList (output
);
129 debugs(86, 6, "ESIVarStateExtractList: Extracted char");
136 esiVarStateFree (void *data
)
138 ESIVarState
*thisNode
= (ESIVarState
*)data
;
139 thisNode
->freeResources();
142 ESIVarState::~ESIVarState()
146 while (!variablesForCleanup
.empty()) {
147 delete variablesForCleanup
.back();
148 variablesForCleanup
.pop_back();
151 delete defaultVariable
;
155 ESIVarState::freeResources()
158 ESISegmentFreeList (output
);
163 ESIVarState::operator new(size_t byteCount
)
165 assert (byteCount
== sizeof (ESIVarState
));
167 CBDATA_INIT_TYPE_FREECB(ESIVarState
, esiVarStateFree
);
168 rv
= (void *)cbdataAlloc (ESIVarState
);
173 ESIVarState::operator delete (void *address
)
175 cbdataFree (address
);
179 ESIVariableUserAgent::getProductVersion (char const *s
)
188 len
= strcspn(t
, " \r\n()<>@,;:\\\"/[]?={}");
190 return xstrndup(t
, len
+ 1);
193 ESIVariableQuery::ESIVariableQuery(char const *uri
) : query (NULL
), query_sz (0), query_elements (0), query_string (NULL
)
195 /* Count off the query elements */
196 char const *query_start
= strchr (uri
, '?');
198 if (query_start
&& query_start
[1] != '\0' ) {
200 query_string
= xstrdup(query_start
+ 1);
202 char const *query_pos
= query_start
+ 1;
204 while ((query_pos
= strchr(query_pos
, '&'))) {
209 query
= (_query_elem
*)memReallocBuf(query
, query_elements
* sizeof (struct _query_elem
),
211 query_pos
= query_start
+ 1;
215 char const *next
= strchr(query_pos
, '&');
216 char const *div
= strchr(query_pos
, '=');
221 assert (n
< query_elements
);
226 if (!(div
- query_pos
+ 1))
227 /* zero length between & and = or & and & */
230 query
[n
].var
= xstrndup(query_pos
, div
- query_pos
+ 1) ;
233 query
[n
].val
= xstrdup("");
235 query
[n
].val
= xstrndup(div
+ 1, next
- div
- 1);
242 query_string
= xstrdup("");
247 debugs(86, 6, "esiVarStateNew: Parsed Query string: '" << uri
<< "'");
249 while (n
< query_elements
) {
250 debugs(86, 6, "esiVarStateNew: Parsed Query element " << n
+ 1 << " '" << query
[n
].var
<< "'='" << query
[n
].val
<< "'");
256 ESIVariableQuery::~ESIVariableQuery()
261 for (i
= 0; i
< query_elements
; ++i
) {
262 safe_free(query
[i
].var
);
263 safe_free(query
[i
].val
);
266 memFreeBuf (query_sz
, query
);
269 safe_free (query_string
);
272 ESIVarState::ESIVarState(HttpHeader
const *aHeader
, char const *uri
) :
276 memset(&flags
, 0, sizeof(flags
));
278 /* TODO: only grab the needed headers */
279 /* Note that as we pass these through to included requests, we
280 * cannot trim them */
283 /* populate our variables trie with the available variables.
284 * Additional ones can be added during the parsing.
285 * If there is a lazy evaluation approach to this, consider it!
287 defaultVariable
= new Variable
;
288 addVariable ("HTTP_ACCEPT_LANGUAGE", 20, new ESIVariableLanguage
);
289 addVariable ("HTTP_COOKIE", 11, new ESIVariableCookie
);
290 addVariable ("HTTP_HOST", 9, new ESIVariableHost
);
291 addVariable ("HTTP_REFERER", 12, new ESIVariableReferer
);
292 addVariable ("HTTP_USER_AGENT", 15, new ESIVariableUserAgent(*this));
293 addVariable ("QUERY_STRING", 12, new ESIVariableQuery(uri
));
297 ESIVarState::removeVariable (String
const &name
)
299 Variable
*candidate
= static_cast <Variable
*>(variables
.find (name
.rawBuf(), name
.size()));
303 /* Note - this involves:
304 * extend libTrie to have a remove() call.
305 * delete from the vector.
312 ESIVarState::addVariable(char const *name
, size_t len
, Variable
*aVariable
)
315 temp
.limitInit (name
, len
);
316 removeVariable (temp
);
317 variables
.add(name
, len
, aVariable
);
318 variablesForCleanup
.push_back(aVariable
);
321 ESIVariableUserAgent::~ESIVariableUserAgent()
323 safe_free (browserversion
);
326 ESIVariableUserAgent::ESIVariableUserAgent(ESIVarState
&state
)
329 * User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705) */
330 /* Grr this Node is painful - RFC 2616 specifies that 'by convention' the tokens are in order of importance
331 * in identifying the product. According to the RFC the above should be interpreted as:
332 * Product - Mozilla version 4.0
333 * in comments - compatible; .... 3705
335 * Useing the RFC a more appropriate header would be
336 * User-Agent: MSIE/6.0 Mozilla/4.0 Windows-NT/5.1 .NET-CLR/1.0.3705
337 * or something similar.
339 * Because we can't parse under those rules and get real-world useful answers, we follow the following
341 * if the string Windows appears in the header, the OS is WIN.
342 * If the string Mac appears in the header, the OS is MAC.
343 * If the string nix, or BSD appears in the header, the OS is UNIX.
344 * If the string MSIE appears in the header, the BROWSER is MSIE, and the version is the string from
345 * MSIE<sp> to the first ;, or end of string.
346 * If the String MSIE does not appear in the header, and MOZILLA does, we use the version from the
348 * if MOZILLA doesn't appear, the browser is set to OTHER.
349 * In future, this may be better implemented as a regexp.
352 if (state
.header().has(HDR_USER_AGENT
)) {
353 char const *s
= state
.header().getStr(HDR_USER_AGENT
);
354 UserOs
= identifyOs(s
);
357 /* Now the browser and version */
359 if ((t
= strstr (s
, "MSIE"))) {
360 browser
= ESI_BROWSER_MSIE
;
364 browserversion
= xstrdup("");
369 browserversion
= xstrdup(t
+ 1);
371 browserversion
= xstrndup(t
+ 1, t1
-t
);
373 } else if (strstr (s
, "Mozilla")) {
374 browser
= ESI_BROWSER_MOZILLA
;
375 browserversion
= getProductVersion(s
);
377 browser
= ESI_BROWSER_OTHER
;
378 browserversion
= getProductVersion(s
);
381 UserOs
= ESI_OS_OTHER
;
382 browser
= ESI_BROWSER_OTHER
;
383 browserversion
= xstrdup("");
387 ESIVariableUserAgent::esiUserOs_t
388 ESIVariableUserAgent::identifyOs(char const *s
) const
393 if (strstr (s
, "Windows"))
395 else if (strstr (s
, "Mac"))
397 else if (strstr (s
, "nix") || strstr (s
, "BSD"))
404 ESIVariableCookie::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
406 const char *s
= NULL
;
409 if (state
.header().has(HDR_COOKIE
)) {
411 s
= state
.header().getStr (HDR_COOKIE
);
413 String S
= state
.header().getListMember (HDR_COOKIE
, subref
, ';');
416 ESISegment::ListAppend (state
.getOutput(), S
.rawBuf(), S
.size());
417 else if (found_default
)
418 ESISegment::ListAppend (state
.getOutput(), found_default
, strlen (found_default
));
424 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
428 ESIVariableHost::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
430 const char *s
= NULL
;
433 if (!subref
&& state
.header().has(HDR_HOST
)) {
434 s
= state
.header().getStr (HDR_HOST
);
438 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
442 ESIVariableLanguage::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
444 char const *s
= NULL
;
445 state
.languageUsed();
447 if (state
.header().has(HDR_ACCEPT_LANGUAGE
)) {
449 String
S (state
.header().getList (HDR_ACCEPT_LANGUAGE
));
450 ESISegment::ListAppend (state
.getOutput(), S
.rawBuf(), S
.size());
452 if (state
.header().hasListMember (HDR_ACCEPT_LANGUAGE
, subref
, ',')) {
458 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
462 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
467 ESIVariableQuery::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
469 char const *s
= NULL
;
476 while (i
< queryElements() && !s
) {
477 if (!strcmp (subref
, queryVector()[i
].var
))
478 s
= queryVector()[i
].val
;
487 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
491 ESIVariableReferer::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
493 const char *s
= NULL
;
496 if (!subref
&& state
.header().has(HDR_REFERER
))
497 s
= state
.header().getStr (HDR_REFERER
);
501 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
505 ESIVariableUserAgent::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
507 char const *s
= NULL
;
508 state
.useragentUsed();
510 if (state
.header().has(HDR_USER_AGENT
)) {
512 s
= state
.header().getStr (HDR_USER_AGENT
);
514 if (!strcmp (subref
, "os")) {
515 s
= esiUserOs
[UserOs
];
516 } else if (!strcmp (subref
, "browser")) {
517 s
= esiBrowsers
[browser
];
518 } else if (!strcmp (subref
, "version")) {
519 s
= browserVersion();
526 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
529 /* thoughts on long term:
532 * hand off to handler.
533 * one handler for variables.
534 * one handler for each function.
537 class ESIVariableProcessor
;
543 static ESIFunction
*GetFunction (char const *symbol
, ESIVariableProcessor
&);
544 ESIFunction(ESIVariableProcessor
&);
548 ESIVariableProcessor
&processor
;
552 ESIFunction::ESIFunction(ESIVariableProcessor
&aProcessor
) : processor(aProcessor
)
556 ESIFunction::GetFunction(char const *symbol
, ESIVariableProcessor
&aProcessor
)
559 return new ESIFunction(aProcessor
);
564 class ESIVariableProcessor
568 ESIVariableProcessor(char *, ESISegment::Pointer
&, Trie
&, ESIVarState
*);
569 ~ESIVariableProcessor();
573 bool validChar (char c
);
574 void eval (ESIVarState::Variable
*var
, char const *subref
, char const *foundDefault
);
576 void identifyFunction();
578 ESISegment::Pointer
&output
;
580 ESIVarState
*varState
;
588 ESIVarState::Variable
*vartype
;
589 ESIFunction
*currentFunction
;
593 ESIVariableProcessor::eval (ESIVarState::Variable
*var
, char const *subref
, char const *foundDefault
)
600 var
->eval (*varState
, subref
, foundDefault
);
604 ESIVariableProcessor::validChar (char c
)
606 if (('A' <= c
&& c
<= 'Z') ||
607 ('a' <= c
&& c
<= 'z') ||
608 '_' == c
|| '-' == c
)
614 ESIVarState::Variable
*
615 ESIVarState::GetVar(char const *symbol
, int len
)
619 void *result
= variables
.find (symbol
, len
);
622 return static_cast<Variable
*>(result
);
624 return defaultVariable
;
630 char *string
= input
->listToChar();
631 ESISegmentFreeList (input
);
632 ESIVariableProcessor
theProcessor(string
, output
, variables
, this);
637 #define LOOKFORSTART 0
638 ESIVariableProcessor::ESIVariableProcessor(char *aString
, ESISegment::Pointer
&aSegment
, Trie
&aTrie
, ESIVarState
*aState
) :
639 string(aString
), output (aSegment
), variables(aTrie
), varState (aState
),
640 state(LOOKFORSTART
), pos(0), var_pos(0), done_pos(0), found_subref (NULL
),
641 found_default (NULL
), currentFunction(NULL
)
643 len
= strlen (string
);
644 vartype
= varState
->GetVar("",0);
651 /* because we are only used to process:
655 * buffering is ok - we won't delay the start of async activity, or
656 * of output data preparation
658 /* Should make these an enum or something...
661 ESIVariableProcessor::doFunction()
663 if (!currentFunction
)
666 /* stay in here whilst operating */
667 while (pos
< len
&& state
)
670 case 2: /* looking for variable name */
672 if (!validChar(string
[pos
])) {
673 /* not a variable name char */
676 vartype
= varState
->GetVar (string
+ var_pos
, pos
- var_pos
);
686 case 3: /* looking for variable subref, end bracket or default indicator */
688 if (string
[pos
] == ')') {
690 eval(vartype
, found_subref
, found_default
);
692 safe_free(found_subref
);
693 safe_free(found_default
);
694 state
= LOOKFORSTART
;
695 } else if (!found_subref
&& !found_default
&& string
[pos
] == '{') {
696 debugs(86, 6, "ESIVarStateDoIt: Subref of some sort");
697 /* subreference of some sort */
698 /* look for the entry name */
701 } else if (!found_default
&& string
[pos
] == '|') {
702 debugs(86, 6, "esiVarStateDoIt: Default present");
703 /* extract default value */
707 /* unexpected char, not a variable after all */
708 debugs(86, 6, "esiVarStateDoIt: unexpected char after varname");
709 state
= LOOKFORSTART
;
715 case 4: /* looking for variable subref */
717 if (string
[pos
] == '}') {
719 found_subref
= xstrndup (&string
[var_pos
], pos
- var_pos
+ 1);
720 debugs(86, 6, "esiVarStateDoIt: found end of variable subref '" << found_subref
<< "'");
723 } else if (!validChar (string
[pos
])) {
724 debugs(86, 6, "esiVarStateDoIt: found invalid char in variable subref");
725 /* not a valid subref */
726 safe_free(found_subref
);
727 state
= LOOKFORSTART
;
735 case 5: /* looking for a default value */
737 if (string
[pos
] == '\'') {
738 /* begins with a quote */
739 debugs(86, 6, "esiVarStateDoIt: found quoted default");
744 debugs(86, 6, "esiVarStateDoIt: found unquoted default");
751 case 6: /* looking for a quote terminate default value */
753 if (string
[pos
] == '\'') {
755 found_default
= xstrndup (&string
[var_pos
], pos
- var_pos
+ 1);
756 debugs(86, 6, "esiVarStateDoIt: found end of quoted default '" << found_default
<< "'");
763 case 7: /* looking for } terminate default value */
765 if (string
[pos
] == ')') {
766 /* end of default - end of variable*/
767 found_default
= xstrndup (&string
[var_pos
], pos
- var_pos
+ 1);
768 debugs(86, 6, "esiVarStateDoIt: found end of variable (w/ unquoted default) '" << found_default
<< "'");
769 eval(vartype
,found_subref
, found_default
);
771 safe_free(found_default
);
772 safe_free(found_subref
);
773 state
= LOOKFORSTART
;
780 fatal("esiVarStateDoIt: unexpected state\n");
785 ESIVariableProcessor::identifyFunction()
787 delete currentFunction
;
788 currentFunction
= ESIFunction::GetFunction (&string
[pos
], *this);
790 if (!currentFunction
) {
791 state
= LOOKFORSTART
;
793 state
= 2; /* process a function */
794 /* advance past function name */
800 ESIVariableProcessor::doIt()
802 assert (output
== NULL
);
805 /* skipping pre-variables */
807 if (string
[pos
] != '$') {
811 /* extract known plain text */
812 ESISegment::ListAppend (output
, string
+ done_pos
, pos
- done_pos
);
824 /* pos-done_pos chars are ready to copy */
826 ESISegment::ListAppend (output
, string
+done_pos
, pos
- done_pos
);
828 safe_free (found_default
);
830 safe_free (found_subref
);
833 ESIVariableProcessor::~ESIVariableProcessor()
835 delete currentFunction
;
838 /* XXX FIXME: this should be comma delimited, no? */
840 ESIVarState::buildVary (HttpReply
*rep
)
846 strcat (tempstr
, "Accept-Language ");
849 strcat (tempstr
, "Cookie ");
852 strcat (tempstr
, "Host ");
855 strcat (tempstr
, "Referer ");
858 strcat (tempstr
, "User-Agent ");
863 String
strVary (rep
->header
.getList (HDR_VARY
));
865 if (!strVary
.size() || strVary
[0] != '*') {
866 rep
->header
.putStr (HDR_VARY
, tempstr
);