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

PowerDNS / pdns / 19957761886

05 Dec 2025 08:52AM UTC coverage: 73.063% (-0.07%) from 73.131%
19957761886

push

github

web-flow
Merge pull request #16590 from rgacogne/ddist-coverity-20251204

dnsdist: Fix missed optimizations reported by Coverity in config

38511 of 63422 branches covered (60.72%)

Branch coverage included in aggregate %.

3 of 5 new or added lines in 1 file covered. (60.0%)

13941 existing lines in 122 files now uncovered.

128037 of 164529 relevant lines covered (77.82%)

5473411.95 hits per line

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

44.44
/modules/remotebackend/httpconnector.cc
1
/*
2
 * This file is part of PowerDNS or dnsdist.
3
 * Copyright -- PowerDNS.COM B.V. and its contributors
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of version 2 of the GNU General Public License as
7
 * published by the Free Software Foundation.
8
 *
9
 * In addition, for the avoidance of any doubt, permission is granted to
10
 * link this program with OpenSSL and to (re)distribute the binaries
11
 * produced as the result of such linking.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
 */
22
#ifdef HAVE_CONFIG_H
23
#include "config.h"
24
#endif
25
#include "remotebackend.hh"
26
#include <sys/socket.h>
27
#include <unistd.h>
28
#include <fcntl.h>
29

30
#include <sstream>
31
#include "pdns/lock.hh"
32

33
#ifndef UNIX_PATH_MAX
34
#define UNIX_PATH_MAX 108
35
#endif
36

37
HTTPConnector::HTTPConnector(std::map<std::string, std::string> options) :
38
  d_socket(nullptr)
UNCOV
39
{
23✔
40

UNCOV
41
  if (options.find("url") == options.end()) {
23!
42
    throw PDNSException("Cannot find 'url' option in the remote backend HTTP connector's parameters");
×
43
  }
×
44

UNCOV
45
  this->d_url = options.find("url")->second;
23✔
46

UNCOV
47
  try {
23✔
UNCOV
48
    YaHTTP::URL url(d_url);
23✔
UNCOV
49
    d_host = url.host;
23✔
UNCOV
50
    d_port = url.port;
23✔
UNCOV
51
  }
23✔
UNCOV
52
  catch (const std::exception& e) {
23✔
53
    throw PDNSException("Error parsing the 'url' option provided to the remote backend HTTP connector: " + std::string(e.what()));
×
54
  }
×
55

UNCOV
56
  if (options.find("url-suffix") != options.end()) {
23!
57
    this->d_url_suffix = options.find("url-suffix")->second;
×
58
  }
×
UNCOV
59
  else {
23✔
UNCOV
60
    this->d_url_suffix = "";
23✔
UNCOV
61
  }
23✔
UNCOV
62
  this->timeout = 2;
23✔
UNCOV
63
  this->d_post = false;
23✔
UNCOV
64
  this->d_post_json = false;
23✔
65

UNCOV
66
  if (options.find("timeout") != options.end()) {
23✔
UNCOV
67
    this->timeout = std::stoi(options.find("timeout")->second) / 1000;
12✔
UNCOV
68
  }
12✔
UNCOV
69
  if (options.find("post") != options.end()) {
23!
70
    std::string val = options.find("post")->second;
×
71
    if (val == "yes" || val == "true" || val == "on" || val == "1") {
×
72
      this->d_post = true;
×
73
    }
×
74
  }
×
UNCOV
75
  if (options.find("post_json") != options.end()) {
23!
76
    std::string val = options.find("post_json")->second;
×
77
    if (val == "yes" || val == "true" || val == "on" || val == "1") {
×
78
      this->d_post_json = true;
×
79
    }
×
80
  }
×
UNCOV
81
}
23✔
82

UNCOV
83
HTTPConnector::~HTTPConnector() = default;
19✔
84

85
void HTTPConnector::addUrlComponent(const Json& parameters, const string& element, std::stringstream& ss)
UNCOV
86
{
1,009✔
UNCOV
87
  std::string sparam;
1,009✔
UNCOV
88
  if (parameters[element] != Json()) {
1,009✔
UNCOV
89
    ss << "/" << YaHTTP::Utility::encodeURL(asString(parameters[element]), false);
257✔
UNCOV
90
  }
257✔
UNCOV
91
}
1,009✔
92

