]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/ESIVarState.cc
3 * $Id: ESIVarState.cc,v 1.2 2003/07/23 10:41:20 robertc 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 debug (86,6)("esiVarState::feedData: accepting %d bytes\n", len
);
133 ESISegment::ListAppend (input
, buf
, len
);
137 ESIVarState::extractList()
140 ESISegment::Pointer rv
= output
;
142 debug (86,6)("ESIVarStateExtractList: Extracted list\n");
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 debug (86,6)("ESIVarStateExtractList: Extracted char\n");
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
);
186 httpHeaderClean (&hdr
);
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 ESIVarState::deleteSelf() const
212 ESIVariableUserAgent::getProductVersion (char const *s
)
221 len
= strcspn (t
, " \r\n()<>@,;:\\\"/[]?={}");
223 return xstrndup (t
, len
+ 1);
226 ESIVariableQuery::ESIVariableQuery(char const *uri
) : query (NULL
), query_sz (0), query_elements (0), query_string (NULL
)
228 /* Count off the query elements */
229 char const *query_start
= strchr (uri
, '?');
231 if (query_start
&& query_start
[1] != '\0' ) {
233 query_string
= xstrdup (query_start
+ 1);
235 char const *query_pos
= query_start
+ 1;
237 while ((query_pos
= strchr (query_pos
, '&'))) {
242 query
= (_query_elem
*)memReallocBuf(query
, query_elements
* sizeof (struct _query_elem
),
244 query_pos
= query_start
+ 1;
248 char *next
= strchr (query_pos
, '&');
249 char *div
= strchr (query_pos
, '=');
254 assert (n
< query_elements
);
259 if (!(div
- query_pos
+ 1))
260 /* zero length between & and = or & and & */
263 query
[n
].var
= xstrndup (query_pos
, div
- query_pos
+ 1) ;
266 query
[n
].val
= xstrdup ("");
268 query
[n
].val
= xstrndup (div
+ 1, next
- div
- 1);
275 query_string
= xstrdup ("");
280 debug (86,6)("esiVarStateNew: Parsed Query string: '%s'\n",uri
);
282 while (n
< query_elements
) {
283 debug (86,6)("esiVarStateNew: Parsed Query element %d '%s'='%s'\n",n
+ 1, query
[n
].var
, query
[n
].val
);
289 ESIVariableQuery::~ESIVariableQuery()
294 for (i
= 0; i
< query_elements
; ++i
) {
295 safe_free(query
[i
].var
);
296 safe_free(query
[i
].val
);
299 memFreeBuf (query_sz
, query
);
302 safe_free (query_string
);
305 ESIVarState::ESIVarState (HttpHeader
const *aHeader
, char const *uri
)
306 : output (NULL
), hdr(hoReply
)
308 /* TODO: only grab the needed headers */
309 /* Note that as we pass these through to included requests, we
310 * cannot trim them */
311 httpHeaderAppend (&hdr
, aHeader
);
313 /* populate our variables trie with the available variables.
314 * Additional ones can be added during the parsing.
315 * If there is a lazy evaluation approach to this, consider it!
317 defaultVariable
= new Variable
;
318 addVariable ("HTTP_ACCEPT_LANGUAGE", 20, new ESIVariableLanguage
);
319 addVariable ("HTTP_COOKIE", 11, new ESIVariableCookie
);
320 addVariable ("HTTP_HOST", 9, new ESIVariableHost
);
321 addVariable ("HTTP_REFERER", 12, new ESIVariableReferer
);
322 addVariable ("HTTP_USER_AGENT", 15, new ESIVariableUserAgent(*this));
323 addVariable ("QUERY_STRING", 12, new ESIVariableQuery(uri
));
327 ESIVarState::removeVariable (String
const &name
)
329 Variable
*candidate
= static_cast <Variable
*>(variables
.find (name
.buf(), name
.size()));
333 /* Note - this involves:
334 * extend libTrie to have a remove() call.
335 * delete from the vector.
342 ESIVarState::addVariable(char const *name
, size_t len
, Variable
*aVariable
)
345 temp
.limitInit (name
, len
);
346 removeVariable (temp
);
347 variables
.add(name
, len
, aVariable
);
348 variablesForCleanup
.push_back(aVariable
);
351 ESIVariableUserAgent::~ESIVariableUserAgent()
353 safe_free (browserversion
);
356 ESIVariableUserAgent::ESIVariableUserAgent(ESIVarState
&state
)
359 * User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705) */
360 /* Grr this Node is painful - RFC 2616 specifies that 'by convention' the tokens are in order of importance
361 * in identifying the product. According to the RFC the above should be interpreted as:
362 * Product - Mozilla version 4.0
363 * in comments - compatible; .... 3705
365 * Useing the RFC a more appropriate header would be
366 * User-Agent: MSIE/6.0 Mozilla/4.0 Windows-NT/5.1 .NET-CLR/1.0.3705
367 * or something similar.
369 * Because we can't parse under those rules and get real-world useful answers, we follow the following
371 * if the string Windows appears in the header, the OS is WIN.
372 * If the string Mac appears in the header, the OS is MAC.
373 * If the string nix, or BSD appears in the header, the OS is UNIX.
374 * If the string MSIE appears in the header, the BROWSER is MSIE, and the version is the string from
375 * MSIE<sp> to the first ;, or end of string.
376 * If the String MSIE does not appear in the header, and MOZILLA does, we use the version from the
378 * if MOZILLA doesn't appear, the browser is set to OTHER.
379 * In future, this may be better implemented as a regexp.
382 if (httpHeaderHas(&state
.header(), HDR_USER_AGENT
)) {
383 char const *s
= httpHeaderGetStr (&state
.header(), HDR_USER_AGENT
);
384 UserOs
= identifyOs(s
);
387 /* Now the browser and version */
389 if ((t
= strstr (s
, "MSIE"))) {
390 browser
= ESI_BROWSER_MSIE
;
394 browserversion
= xstrdup ("");
399 browserversion
= xstrdup (t
+ 1);
401 browserversion
= xstrndup (t
+ 1, t1
-t
);
403 } else if (strstr (s
, "Mozilla")) {
404 browser
= ESI_BROWSER_MOZILLA
;
405 browserversion
= getProductVersion(s
);
407 browser
= ESI_BROWSER_OTHER
;
408 browserversion
= getProductVersion(s
);
411 UserOs
= ESI_OS_OTHER
;
412 browser
= ESI_BROWSER_OTHER
;
413 browserversion
= xstrdup ("");
417 ESIVariableUserAgent::esiUserOs_t
418 ESIVariableUserAgent::identifyOs(char const *s
) const
423 if (strstr (s
, "Windows"))
425 else if (strstr (s
, "Mac"))
427 else if (strstr (s
, "nix") || strstr (s
, "BSD"))
434 ESIVariableCookie::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
436 const char *s
= NULL
;
439 if (httpHeaderHas(&state
.header(), HDR_COOKIE
)) {
441 s
= httpHeaderGetStr (&state
.header(), HDR_COOKIE
);
443 String S
= httpHeaderGetListMember (&state
.header(), HDR_COOKIE
, subref
, ';');
446 ESISegment::ListAppend (state
.getOutput(), S
.buf(), S
.size());
447 else if (found_default
)
448 ESISegment::ListAppend (state
.getOutput(), found_default
, strlen (found_default
));
454 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
458 ESIVariableHost::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
460 const char *s
= NULL
;
463 if (!subref
&& httpHeaderHas(&state
.header(),HDR_HOST
)) {
464 s
= httpHeaderGetStr (&state
.header(), HDR_HOST
);
468 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
472 ESIVariableLanguage::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
474 char const *s
= NULL
;
475 state
.languageUsed();
477 if (httpHeaderHas(&state
.header(), HDR_ACCEPT_LANGUAGE
)) {
479 String
S (httpHeaderGetList (&state
.header(), HDR_ACCEPT_LANGUAGE
));
480 ESISegment::ListAppend (state
.getOutput(), S
.buf(), S
.size());
482 if (httpHeaderHasListMember (&state
.header(), HDR_ACCEPT_LANGUAGE
, subref
, ',')) {
488 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
492 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
497 ESIVariableQuery::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
499 char const *s
= NULL
;
506 while (i
< queryElements() && !s
) {
507 if (!strcmp (subref
, queryVector()[i
].var
))
508 s
= queryVector()[i
].val
;
517 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
521 ESIVariableReferer::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
523 const char *s
= NULL
;
526 if (!subref
&& httpHeaderHas(&state
.header(), HDR_REFERER
))
527 s
= httpHeaderGetStr (&state
.header(), HDR_REFERER
);
531 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
535 ESIVariableUserAgent::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
537 char const *s
= NULL
;
538 state
.useragentUsed();
540 if (httpHeaderHas(&state
.header(), HDR_USER_AGENT
)) {
542 s
= httpHeaderGetStr (&state
.header(), HDR_USER_AGENT
);
544 if (!strcmp (subref
, "os")) {
545 s
= esiUserOs
[UserOs
];
546 } else if (!strcmp (subref
, "browser")) {
547 s
= esiBrowsers
[browser
];
548 } else if (!strcmp (subref
, "version")) {
549 s
= browserVersion();
556 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
559 /* thoughts on long term:
562 * hand off to handler.
563 * one handler for variables.
564 * one handler for each function.
567 class ESIVariableProcessor
;
573 static ESIFunction
*GetFunction (char const *symbol
, ESIVariableProcessor
&);
574 ESIFunction(ESIVariableProcessor
&);
578 ESIVariableProcessor
&processor
;
582 ESIFunction::ESIFunction(ESIVariableProcessor
&aProcessor
) : processor(aProcessor
)
586 ESIFunction::GetFunction(char const *symbol
, ESIVariableProcessor
&aProcessor
)
589 return new ESIFunction(aProcessor
);
594 class ESIVariableProcessor
598 ESIVariableProcessor(char *, ESISegment::Pointer
&, Trie
&, ESIVarState
*);
599 ~ESIVariableProcessor();
603 bool validChar (char c
);
604 void eval (ESIVarState::Variable
*var
, char const *subref
, char const *found_default
);
606 void identifyFunction();
608 ESISegment::Pointer
&output
;
610 ESIVarState
*varState
;
618 ESIVarState::Variable
*vartype
;
619 ESIFunction
*currentFunction
;
623 ESIVariableProcessor::eval (ESIVarState::Variable
*var
, char const *subref
, char const *found_default
)
630 var
->eval (*varState
, subref
, found_default
);
634 ESIVariableProcessor::validChar (char c
)
636 if (('A' <= c
&& c
<= 'Z') ||
637 ('a' <= c
&& c
<= 'z') ||
638 '_' == c
|| '-' == c
)
644 ESIVarState::Variable
*
645 ESIVarState::GetVar(char const *symbol
, int len
)
649 void *result
= variables
.find (symbol
, len
);
652 return static_cast<Variable
*>(result
);
654 return defaultVariable
;
660 char *string
= input
->listToChar();
661 ESISegmentFreeList (input
);
662 ESIVariableProcessor
theProcessor(string
, output
, variables
, this);
667 #define LOOKFORSTART 0
668 ESIVariableProcessor::ESIVariableProcessor(char *aString
, ESISegment::Pointer
&aSegment
, Trie
&aTrie
, ESIVarState
*aState
) :
669 string(aString
), output (aSegment
), variables(aTrie
), varState (aState
),
670 state(LOOKFORSTART
), pos(0), var_pos(0), done_pos(0), found_subref (NULL
),
671 found_default (NULL
), currentFunction(NULL
)
673 len
= strlen (string
);
674 vartype
= varState
->GetVar("",0);
681 /* because we are only used to process:
685 * buffering is ok - we won't delay the start of async activity, or
686 * of output data preparation
688 /* Should make these an enum or something...
691 ESIVariableProcessor::doFunction()
693 if (!currentFunction
)
696 /* stay in here whilst operating */
697 while (pos
< len
&& state
)
700 case 2: /* looking for variable name */
702 if (!validChar(string
[pos
])) {
703 /* not a variable name char */
706 vartype
= varState
->GetVar (string
+ var_pos
, pos
- var_pos
);
716 case 3: /* looking for variable subref, end bracket or default indicator */
718 if (string
[pos
] == ')') {
720 eval(vartype
, found_subref
, found_default
);
722 safe_free(found_subref
);
723 safe_free(found_default
);
724 state
= LOOKFORSTART
;
725 } else if (!found_subref
&& !found_default
&& string
[pos
] == '{') {
726 debug (86,6)("ESIVarStateDoIt: Subref of some sort\n");
727 /* subreference of some sort */
728 /* look for the entry name */
731 } else if (!found_default
&& string
[pos
] == '|') {
732 debug (86,6)("esiVarStateDoIt: Default present\n");
733 /* extract default value */
737 /* unexpected char, not a variable after all */
738 debug (86,6)("esiVarStateDoIt: unexpected char after varname\n");
739 state
= LOOKFORSTART
;
745 case 4: /* looking for variable subref */
747 if (string
[pos
] == '}') {
749 found_subref
= xstrndup (&string
[var_pos
], pos
- var_pos
+ 1);
750 debug (86,6)("esiVarStateDoIt: found end of variable subref '%s'\n", found_subref
);
753 } else if (!validChar (string
[pos
])) {
754 debug (86,6)("esiVarStateDoIt: found invalid char in variable subref\n");
755 /* not a valid subref */
756 safe_free(found_subref
);
757 state
= LOOKFORSTART
;
765 case 5: /* looking for a default value */
767 if (string
[pos
] == '\'') {
768 /* begins with a quote */
769 debug (86,6)("esiVarStateDoIt: found quoted default\n");
774 debug (86,6)("esiVarStateDoIt: found unquoted default\n");
781 case 6: /* looking for a quote terminate default value */
783 if (string
[pos
] == '\'') {
785 found_default
= xstrndup (&string
[var_pos
], pos
- var_pos
+ 1);
786 debug (86,6)("esiVarStateDoIt: found end of quoted default '%s'\n", found_default
);
793 case 7: /* looking for } terminate default value */
795 if (string
[pos
] == ')') {
796 /* end of default - end of variable*/
797 found_default
= xstrndup (&string
[var_pos
], pos
- var_pos
+ 1);
798 debug (86,6)("esiVarStateDoIt: found end of variable (w/ unquoted default) '%s'\n",found_default
);
799 eval(vartype
,found_subref
, found_default
);
801 safe_free(found_default
);
802 safe_free(found_subref
);
803 state
= LOOKFORSTART
;
810 fatal("esiVarStateDoIt: unexpected state\n");
815 ESIVariableProcessor::identifyFunction()
817 delete currentFunction
;
818 currentFunction
= ESIFunction::GetFunction (&string
[pos
], *this);
820 if (!currentFunction
) {
821 state
= LOOKFORSTART
;
823 state
= 2; /* process a function */
824 /* advance past function name */
830 ESIVariableProcessor::doIt()
832 assert (output
== NULL
);
835 /* skipping pre-variables */
837 if (string
[pos
] != '$') {
841 /* extract known plain text */
842 ESISegment::ListAppend (output
, string
+ done_pos
, pos
- done_pos
);
854 /* pos-done_pos chars are ready to copy */
856 ESISegment::ListAppend (output
, string
+done_pos
, pos
- done_pos
);
858 safe_free (found_default
);
860 safe_free (found_subref
);
863 ESIVariableProcessor::~ESIVariableProcessor()
865 delete currentFunction
;
869 /* XXX FIXME: this should be comma delimited, no? */
871 ESIVarState::buildVary (HttpReply
*rep
)
877 strcat (tempstr
, "Accept-Language ");
880 strcat (tempstr
, "Cookie ");
883 strcat (tempstr
, "Host ");
886 strcat (tempstr
, "Referer ");
889 strcat (tempstr
, "User-Agent ");
894 String
strVary (httpHeaderGetList (&rep
->header
, HDR_VARY
));
896 if (!strVary
.size() || strVary
.buf()[0] != '*') {
897 httpHeaderPutStr (&rep
->header
, HDR_VARY
, tempstr
);