• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 24774517049

22 Apr 2026 10:56AM UTC coverage: 63.023% (+0.02%) from 63.004%
24774517049

push

github

web-flow
Merge pull request #17191 from miodvallat/50x.sa-2026-05

auth 5.0: backport SA 2026-05 fixes

13321 of 27924 branches covered (47.7%)

Branch coverage included in aggregate %.

62 of 168 new or added lines in 9 files covered. (36.9%)

15 existing lines in 2 files now uncovered.

42459 of 60584 relevant lines covered (70.08%)

9140862.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.28
/ext/yahttp/yahttp/reqresp.cpp
1
#include "yahttp.hpp"
2

3
#include <limits>
4

5
namespace YaHTTP {
6

7
  template class AsyncLoader<Request>;
8
  template class AsyncLoader<Response>;
9

10
  bool isspace(char c) {
111,028✔
11
    return std::isspace(c) != 0;
111,028✔
12
  }
111,028✔
13

14
  bool isspace(char c, const std::locale& loc) {
34,368✔
15
    return std::isspace(c, loc);
34,368✔
16
  }
34,368✔
17

18
  bool isxdigit(char c) {
×
19
    return std::isxdigit(c) != 0;
×
20
  }
×
21

22
  bool isxdigit(char c, const std::locale& loc) {
×
23
    return std::isxdigit(c, loc);
×
24
  }
×
25

26
  bool isdigit(char c) {
×
27
    return std::isdigit(c) != 0;
×
28
  }
×
29

30
  bool isdigit(char c, const std::locale& loc) {
×
31
    return std::isdigit(c, loc);
×
32
  }
×
33

34
  bool isalnum(char c) {
8,682✔
35
    return std::isalnum(c) != 0;
8,682✔
36
  }
8,682✔
37

38
  bool isalnum(char c, const std::locale& loc) {
×
39
    return std::isalnum(c, loc);
×
40
  }
×
41

42
  template <class T>
43
  bool AsyncLoader<T>::feed(const std::string& somedata)
44
  {
2,111✔
45
    if (state < 2) {
2,111!
46
      headersize += somedata.length(); // maye include some body data, we don't know yet...
1,900✔
47
      if (headersize > target->max_header_size) {
1,900!
NEW
48
        if (target->kind == YAHTTP_TYPE_REQUEST) {
×
NEW
49
          throw ParseError("Request header too large");
×
NEW
50
        }
×
NEW
51
        else {
×
NEW
52
          throw ParseError("Response header too large");
×
NEW
53
        }
×
NEW
54
      }
×
55
    }
1,900✔
56
    buffer.append(somedata);
2,111✔
57
    while(state < 2) {
15,463!
58
      int cr=0;
15,252✔
59
      pos = buffer.find_first_of("\n");
15,252✔
60
      // need to find CRLF in buffer
61
      if (pos == std::string::npos) return false;
15,252!
62
      if (pos>0 && buffer[pos-1]=='\r')
15,248!
63
        cr=1;
15,248✔
64
      std::string line(buffer.begin(), buffer.begin()+pos-cr); // exclude CRLF
15,248✔
65
      buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer including CRLF
15,248✔
66

67
      if (state == 0) { // startup line
15,248✔
68
        if (target->kind == YAHTTP_TYPE_REQUEST) {
1,896!
69
          std::string ver;
1,624✔
70
          std::string tmpurl;
1,624✔
71
          std::istringstream iss(line);
1,624✔
72
          iss >> target->method >> tmpurl >> ver;
1,624✔
73
          if (ver.size() == 0)
1,624!
74
            target->version = 9;
×
75
          else if (ver.find("HTTP/0.9") == 0)
1,624!
76
            target->version = 9;
×
77
          else if (ver.find("HTTP/1.0") == 0)
1,624!
78
            target->version = 10;
4✔
79
          else if (ver.find("HTTP/1.1") == 0)
1,620!
80
            target->version = 11;
1,620✔
81
          else
×
82
            throw ParseError("HTTP version not supported");
×
83
          // uppercase the target method
84
          std::transform(target->method.begin(), target->method.end(), target->method.begin(), ::toupper);
1,624✔
85
          target->url.parse(tmpurl);
1,624✔
86
          target->getvars = Utility::parseUrlParameters(target->url.parameters);
1,624✔
87
          state = 1;
1,624✔
88
        } else if(target->kind == YAHTTP_TYPE_RESPONSE) {
1,896!
89
          std::string ver;
272✔
90
          std::istringstream iss(line);
272✔
91
          std::string::size_type pos1;
272✔
92
          iss >> ver >> target->status;
272✔
93
          std::getline(iss, target->statusText);
272✔
94
          pos1=0;
272✔
95
          while(pos1 < target->statusText.size() && YaHTTP::isspace(target->statusText.at(pos1))) pos1++;
544!
96
          target->statusText = target->statusText.substr(pos1); 
272✔
97
          if ((pos1 = target->statusText.find("\r")) != std::string::npos) {
272!
98
            target->statusText = target->statusText.substr(0, pos1-1);
×
99
          }
×
100
          if (ver.size() == 0) {
272!
101
            target->version = 9;
×
102
          } else if (ver.find("HTTP/0.9") == 0)
272!
103
            target->version = 9;
×
104
          else if (ver.find("HTTP/1.0") == 0)
272!
105
            target->version = 10;
272✔
106
          else if (ver.find("HTTP/1.1") == 0)
×
107
            target->version = 11;
×
108
          else
×
109
            throw ParseError("HTTP version not supported");
×
110
          state = 1;
272✔
111
        }
272✔
112
      } else if (state == 1) {
13,352!
113
        std::string key,value;
13,352✔
114
        size_t pos1;
13,352✔
115
        if (line.empty()) {
13,352✔
116
          chunked = (target->headers.find("transfer-encoding") != target->headers.end() && target->headers["transfer-encoding"] == "chunked");
1,896!
117
          state = 2;
1,896✔
118
          break;
1,896✔
119
        }
1,896✔
120
        // split headers
121
        if ((pos1 = line.find(":")) == std::string::npos) {
11,456!
122
          throw ParseError("Malformed header line");
×
123
        }
×
124
        key = line.substr(0, pos1);
11,456✔
125
        value = line.substr(pos1 + 1);
11,456✔
126
        for(std::string::iterator it=key.begin(); it != key.end(); it++)
121,940✔
127
          if (YaHTTP::isspace(*it))
110,484!
128
            throw ParseError("Header key contains whitespace which is not allowed by RFC");
×
129

130
        Utility::trim(value);
11,456✔
131
        std::transform(key.begin(), key.end(), key.begin(), ::tolower);
11,456✔
132
        // is it already defined
133

134
        if (key == "set-cookie" && target->kind == YAHTTP_TYPE_RESPONSE) {
11,456!
135
          target->jar.parseSetCookieHeader(value);
×
136
        } else if (key == "cookie" && target->kind == YAHTTP_TYPE_REQUEST) {
11,456!
137
          target->jar.parseCookieHeader(value);
×
138
        } else {
11,456✔
139
          if (key == "host" && target->kind == YAHTTP_TYPE_REQUEST) {
11,456!
140
            // maybe it contains port?
141
            if ((pos1 = value.find(":")) == std::string::npos) {
1,620!
142
              target->url.host = value;
×
143
            } else {
1,620✔
144
              target->url.host = value.substr(0, pos1);
1,620✔
145
              target->url.port = ::atoi(value.substr(pos1).c_str());
1,620✔
146
            }
1,620✔
147
          }
1,620✔
148
          if (target->headers.find(key) != target->headers.end()) {
11,456!
149
            target->headers[key] = target->headers[key] + ";" + value;
×
150
          } else {
11,456✔
151
            target->headers[key] = std::move(value);
11,456✔
152
          }
11,456✔
153
        }
11,456✔
154
      }
11,456✔
155
    }
15,248✔
156

157
    minbody = 0;
2,107✔
158
    // check for expected body size
159
    if (target->kind == YAHTTP_TYPE_REQUEST) maxbody = target->max_request_size;
2,107!
160
    else if (target->kind == YAHTTP_TYPE_RESPONSE) maxbody = target->max_response_size;
483!
161
    else maxbody = 0;
×
162

163
    if (!chunked) {
2,107!
164
      if (target->headers.find("content-length") != target->headers.end()) {
2,107!
165
        std::istringstream maxbodyS(target->headers["content-length"]);
1,677✔
166
        maxbodyS >> minbody;
1,677✔
167
        maxbody = minbody;
1,677✔
168
      }
1,677✔
169
      if (minbody < 1) return true; // guess there isn't anything left.
2,107!
170
      if (target->kind == YAHTTP_TYPE_REQUEST && minbody > target->max_request_size) throw ParseError("Max request body size exceeded");
1,576!
171
      else if (target->kind == YAHTTP_TYPE_RESPONSE && minbody > target->max_response_size) throw ParseError("Max response body size exceeded");
1,576!
172
    }
1,576✔
173

174
    if (maxbody == 0) hasBody = false;
1,576!
175
    else hasBody = true;
1,576✔
176

177
    if (buffer.size() == 0) return ready();
1,576!
178

179
    while(buffer.size() > 0) {
2,730✔
180
      if (chunked) {
1,365!
181
        if (chunk_size == 0) {
×
182
          char buf[100];
×
183
          // read chunk length
184
          if ((pos = buffer.find('\n')) == std::string::npos) return false;
×
185
          if (pos > 99)
×
186
            throw ParseError("Impossible chunk_size");
×
187
          buffer.copy(buf, pos);
×
188
          buf[pos]=0; // just in case...
×
189
          buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer
×
NEW
190
          if (sscanf(buf, "%zx", &chunk_size) != 1) {
×
191
            throw ParseError("Unable to parse chunk size");
×
192
          }
×
193
          if (chunk_size == 0) { state = 3; break; } // last chunk
×
NEW
194
          if (chunk_size > (std::numeric_limits<decltype(chunk_size)>::max() - 2) || chunk_size > maxbody) {
×
195
            throw ParseError("Chunk is too large");
×
196
          }
×
197
        } else {
×
198
          int crlf=1;
×
NEW
199
          if (buffer.size() < chunk_size+1) return false; // expect newline
×
200
          if (buffer.at(chunk_size) == '\r') {
×
NEW
201
            if (buffer.size() < chunk_size+2 || buffer.at(chunk_size+1) != '\n') return false; // expect newline after carriage return
×
202
            crlf=2;
×
203
          } else if (buffer.at(chunk_size) != '\n') return false;
×
NEW
204
          if (bodybuf.str().length() + chunk_size > maxbody) {
×
NEW
205
            throw ParseError("Chunked body is too large");
×
NEW
206
          }
×
207
          std::string tmp = buffer.substr(0, chunk_size);
×
208
          buffer.erase(buffer.begin(), buffer.begin()+chunk_size+crlf);
×
209
          bodybuf << tmp;
×
210
          chunk_size = 0;
×
211
          if (buffer.size() == 0) break; // just in case
×
212
        }
×
213
      } else {
1,365✔
214
        if (bodybuf.str().length() + buffer.length() > maxbody)
1,365!
215
          bodybuf << buffer.substr(0, maxbody - bodybuf.str().length());
×
216
        else
1,365✔
217
          bodybuf << buffer;
1,365✔
218
        buffer = "";
1,365✔
219
      }
1,365✔
220
    }
1,365✔
221

222
    if (chunk_size!=0) return false; // need more data
1,365!
223

224
    return ready();
1,365✔
225
  };
1,365✔
226

227
  void HTTPBase::write(std::ostream& os) const {
1,896✔
228
    if (kind == YAHTTP_TYPE_REQUEST) {
1,896✔
229
      std::ostringstream getparmbuf;
272✔
230
      std::string getparms;
272✔
231
      // prepare URL
232
      for(strstr_map_t::const_iterator i = getvars.begin(); i != getvars.end(); i++) {
273✔
233
        getparmbuf << Utility::encodeURL(i->first, false) << "=" << Utility::encodeURL(i->second, false) << "&";
1✔
234
      }
1✔
235
      if (getparmbuf.str().length() > 0) {
272✔
236
        std::string buf = getparmbuf.str();
1✔
237
        getparms = "?" + std::string(buf.begin(), buf.end() - 1);
1✔
238
      }
1✔
239
      else
271✔
240
        getparms = "";
271✔
241
      os << method << " " << url.path << getparms << " HTTP/" << versionStr(this->version);
272✔
242
    } else if (kind == YAHTTP_TYPE_RESPONSE) {
1,896!
243
      os << "HTTP/" << versionStr(this->version) << " " << status << " ";
1,624✔
244
      if (statusText.empty())
1,624!
245
        os << Utility::status2text(status);
1,624✔
246
      else
×
247
        os << statusText;
×
248
    }
1,624✔
249
    os << "\r\n";
1,896✔
250

251
    bool cookieSent = false;
1,896✔
252
    bool sendChunked = false;
1,896✔
253

254
    if (this->version > 10) { // 1.1 or better
1,896!
255
      if (headers.find("content-length") == headers.end() && !this->is_multipart) {
1,896!
256
        // must use chunked on response
257
        sendChunked = (kind == YAHTTP_TYPE_RESPONSE);
151✔
258
        if ((headers.find("transfer-encoding") != headers.end() && headers.find("transfer-encoding")->second != "chunked")) {
151!
259
          throw YaHTTP::Error("Transfer-encoding must be chunked, or Content-Length defined");
×
260
        }
×
261
        if ((headers.find("transfer-encoding") == headers.end() && kind == YAHTTP_TYPE_RESPONSE)) {
151!
262
          sendChunked = true;
×
263
          os << "Transfer-Encoding: chunked\r\n";
×
264
        }
×
265
      } else {
1,872✔
266
        sendChunked = false;
1,745✔
267
      }
1,745✔
268
    }
1,896✔
269

270
    // write headers
271
    strstr_map_t::const_iterator iter = headers.begin();
1,896✔
272
    while(iter != headers.end()) {
17,422✔
273
      if (iter->first == "host" && (kind != YAHTTP_TYPE_REQUEST || version < 10)) { iter++; continue; }
15,526!
274
      if (iter->first == "transfer-encoding" && sendChunked) { iter++; continue; }
15,526!
275
      std::string header = Utility::camelizeHeader(iter->first);
15,526✔
276
      if (header == "Cookie" || header == "Set-Cookie") cookieSent = true;
15,526!
277
      os << Utility::camelizeHeader(iter->first) << ": " << iter->second << "\r\n";
15,526✔
278
      iter++;
15,526✔
279
    }
15,526✔
280
    if (version > 9 && !cookieSent && jar.cookies.size() > 0) { // write cookies
1,896!
281
     if (kind == YAHTTP_TYPE_REQUEST) {
×
282
        bool first = true;
×
283
        os << "Cookie: ";
×
284
        for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) {
×
285
          if (first)
×
286
            first = false;
×
287
          else
×
288
            os << "; ";
×
289
          os << Utility::encodeURL(i->second.name) << "=" << Utility::encodeURL(i->second.value);
×
290
        }
×
291
     } else if (kind == YAHTTP_TYPE_RESPONSE) {
×
292
        for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) {
×
293
          os << "Set-Cookie: ";
×
294
          os << i->second.str() << "\r\n";
×
295
        }
×
296
      }
×
297
    }
