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

PowerDNS / pdns / 14230500445

02 Apr 2025 01:53PM UTC coverage: 52.058% (-11.4%) from 63.455%
14230500445

push

github

web-flow
Merge pull request #15385 from rgacogne/ddist-enable-quiche-sni-tests

dnsdist: Enable the DoQ and DoH3 parts of the SNI tests in our CI

21502 of 68138 branches covered (31.56%)

Branch coverage included in aggregate %.

77059 of 121190 relevant lines covered (63.59%)

4345179.2 hits per line

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

34.55
/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) {
4,902✔
11
    return std::isspace(c) != 0;
4,902✔
12
  }
4,902✔
13

14
  bool isspace(char c, const std::locale& loc) {
1,548✔
15
    return std::isspace(c, loc);
1,548✔
16
  }
1,548✔
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) {
6,723✔
35
    return std::isalnum(c) != 0;
6,723✔
36
  }
6,723✔
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) {
255✔
44
    buffer.append(somedata);
255✔
45
    while(state < 2) {
900!
46
      int cr=0;
774✔
47
      pos = buffer.find_first_of("\n");
774✔
48
      // need to find CRLF in buffer
49
      if (pos == std::string::npos) return false;
774!
50
      if (pos>0 && buffer[pos-1]=='\r')
774!
51
        cr=1;
774✔
52
      std::string line(buffer.begin(), buffer.begin()+pos-cr); // exclude CRLF
774✔
53
      buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer including CRLF
774✔
54

55
      if (state == 0) { // startup line
774!
56
        if (target->kind == YAHTTP_TYPE_REQUEST) {
129!
57
          std::string ver;
×
58
          std::string tmpurl;
×
59
          std::istringstream iss(line);
×
60
          iss >> target->method >> tmpurl >> ver;
×
61
          if (ver.size() == 0)
×
62
            target->version = 9;
×
63
          else if (ver.find("HTTP/0.9") == 0)
×
64
            target->version = 9;
×
65
          else if (ver.find("HTTP/1.0") == 0)
×
66
            target->version = 10;
×
67
          else if (ver.find("HTTP/1.1") == 0)
×
68
            target->version = 11;
×
69
          else
×
70
            throw ParseError("HTTP version not supported");
×
71
          // uppercase the target method
72
          std::transform(target->method.begin(), target->method.end(), target->method.begin(), ::toupper);
×
73
          target->url.parse(tmpurl);
×
74
          target->getvars = Utility::parseUrlParameters(target->url.parameters);
×
75
          state = 1;
×
76
        } else if(target->kind == YAHTTP_TYPE_RESPONSE) {
129!
77
          std::string ver;
129✔
78
          std::istringstream iss(line);
129✔
79
          std::string::size_type pos1;
129✔
80
          iss >> ver >> target->status;
129✔
81
          std::getline(iss, target->statusText);
129✔
82
          pos1=0;
129✔
83
          while(pos1 < target->statusText.size() && YaHTTP::isspace(target->statusText.at(pos1))) pos1++;
258!
84
          target->statusText = target->statusText.substr(pos1); 
129✔
85
          if ((pos1 = target->statusText.find("\r")) != std::string::npos) {
129!
86
            target->statusText = target->statusText.substr(0, pos1-1);
×
87
          }
×
88
          if (ver.size() == 0) {
129!
89
            target->version = 9;
×
90
          } else if (ver.find("HTTP/0.9") == 0)
129!
91
            target->version = 9;
×
92
          else if (ver.find("HTTP/1.0") == 0)
129!
93
            target->version = 10;
129✔
94
          else if (ver.find("HTTP/1.1") == 0)
×
95
            target->version = 11;
×
96
          else
×
97
            throw ParseError("HTTP version not supported");
×
98
          state = 1;
129✔
99
        }
129✔
100
      } else if (state == 1) {
645!
101
        std::string key,value;
645✔
102
        size_t pos1;
645✔
103
        if (line.empty()) {
645!
104
          chunked = (target->headers.find("transfer-encoding") != target->headers.end() && target->headers["transfer-encoding"] == "chunked");
129!
105
          state = 2;
129✔
106
          break;
129✔
107
        }
129✔
108
        // split headers
109
        if ((pos1 = line.find(":")) == std::string::npos) {
516!
110
          throw ParseError("Malformed header line");
×
111
        }
×
112
        key = line.substr(0, pos1);
516✔
113
        value = line.substr(pos1 + 1);
516✔
114
        for(std::string::iterator it=key.begin(); it != key.end(); it++)
5,160!
115
          if (YaHTTP::isspace(*it))
4,644!
116
            throw ParseError("Header key contains whitespace which is not allowed by RFC");
×
117

118
        Utility::trim(value);
516✔
119
        std::transform(key.begin(), key.end(), key.begin(), ::tolower);
516✔
120
        // is it already defined
121

122
        if (key == "set-cookie" && target->kind == YAHTTP_TYPE_RESPONSE) {
516!
123
          target->jar.parseSetCookieHeader(value);
×
124
        } else if (key == "cookie" && target->kind == YAHTTP_TYPE_REQUEST) {
516!
125
          target->jar.parseCookieHeader(value);
×
126
        } else {
516✔
127
          if (key == "host" && target->kind == YAHTTP_TYPE_REQUEST) {
516!
128
            // maybe it contains port?
129
            if ((pos1 = value.find(":")) == std::string::npos) {
×
130
              target->url.host = value;
×
131
            } else {
×
132
              target->url.host = value.substr(0, pos1);
×
133
              target->url.port = ::atoi(value.substr(pos1).c_str());
×
134
            }
×
135
          }
×
136
          if (target->headers.find(key) != target->headers.end()) {
516!
137
            target->headers[key] = target->headers[key] + ";" + value;
×
138
          } else {
516✔
139
            target->headers[key] = std::move(value);
516✔
140
          }
516✔
141
        }
516✔
142
      }
516✔
143
    }
774✔
144

145
    minbody = 0;
255✔
146
    // check for expected body size
147
    if (target->kind == YAHTTP_TYPE_REQUEST) maxbody = target->max_request_size;
255!
148
    else if (target->kind == YAHTTP_TYPE_RESPONSE) maxbody = target->max_response_size;
255!
149
    else maxbody = 0;
×
150

151
    if (!chunked) {
255!
152
      if (target->headers.find("content-length") != target->headers.end()) {
255!
153
        std::istringstream maxbodyS(target->headers["content-length"]);
255✔
154
        maxbodyS >> minbody;
255✔
155
        maxbody = minbody;
255✔
156
      }
255✔
157
      if (minbody < 1) return true; // guess there isn't anything left.
255!
158
      if (target->kind == YAHTTP_TYPE_REQUEST && static_cast<ssize_t>(minbody) > target->max_request_size) throw ParseError("Max request body size exceeded");
255!
159
      else if (target->kind == YAHTTP_TYPE_RESPONSE && static_cast<ssize_t>(minbody) > target->max_response_size) throw ParseError("Max response body size exceeded");
255!
160
    }
255✔
161

162
    if (maxbody == 0) hasBody = false;
255!
163
    else hasBody = true;
255✔
164

165
    if (buffer.size() == 0) return ready();
255!
166

167
    while(buffer.size() > 0) {
258!
168
      if (chunked) {
129!
169
        if (chunk_size == 0) {
×
170
          char buf[100];
×
171
          // read chunk length
172
          if ((pos = buffer.find('\n')) == std::string::npos) return false;
×
173
          if (pos > 99)
×
174
            throw ParseError("Impossible chunk_size");
×
175
          buffer.copy(buf, pos);
×
176
          buf[pos]=0; // just in case...
×
177
          buffer.erase(buffer.begin(), buffer.begin()+pos+1); // remove line from buffer
×
178
          if (sscanf(buf, "%x", &chunk_size) != 1) {
×
179
            throw ParseError("Unable to parse chunk size");
×
180
          }
×
181
          if (chunk_size == 0) { state = 3; break; } // last chunk
×
182
          if (chunk_size > (std::numeric_limits<decltype(chunk_size)>::max() - 2)) {
×
183
            throw ParseError("Chunk is too large");
×
184
          }
×
185
        } else {
×
186
          int crlf=1;
×
187
          if (buffer.size() < static_cast<size_t>(chunk_size+1)) return false; // expect newline
×
188
          if (buffer.at(chunk_size) == '\r') {
×
189
            if (buffer.size() < static_cast<size_t>(chunk_size+2) || buffer.at(chunk_size+1) != '\n') return false; // expect newline after carriage return
×
190
            crlf=2;
×
191
          } else if (buffer.at(chunk_size) != '\n') return false;
×
192
          std::string tmp = buffer.substr(0, chunk_size);
×
193
          buffer.erase(buffer.begin(), buffer.begin()+chunk_size+crlf);
×
194
          bodybuf << tmp;
×
195
          chunk_size = 0;
×
196
          if (buffer.size() == 0) break; // just in case
×
197
        }
×
198
      } else {
129✔
199
        if (bodybuf.str().length() + buffer.length() > maxbody)
129!
200
          bodybuf << buffer.substr(0, maxbody - bodybuf.str().length());
×
201
        else
129✔
202
          bodybuf << buffer;
129✔
203
        buffer = "";
129✔
204
      }
129✔
205
    }
129✔
206

207
    if (chunk_size!=0) return false; // need more data
129!
208

209
    return ready();
129✔
210
  };