93
std::string HTTPConnector::buildMemberListArgs(const std::string& prefix, const Json& args)
94
{
×
95
  std::stringstream stream;
×
96

97
  for (const auto& pair : args.object_items()) {
×
98
    stream << prefix << "[" << YaHTTP::Utility::encodeURL(pair.first, false) << "]=";
×
99
    if (pair.second.is_bool()) {
×
100
      stream << (pair.second.bool_value() ? "1" : "0");
×
101
    }
×
102
    else if (!pair.second.is_null()) {
×
103
      stream << YaHTTP::Utility::encodeURL(HTTPConnector::asString(pair.second), false);
×
104
    }
×
105
    stream << "&";
×
106
  }
×
107

108
  return stream.str().substr(0, stream.str().size() - 1); // snip the trailing &
×
109
}
×
110

111
// builds our request (near-restful)
112
void HTTPConnector::restful_requestbuilder(const std::string& method, const Json& parameters, YaHTTP::Request& req)
UNCOV
113
{
143✔
UNCOV
114
  std::stringstream ss;
143✔
UNCOV
115
  std::string sparam;
143✔
UNCOV
116
  std::string verb;
143✔
117

118
  // special names are qname, name, zonename, kind, others go to headers
119

UNCOV
120
  ss << d_url;
143✔
121

UNCOV
122
  ss << "/" << method;
143✔
123

124
  // add the url components, if found, in following order.
125
  // id must be first due to the fact that the qname/name can be empty
126

UNCOV
127
  addUrlComponent(parameters, "id", ss);
143✔
UNCOV
128
  addUrlComponent(parameters, "domain_id", ss);
143✔
UNCOV
129
  addUrlComponent(parameters, "zonename", ss);
143✔
UNCOV
130
  addUrlComponent(parameters, "qname", ss);
143✔
UNCOV
131
  addUrlComponent(parameters, "name", ss);
143✔
UNCOV
132
  addUrlComponent(parameters, "kind", ss);
143✔
UNCOV
133
  addUrlComponent(parameters, "qtype", ss);
143✔
134

135
  // set the correct type of request based on method
UNCOV
136
  if (method == "activateDomainKey" || method == "deactivateDomainKey" || method == "publishDomainKey" || method == "unpublishDomainKey") {
143!
137
    // create an empty post
138
    req.preparePost();
×
139
    verb = "POST";
×
140
  }
×
UNCOV
141
  else if (method == "setTSIGKey") {
143!
142
    req.POST()["algorithm"] = parameters["algorithm"].string_value();
×
143
    req.POST()["content"] = parameters["content"].string_value();
×
144
    req.preparePost();
×
145
    verb = "PATCH";
×
146
  }
×
UNCOV
147
  else if (method == "deleteTSIGKey") {
143!
148
    verb = "DELETE";
×
149
  }
×
UNCOV
150
  else if (method == "addDomainKey") {
143✔
UNCOV
151
    const Json& param = parameters["key"];
2✔
UNCOV
152
    req.POST()["flags"] = asString(param["flags"]);
2✔
UNCOV
153
    req.POST()["active"] = (param["active"].bool_value() ? "1" : "0");
2!
UNCOV
154
    req.POST()["published"] = (param["published"].bool_value() ? "1" : "0");
2!
UNCOV
155
    req.POST()["content"] = param["content"].string_value();
2✔
UNCOV
156
    req.preparePost();
2✔
UNCOV
157
    verb = "PUT";
2✔
UNCOV
158
  }
2✔
UNCOV
159
  else if (method == "superMasterBackend") {
141!
160
    std::stringstream ss2;
×
161
    addUrlComponent(parameters, "ip", ss);
×
162
    addUrlComponent(parameters, "domain", ss);
×
163
    // then we need to serialize rrset payload into POST
164
    for (size_t index = 0; index < parameters["nsset"].array_items().size(); index++) {
×
165
      ss2 << buildMemberListArgs("nsset[" + std::to_string(index) + "]", parameters["nsset"][index]) << "&";
×
166
    }
×
167
    req.body = ss2.str().substr(0, ss2.str().size() - 1);
×
168
    req.headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8";
×
169
    req.headers["content-length"] = std::to_string(req.body.size());
×
170
    verb = "POST";
×
171
  }
×
UNCOV
172
  else if (method == "createSlaveDomain") {
141!
173
    addUrlComponent(parameters, "ip", ss);
×
174
    addUrlComponent(parameters, "domain", ss);
×
175
    if (!parameters["account"].is_null() && parameters["account"].is_string()) {
×
176
      req.POST()["account"] = parameters["account"].string_value();
×
177
    }
×
178
    req.preparePost();
×
179
    verb = "PUT";
×
180
  }
×
UNCOV
181
  else if (method == "replaceRRSet") {
141!
182
    std::stringstream ss2;
×
183
    for (size_t index = 0; index < parameters["rrset"].array_items().size(); index++) {
×
184
      ss2 << buildMemberListArgs("rrset[" + std::to_string(index) + "]", parameters["rrset"][index]) << "&";
×
185
    }
×
186
    req.body = ss2.str().substr(0, ss2.str().size() - 1); // remove trailing &
×
187
    req.headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8";
×
188
    req.headers["content-length"] = std::to_string(req.body.size());
×
189
    verb = "PATCH";
×
190
  }
×
UNCOV
191
  else if (method == "feedRecord") {
141!
192
    addUrlComponent(parameters, "trxid", ss);
×
193
    req.body = buildMemberListArgs("rr", parameters["rr"]);
×
194
    req.headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8";
×
195
    req.headers["content-length"] = std::to_string(req.body.size());
×
196
    verb = "PATCH";
×
197
  }
×
UNCOV
198
  else if (method == "feedEnts") {
141!
199
    std::stringstream ss2;
×
200
    addUrlComponent(parameters, "trxid", ss);
×
201
    for (const auto& param : parameters["nonterm"].array_items()) {
×
202
      ss2 << "nonterm[]=" << YaHTTP::Utility::encodeURL(param.string_value(), false) << "&";
×
203
    }
×
204
    for (const auto& param : parameters["auth"].array_items()) {
×
205
      ss2 << "auth[]=" << (param["auth"].bool_value() ? "1" : "0") << "&";
×
206
    }
×
207
    req.body = ss2.str().substr(0, ss2.str().size() - 1);
×
208
    req.headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8";
×
209
    req.headers["content-length"] = std::to_string(req.body.size());
×
210
    verb = "PATCH";
×
211
  }
×
UNCOV
212
  else if (method == "feedEnts3") {
141!
213
    std::stringstream ss2;
×
214
    addUrlComponent(parameters, "domain", ss);
×
215
    addUrlComponent(parameters, "trxid", ss);
×
216
    ss2 << "times=" << parameters["times"].int_value() << "&salt=" << YaHTTP::Utility::encodeURL(parameters["salt"].string_value(), false) << "&narrow=" << (parameters["narrow"].bool_value() ? 1 : 0) << "&";
×
217
    for (const auto& param : parameters["nonterm"].array_items()) {
×
218
      ss2 << "nonterm[]=" << YaHTTP::Utility::encodeURL(param.string_value(), false) << "&";
×
219
    }
×
220
    for (const auto& param : parameters["auth"].array_items()) {
×
221
      ss2 << "auth[]=" << (param["auth"].bool_value() ? "1" : "0") << "&";
×
222
    }
×
223
    req.body = ss2.str().substr(0, ss2.str().size() - 1);
×
224
    req.headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8";
×
225
    req.headers["content-length"] = std::to_string(req.body.size());
×
226
    verb = "PATCH";
×
227
  }
×
UNCOV
228
  else if (method == "startTransaction") {
141✔
UNCOV
229
    addUrlComponent(parameters, "domain", ss);
4✔
UNCOV
230
    addUrlComponent(parameters, "trxid", ss);
4✔
UNCOV
231
    req.preparePost();
4✔
UNCOV
232
    verb = "POST";
4✔
UNCOV
233
  }
4✔
UNCOV
234
  else if (method == "commitTransaction" || method == "abortTransaction") {
137!
235
    addUrlComponent(parameters, "trxid", ss);
×
236
    req.preparePost();
×
237
    verb = "POST";
×
238
  }
×
UNCOV
239
  else if (method == "setDomainMetadata") {
137!
240
    // copy all metadata values into post
241
    std::stringstream ss2;
×
242
    // this one has values too
243
    if (parameters["value"].is_array()) {
×
244
      for (const auto& val : parameters["value"].array_items()) {
×
245
        ss2 << "value[]=" << YaHTTP::Utility::encodeURL(val.string_value(), false) << "&";
×
246
      }
×
247
    }
×
248
    req.body = ss2.str().substr(0, ss2.str().size() - 1);
×
249
    req.headers["content-type"] = "application/x-www-form-urlencoded; charset=utf-8";
×
250
    req.headers["content-length"] = std::to_string(req.body.size());
×
251
    verb = "PATCH";
×
252
  }
×
UNCOV
253
  else if (method == "removeDomainKey") {
137!
254
    // this one is delete
255
    verb = "DELETE";
×
256
  }
×
UNCOV
257
  else if (method == "setNotified") {
137!
258
    req.POST()["serial"] = asString(parameters["serial"]);
×
259
    req.preparePost();
×
260
    verb = "PATCH";
×
261
  }
×
UNCOV
262
  else if (method == "setStale") {
137!
263
    req.preparePost();
×
264
    verb = "PATCH";
×
265
  }
×
UNCOV
266
  else if (method == "setFresh") {
137!
267
    req.preparePost();
×
268
    verb = "PATCH";
×
269
  }
×
UNCOV
270
  else if (method == "directBackendCmd") {
137✔
UNCOV
271
    req.POST()["query"] = parameters["query"].string_value();
2✔
UNCOV
272
    req.preparePost();
2✔
UNCOV
273
    verb = "POST";
2✔
UNCOV
274
  }
2✔
UNCOV
275
  else if (method == "searchRecords" || method == "searchComments") {
135!
276
    req.GET()["pattern"] = parameters["pattern"].string_value();
×
277
    req.GET()["maxResults"] = std::to_string(parameters["maxResults"].int_value());
×
278
    verb = "GET";
×
279
  }
×
UNCOV
280
  else if (method == "getAllDomains") {
135!
281
    req.GET()["includeDisabled"] = (parameters["include_disabled"].bool_value() ? "true" : "false");
×
282
    verb = "GET";
×
283
  }
×
UNCOV
284
  else {
135✔
285
    // perform normal get
UNCOV
286
    verb = "GET";
135✔
UNCOV
287
  }
135✔
288

289
  // put everything else into headers
UNCOV
290
  for (const auto& pair : parameters.object_items()) {
413✔
UNCOV
291
    std::string member = pair.first;
413✔
292
    // whitelist header parameters
UNCOV
293
    if ((member == "trxid" || member == "local" || member == "remote" || member == "real-remote" || member == "zone-id")) {
413✔
UNCOV
294
      std::string hdr = "x-remotebackend-" + member;
152✔
UNCOV
295
      req.headers[hdr] = asString(pair.second);
152✔
UNCOV
296
    }
152✔
UNCOV
297
  };
413✔
298

299
  // finally add suffix and store url
UNCOV
300
  ss << d_url_suffix;
143✔
301

UNCOV
302
  req.setup(verb, ss.str());
143✔
UNCOV
303
  req.headers["accept"] = "application/json";
143✔
UNCOV
304
}
143✔
305

306
void HTTPConnector::post_requestbuilder(const Json& input, YaHTTP::Request& req)
307
{
×
308
  if (this->d_post_json) {
×
309
    std::string out = input.dump();
×
310
    req.setup("POST", d_url);
×
311
    // simple case, POST JSON into url. nothing fancy.
312
    req.headers["Content-Type"] = "text/javascript; charset=utf-8";
×
313
    req.headers["Content-Length"] = std::to_string(out.size());
×
314
    req.headers["accept"] = "application/json";
×
315
    req.body = out;
×
316
  }
×
317
  else {
×
318
    std::stringstream url;
×
319
    std::stringstream content;
×
320
    // call url/method.suffix
321
    url << d_url << "/" << input["method"].string_value() << d_url_suffix;
×
322
    req.setup("POST", url.str());
×
323
    // then build content
324
    req.POST()["parameters"] = input["parameters"].dump();
×
325
    req.preparePost();
×
326
    req.headers["accept"] = "application/json";
×
327
  }
×
328
}
×
329

330
int HTTPConnector::send_message(const Json& input)
UNCOV
331
{
143✔
UNCOV
332
  int rv = 0;
143✔
UNCOV
333
  int ec = 0;
143✔
UNCOV
334
  int fd = 0;
143✔
335

UNCOV
336
  std::vector<std::string> members;
143✔
UNCOV
337
  std::string method;
143✔
UNCOV
338
  std::ostringstream out;
143✔
339

340
  // perform request
UNCOV
341
  YaHTTP::Request req;
143✔
342

UNCOV
343
  if (d_post) {
143!
344
    post_requestbuilder(input, req);
×
345
  }
×
UNCOV
346
  else {
143✔
UNCOV
347
    restful_requestbuilder(input["method"].string_value(), input["parameters"], req);
143✔
UNCOV
348
  }
143✔
349

UNCOV
350
  rv = -1;
143✔
UNCOV
351
  req.headers["connection"] = "Keep-Alive"; // see if we can streamline requests (not needed, strictly speaking)
143✔
352

UNCOV
353
  out << req;
143✔
354

355
  // try sending with current socket, if it fails retry with new socket
UNCOV
356
  if (this->d_socket != nullptr) {
143✔
UNCOV
357
    fd = this->d_socket->getHandle();
125✔
358
    // there should be no data waiting
UNCOV
359
    if (waitForRWData(fd, true, 0, 1) < 1) {
125!
360
      try {
×
361
        d_socket->writenWithTimeout(out.str().c_str(), out.str().size(), timeout);
×
362
        rv = 1;
×
363
      }
×
364
      catch (NetworkError& ne) {
×
365
        g_log << Logger::Error << "While writing to HTTP endpoint " << d_addr.toStringWithPort() << ": " << ne.what() << std::endl;
×
366
      }
×
367
      catch (...) {
×
368
        g_log << Logger::Error << "While writing to HTTP endpoint " << d_addr.toStringWithPort() << ": exception caught" << std::endl;
×
369
      }
×
370
    }
×
UNCOV
371
  }
125✔
372

UNCOV
373
  if (rv == 1) {
143!
374
    return rv;
×
375
  }
×
376

UNCOV
377
  this->d_socket.reset();
143✔
378

379
  // connect using tcp
UNCOV
380
  struct addrinfo* gAddr = nullptr;
143✔
UNCOV
381
  struct addrinfo* gAddrPtr = nullptr;
143✔
UNCOV
382
  struct addrinfo hints{};
143✔
UNCOV
383
  std::string sPort = std::to_string(d_port);
143✔
UNCOV
384
  memset(&hints, 0, sizeof hints);
143✔
UNCOV
385
  hints.ai_family = AF_UNSPEC;
143✔
UNCOV
386
  hints.ai_flags = AI_ADDRCONFIG;
143✔
UNCOV
387
  hints.ai_socktype = SOCK_STREAM;
143✔
UNCOV
388
  hints.ai_protocol = IPPROTO_TCP;
143✔
UNCOV
389
  if ((ec = getaddrinfo(d_host.c_str(), sPort.c_str(), &hints, &gAddr)) == 0) {
143!
390
    // try to connect to each address.
UNCOV
391
    gAddrPtr = gAddr;
143✔
392

UNCOV
393
    while (gAddrPtr != nullptr) {
143!
UNCOV
394
      try {
143✔
UNCOV
395
        d_socket = std::make_unique<Socket>(gAddrPtr->ai_family, gAddrPtr->ai_socktype, gAddrPtr->ai_protocol);
143✔
UNCOV
396
        d_addr.setSockaddr(gAddrPtr->ai_addr, gAddrPtr->ai_addrlen);
143✔
UNCOV
397
        d_socket->connect(d_addr);
143✔
UNCOV
398
        d_socket->setNonBlocking();
143✔
UNCOV
399
        d_socket->writenWithTimeout(out.str().c_str(), out.str().size(), timeout);
143✔
UNCOV
400
        rv = 1;
143✔
UNCOV
401
      }
143✔
UNCOV
402
      catch (NetworkError& ne) {
143✔
403
        g_log << Logger::Error << "While writing to HTTP endpoint " << d_addr.toStringWithPort() << ": " << ne.what() << std::endl;
×
404
      }
×
UNCOV
405
      catch (...) {
143✔
406
        g_log << Logger::Error << "While writing to HTTP endpoint " << d_addr.toStringWithPort() << ": exception caught" << std::endl;
×
407
      }
×
408

UNCOV
409
      if (rv > -1) {
143!
UNCOV
410
        break;
143✔
UNCOV
411
      }
143✔
412
      d_socket.reset();
×
413
      gAddrPtr = gAddrPtr->ai_next;
×
414
    }
×
UNCOV
415
    freeaddrinfo(gAddr);
143✔
UNCOV
416
  }
143✔
417
  else {
×
418
    g_log << Logger::Error << "Unable to resolve " << d_host << ": " << gai_strerror(ec) << std::endl;
×
419
  }
×
420

UNCOV
421
  return rv;
143✔
UNCOV
422
}
143✔
423

424
int HTTPConnector::recv_message(Json& output)
UNCOV
425
{
143✔
UNCOV
426
  YaHTTP::AsyncResponseLoader arl;
143✔
UNCOV
427
  YaHTTP::Response resp;
143✔
428

UNCOV
429
  if (d_socket == nullptr) {
143!
430
    return -1; // cannot receive :(
×
431
  }
×
UNCOV
432
  std::array<char, 4096> buffer{};
143✔
UNCOV
433
  time_t time0 = 0;
143✔
434

UNCOV
435
  arl.initialize(&resp);
143✔
436

UNCOV
437
  try {
143✔
UNCOV
438
    time0 = time(nullptr);
143✔
UNCOV
439
    while (!arl.ready() && (labs(time(nullptr) - time0) <= timeout)) {
414!
UNCOV
440
      auto readBytes = d_socket->readWithTimeout(buffer.data(), buffer.size(), timeout);
271✔
UNCOV
441
      if (readBytes == 0) {
271!
442
        throw NetworkError("EOF while reading");
×
443
      }
×
UNCOV
444
      arl.feed(std::string(buffer.data(), readBytes));
271✔
UNCOV
445
    }
271✔
446
    // timeout occurred.
UNCOV
447
    if (!arl.ready()) {
143!
448
      throw NetworkError("timeout");
×
449
    }
×
UNCOV
450
  }
143✔
UNCOV
451
  catch (NetworkError& ne) {
143✔
452
    d_socket.reset();
×
453
    throw PDNSException("While reading from HTTP endpoint " + d_addr.toStringWithPort() + ": " + ne.what());
×
454
  }
×
UNCOV
455
  catch (...) {
143✔
456
    d_socket.reset();
×
457
    throw PDNSException("While reading from HTTP endpoint " + d_addr.toStringWithPort() + ": unknown error");
×
458
  }
×
459

UNCOV
460
  arl.finalize();
143✔
461

UNCOV
462
  if ((resp.status < 200 || resp.status >= 400) && resp.status != 404) {
143!
463
    // bad.
464
    throw PDNSException("Received unacceptable HTTP status code " + std::to_string(resp.status) + " from HTTP endpoint " + d_addr.toStringWithPort());
×
465
  }
×
466

UNCOV
467
  std::string err;
143✔
UNCOV
468
  output = Json::parse(resp.body, err);
143✔
UNCOV
469
  if (output != nullptr) {
143!
UNCOV
470
    return static_cast<int>(resp.body.size());
143✔
UNCOV
471
  }
143✔
472
  g_log << Logger::Error << "Cannot parse JSON reply: " << err << endl;
×
473

474
  return -1;
×
UNCOV
475
}
143✔
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