xref: /trafficserver/plugins/esi/lib/Variables.cc (revision af0ad4a1)
1 /** @file
2 
3   A brief file description
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 #include "Variables.h"
25 #include "Attribute.h"
26 #include "Utils.h"
27 
28 #include <cerrno>
29 
30 using std::list;
31 using std::pair;
32 using std::string;
33 using namespace EsiLib;
34 
35 const string Variables::EMPTY_STRING("");
36 const string Variables::TRUE_STRING("true");
37 const string Variables::VENDOR_STRING("vendor");
38 const string Variables::VERSION_STRING("version");
39 const string Variables::PLATFORM_STRING("platform");
40 const string Variables::SIMPLE_HEADERS[] = {string("HOST"), string("REFERER"), string("")};
41 
42 const string Variables::SPECIAL_HEADERS[] = {string("ACCEPT-LANGUAGE"), string("COOKIE"), string("USER-AGENT"),
43                                              string("QUERY_STRING"), string("")};
44 
45 const string Variables::NORM_SIMPLE_HEADERS[] = {string("HTTP_HOST"), string("HTTP_REFERER"), string("")};
46 
47 const string Variables::NORM_SPECIAL_HEADERS[] = {string("HTTP_ACCEPT_LANGUAGE"), string("HTTP_COOKIE"), string("HTTP_USER_AGENT"),
48                                                   string("QUERY_STRING"),         string("HTTP_HEADER"), string("")};
49 
50 inline string &
_toUpperCase(string & str) const51 Variables::_toUpperCase(string &str) const
52 {
53   for (char &i : str) {
54     if ((i >= 'a') && (i <= 'z')) {
55       i = 'A' + (i - 'a');
56     }
57   }
58   return str;
59 }
60 
61 inline int
_searchHeaders(const string headers[],const char * name,int name_len) const62 Variables::_searchHeaders(const string headers[], const char *name, int name_len) const
63 {
64   int curr_header_size;
65   for (int i = 0; (curr_header_size = static_cast<int>(headers[i].size())); ++i) {
66     if ((name_len == curr_header_size) && (strncasecmp(headers[i].data(), name, curr_header_size) == 0)) {
67       return i;
68     }
69   }
70   return -1;
71 }
72 
73 inline void
_insert(StringHash & hash,const std::string & key,const std::string & value)74 Variables::_insert(StringHash &hash, const std::string &key, const std::string &value)
75 {
76   std::pair<StringHash::iterator, bool> result = hash.insert(StringHash::value_type(key, value));
77   if (!result.second) {
78     result.first->second = value;
79   }
80 }
81 
82 void
populate(const HttpHeader & header)83 Variables::populate(const HttpHeader &header)
84 {
85   if (header.name && header.name_len && header.value && header.value_len) {
86     // doing this because we can't change our const input arg
87     int name_len  = (header.name_len == -1) ? strlen(header.name) : header.name_len;
88     int value_len = (header.value_len == -1) ? strlen(header.value) : header.value_len;
89 
90     // we need to save the cookie string to build the jar from
91     if ((name_len == 6) && (strncasecmp(header.name, "Cookie", 6) == 0)) {
92       _releaseCookieJar();
93       if (_cookie_str.size()) {
94         _cookie_str.append(", ");
95       }
96       _cookie_str.append(header.value, value_len);
97     }
98 
99     if (_headers_parsed) {
100       _parseHeader(header.name, name_len, header.value, value_len);
101     } else {
102       int match_index = _searchHeaders(SIMPLE_HEADERS, header.name, name_len);
103       if (match_index != -1) {
104         _cached_simple_headers[match_index].push_back(string(header.value, value_len));
105       } else {
106         match_index = _searchHeaders(SPECIAL_HEADERS, header.name, name_len);
107         if (match_index != -1) {
108           _cached_special_headers[match_index].push_back(string(header.value, value_len));
109         }
110       }
111     }
112     _insert(_dict_data[HTTP_HEADER], string(header.name, name_len), string(header.value, value_len));
113   }
114 }
115 
116 inline void
_parseSimpleHeader(SimpleHeader hdr,const string & value)117 Variables::_parseSimpleHeader(SimpleHeader hdr, const string &value)
118 {
119   _debugLog(_debug_tag, "[%s] Inserting value for simple header [%s]", __FUNCTION__, SIMPLE_HEADERS[hdr].c_str());
120   _simple_data[NORM_SIMPLE_HEADERS[hdr]] = value;
121 }
122 
123 inline void
_parseSimpleHeader(SimpleHeader hdr,const char * value,int value_len)124 Variables::_parseSimpleHeader(SimpleHeader hdr, const char *value, int value_len)
125 {
126   _parseSimpleHeader(hdr, string(value, value_len));
127 }
128 
129 void
_parseSpecialHeader(SpecialHeader hdr,const char * value,int value_len)130 Variables::_parseSpecialHeader(SpecialHeader hdr, const char *value, int value_len)
131 {
132   switch (hdr) {
133   case HTTP_ACCEPT_LANGUAGE:
134     _parseAcceptLangString(value, value_len);
135     break;
136   case HTTP_COOKIE:
137     _parseCookieString(value, value_len);
138     break;
139   case HTTP_USER_AGENT:
140     _parseUserAgentString(value, value_len);
141     break;
142   default:
143     _debugLog(_debug_tag, "[%s] Skipping unrecognized header", __FUNCTION__);
144     break;
145   }
146 }
147 
148 void
_parseHeader(const char * name,int name_len,const char * value,int value_len)149 Variables::_parseHeader(const char *name, int name_len, const char *value, int value_len)
150 {
151   int match_index = _searchHeaders(SIMPLE_HEADERS, name, name_len);
152   if (match_index != -1) {
153     _parseSimpleHeader(static_cast<SimpleHeader>(match_index), value, value_len);
154   } else {
155     match_index = _searchHeaders(SPECIAL_HEADERS, name, name_len);
156     if (match_index != -1) {
157       _parseSpecialHeader(static_cast<SpecialHeader>(match_index), value, value_len);
158     } else {
159       _debugLog(_debug_tag, "[%s] Unrecognized header [%.*s]", __FUNCTION__, value_len, value);
160     }
161   }
162 }
163 
164 void
_parseQueryString(const char * query_string,int query_string_len)165 Variables::_parseQueryString(const char *query_string, int query_string_len)
166 {
167   _insert(_simple_data, string("QUERY_STRING"), string(query_string, query_string_len));
168   AttributeList attr_list;
169   Utils::parseAttributes(query_string, query_string_len, attr_list, "&");
170   for (auto &iter : attr_list) {
171     _debugLog(_debug_tag, "[%s] Inserting query string variable [%.*s] with value [%.*s]", __FUNCTION__, iter.name_len, iter.name,
172               iter.value_len, iter.value);
173     _insert(_dict_data[QUERY_STRING], string(iter.name, iter.name_len), string(iter.value, iter.value_len));
174   }
175 }
176 
177 void
_parseCachedHeaders()178 Variables::_parseCachedHeaders()
179 {
180   _debugLog(_debug_tag, "[%s] Parsing headers", __FUNCTION__);
181   for (int i = 0; i < N_SIMPLE_HEADERS; ++i) {
182     for (Utils::HeaderValueList::iterator value_iter = _cached_simple_headers[i].begin();
183          value_iter != _cached_simple_headers[i].end(); ++value_iter) {
184       _parseSimpleHeader(static_cast<SimpleHeader>(i), *value_iter);
185     }
186   }
187   for (int i = 0; i < N_SPECIAL_HEADERS; ++i) {
188     for (Utils::HeaderValueList::iterator value_iter = _cached_special_headers[i].begin();
189          value_iter != _cached_special_headers[i].end(); ++value_iter) {
190       _parseSpecialHeader(static_cast<SpecialHeader>(i), value_iter->data(), value_iter->size());
191     }
192   }
193 }
194 
195 const std::string &
getValue(const string & name) const196 Variables::getValue(const string &name) const
197 {
198   if (!_headers_parsed || !_query_string_parsed) {
199     // we need to do this because we want to
200     // 1) present const getValue() to clients
201     // 2) parse lazily (only on demand)
202     Variables &non_const_self = const_cast<Variables &>(*this);
203     if (!_headers_parsed) {
204       non_const_self._parseCachedHeaders();
205       non_const_self._headers_parsed = true;
206     }
207     if (!_query_string_parsed) {
208       int query_string_size = static_cast<int>(_query_string.size());
209       if (query_string_size) {
210         non_const_self._parseQueryString(_query_string.data(), query_string_size);
211         non_const_self._query_string_parsed = true;
212       }
213     }
214   }
215   string search_key(name);
216   _toUpperCase(search_key);
217   StringHash::const_iterator iter = _simple_data.find(search_key);
218   if (iter != _simple_data.end()) {
219     _debugLog(_debug_tag, "[%s] Found value [%.*s] for variable [%.*s] in simple data", __FUNCTION__, iter->second.size(),
220               iter->second.data(), name.size(), name.data());
221     return iter->second;
222   }
223   const char *header;
224   int header_len;
225   const char *attr;
226   int attr_len;
227   if (!_parseDictVariable(name, header, header_len, attr, attr_len)) {
228     _debugLog(_debug_tag, "[%s] Unmatched simple variable [%.*s] not in dict variable form", __FUNCTION__, name.size(),
229               name.data());
230     return EMPTY_STRING;
231   }
232   int dict_index = _searchHeaders(NORM_SPECIAL_HEADERS, header, header_len); // ignore the HTTP_ prefix
233   if (dict_index == -1) {
234     _debugLog(_debug_tag, "[%s] Dict variable [%.*s] refers to unknown dictionary", __FUNCTION__, name.size(), name.data());
235     return EMPTY_STRING;
236   }
237 
238   // Disallow Cookie retrieval though HTTP_HEADER
239   if (dict_index == HTTP_HEADER && ((attr_len == 6) && (strncasecmp(attr, "Cookie", 6) == 0))) {
240     _errorLog("[%s] Cannot use HTTP_HEADER to retrieve Cookie", __FUNCTION__);
241     return EMPTY_STRING;
242   }
243 
244   // change variable name to use only the attribute field
245   search_key.assign(attr, attr_len);
246 
247   iter = _dict_data[dict_index].find(search_key);
248 
249   if (dict_index == HTTP_ACCEPT_LANGUAGE) {
250     _debugLog(_debug_tag, "[%s] Returning boolean literal for lang variable [%.*s]", __FUNCTION__, search_key.size(),
251               search_key.data());
252     return (iter == _dict_data[dict_index].end()) ? EMPTY_STRING : TRUE_STRING;
253   }
254 
255   if (iter != _dict_data[dict_index].end()) {
256     _debugLog(_debug_tag, "[%s] Found variable [%.*s] in %s dictionary with value [%.*s]", __FUNCTION__, search_key.size(),
257               search_key.data(), NORM_SPECIAL_HEADERS[dict_index].c_str(), iter->second.size(), iter->second.data());
258     return iter->second;
259   }
260 
261   size_t cookie_part_divider = (dict_index == HTTP_COOKIE) ? search_key.find(';') : search_key.size();
262   if (cookie_part_divider && (cookie_part_divider < (search_key.size() - 1))) {
263     _debugLog(_debug_tag, "[%s] Cookie variable [%s] refers to sub cookie", __FUNCTION__, search_key.c_str());
264     return _getSubCookieValue(search_key, cookie_part_divider);
265   }
266 
267   _debugLog(_debug_tag, "[%s] Found no value for dict variable [%s]", __FUNCTION__, name.c_str());
268   return EMPTY_STRING;
269 }
270 
271 void
_parseSubCookies()272 Variables::_parseSubCookies()
273 {
274   StringHash &cookies = _dict_data[HTTP_COOKIE];
275   for (StringHash::const_iterator it_cookie = cookies.begin(); it_cookie != cookies.end(); ++it_cookie) {
276     const std::string &name  = it_cookie->first;
277     const std::string &value = it_cookie->second;
278     if (strchr(value.c_str(), '=') == nullptr) {
279       continue;
280     }
281 
282     StringHash &subcookies = _sub_cookies[name];
283     AttributeList attr_list;
284     Utils::parseAttributes(value.c_str(), value.length(), attr_list, "&");
285     for (auto &iter : attr_list) {
286       _debugLog(_debug_tag, "[%s] Inserting query string variable [%.*s] with value [%.*s]", __FUNCTION__, iter.name_len, iter.name,
287                 iter.value_len, iter.value);
288       _insert(subcookies, string(iter.name, iter.name_len), string(iter.value, iter.value_len));
289     }
290   }
291 }
292 
293 const string &
_getSubCookieValue(const string & cookie_str,size_t cookie_part_divider) const294 Variables::_getSubCookieValue(const string &cookie_str, size_t cookie_part_divider) const
295 {
296   if (!_cookie_jar_created) {
297     if (_cookie_str.size() == 0) {
298       _debugLog(_debug_tag, "[%s] Cookie string empty; nothing to construct jar from", __FUNCTION__);
299       return EMPTY_STRING;
300     }
301 
302     Variables &non_const_self = const_cast<Variables &>(*this); // same reasoning as in getValue()
303     non_const_self._parseSubCookies();
304     non_const_self._cookie_jar_created = true;
305   }
306 
307   // we need to do this as we are going to manipulate the 'divider'
308   // character, and we don't need to create a copy of the string for
309   // that; hence this shortcut
310   string &non_const_cookie_str = const_cast<string &>(cookie_str);
311   StringHash::const_iterator it_part;
312 
313   non_const_cookie_str[cookie_part_divider] = '\0';                        // make sure cookie name is NULL terminated
314   const char *cookie_name                   = non_const_cookie_str.data(); /* above NULL will take effect */
315   const char *part_name                     = non_const_cookie_str.c_str() + cookie_part_divider + 1;
316 
317   StringKeyHash<StringHash>::const_iterator it_cookie = _sub_cookies.find(cookie_name);
318   if (it_cookie == _sub_cookies.end()) {
319     _debugLog(_debug_tag, "[%s] Could not find value for cookie [%s]", __FUNCTION__, cookie_name);
320     goto fail;
321   }
322 
323   it_part = it_cookie->second.find(part_name);
324   if (it_part == it_cookie->second.end()) {
325     _debugLog(_debug_tag, "[%s] Could not find value for part [%s] of cookie [%.*s]", __FUNCTION__, part_name, cookie_part_divider,
326               cookie_name);
327     goto fail;
328   }
329 
330   _debugLog(_debug_tag, "[%s] Got value [%s] for cookie name [%.*s] and part [%s]", __FUNCTION__, it_part->second.c_str(),
331             cookie_part_divider, cookie_name, part_name);
332 
333   non_const_cookie_str[cookie_part_divider] = ';'; // restore before returning
334 
335   this->_cached_sub_cookie_value.assign(it_part->second);
336   return this->_cached_sub_cookie_value;
337 
338 fail:
339   non_const_cookie_str[cookie_part_divider] = ';'; // restore before returning
340   return EMPTY_STRING;
341 }
342 
343 void
clear()344 Variables::clear()
345 {
346   _simple_data.clear();
347   for (int i = 0; i < N_SPECIAL_HEADERS; ++i) {
348     _dict_data[i].clear();
349     _cached_special_headers[i].clear();
350   }
351   for (auto &_cached_simple_header : _cached_simple_headers) {
352     _cached_simple_header.clear();
353   }
354   _query_string.clear();
355   _headers_parsed = _query_string_parsed = false;
356   _cookie_str.clear();
357   _releaseCookieJar();
358 }
359 
360 void
_parseCookieString(const char * str,int str_len)361 Variables::_parseCookieString(const char *str, int str_len)
362 {
363   AttributeList cookies;
364   Utils::parseAttributes(str, str_len, cookies, ";,");
365   for (auto &iter : cookies) {
366     std::string cookie = iter.name;
367     size_t pos         = cookie.find('=');
368 
369     if (pos != std::string::npos) {
370       cookie = cookie.substr(0, pos);
371     }
372 
373     bool found = false;
374     for (auto &_whitelistCookie : _whitelistCookies) {
375       if ((_whitelistCookie == "*") || (_whitelistCookie == cookie)) {
376         found = true;
377       }
378     }
379 
380     if (found == true) {
381       _insert(_dict_data[HTTP_COOKIE], string(iter.name, iter.name_len), string(iter.value, iter.value_len));
382       _debugLog(_debug_tag, "[%s] Inserted cookie with name [%.*s] and value [%.*s]", __FUNCTION__, iter.name_len, iter.name,
383                 iter.value_len, iter.value);
384     }
385   }
386 }
387 
388 void
_parseUserAgentString(const char *,int)389 Variables::_parseUserAgentString(const char * /* str ATS_UNUSED */, int /* str_len ATS_UNUSED */)
390 {
391   /*
392   string user_agent_str(str, str_len); // need NULL-terminated version
393   // TODO - code was here
394   char version_buf[64];
395   // TODO - code was here
396   _insert(_dict_data[HTTP_USER_AGENT], VERSION_STRING, version_buf);
397   */
398 }
399 
400 void
_parseAcceptLangString(const char * str,int str_len)401 Variables::_parseAcceptLangString(const char *str, int str_len)
402 {
403   int i;
404   for (i = 0; (i < str_len) && ((isspace(str[i]) || str[i] == ',')); ++i) {
405     ;
406   }
407   const char *lang = str + i;
408   int lang_len;
409   for (; i <= str_len; ++i) {
410     if ((i == str_len) || (str[i] == ',')) {
411       lang_len = str + i - lang;
412       for (; lang_len && isspace(lang[lang_len - 1]); --lang_len) {
413         ;
414       }
415       if (lang_len) {
416         _insert(_dict_data[HTTP_ACCEPT_LANGUAGE], string(lang, lang_len), EMPTY_STRING);
417         _debugLog(_debug_tag, "[%s] Added language [%.*s]", __FUNCTION__, lang_len, lang);
418       }
419       for (; (i < str_len) && ((isspace(str[i]) || str[i] == ',')); ++i) {
420         ;
421       }
422       lang = str + i;
423     }
424   }
425 }
426 
427 bool
_parseDictVariable(const std::string & variable,const char * & header,int & header_len,const char * & attr,int & attr_len) const428 Variables::_parseDictVariable(const std::string &variable, const char *&header, int &header_len, const char *&attr,
429                               int &attr_len) const
430 {
431   const char *var_ptr = variable.data();
432   int var_size        = variable.size();
433   if ((var_size <= 4) || (variable[var_size - 1] != '}')) {
434     return false;
435   }
436   int paranth_index = -1;
437   for (int i = 0; i < (var_size - 1); ++i) {
438     if (variable[i] == '{') {
439       if (paranth_index != -1) {
440         _debugLog(_debug_tag, "[%s] Cannot have multiple parenthesis in dict variable [%.*s]", __FUNCTION__, var_size, var_ptr);
441         return false;
442       }
443       paranth_index = i;
444     }
445     if (variable[i] == '}') {
446       _debugLog(_debug_tag, "[%s] Cannot have multiple parenthesis in dict variable [%.*s]", __FUNCTION__, var_size, var_ptr);
447       return false;
448     }
449   }
450   if (paranth_index == -1) {
451     _debugLog(_debug_tag, "[%s] Could not find opening parenthesis in variable [%.*s]", __FUNCTION__, var_size, var_ptr);
452     return false;
453   }
454   if (paranth_index == 0) {
455     _debugLog(_debug_tag, "[%s] Dict variable has no dict name [%.*s]", __FUNCTION__, var_size, var_ptr);
456     return false;
457   }
458   if (paranth_index == (var_size - 2)) {
459     _debugLog(_debug_tag, "[%s] Dict variable has no attribute name [%.*s]", __FUNCTION__, var_size, var_ptr);
460     return false;
461   }
462   header     = var_ptr;
463   header_len = paranth_index;
464   attr       = var_ptr + paranth_index + 1;
465   attr_len   = var_size - header_len - 2;
466   return true;
467 }
468