129✔
211

212
  void HTTPBase::write(std::ostream& os) const {
129✔
213
    if (kind == YAHTTP_TYPE_REQUEST) {
129!
214
      std::ostringstream getparmbuf;
129✔
215
      std::string getparms;
129✔
216
      // prepare URL
217
      for(strstr_map_t::const_iterator i = getvars.begin(); i != getvars.end(); i++) {
130✔
218
        getparmbuf << Utility::encodeURL(i->first, false) << "=" << Utility::encodeURL(i->second, false) << "&";
1✔
219
      }
1✔
220
      if (getparmbuf.str().length() > 0) {
129✔
221
        std::string buf = getparmbuf.str();
1✔
222
        getparms = "?" + std::string(buf.begin(), buf.end() - 1);
1✔
223
      }
1✔
224
      else
128✔
225
        getparms = "";
128✔
226
      os << method << " " << url.path << getparms << " HTTP/" << versionStr(this->version);
129✔
227
    } else if (kind == YAHTTP_TYPE_RESPONSE) {
129!
228
      os << "HTTP/" << versionStr(this->version) << " " << status << " ";
×
229
      if (statusText.empty())
×
230
        os << Utility::status2text(status);
×
231
      else
×
232
        os << statusText;
×
233
    }
×
234
    os << "\r\n";
129✔
235

236
    bool cookieSent = false;
129✔
237
    bool sendChunked = false;
129✔
238

239
    if (this->version > 10) { // 1.1 or better
129!
240
      if (headers.find("content-length") == headers.end() && !this->is_multipart) {
129!
241
        // must use chunked on response
242
        sendChunked = (kind == YAHTTP_TYPE_RESPONSE);
16✔
243
        if ((headers.find("transfer-encoding") != headers.end() && headers.find("transfer-encoding")->second != "chunked")) {
16!
244
          throw YaHTTP::Error("Transfer-encoding must be chunked, or Content-Length defined");
×
245
        }
×
246
        if ((headers.find("transfer-encoding") == headers.end() && kind == YAHTTP_TYPE_RESPONSE)) {
16!
247
          sendChunked = true;
×
248
          os << "Transfer-Encoding: chunked\r\n";
×
249
        }
×
250
      } else {
113✔
251
        sendChunked = false;
113✔
252
      }
113✔
253
    }
129✔
254

255
    // write headers
256
    strstr_map_t::const_iterator iter = headers.begin();
129✔
257
    while(iter != headers.end()) {
894✔
258
      if (iter->first == "host" && (kind != YAHTTP_TYPE_REQUEST || version < 10)) { iter++; continue; }
765!
259
      if (iter->first == "transfer-encoding" && sendChunked) { iter++; continue; }
765!
260
      std::string header = Utility::camelizeHeader(iter->first);
765✔
261
      if (header == "Cookie" || header == "Set-Cookie") cookieSent = true;
765!
262
      os << Utility::camelizeHeader(iter->first) << ": " << iter->second << "\r\n";
765✔
263
      iter++;
765✔
264
    }
765✔
265
    if (version > 9 && !cookieSent && jar.cookies.size() > 0) { // write cookies
129!
266
     if (kind == YAHTTP_TYPE_REQUEST) {
×
267
        bool first = true;
×
268
        os << "Cookie: ";
×
269
        for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) {
×
270
          if (first)
×
271
            first = false;
×
272
          else
×
273
            os << "; ";
×
274
          os << Utility::encodeURL(i->second.name) << "=" << Utility::encodeURL(i->second.value);
×
275
        }
×
276
     } else if (kind == YAHTTP_TYPE_RESPONSE) {
×
277
        for(strcookie_map_t::const_iterator i = jar.cookies.begin(); i != jar.cookies.end(); i++) {
×
278
          os << "Set-Cookie: ";
×
279
          os << i->second.str() << "\r\n";
×
280
        }
×
281
      }
×
282
    }
