]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/esi/VarState.cc
2 * Copyright (C) 1996-2022 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"
14 #include "HttpReply.h"
16 char const *ESIVariableUserAgent::esiUserOs
[]= {
23 char const * esiBrowsers
[]= {"MSIE",
28 CBDATA_CLASS_INIT(ESIVarState
);
31 ESIVarState::Variable::eval(ESIVarState
&state
, char const *, char const *found_default
) const
33 /* No-op. We swallow it */
36 ESISegment::ListAppend (state
.getOutput(), found_default
, strlen (found_default
));
40 ESIVarState::hostUsed()
46 ESIVarState::cookieUsed()
52 ESIVarState::languageUsed()
58 ESIVarState::refererUsed()
64 ESIVarState::useragentUsed()
76 ESIVarState::getOutput()
82 ESIVariableQuery::queryString() const
87 struct _query_elem
const *
88 ESIVariableQuery::queryVector() const {
93 ESIVariableQuery::queryElements() const
95 return query_elements
;
99 ESIVarState::feedData (const char *buf
, size_t len
)
101 /* TODO: if needed - tune to skip segment iteration */
102 debugs (86,6, "esiVarState::feedData: accepting " << len
<< " bytes");
103 ESISegment::ListAppend (input
, buf
, len
);
107 ESIVarState::extractList()
110 ESISegment::Pointer rv
= output
;
112 debugs(86, 6, "ESIVarStateExtractList: Extracted list");
117 ESIVarState::extractChar ()
120 fatal ("Attempt to extract variable state with no data fed in \n");
124 char *rv
= output
->listToChar();
126 ESISegmentFreeList (output
);
128 debugs(86, 6, "ESIVarStateExtractList: Extracted char");
133 ESIVarState::~ESIVarState()
137 ESISegmentFreeList(output
);
140 while (!variablesForCleanup
.empty()) {
141 delete variablesForCleanup
.back();
142 variablesForCleanup
.pop_back();
145 delete defaultVariable
;
149 ESIVariableUserAgent::getProductVersion (char const *s
)
158 len
= strcspn(t
, " \r\n()<>@,;:\\\"/[]?={}");
160 return xstrndup(t
, len
+ 1);
163 ESIVariableQuery::ESIVariableQuery(char const *uri
) : query (nullptr), query_sz (0), query_elements (0), query_string (nullptr)
165 /* Count off the query elements */
166 char const *query_start
= strchr (uri
, '?');
168 if (query_start
&& query_start
[1] != '\0' ) {
170 query_string
= xstrdup(query_start
+ 1);
172 char const *query_pos
= query_start
+ 1;
174 while ((query_pos
= strchr(query_pos
, '&'))) {
179 query
= (_query_elem
*)memReallocBuf(query
, query_elements
* sizeof (struct _query_elem
),
181 query_pos
= query_start
+ 1;
185 char const *next
= strchr(query_pos
, '&');
186 char const *div
= strchr(query_pos
, '=');
191 assert (n
< query_elements
);
196 if (!(div
- query_pos
+ 1))
197 /* zero length between & and = or & and & */
200 query
[n
].var
= xstrndup(query_pos
, div
- query_pos
+ 1) ;
203 query
[n
].val
= xstrdup("");
205 query
[n
].val
= xstrndup(div
+ 1, next
- div
- 1);
212 query_string
= xstrdup("");
217 debugs(86, 6, "esiVarStateNew: Parsed Query string: '" << uri
<< "'");
219 while (n
< query_elements
) {
220 debugs(86, 6, "esiVarStateNew: Parsed Query element " << n
+ 1 << " '" << query
[n
].var
<< "'='" << query
[n
].val
<< "'");
226 ESIVariableQuery::~ESIVariableQuery()
231 for (i
= 0; i
< query_elements
; ++i
) {
232 safe_free(query
[i
].var
);
233 safe_free(query
[i
].val
);
236 memFreeBuf (query_sz
, query
);
239 safe_free (query_string
);
242 ESIVarState::ESIVarState(HttpHeader
const *aHeader
, char const *uri
) :
246 memset(&flags
, 0, sizeof(flags
));
248 /* TODO: only grab the needed headers */
249 /* Note that as we pass these through to included requests, we
250 * cannot trim them */
253 /* populate our variables trie with the available variables.
254 * Additional ones can be added during the parsing.
255 * If there is a lazy evaluation approach to this, consider it!
257 defaultVariable
= new Variable
;
258 addVariable ("HTTP_ACCEPT_LANGUAGE", 20, new ESIVariableLanguage
);
259 addVariable ("HTTP_COOKIE", 11, new ESIVariableCookie
);
260 addVariable ("HTTP_HOST", 9, new ESIVariableHost
);
261 addVariable ("HTTP_REFERER", 12, new ESIVariableReferer
);
262 addVariable ("HTTP_USER_AGENT", 15, new ESIVariableUserAgent(*this));
263 addVariable ("QUERY_STRING", 12, new ESIVariableQuery(uri
));
267 ESIVarState::removeVariable (String
const &name
)
269 Variable
*candidate
= static_cast <Variable
*>(variables
.find (name
.rawBuf(), name
.size()));
273 /* Note - this involves:
274 * extend libTrie to have a remove() call.
275 * delete from the vector.
282 ESIVarState::addVariable(char const *name
, size_t len
, Variable
*aVariable
)
285 temp
.assign(name
, len
);
286 removeVariable (temp
);
287 variables
.add(name
, len
, aVariable
);
288 variablesForCleanup
.push_back(aVariable
);
291 ESIVariableUserAgent::~ESIVariableUserAgent()
293 safe_free (browserversion
);
296 ESIVariableUserAgent::ESIVariableUserAgent(ESIVarState
&state
)
299 * User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705) */
300 /* Grr this Node is painful - RFC 2616 specifies that 'by convention' the tokens are in order of importance
301 * in identifying the product. According to the RFC the above should be interpreted as:
302 * Product - Mozilla version 4.0
303 * in comments - compatible; .... 3705
305 * Using the RFC a more appropriate header would be
306 * User-Agent: MSIE/6.0 Mozilla/4.0 Windows-NT/5.1 .NET-CLR/1.0.3705
307 * or something similar.
309 * Because we can't parse under those rules and get real-world useful answers, we follow the following
311 * if the string Windows appears in the header, the OS is WIN.
312 * If the string Mac appears in the header, the OS is MAC.
313 * If the string nix, or BSD appears in the header, the OS is UNIX.
314 * If the string MSIE appears in the header, the BROWSER is MSIE, and the version is the string from
315 * MSIE<sp> to the first ;, or end of string.
316 * If the String MSIE does not appear in the header, and MOZILLA does, we use the version from the
318 * if MOZILLA doesn't appear, the browser is set to OTHER.
319 * In future, this may be better implemented as a regexp.
322 if (state
.header().has(Http::HdrType::USER_AGENT
)) {
323 char const *s
= state
.header().getStr(Http::HdrType::USER_AGENT
);
324 UserOs
= identifyOs(s
);
327 /* Now the browser and version */
329 if ((t
= strstr (s
, "MSIE"))) {
330 browser
= ESI_BROWSER_MSIE
;
334 browserversion
= xstrdup("");
339 browserversion
= xstrdup(t
+ 1);
341 browserversion
= xstrndup(t
+ 1, t1
-t
);
343 } else if (strstr (s
, "Mozilla")) {
344 browser
= ESI_BROWSER_MOZILLA
;
345 browserversion
= getProductVersion(s
);
347 browser
= ESI_BROWSER_OTHER
;
348 browserversion
= getProductVersion(s
);
351 UserOs
= ESI_OS_OTHER
;
352 browser
= ESI_BROWSER_OTHER
;
353 browserversion
= xstrdup("");
357 ESIVariableUserAgent::esiUserOs_t
358 ESIVariableUserAgent::identifyOs(char const *s
) const
363 if (strstr (s
, "Windows"))
365 else if (strstr (s
, "Mac"))
367 else if (strstr (s
, "nix") || strstr (s
, "BSD"))
374 ESIVariableCookie::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
376 const char *s
= nullptr;
379 if (state
.header().has(Http::HdrType::COOKIE
)) {
381 s
= state
.header().getStr (Http::HdrType::COOKIE
);
383 const auto subCookie
= state
.header().getListMember(Http::HdrType::COOKIE
, subref
, ';');
385 if (subCookie
.length())
386 ESISegment::ListAppend(state
.getOutput(), subCookie
.rawContent(), subCookie
.length());
387 else if (found_default
)
388 ESISegment::ListAppend (state
.getOutput(), found_default
, strlen (found_default
));
394 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
398 ESIVariableHost::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
400 const char *s
= nullptr;
403 if (!subref
&& state
.header().has(Http::HdrType::HOST
)) {
404 s
= state
.header().getStr (Http::HdrType::HOST
);
408 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
412 ESIVariableLanguage::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
414 char const *s
= nullptr;
415 state
.languageUsed();
417 if (state
.header().has(Http::HdrType::ACCEPT_LANGUAGE
)) {
419 String
S (state
.header().getList (Http::HdrType::ACCEPT_LANGUAGE
));
420 ESISegment::ListAppend (state
.getOutput(), S
.rawBuf(), S
.size());
422 if (state
.header().hasListMember (Http::HdrType::ACCEPT_LANGUAGE
, subref
, ',')) {
428 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
432 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
437 ESIVariableQuery::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
439 char const *s
= nullptr;
446 while (i
< queryElements() && !s
) {
447 if (!strcmp (subref
, queryVector()[i
].var
))
448 s
= queryVector()[i
].val
;
457 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
461 ESIVariableReferer::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
463 const char *s
= nullptr;
466 if (!subref
&& state
.header().has(Http::HdrType::REFERER
))
467 s
= state
.header().getStr (Http::HdrType::REFERER
);
471 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
475 ESIVariableUserAgent::eval (ESIVarState
&state
, char const *subref
, char const *found_default
) const
477 char const *s
= nullptr;
478 state
.useragentUsed();
480 if (state
.header().has(Http::HdrType::USER_AGENT
)) {
482 s
= state
.header().getStr (Http::HdrType::USER_AGENT
);
484 if (!strcmp (subref
, "os")) {
485 s
= esiUserOs
[UserOs
];
486 } else if (!strcmp (subref
, "browser")) {
487 s
= esiBrowsers
[browser
];
488 } else if (!strcmp (subref
, "version")) {
489 s
= browserVersion();
496 ESISegment::ListAppend (state
.getOutput(), s
, strlen (s
));
499 /* thoughts on long term:
502 * hand off to handler.
503 * one handler for variables.
504 * one handler for each function.
507 class ESIVariableProcessor
;
513 static ESIFunction
*GetFunction (char const *symbol
, ESIVariableProcessor
&);
514 ESIFunction(ESIVariableProcessor
&);
518 ESIVariableProcessor
&processor
;
522 ESIFunction::ESIFunction(ESIVariableProcessor
&aProcessor
) : processor(aProcessor
)
526 ESIFunction::GetFunction(char const *symbol
, ESIVariableProcessor
&aProcessor
)
529 return new ESIFunction(aProcessor
);
534 class ESIVariableProcessor
538 ESIVariableProcessor(char *, ESISegment::Pointer
&, Trie
&, ESIVarState
*);
539 ~ESIVariableProcessor();
543 bool validChar (char c
);
544 void eval (ESIVarState::Variable
*var
, char const *subref
, char const *foundDefault
);
546 void identifyFunction();
548 ESISegment::Pointer
&output
;
550 ESIVarState
*varState
;
558 ESIVarState::Variable
*vartype
;
559 ESIFunction
*currentFunction
;
563 ESIVariableProcessor::eval (ESIVarState::Variable
*var
, char const *subref
, char const *foundDefault
)
570 var
->eval (*varState
, subref
, foundDefault
);
574 ESIVariableProcessor::validChar (char c
)
576 if (('A' <= c
&& c
<= 'Z') ||
577 ('a' <= c
&& c
<= 'z') ||
578 '_' == c
|| '-' == c
)
584 ESIVarState::Variable
*
585 ESIVarState::GetVar(char const *symbol
, int len
)
589 void *result
= variables
.find (symbol
, len
);
592 return static_cast<Variable
*>(result
);
594 return defaultVariable
;
600 char *string
= input
->listToChar();
601 ESISegmentFreeList (input
);
602 ESIVariableProcessor
theProcessor(string
, output
, variables
, this);
607 #define LOOKFORSTART 0
608 ESIVariableProcessor::ESIVariableProcessor(char *aString
, ESISegment::Pointer
&aSegment
, Trie
&aTrie
, ESIVarState
*aState
) :
609 string(aString
), output (aSegment
), variables(aTrie
), varState (aState
),
610 state(LOOKFORSTART
), pos(0), var_pos(0), done_pos(0), found_subref (nullptr),
611 found_default (nullptr), currentFunction(nullptr)
613 len
= strlen (string
);
614 vartype
= varState
->GetVar("",0);
621 /* because we are only used to process:
625 * buffering is ok - we won't delay the start of async activity, or
626 * of output data preparation
628 /* Should make these an enum or something...
631 ESIVariableProcessor::doFunction()
633 if (!currentFunction
)
636 /* stay in here whilst operating */
637 while (pos
< len
&& state
)
640 case 2: /* looking for variable name */
642 if (!validChar(string
[pos
])) {
643 /* not a variable name char */
646 vartype
= varState
->GetVar (string
+ var_pos
, pos
- var_pos
);
656 case 3: /* looking for variable subref, end bracket or default indicator */
658 if (string
[pos
] == ')') {
660 eval(vartype
, found_subref
, found_default
);
662 safe_free(found_subref
);
663 safe_free(found_default
);
664 state
= LOOKFORSTART
;
665 } else if (!found_subref
&& !found_default
&& string
[pos
] == '{') {
666 debugs(86, 6, "ESIVarStateDoIt: Subref of some sort");
667 /* subreference of some sort */
668 /* look for the entry name */
671 } else if (!found_default
&& string
[pos
] == '|') {
672 debugs(86, 6, "esiVarStateDoIt: Default present");
673 /* extract default value */
677 /* unexpected char, not a variable after all */
678 debugs(86, 6, "esiVarStateDoIt: unexpected char after varname");
679 state
= LOOKFORSTART
;
685 case 4: /* looking for variable subref */
687 if (string
[pos
] == '}') {
689 found_subref
= xstrndup (&string
[var_pos
], pos
- var_pos
+ 1);
690 debugs(86, 6, "esiVarStateDoIt: found end of variable subref '" << found_subref
<< "'");
693 } else if (!validChar (string
[pos
])) {
694 debugs(86, 6, "esiVarStateDoIt: found invalid char in variable subref");
695 /* not a valid subref */
696 safe_free(found_subref
);
697 state
= LOOKFORSTART
;
705 case 5: /* looking for a default value */
707 if (string
[pos
] == '\'') {
708 /* begins with a quote */
709 debugs(86, 6, "esiVarStateDoIt: found quoted default");
714 debugs(86, 6, "esiVarStateDoIt: found unquoted default");
721 case 6: /* looking for a quote terminate default value */
723 if (string
[pos
] == '\'') {
725 found_default
= xstrndup (&string
[var_pos
], pos
- var_pos
+ 1);
726 debugs(86, 6, "esiVarStateDoIt: found end of quoted default '" << found_default
<< "'");
733 case 7: /* looking for } terminate default value */
735 if (string
[pos
] == ')') {
736 /* end of default - end of variable*/
737 found_default
= xstrndup (&string
[var_pos
], pos
- var_pos
+ 1);
738 debugs(86, 6, "esiVarStateDoIt: found end of variable (w/ unquoted default) '" << found_default
<< "'");
739 eval(vartype
,found_subref
, found_default
);
741 safe_free(found_default
);
742 safe_free(found_subref
);
743 state
= LOOKFORSTART
;
750 fatal("esiVarStateDoIt: unexpected state\n");
755 ESIVariableProcessor::identifyFunction()
757 delete currentFunction
;
758 currentFunction
= ESIFunction::GetFunction (&string
[pos
], *this);
760 if (!currentFunction
) {
761 state
= LOOKFORSTART
;
763 state
= 2; /* process a function */
764 /* advance past function name */
770 ESIVariableProcessor::doIt()
772 assert (output
== nullptr);
775 /* skipping pre-variables */
777 if (string
[pos
] != '$') {
781 /* extract known plain text */
782 ESISegment::ListAppend (output
, string
+ done_pos
, pos
- done_pos
);
794 /* pos-done_pos chars are ready to copy */
796 ESISegment::ListAppend (output
, string
+done_pos
, pos
- done_pos
);
798 safe_free (found_default
);
800 safe_free (found_subref
);
803 ESIVariableProcessor::~ESIVariableProcessor()
805 delete currentFunction
;
808 /* XXX: this should be comma delimited, no? */
810 ESIVarState::buildVary (HttpReply
*rep
)
816 strcat (tempstr
, "Accept-Language ");
819 strcat (tempstr
, "Cookie ");
822 strcat (tempstr
, "Host ");
825 strcat (tempstr
, "Referer ");
828 strcat (tempstr
, "User-Agent ");
833 String
strVary (rep
->header
.getList (Http::HdrType::VARY
));
835 if (!strVary
.size() || strVary
[0] != '*') {
836 rep
->header
.putStr (Http::HdrType::VARY
, tempstr
);