Coverage Report

Created: 2025-06-13 06:28

/src/pdns/ext/yahttp/yahttp/utility.hpp
Line
Count
Source (jump to first uncovered line)
1
#pragma once
2
3
#ifndef YAHTTP_MAX_REQUEST_LINE_SIZE
4
4.13k
#define YAHTTP_MAX_REQUEST_LINE_SIZE 8192
5
#endif
6
7
#ifndef YAHTTP_MAX_REQUEST_FIELDS
8
16.8k
#define YAHTTP_MAX_REQUEST_FIELDS 100
9
#endif
10
11
namespace YaHTTP {
12
  static const char *MONTHS[] = {0,"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec",0}; //<! List of months 
13
  static const char *DAYS[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat",0}; //<! List of days
14
15
  bool isspace(char c);
16
  bool isspace(char c, const std::locale& loc);
17
  bool isxdigit(char c);
18
  bool isxdigit(char c, const std::locale& loc);
19
  bool isdigit(char c);
20
  bool isdigit(char c, const std::locale& loc);
21
  bool isalnum(char c);
22
  bool isalnum(char c, const std::locale& loc);
23
24
  /*! Case-Insensitive NULL safe comparator for string maps */
25
  struct ASCIICINullSafeComparator {
26
3.08M
    bool operator() (const std::string& lhs, const std::string& rhs) const {
27
3.08M
      int v;
28
3.08M
      std::string::const_iterator lhi = lhs.begin();
29
3.08M
      std::string::const_iterator rhi = rhs.begin();
30
6.13M
      for(;lhi != lhs.end() && rhi != rhs.end(); lhi++, rhi++)
31
3.97M
        if ((v = ::tolower(*lhi) - ::tolower(*rhi)) != 0) return v<0; 
32
2.16M
      if (lhi == lhs.end() && rhi != rhs.end()) return true;
33
1.18M
      if (lhi != lhs.end() && rhi == rhs.end()) return false;
34
1.16M
      return false; // they are equal
35
1.18M
    }
36
  };
37
38
  typedef std::map<std::string,std::string,ASCIICINullSafeComparator> strstr_map_t; //<! String to String map
39
40
  /*! Represents a date/time with utc offset */
41
  class DateTime {
42
  public:
43
     bool isSet; //<! if this is initialized yet
44
45
     int year; //<! year, 0 is year 0, not 1900
46
47
     int month; //<! month, range 1-12
48
     int day; //<! day, range 1-31
49
     int wday; //<! week day, range 1-7
50
51
     int hours; //<! hours, range 0-23
52
     int minutes; //<! minutes, range 0-59
53
     int seconds; //<! seconds, range 0-60
54
55
     int utc_offset; //<! UTC offset with minutes (hhmm)
56
57
1.18M
     DateTime() { 
58
1.18M
       initialize();
59
1.18M
     }; //<! Construct and initialize
60
61
1.18M
     void initialize() {
62
1.18M
       isSet = false; 
63
1.18M
       year = month = day = wday = hours = minutes = seconds = utc_offset = 0;
64
1.18M
       month = 1; // it's invalid otherwise
65
1.18M
     }; //<! Creates year 0 date
66
67
0
     void setLocal() {
68
0
       fromLocaltime(time((time_t*)NULL)); 
69
0
     }; //<! sets current local time
70
71
0
     void setGm() {
72
0
       fromGmtime(time((time_t*)NULL));
73
0
     }; //<! sets current gmtime (almost UTC)
74
75
0
     void fromLocaltime(time_t t) {
76
0
#ifdef HAVE_LOCALTIME_R
77
0
       struct tm tm;
78
0
       localtime_r(&t, &tm);
79
0
       fromTm(&tm);
80
0
#else
81
0
       struct tm *tm;
82
0
       #error define HAVE_LOCALTIME_R
83
0
       tm = localtime(&t); // lgtm [cpp/potentially-dangerous-function]
84
0
       fromTm(tm);
85
0
#endif
86
0
#ifndef HAVE_TM_GMTOFF
87
0
       time_t t2;
88
0
# ifdef HAVE_LOCALTIME_R
89
0
       gmtime_r(&t, &tm);
90
0
       t2 = mktime(&tm);
91
0
# else
92
0
       #error define HAVE_LOCALTIME_R
93
0
       tm = gmtime(&t); // lgtm [cpp/potentially-dangerous-function]
94
0
       t2 = mktime(tm);
95
0
# endif
96
0
       this->utc_offset = ((t2-t)/10)*10; // removes any possible differences. 
97
0
#endif
98
0
     }; //<! uses localtime for time
99
100
0
     void fromGmtime(time_t t) {
101
0
#ifdef HAVE_GMTIME_R
102
0
       struct tm tm;
103
0
       gmtime_r(&t, &tm);
104
0
       fromTm(&tm);
105
0
#else
106
0
       struct tm *tm;
107
0
       #error define HAVE_GMTIME_R
108
0
       tm = gmtime(&t);// lgtm [cpp/potentially-dangerous-function]
109
0
       fromTm(tm);
110
0
#endif
111
0
#ifndef HAVE_TM_GMTOFF
112
0
       this->utc_offset = 0;
113
0
#endif
114
0
     }; //<! uses gmtime for time
115
116
0
     void fromTm(const struct tm *tm) {
117
0
       year = tm->tm_year + 1900;
118
0
       month = tm->tm_mon + 1;
119
0
       day = tm->tm_mday;
120
0
       hours = tm->tm_hour;
121
0
       minutes = tm->tm_min;
122
0
       seconds = tm->tm_sec;
123
0
       wday = tm->tm_wday;
124
#ifdef HAVE_TM_GMTOFF
125
       utc_offset = tm->tm_gmtoff;
126
#endif
127
0
       isSet = true;
128
0
     }; //<! parses date from struct tm 
129
130
0
     void validate() const {
131
0
       if (wday < 0 || wday > 6) throw std::range_error("Invalid date");
132
0
       if (month < 1 || month > 12) throw std::range_error("Invalid date");
133
0
       if (year < 0) throw std::range_error("Invalid date");
134
0
       if (hours < 0 || hours > 23 ||
135
0
           minutes < 0 || minutes > 59 ||
136
0
           seconds < 0 || seconds > 60) throw std::range_error("Invalid date");
137
0
     }; //<! make sure we are within ranges (not a *REAL* validation, just range check)
138
139
0
     std::string rfc_str() const {
140
0
       std::ostringstream oss;
141
0
       validate();
142
0
       oss << DAYS[wday] << ", " << std::setfill('0') << std::setw(2) << day << " " << MONTHS[month] << " " <<
143
0
          std::setfill('0') << std::setw(2) <<  year << " " << 
144
0
          std::setfill('0') << std::setw(2) << hours << ":" << 
145
0
          std::setfill('0') << std::setw(2) << minutes << ":" << 
146
0
          std::setfill('0') << std::setw(2) << seconds << " ";
147
0
       if (utc_offset>=0) oss << "+";
148
0
       else oss << "-";
149
0
       int tmp_off = ( utc_offset < 0 ? utc_offset*-1 : utc_offset ); 
150
0
       oss << std::setfill('0') << std::setw(2) << (tmp_off/3600);
151
0
       oss << std::setfill('0') << std::setw(2) << (tmp_off%3600)/60;
152
0
153
0
       return oss.str(); 
154
0
     }; //<! converts this date into a RFC-822 format
155
 
156
0
     std::string cookie_str() const {
157
0
       std::ostringstream oss;
158
0
       validate();
159
0
       oss << std::setfill('0') << std::setw(2) << day << "-" << MONTHS[month] << "-" << year << " " <<
160
0
         std::setfill('0') << std::setw(2) << hours << ":" << 
161
0
         std::setfill('0') << std::setw(2) << minutes << ":" << 
162
0
         std::setfill('0') << std::setw(2) << seconds << " GMT";
163
0
       return oss.str();
164
0
     }; //<! converts this date into a HTTP Cookie date
165
 
166
0
     void parse822(const std::string &rfc822_date) {
167
0
       struct tm tm;
168
0
       const char *ptr;
169
0
#ifdef HAVE_TM_GMTOFF
170
0
       if ( (ptr = strptime(rfc822_date.c_str(), "%a, %d %b %Y %T %z", &tm)) != NULL) {
171
0
#else
172
0
  if ( (ptr = strptime(rfc822_date.c_str(), "%a, %d %b %Y %T", &tm)) != NULL) {
173
0
          int sign;
174
0
      // parse the timezone parameter
175
0
          while(*ptr && YaHTTP::isspace(*ptr)) ptr++;
176
0
          if (*ptr == '+') sign = 0;
177
0
          else if (*ptr == '-') sign = -1;
178
0
          else throw YaHTTP::ParseError("Unparseable date");
179
0
          ptr++;
180
0
          utc_offset = ::atoi(ptr) * sign;
181
0
          while(*ptr != '\0' && YaHTTP::isdigit(*ptr)) ptr++;
182
0
#endif
183
0
          while(*ptr != '\0' && YaHTTP::isspace(*ptr)) ptr++;
184
0
          if (*ptr != '\0') throw YaHTTP::ParseError("Unparseable date"); // must be final.
185
0
          fromTm(&tm);
186
0
       } else {
187
0
          throw YaHTTP::ParseError("Unparseable date");
188
0
       }
189
0
     }; //<! parses RFC-822 date
190
191
0
     void parseCookie(const std::string &cookie_date) {
192
0
       struct tm tm;
193
0
       const char *ptr;
194
0
       if ( (ptr = strptime(cookie_date.c_str(), "%d-%b-%Y %T", &tm)) != NULL
195
#ifdef HAVE_TM_GMTOFF
196
          || (ptr = strptime(cookie_date.c_str(), "%d-%b-%Y %T %z", &tm)) != NULL
197
          || (ptr = strptime(cookie_date.c_str(), "%a, %d-%b-%Y %T %Z", &tm)) != NULL
198
#endif
199
0
          ) {
200
0
          while(*ptr != '\0' && ( YaHTTP::isspace(*ptr) || YaHTTP::isalnum(*ptr) )) ptr++;
201
0
          if (*ptr != '\0') throw YaHTTP::ParseError("Unparseable date (non-final)"); // must be final.
202
0
          fromTm(&tm);
203
0
          this->utc_offset = 0;
204
0
       } else {
205
0
          std::cout << cookie_date << std::endl;
206
0
          throw YaHTTP::ParseError("Unparseable date (did not match pattern cookie)");
207
0
       }
208
0
     }; //<! parses HTTP Cookie date
209
210
0
     time_t unixtime() const {
211
0
       struct tm tm;
212
0
       tm.tm_year = year-1900;
213
0
       tm.tm_mon = month-1;
214
0
       tm.tm_mday = day;
215
0
       tm.tm_hour = hours;
216
0
       tm.tm_min = minutes;
217
0
       tm.tm_sec = seconds;
218
0
       tm.tm_isdst = 0;
219
0
#ifdef HAVE_TM_GMTOFF
220
0
       tm.tm_gmtoff = utc_offset;
221
0
#endif
222
0
       return mktime(&tm);
223
0
     }; //<! returns this datetime as unixtime. will not work for dates before 1970/1/1 00:00:00 GMT
224
  };
225
226
  /*! Various helpers needed in the code */ 
227
  class Utility {
228
  public:
229
2.37M
    static std::string decodeURL(const std::string& component) {
230
2.37M
        std::string result = component;
231
2.37M
        size_t pos1,pos2;
232
2.37M
        pos2 = 0;
233
2.91M
        while((pos1 = result.find_first_of("%", pos2))!=std::string::npos) {
234
546k
           std::string code;
235
546k
           char a,b,c;
236
546k
           if (pos1 + 2 > result.length()) return result; // end of result
237
539k
           code = result.substr(pos1+1, 2);
238
539k
           a = std::tolower(code[0]); b = std::tolower(code[1]);
239
240
539k
           if ((( '0' > a || a > '9') && ('a' > a || a > 'f')) ||
241
539k
              (( '0' > b || b > '9') && ('a' > b || b > 'f'))) {
242
531k
              pos2 = pos1+3;
243
531k
              continue;
244
531k
           }
245
246
8.27k
           if ('0' <= a && a <= '9') a = a - '0';
247
3.91k
           else if ('a' <= a && a <= 'f') a = a - 'a' + 0x0a;
248
8.27k
           if ('0' <= b && b <= '9') b = b - '0';
249
3.93k
           else if ('a' <= b && b <= 'f') b = b - 'a' + 0x0a;
250
251
8.27k
           c = (a<<4)+b;
252
8.27k
           result = result.replace(pos1,3,1,c);
253
8.27k
           pos2=pos1;
254
8.27k
        }
255
2.36M
        return result;
256
2.37M
    }; //<! Decodes %xx from string into bytes
257
    
258
0
    static std::string encodeURL(const std::string& component, bool asUrl = true) {
259
0
      std::string result = component;
260
0
      std::string skip = "+-.:,&;_#%[]?/@(){}=";
261
0
      char repl[3];
262
0
      size_t pos;
263
0
      for(std::string::iterator iter = result.begin(); iter != result.end(); iter++) {
264
0
        if (!YaHTTP::isalnum(*iter) && (!asUrl || skip.find(*iter) == std::string::npos)) {
265
          // replace with different thing
266
0
          pos = std::distance(result.begin(), iter);
267
0
          ::snprintf(repl,3,"%02x", static_cast<unsigned char>(*iter));
268
0
          result = result.replace(pos, 1, "%", 1).insert(pos+1, repl, 2);
269
0
          iter = result.begin() + pos + 2;
270
0
        }
271
0
      }
272
0
      return result;
273
0
    }; //<! Escapes any characters into %xx representation when necessary, set asUrl to false to fully encode the url
274
275
0
    static std::string encodeURL(const std::wstring& component, bool asUrl = true) {
276
0
      unsigned char const *p = reinterpret_cast<unsigned char const*>(&component[0]);
277
0
      std::size_t s = component.size() * sizeof((*component.begin()));
278
0
      std::vector<unsigned char> vec(p, p+s);
279
0
280
0
      std::ostringstream result;
281
0
      std::string skip = "+-.,&;_#%[]?/@(){}=";
282
0
      for(std::vector<unsigned char>::iterator iter = vec.begin(); iter != vec.end(); iter++) {
283
0
        if (!YaHTTP::isalnum((char)*iter) && (!asUrl || skip.find((char)*iter) == std::string::npos)) {
284
0
          // bit more complex replace
285
0
          result << "%" << std::hex << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(*iter);
286
0
        } else result << (char)*iter;
287
0
      }
288
0
      return result.str();
289
0
    }; //<! Escapes any characters into %xx representation when necessary, set asUrl to false to fully encode the url, for wide strings, returns ordinary string
290
291
0
    static std::string status2text(int status) {
292
0
       switch(status) {
293
0
       case 200:
294
0
           return "OK";
295
0
       case 201:
296
0
           return "Created";
297
0
       case 202:
298
0
           return "Accepted";
299
0
       case 203:
300
0
           return "Non-Authoritative Information";
301
0
       case 204:
302
0
           return "No Content";
303
0
       case 205:
304
0
           return "Reset Content";
305
0
       case 206:
306
0
           return "Partial Content";
307
0
       case 300:
308
0
           return "Multiple Choices";
309
0
       case 301:
310
0
           return "Moved Permanently";
311
0
       case 302:
312
0
           return "Found";
313
0
       case 303:
314
0
           return "See Other";
315
0
       case 304:
316
0
           return "Not Modified";
317
0
       case 305:
318
0
           return "Use Proxy";
319
0
       case 307:
320
0
           return "Temporary Redirect";
321
0
       case 400:
322
0
           return "Bad Request";
323
0
       case 401:
324
0
           return "Unauthorized";
325
0
       case 402:
326
0
           return "Payment Required";
327
0
       case 403: 
328
0
           return "Forbidden";
329
0
       case 404:
330
0
           return "Not Found";
331
0
       case 405:
332
0
           return "Method Not Allowed";
333
0
       case 406:
334
0
           return "Not Acceptable";
335
0
       case 407:
336
0
           return "Proxy Authentication Required";
337
0
       case 408:
338
0
           return "Request Time-out";
339
0
       case 409:
340
0
           return "Conflict";
341
0
       case 410:
342
0
           return "Gone";
343
0
       case 411:
344
0
           return "Length Required";
345
0
       case 412:
346
0
           return "Precondition Failed";
347
0
       case 413:
348
0
           return "Request Entity Too Large";
349
0
       case 414:
350
0
           return "Request-URI Too Large";
351
0
       case 415:
352
0
           return "Unsupported Media Type";
353
0
       case 416:
354
0
           return "Requested range not satisfiable";
355
0
       case 417:
356
0
           return "Expectation Failed";
357
0
       case 422:
358
0
           return "Unprocessable Entity";
359
0
       case 500:
360
0
           return "Internal Server Error";
361
0
       case 501:
362
0
           return "Not Implemented";
363
0
       case 502:
364
0
           return "Bad Gateway";
365
0
       case 503:
366
0
           return "Service Unavailable";
367
0
       case 504:
368
0
           return "Gateway Time-out";
369
0
       case 505:
370
0
           return "HTTP Version not supported";
371
0
       default:
372
0
           return "Unknown Status";
373
0
       }
374
0
    }; //<! static HTTP codes to text mappings
375
376
4.13k
    static strstr_map_t parseUrlParameters(const std::string& parameters) {
377
4.13k
      if (parameters.size() > YAHTTP_MAX_REQUEST_LINE_SIZE) {
378
5
        return {};
379
5
      }
380
4.12k
      std::string::size_type pos = 0;
381
4.12k
      strstr_map_t parameter_map;
382
21.0k
      while (pos != std::string::npos) {
383
        // find next parameter start
384
21.0k
        std::string::size_type nextpos = parameters.find("&", pos);
385
21.0k
        std::string::size_type delim = parameters.find("=", pos);
386
21.0k
        if (delim > nextpos) {
387
15.1k
          delim = nextpos;
388
15.1k
        }
389
21.0k
        std::string key;
390
21.0k
        std::string value;
391
21.0k
        if (delim == std::string::npos) {
392
3.87k
          key = parameters.substr(pos);
393
17.1k
        } else {
394
17.1k
          key = parameters.substr(pos, delim-pos);
395
17.1k
          if (nextpos == std::string::npos) {
396
121
            value = parameters.substr(delim+1);
397
17.0k
          } else {
398
17.0k
            value = parameters.substr(delim+1, nextpos-delim-1);
399
17.0k
          }
400
17.1k
        }
401
21.0k
        if (key.empty()) {
402
          // no parameters at all
403
3.39k
          break;
404
3.39k
        }
405
17.6k
        parameter_map[decodeURL(key)] = decodeURL(value);
406
17.6k
        if (nextpos == std::string::npos) {
407
          // no more parameters left
408
726
          break;
409
726
        }
410
16.8k
        if (parameter_map.size() >= YAHTTP_MAX_REQUEST_FIELDS) {
411
5
          break;
412
5
        }
413
414
16.8k
        pos = nextpos+1;
415
16.8k
      }
416
4.12k
      return parameter_map;
417
4.13k
    }; //<! parses URL parameters into string map 
418
419
130
    static bool iequals(const std::string& a, const std::string& b, size_t length) {
420
130
      std::string::const_iterator ai, bi;
421
130
      size_t i;
422
3.50k
      for(ai = a.begin(), bi = b.begin(), i = 0; ai != a.end() && bi != b.end() && i < length; ai++,bi++,i++) {
423
3.38k
        if (::toupper(*ai) != ::toupper(*bi)) return false;
424
3.38k
      }
425
426
118
      if (ai == a.end() && bi == b.end()) return true;
427
118
      if ((ai == a.end() && bi != b.end()) ||
428
118
          (ai != a.end() && bi == b.end())) return false;
429
      
430
100
      return ::toupper(*ai) == ::toupper(*bi);
431
118
    }; //<! case-insensitive comparison with length
432
433
0
    static bool iequals(const std::string& a, const std::string& b) {
434
0
      if (a.size() != b.size()) return false;
435
0
      return iequals(a,b,a.size());
436
0
    }; //<! case-insensitive comparison
437
438
26.3k
    static void trimLeft(std::string &str) {
439
26.3k
       const std::locale &loc = std::locale::classic();
440
26.3k
       std::string::iterator iter = str.begin();
441
27.6k
       while(iter != str.end() && YaHTTP::isspace(*iter, loc)) iter++;
442
26.3k
       str.erase(str.begin(), iter);
443
26.3k
    }; //<! removes whitespace from left
444
445
26.3k
    static void trimRight(std::string &str) {
446
26.3k
       const std::locale &loc = std::locale::classic();
447
26.3k
       std::string::reverse_iterator iter = str.rbegin();
448
29.1k
       while(iter != str.rend() && YaHTTP::isspace(*iter, loc)) iter++;
449
26.3k
       str.erase(iter.base(), str.end());
450
26.3k
    }; //<! removes whitespace from right
451
452
26.3k
    static void trim(std::string &str) {
453
26.3k
       trimLeft(str);
454
26.3k
       trimRight(str);
455
26.3k
    }; //<! removes whitespace from left and right
456
457
0
    static std::string camelizeHeader(const std::string &str) {
458
0
       std::string::const_iterator iter = str.begin();
459
0
       std::string result;
460
0
       const std::locale &loc = std::locale::classic();
461
462
0
       bool doNext = true;
463
464
0
       while(iter != str.end()) {
465
0
         if (doNext) 
466
0
            result.insert(result.end(), std::toupper(*iter, loc));
467
0
         else 
468
0
            result.insert(result.end(), std::tolower(*iter, loc)); 
469
0
         doNext = (*(iter++) == '-');
470
0
       }
471
472
0
       return result;
473
0
   }; //<! camelizes headers, such as, content-type => Content-Type
474
  };
475
};