×
283
    os << "\r\n";
129✔
284
#ifdef HAVE_CPP_FUNC_PTR
129✔
285
    this->renderer(this, os, sendChunked);
129✔
286
#else
287
    SendbodyRenderer r;
288
    r(this, os, chunked)
289
#endif
290
  };
129✔
291

292
  std::ostream& operator<<(std::ostream& os, const Response &resp) {
×
293
    resp.write(os);
×
294
    return os;
×
295
  };
×
296

297
  std::istream& operator>>(std::istream& is, Response &resp) {
×
298
    YaHTTP::AsyncResponseLoader arl;
×
299
    arl.initialize(&resp);
×
300
    while(is.good()) {
×
301
      char buf[1024];
×
302
      is.read(buf, 1024);
×
303
      if (is.gcount()>0) { // did we actually read anything
×
304
        is.clear();
×
305
        if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed
×
306
      }
×
307
    }
×
308
    // throw unless ready
309
    if (arl.ready() == false)
×
310
      throw ParseError("Was not able to extract a valid Response from stream");
×
311
    arl.finalize();
×
312
    return is;
×
313
  };
×
314

315
  std::ostream& operator<<(std::ostream& os, const Request &req) {
129✔
316
    req.write(os);
129✔
317
    return os;
129✔
318
  };
129✔
319

320
  std::istream& operator>>(std::istream& is, Request &req) {
×
321
    YaHTTP::AsyncRequestLoader arl;
×
322
    arl.initialize(&req);
×
323
    while(is.good()) {
×
324
      char buf[1024];
×
325
      is.read(buf, 1024);
×
326
      if (is.gcount() > 0) { // did we actually read anything
×
327
        is.clear();
×
328
        if (arl.feed(std::string(buf, is.gcount())) == true) break; // completed
×
329
      }
×
330
    }
×
331
    if (arl.ready() == false)
×
332
      throw ParseError("Was not able to extract a valid Request from stream");
×
333
    arl.finalize();
×
334
    return is;
×
335
  };
×
336
};
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

© 2025 Coveralls, Inc