×
298
    os << "\r\n";
1,896✔
299
#ifdef HAVE_CPP_FUNC_PTR
1,896✔
300
    this->renderer(this, os, sendChunked);
1,896✔
301
#else
302
    SendbodyRenderer r;
303
    r(this, os, chunked)
304
#endif
305
  };
1,896✔
306

307
  std::ostream& operator<<(std::ostream& os, const Response &resp) {
×
308
    resp.write(os);
×
309
    return os;
×
310
  };
×
311

312
  std::istream& operator>>(std::istream& is, Response &resp) {
×
313
    YaHTTP::AsyncResponseLoader arl;
×
314
    arl.initialize(&resp);
×
315
    while(is.good()) {
×
316
      char buf[1024];
×
317
      is.read(buf, 1024);
×
318
      if (is.gcount()>0) { // did we actually read anything
×
319
        is.clear();
×
320
        if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed
×
321
      }
×
322
    }
×
323
    // throw unless ready
324
    if (arl.ready() == false)
×
325
      throw ParseError("Was not able to extract a valid Response from stream");
×
326
    arl.finalize();
×
327
    return is;
×
328
  };
×
329

330
  std::ostream& operator<<(std::ostream& os, const Request &req) {
272✔
331
    req.write(os);
272✔
332
    return os;
272✔
333
  };
272✔
334

335
  std::istream& operator>>(std::istream& is, Request &req) {
×
336
    YaHTTP::AsyncRequestLoader arl;
×
337
    arl.initialize(&req);
×
338
    while(is.good()) {
×
339
      char buf[1024];
×
340
      is.read(buf, 1024);
×
341
      if (is.gcount() > 0) { // did we actually read anything
×
342
        is.clear();
×
343
        if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed
×
344
      }
×
345
    }
×
346
    if (arl.ready() == false)
×
347
      throw ParseError("Was not able to extract a valid Request from stream");
×
348
    arl.finalize();
×
349
    return is;
×
350
  };
×
351
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc