]> git.ipfire.org Git - thirdparty/squid.git/blob - src/esi/VarState.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / esi / VarState.cc
1 /*
2 * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
3 *
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.
7 */
8
9 /* DEBUG: section 86 ESI processing */
10
11 #include "squid.h"
12 #include "esi/VarState.h"
13 #include "fatal.h"
14 #include "HttpReply.h"
15
16 char const *ESIVariableUserAgent::esiUserOs[]= {
17 "WIN",
18 "MAC",
19 "UNIX",
20 "OTHER"
21 };
22
23 char const * esiBrowsers[]= {"MSIE",
24 "MOZILLA",
25 "OTHER"
26 };
27
28 CBDATA_CLASS_INIT(ESIVarState);
29
30 void
31 ESIVarState::Variable::eval (ESIVarState &state, char const *subref, char const *found_default) const
32 {
33 /* No-op. We swallow it */
34
35 if (found_default)
36 ESISegment::ListAppend (state.getOutput(), found_default, strlen (found_default));
37 }
38
39 void
40 ESIVarState::hostUsed()
41 {
42 flags.host = 1;
43 }
44
45 void
46 ESIVarState::cookieUsed()
47 {
48 flags.cookie = 1;
49 }
50
51 void
52 ESIVarState::languageUsed()
53 {
54 flags.language = 1;
55 }
56
57 void
58 ESIVarState::refererUsed()
59 {
60 flags.referer = 1;
61 }
62
63 void
64 ESIVarState::useragentUsed()
65 {
66 flags.useragent = 1;
67 }
68
69 HttpHeader &
70 ESIVarState::header()
71 {
72 return hdr;
73 }
74
75 ESISegment::Pointer &
76 ESIVarState::getOutput()
77 {
78 return output;
79 }
80
81 char const *
82 ESIVariableQuery::queryString() const
83 {
84 return query_string;
85 }
86
87 struct _query_elem const *
88 ESIVariableQuery::queryVector() const {
89 return query;
90 }
91
92 size_t const &
93 ESIVariableQuery::queryElements() const
94 {
95 return query_elements;
96 }
97
98 void
99 ESIVarState::feedData (const char *buf, size_t len)
100 {
101 /* TODO: if needed - tune to skip segment iteration */
102 debugs (86,6, "esiVarState::feedData: accepting " << len << " bytes");
103 ESISegment::ListAppend (input, buf, len);
104 }
105
106 ESISegment::Pointer
107 ESIVarState::extractList()
108 {
109 doIt();
110 ESISegment::Pointer rv = output;
111 output = NULL;
112 debugs(86, 6, "ESIVarStateExtractList: Extracted list");
113 return rv;
114 }
115
116 char *
117 ESIVarState::extractChar ()
118 {
119 if (!input.getRaw())
120 fatal ("Attempt to extract variable state with no data fed in \n");
121
122 doIt();
123
124 char *rv = output->listToChar();
125
126 ESISegmentFreeList (output);
127
128 debugs(86, 6, "ESIVarStateExtractList: Extracted char");
129
130 return rv;
131 }
132
133 ESIVarState::~ESIVarState()
134 {
135 // freeResources
136 input = NULL;
137 ESISegmentFreeList(output);
138 hdr.clean();
139
140 while (!variablesForCleanup.empty()) {
141 delete variablesForCleanup.back();
142 variablesForCleanup.pop_back();
143 }
144
145 delete defaultVariable;
146 }
147
148 char *
149 ESIVariableUserAgent::getProductVersion (char const *s)
150 {
151 char const *t;
152 int len;
153 t = index(s,'/');
154
155 if (!t || !*(++t))
156 return xstrdup("");
157
158 len = strcspn(t, " \r\n()<>@,;:\\\"/[]?={}");
159
160 return xstrndup(t, len + 1);
161 }
162
163 ESIVariableQuery::ESIVariableQuery(char const *uri) : query (NULL), query_sz (0), query_elements (0), query_string (NULL)
164 {
165 /* Count off the query elements */
166 char const *query_start = strchr (uri, '?');
167
168 if (query_start && query_start[1] != '\0' ) {
169 unsigned int n;
170 query_string = xstrdup(query_start + 1);
171 query_elements = 1;
172 char const *query_pos = query_start + 1;
173
174 while ((query_pos = strchr(query_pos, '&'))) {
175 ++query_elements;
176 ++query_pos;
177 }
178
179 query = (_query_elem *)memReallocBuf(query, query_elements * sizeof (struct _query_elem),
180 &query_sz);
181 query_pos = query_start + 1;
182 n = 0;
183
184 while (query_pos) {
185 char const *next = strchr(query_pos, '&');
186 char const *div = strchr(query_pos, '=');
187
188 if (next)
189 ++next;
190
191 assert (n < query_elements);
192
193 if (!div)
194 div = next;
195
196 if (!(div - query_pos + 1))
197 /* zero length between & and = or & and & */
198 continue;
199
200 query[n].var = xstrndup(query_pos, div - query_pos + 1) ;
201
202 if (div == next) {
203 query[n].val = xstrdup("");
204 } else {
205 query[n].val = xstrndup(div + 1, next - div - 1);
206 }
207
208 query_pos = next;
209 ++n;
210 }
211 } else {
212 query_string = xstrdup("");
213 }
214
215 if (query) {
216 unsigned int n = 0;
217 debugs(86, 6, "esiVarStateNew: Parsed Query string: '" << uri << "'");
218
219 while (n < query_elements) {
220 debugs(86, 6, "esiVarStateNew: Parsed Query element " << n + 1 << " '" << query[n].var << "'='" << query[n].val << "'");
221 ++n;
222 }
223 }
224 }
225
226 ESIVariableQuery::~ESIVariableQuery()
227 {
228 if (query) {
229 unsigned int i;
230
231 for (i = 0; i < query_elements; ++i) {
232 safe_free(query[i].var);
233 safe_free(query[i].val);
234 }
235
236 memFreeBuf (query_sz, query);
237 }
238
239 safe_free (query_string);
240 }
241
242 ESIVarState::ESIVarState(HttpHeader const *aHeader, char const *uri) :
243 output(NULL),
244 hdr(hoReply)
245 {
246 memset(&flags, 0, sizeof(flags));
247
248 /* TODO: only grab the needed headers */
249 /* Note that as we pass these through to included requests, we
250 * cannot trim them */
251 hdr.append(aHeader);
252
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!
256 */
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));
264 }
265
266 void
267 ESIVarState::removeVariable (String const &name)
268 {
269 Variable *candidate = static_cast <Variable *>(variables.find (name.rawBuf(), name.size()));
270
271 if (candidate) {
272 /* XXX: remove me */
273 /* Note - this involves:
274 * extend libTrie to have a remove() call.
275 * delete from the vector.
276 * delete the object.
277 */
278 }
279 }
280
281 void
282 ESIVarState::addVariable(char const *name, size_t len, Variable *aVariable)
283 {
284 String temp;
285 temp.limitInit (name, len);
286 removeVariable (temp);
287 variables.add(name, len, aVariable);
288 variablesForCleanup.push_back(aVariable);
289 }
290
291 ESIVariableUserAgent::~ESIVariableUserAgent()
292 {
293 safe_free (browserversion);
294 }
295
296 ESIVariableUserAgent::ESIVariableUserAgent(ESIVarState &state)
297 {
298 /* An example:
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
304 *
305 * Useing 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.
308 *
309 * Because we can't parse under those rules and get real-world useful answers, we follow the following
310 * algorithm:
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
317 * /version field.
318 * if MOZILLA doesn't appear, the browser is set to OTHER.
319 * In future, this may be better implemented as a regexp.
320 */
321
322 if (state.header().has(Http::HdrType::USER_AGENT)) {
323 char const *s = state.header().getStr(Http::HdrType::USER_AGENT);
324 UserOs = identifyOs(s);
325 char const *t, *t1;
326
327 /* Now the browser and version */
328
329 if ((t = strstr (s, "MSIE"))) {
330 browser = ESI_BROWSER_MSIE;
331 t = index (t, ' ');
332
333 if (!t)
334 browserversion = xstrdup("");
335 else {
336 t1 = index(t, ';');
337
338 if (!t1)
339 browserversion = xstrdup(t + 1);
340 else
341 browserversion = xstrndup(t + 1, t1-t);
342 }
343 } else if (strstr (s, "Mozilla")) {
344 browser = ESI_BROWSER_MOZILLA;
345 browserversion = getProductVersion(s);
346 } else {
347 browser = ESI_BROWSER_OTHER;
348 browserversion = getProductVersion(s);
349 }
350 } else {
351 UserOs = ESI_OS_OTHER;
352 browser = ESI_BROWSER_OTHER;
353 browserversion = xstrdup("");
354 }
355 }
356
357 ESIVariableUserAgent::esiUserOs_t
358 ESIVariableUserAgent::identifyOs(char const *s) const
359 {
360 if (!s)
361 return ESI_OS_OTHER;
362
363 if (strstr (s, "Windows"))
364 return ESI_OS_WIN;
365 else if (strstr (s, "Mac"))
366 return ESI_OS_MAC;
367 else if (strstr (s, "nix") || strstr (s, "BSD"))
368 return ESI_OS_UNIX;
369 else
370 return ESI_OS_OTHER;
371 }
372
373 void
374 ESIVariableCookie::eval (ESIVarState &state, char const *subref, char const *found_default) const
375 {
376 const char *s = NULL;
377 state.cookieUsed();
378
379 if (state.header().has(Http::HdrType::COOKIE)) {
380 if (!subref)
381 s = state.header().getStr (Http::HdrType::COOKIE);
382 else {
383 String S = state.header().getListMember (Http::HdrType::COOKIE, subref, ';');
384
385 if (S.size())
386 ESISegment::ListAppend (state.getOutput(), S.rawBuf(), S.size());
387 else if (found_default)
388 ESISegment::ListAppend (state.getOutput(), found_default, strlen (found_default));
389 }
390 } else
391 s = found_default;
392
393 if (s)
394 ESISegment::ListAppend (state.getOutput(), s, strlen (s));
395 }
396
397 void
398 ESIVariableHost::eval (ESIVarState &state, char const *subref, char const *found_default) const
399 {
400 const char *s = NULL;
401 state.hostUsed();
402
403 if (!subref && state.header().has(Http::HdrType::HOST)) {
404 s = state.header().getStr (Http::HdrType::HOST);
405 } else
406 s = found_default;
407
408 ESISegment::ListAppend (state.getOutput(), s, strlen (s));
409 }
410
411 void
412 ESIVariableLanguage::eval (ESIVarState &state, char const *subref, char const *found_default) const
413 {
414 char const *s = NULL;
415 state.languageUsed();
416
417 if (state.header().has(Http::HdrType::ACCEPT_LANGUAGE)) {
418 if (!subref) {
419 String S (state.header().getList (Http::HdrType::ACCEPT_LANGUAGE));
420 ESISegment::ListAppend (state.getOutput(), S.rawBuf(), S.size());
421 } else {
422 if (state.header().hasListMember (Http::HdrType::ACCEPT_LANGUAGE, subref, ',')) {
423 s = "true";
424 } else {
425 s = "false";
426 }
427
428 ESISegment::ListAppend (state.getOutput(), s, strlen (s));
429 }
430 } else {
431 s = found_default;
432 ESISegment::ListAppend (state.getOutput(), s, strlen (s));
433 }
434 }
435
436 void
437 ESIVariableQuery::eval (ESIVarState &state, char const *subref, char const *found_default) const
438 {
439 char const *s = NULL;
440
441 if (!subref)
442 s = queryString();
443 else {
444 unsigned int i = 0;
445
446 while (i < queryElements() && !s) {
447 if (!strcmp (subref, queryVector()[i].var))
448 s = queryVector()[i].val;
449
450 ++i;
451 }
452
453 if (!s)
454 s = found_default;
455 }
456
457 ESISegment::ListAppend (state.getOutput(), s, strlen (s));
458 }
459
460 void
461 ESIVariableReferer::eval (ESIVarState &state, char const *subref, char const *found_default) const
462 {
463 const char *s = NULL;
464 state.refererUsed();
465
466 if (!subref && state.header().has(Http::HdrType::REFERER))
467 s = state.header().getStr (Http::HdrType::REFERER);
468 else
469 s = found_default;
470
471 ESISegment::ListAppend (state.getOutput(), s, strlen (s));
472 }
473
474 void
475 ESIVariableUserAgent::eval (ESIVarState &state, char const *subref, char const *found_default) const
476 {
477 char const *s = NULL;
478 state.useragentUsed();
479
480 if (state.header().has(Http::HdrType::USER_AGENT)) {
481 if (!subref)
482 s = state.header().getStr (Http::HdrType::USER_AGENT);
483 else {
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();
490 } else
491 s = "";
492 }
493 } else
494 s = found_default;
495
496 ESISegment::ListAppend (state.getOutput(), s, strlen (s));
497 }
498
499 /* thoughts on long term:
500 * get $
501 * get () handler
502 * hand off to handler.
503 * one handler for variables.
504 * one handler for each function.
505 */
506
507 class ESIVariableProcessor;
508
509 class ESIFunction
510 {
511
512 public:
513 static ESIFunction *GetFunction (char const *symbol, ESIVariableProcessor &);
514 ESIFunction(ESIVariableProcessor &);
515 void doIt();
516
517 private:
518 ESIVariableProcessor &processor;
519
520 };
521
522 ESIFunction::ESIFunction(ESIVariableProcessor &aProcessor) : processor(aProcessor)
523 {}
524
525 ESIFunction *
526 ESIFunction::GetFunction(char const *symbol, ESIVariableProcessor &aProcessor)
527 {
528 if (*symbol == '(')
529 return new ESIFunction(aProcessor);
530
531 return NULL;
532 }
533
534 class ESIVariableProcessor
535 {
536
537 public:
538 ESIVariableProcessor(char *, ESISegment::Pointer &, Trie &, ESIVarState *);
539 ~ESIVariableProcessor();
540 void doIt();
541
542 private:
543 bool validChar (char c);
544 void eval (ESIVarState::Variable *var, char const *subref, char const *foundDefault );
545 void doFunction();
546 void identifyFunction();
547 char *string;
548 ESISegment::Pointer &output;
549 Trie &variables;
550 ESIVarState *varState;
551 int state;
552 size_t len;
553 size_t pos;
554 size_t var_pos;
555 size_t done_pos;
556 char * found_subref;
557 char *found_default;
558 ESIVarState::Variable *vartype;
559 ESIFunction *currentFunction;
560 };
561
562 void
563 ESIVariableProcessor::eval (ESIVarState::Variable *var, char const *subref, char const *foundDefault )
564 {
565 assert (var);
566
567 if (!foundDefault)
568 foundDefault = "";
569
570 var->eval (*varState, subref, foundDefault);
571 }
572
573 bool
574 ESIVariableProcessor::validChar (char c)
575 {
576 if (('A' <= c && c <= 'Z') ||
577 ('a' <= c && c <= 'z') ||
578 '_' == c || '-' == c)
579 return true;
580
581 return false;
582 }
583
584 ESIVarState::Variable *
585 ESIVarState::GetVar(char const *symbol, int len)
586 {
587 assert (symbol);
588
589 void *result = variables.find (symbol, len);
590
591 if (result)
592 return static_cast<Variable *>(result);
593
594 return defaultVariable;
595 }
596
597 void
598 ESIVarState::doIt ()
599 {
600 char *string = input->listToChar();
601 ESISegmentFreeList (input);
602 ESIVariableProcessor theProcessor(string, output, variables, this);
603 theProcessor.doIt();
604 safe_free(string);
605 }
606
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 (NULL),
611 found_default (NULL), currentFunction(NULL)
612 {
613 len = strlen (string);
614 vartype = varState->GetVar("",0);
615 }
616
617 void
618 ESIFunction::doIt()
619 {}
620
621 /* because we are only used to process:
622 * - include URL's
623 * - non-esi elements
624 * - choose clauses
625 * buffering is ok - we won't delay the start of async activity, or
626 * of output data preparation
627 */
628 /* Should make these an enum or something...
629 */
630 void
631 ESIVariableProcessor::doFunction()
632 {
633 if (!currentFunction)
634 return;
635
636 /* stay in here whilst operating */
637 while (pos < len && state)
638 switch (state) {
639
640 case 2: /* looking for variable name */
641
642 if (!validChar(string[pos])) {
643 /* not a variable name char */
644
645 if (pos - var_pos) {
646 vartype = varState->GetVar (string + var_pos, pos - var_pos);
647 }
648
649 state = 3;
650 } else {
651 ++pos;
652 }
653
654 break;
655
656 case 3: /* looking for variable subref, end bracket or default indicator */
657
658 if (string[pos] == ')') {
659 /* end of string */
660 eval(vartype, found_subref, found_default);
661 done_pos = ++pos;
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 */
669 var_pos = ++pos;
670 state = 4;
671 } else if (!found_default && string[pos] == '|') {
672 debugs(86, 6, "esiVarStateDoIt: Default present");
673 /* extract default value */
674 state = 5;
675 var_pos = ++pos;
676 } else {
677 /* unexpected char, not a variable after all */
678 debugs(86, 6, "esiVarStateDoIt: unexpected char after varname");
679 state = LOOKFORSTART;
680 pos = done_pos + 2;
681 }
682
683 break;
684
685 case 4: /* looking for variable subref */
686
687 if (string[pos] == '}') {
688 /* end of subref */
689 found_subref = xstrndup (&string[var_pos], pos - var_pos + 1);
690 debugs(86, 6, "esiVarStateDoIt: found end of variable subref '" << found_subref << "'");
691 state = 3;
692 ++pos;
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;
698 pos = done_pos + 2;
699 } else {
700 ++pos;
701 }
702
703 break;
704
705 case 5: /* looking for a default value */
706
707 if (string[pos] == '\'') {
708 /* begins with a quote */
709 debugs(86, 6, "esiVarStateDoIt: found quoted default");
710 state = 6;
711 var_pos = ++pos;
712 } else {
713 /* doesn't */
714 debugs(86, 6, "esiVarStateDoIt: found unquoted default");
715 state = 7;
716 ++pos;
717 }
718
719 break;
720
721 case 6: /* looking for a quote terminate default value */
722
723 if (string[pos] == '\'') {
724 /* end of default */
725 found_default = xstrndup (&string[var_pos], pos - var_pos + 1);
726 debugs(86, 6, "esiVarStateDoIt: found end of quoted default '" << found_default << "'");
727 state = 3;
728 }
729
730 ++pos;
731 break;
732
733 case 7: /* looking for } terminate default value */
734
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);
740 done_pos = ++pos;
741 safe_free(found_default);
742 safe_free(found_subref);
743 state = LOOKFORSTART;
744 }
745
746 ++pos;
747 break;
748
749 default:
750 fatal("esiVarStateDoIt: unexpected state\n");
751 }
752 }
753
754 void
755 ESIVariableProcessor::identifyFunction()
756 {
757 delete currentFunction;
758 currentFunction = ESIFunction::GetFunction (&string[pos], *this);
759
760 if (!currentFunction) {
761 state = LOOKFORSTART;
762 } else {
763 state = 2; /* process a function */
764 /* advance past function name */
765 var_pos = ++pos;
766 }
767 }
768
769 void
770 ESIVariableProcessor::doIt()
771 {
772 assert (output == NULL);
773
774 while (pos < len) {
775 /* skipping pre-variables */
776
777 if (string[pos] != '$') {
778 ++pos;
779 } else {
780 if (pos - done_pos)
781 /* extract known plain text */
782 ESISegment::ListAppend (output, string + done_pos, pos - done_pos);
783
784 done_pos = pos;
785
786 ++pos;
787
788 identifyFunction();
789
790 doFunction();
791 }
792 }
793
794 /* pos-done_pos chars are ready to copy */
795 if (pos-done_pos)
796 ESISegment::ListAppend (output, string+done_pos, pos - done_pos);
797
798 safe_free (found_default);
799
800 safe_free (found_subref);
801 }
802
803 ESIVariableProcessor::~ESIVariableProcessor()
804 {
805 delete currentFunction;
806 }
807
808 /* XXX FIXME: this should be comma delimited, no? */
809 void
810 ESIVarState::buildVary (HttpReply *rep)
811 {
812 char tempstr[1024];
813 tempstr[0]='\0';
814
815 if (flags.language)
816 strcat (tempstr, "Accept-Language ");
817
818 if (flags.cookie)
819 strcat (tempstr, "Cookie ");
820
821 if (flags.host)
822 strcat (tempstr, "Host ");
823
824 if (flags.referer)
825 strcat (tempstr, "Referer ");
826
827 if (flags.useragent)
828 strcat (tempstr, "User-Agent ");
829
830 if (!tempstr[0])
831 return;
832
833 String strVary (rep->header.getList (Http::HdrType::VARY));
834
835 if (!strVary.size() || strVary[0] != '*') {
836 rep->header.putStr (Http::HdrType::VARY, tempstr);
837 }
838 }
839