1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2012-2020 Couchbase, Inc.
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 
18 #include "internal.h"
19 #include "bucketconfig/clconfig.h"
20 #include "http/http.h"
21 #include "http/http-priv.h"
22 #include "auth-priv.h"
23 #include "trace.h"
24 #include "strcodecs/strcodecs.h"
25 
26 #include "../capi/cmd_http.hh"
27 
lcb_resphttp_status(const lcb_RESPHTTP *resp)28 LIBCOUCHBASE_API lcb_STATUS lcb_resphttp_status(const lcb_RESPHTTP *resp)
29 {
30     return resp->ctx.rc;
31 }
32 
lcb_resphttp_cookie(const lcb_RESPHTTP *resp, void **cookie)33 LIBCOUCHBASE_API lcb_STATUS lcb_resphttp_cookie(const lcb_RESPHTTP *resp, void **cookie)
34 {
35     *cookie = resp->cookie;
36     return LCB_SUCCESS;
37 }
38 
lcb_resphttp_http_status(const lcb_RESPHTTP *resp, uint16_t *status)39 LIBCOUCHBASE_API lcb_STATUS lcb_resphttp_http_status(const lcb_RESPHTTP *resp, uint16_t *status)
40 {
41     *status = resp->ctx.response_code;
42     return LCB_SUCCESS;
43 }
44 
lcb_resphttp_path(const lcb_RESPHTTP *resp, const char **path, size_t *path_len)45 LIBCOUCHBASE_API lcb_STATUS lcb_resphttp_path(const lcb_RESPHTTP *resp, const char **path, size_t *path_len)
46 {
47     *path = (const char *)resp->ctx.path;
48     *path_len = resp->ctx.path_len;
49     return LCB_SUCCESS;
50 }
51 
lcb_resphttp_body(const lcb_RESPHTTP *resp, const char **body, size_t *body_len)52 LIBCOUCHBASE_API lcb_STATUS lcb_resphttp_body(const lcb_RESPHTTP *resp, const char **body, size_t *body_len)
53 {
54     *body = (const char *)resp->ctx.body;
55     *body_len = resp->ctx.body_len;
56     return LCB_SUCCESS;
57 }
58 
lcb_resphttp_headers(const lcb_RESPHTTP *resp, const char *const **headers)59 LIBCOUCHBASE_API lcb_STATUS lcb_resphttp_headers(const lcb_RESPHTTP *resp, const char *const **headers)
60 {
61     *headers = resp->headers;
62     return LCB_SUCCESS;
63 }
64 
lcb_resphttp_handle(const lcb_RESPHTTP *resp, lcb_HTTP_HANDLE **handle)65 LIBCOUCHBASE_API lcb_STATUS lcb_resphttp_handle(const lcb_RESPHTTP *resp, lcb_HTTP_HANDLE **handle)
66 {
67     *handle = resp->_htreq;
68     return LCB_SUCCESS;
69 }
70 
lcb_resphttp_error_context(const lcb_RESPHTTP *resp, const lcb_HTTP_ERROR_CONTEXT **ctx)71 LIBCOUCHBASE_API lcb_STATUS lcb_resphttp_error_context(const lcb_RESPHTTP *resp, const lcb_HTTP_ERROR_CONTEXT **ctx)
72 {
73     *ctx = &resp->ctx;
74     return LCB_SUCCESS;
75 }
76 
lcb_resphttp_is_final(const lcb_RESPHTTP *resp)77 LIBCOUCHBASE_API int lcb_resphttp_is_final(const lcb_RESPHTTP *resp)
78 {
79     return resp->rflags & LCB_RESP_F_FINAL;
80 }
81 
lcb_cmdhttp_create(lcb_CMDHTTP **cmd, lcb_HTTP_TYPE type)82 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_create(lcb_CMDHTTP **cmd, lcb_HTTP_TYPE type)
83 {
84     *cmd = new lcb_CMDHTTP{};
85     (*cmd)->type = type;
86     return LCB_SUCCESS;
87 }
88 
lcb_cmdhttp_destroy(lcb_CMDHTTP *cmd)89 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_destroy(lcb_CMDHTTP *cmd)
90 {
91     delete cmd;
92     return LCB_SUCCESS;
93 }
94 
lcb_cmdhttp_parent_span(lcb_CMDHTTP *cmd, lcbtrace_SPAN *span)95 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_parent_span(lcb_CMDHTTP *cmd, lcbtrace_SPAN *span)
96 {
97     cmd->pspan = span;
98     return LCB_SUCCESS;
99 }
100 
lcb_cmdhttp_method(lcb_CMDHTTP *cmd, lcb_HTTP_METHOD method)101 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_method(lcb_CMDHTTP *cmd, lcb_HTTP_METHOD method)
102 {
103     cmd->method = method;
104     return LCB_SUCCESS;
105 }
106 
lcb_cmdhttp_path(lcb_CMDHTTP *cmd, const char *path, size_t path_len)107 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_path(lcb_CMDHTTP *cmd, const char *path, size_t path_len)
108 {
109     LCB_CMD_SET_KEY(cmd, path, path_len);
110     return LCB_SUCCESS;
111 }
112 
lcb_cmdhttp_content_type(lcb_CMDHTTP *cmd, const char *content_type, size_t content_type_len)113 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_content_type(lcb_CMDHTTP *cmd, const char *content_type,
114                                                      size_t content_type_len)
115 {
116     cmd->content_type = content_type;
117     (void)content_type_len;
118     return LCB_SUCCESS;
119 }
120 
lcb_cmdhttp_body(lcb_CMDHTTP *cmd, const char *body, size_t body_len)121 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_body(lcb_CMDHTTP *cmd, const char *body, size_t body_len)
122 {
123     cmd->body = body;
124     cmd->nbody = body_len;
125     return LCB_SUCCESS;
126 }
127 
lcb_cmdhttp_handle(lcb_CMDHTTP *cmd, lcb_HTTP_HANDLE **handle)128 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_handle(lcb_CMDHTTP *cmd, lcb_HTTP_HANDLE **handle)
129 {
130     cmd->reqhandle = handle;
131     return LCB_SUCCESS;
132 }
133 
lcb_cmdhttp_username(lcb_CMDHTTP *cmd, const char *username, size_t username_len)134 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_username(lcb_CMDHTTP *cmd, const char *username, size_t username_len)
135 {
136     cmd->username = username;
137     (void)username_len;
138     return LCB_SUCCESS;
139 }
140 
lcb_cmdhttp_password(lcb_CMDHTTP *cmd, const char *password, size_t password_len)141 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_password(lcb_CMDHTTP *cmd, const char *password, size_t password_len)
142 {
143     cmd->password = password;
144     (void)password_len;
145     return LCB_SUCCESS;
146 }
147 
lcb_cmdhttp_host(lcb_CMDHTTP *cmd, const char *host, size_t host_len)148 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_host(lcb_CMDHTTP *cmd, const char *host, size_t host_len)
149 {
150     cmd->host = host;
151     (void)host_len;
152     return LCB_SUCCESS;
153 }
154 
lcb_cmdhttp_streaming(lcb_CMDHTTP *cmd, int streaming)155 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_streaming(lcb_CMDHTTP *cmd, int streaming)
156 {
157     if (streaming) {
158         cmd->cmdflags |= LCB_CMDHTTP_F_STREAM;
159     } else {
160         cmd->cmdflags &= ~LCB_CMDHTTP_F_STREAM;
161     }
162     return LCB_SUCCESS;
163 }
164 
lcb_cmdhttp_skip_auth_header(lcb_CMDHTTP *cmd, int skip_auth)165 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_skip_auth_header(lcb_CMDHTTP *cmd, int skip_auth)
166 {
167     if (skip_auth) {
168         cmd->cmdflags |= LCB_CMDHTTP_F_NOUPASS;
169     } else {
170         cmd->cmdflags &= ~LCB_CMDHTTP_F_NOUPASS;
171     }
172     return LCB_SUCCESS;
173 }
174 
lcb_cmdhttp_timeout(lcb_CMDHTTP *cmd, uint32_t timeout)175 LIBCOUCHBASE_API lcb_STATUS lcb_cmdhttp_timeout(lcb_CMDHTTP *cmd, uint32_t timeout)
176 {
177     cmd->cmdflags |= LCB_CMDHTTP_F_CASTMO;
178     cmd->cas = timeout;
179     return LCB_SUCCESS;
180 }
181 
182 using namespace lcb::http;
183 
184 #define LOGFMT "<%s%s%s:%s> "
185 #define LOGID(req) ((req)->ipv6 ? "[" : ""), (req)->host.c_str(), ((req)->ipv6 ? "]" : ""), (req)->port.c_str()
186 
187 #define LOGARGS(req, lvl) req->instance->settings, "http-io", LCB_LOG_##lvl, __FILE__, __LINE__
188 
189 static const char *method_strings[] = {
190     "GET ",   /* LCB_HTTP_METHOD_GET */
191     "POST ",  /* LCB_HTTP_METHOD_POST */
192     "PUT ",   /* LCB_HTTP_METHOD_PUT */
193     "DELETE " /* LCB_HTTP_METHOD_DELETE */
194 };
195 
decref()196 void Request::decref()
197 {
198     lcb_assert(refcount > 0);
199     if (--refcount) {
200         return;
201     }
202 
203     delete this;
204 }
205 
finish_or_retry(lcb_STATUS rc)206 void Request::finish_or_retry(lcb_STATUS rc)
207 {
208     if (rc == LCB_ERR_TIMEOUT) {
209         // No point on trying (or even logging) a timeout
210         finish(rc);
211         return;
212     }
213     if (passed_data) {
214         lcb_log(LOGARGS(this, WARN), LOGFMT "Not retrying. Data passed to callback", LOGID(this));
215         finish(rc);
216         return;
217     }
218 
219     // Not a 'data API'. Request may be node-specific
220     if (!is_data_request()) {
221         lcb_log(LOGARGS(this, WARN), LOGFMT "Not retrying non-data-api request", LOGID(this));
222         finish(rc);
223         return;
224     }
225 
226     // See if we can find an API node.
227     const char *nextnode = get_api_node();
228     if (!nextnode) {
229         lcb_log(LOGARGS(this, WARN), LOGFMT "Not retrying. No nodes available", LOGID(this));
230         finish(rc);
231         return;
232     }
233     struct http_parser_url next_info {
234     };
235     if (_lcb_http_parser_parse_url(nextnode, strlen(nextnode), 0, &next_info)) {
236         lcb_log(LOGARGS(this, WARN), LOGFMT "Not retrying. Invalid API endpoint", LOGID(this));
237         finish(LCB_ERR_INVALID_ARGUMENT);
238         return;
239     }
240 
241     // Reassemble URL:
242     lcb_log(LOGARGS(this, DEBUG), LOGFMT "Retrying request on new node %s. Reason: 0x%02x (%s)", LOGID(this), nextnode,
243             rc, lcb_strerror_short(rc));
244 
245     url.replace(url_info.field_data[UF_PORT].off, url_info.field_data[UF_PORT].len,
246                 nextnode + next_info.field_data[UF_PORT].off, next_info.field_data[UF_PORT].len);
247     url.replace(url_info.field_data[UF_HOST].off, url_info.field_data[UF_HOST].len,
248                 nextnode + next_info.field_data[UF_HOST].off, next_info.field_data[UF_HOST].len);
249 
250     lcb_STATUS newrc;
251     newrc = assign_url(nullptr, 0, nullptr, 0);
252     if (newrc != LCB_SUCCESS) {
253         lcb_log(LOGARGS(this, ERR), LOGFMT "Failed to assign URL for retry request on next endpoint (%s): 0x%02x (%s)",
254                 LOGID(this), nextnode, newrc, lcb_strerror_short(newrc));
255         finish(rc);
256         return;
257     }
258 
259     newrc = submit();
260     if (newrc != LCB_SUCCESS) {
261         lcb_log(LOGARGS(this, WARN), LOGFMT "Failed to retry request on next endpoint (%s): 0x%02x (%s)", LOGID(this),
262                 nextnode, newrc, lcb_strerror_short(newrc));
263         finish(rc);
264     }
265 }
266 
maybe_refresh_config(lcb_STATUS err)267 void Request::maybe_refresh_config(lcb_STATUS err)
268 {
269     int htstatus_ok;
270     if (!parser) {
271         return;
272     }
273 
274     if (!LCBT_SETTING(instance, refresh_on_hterr)) {
275         return;
276     }
277 
278     const lcb::htparse::Response &resp = parser->get_cur_response();
279     htstatus_ok = resp.status >= 200 && resp.status < 299;
280 
281     if (err != LCB_SUCCESS && (err == LCB_ERR_SOCKET_SHUTDOWN && htstatus_ok) == 0) {
282         /* ignore graceful close */
283         instance->bootstrap(BS_REFRESH_ALWAYS);
284         return;
285     }
286 
287     if (htstatus_ok) {
288         return;
289     }
290     instance->bootstrap(BS_REFRESH_ALWAYS);
291 }
292 
293 void Request::init_resp(lcb_RESPHTTP *res)
294 {
295     const lcb::htparse::Response &htres = parser->get_cur_response();
296 
297     res->cookie = const_cast<void *>(command_cookie);
298     res->ctx.path = url.c_str() + url_info.field_data[UF_PATH].off;
299     res->ctx.path_len = url_info.field_data[UF_PATH].len;
300     res->_htreq = static_cast<lcb_HTTP_HANDLE *>(this);
301     if (!response_headers.empty()) {
302         res->headers = &response_headers_clist[0];
303     }
304     res->ctx.response_code = htres.status;
305     res->ctx.endpoint = peer.c_str();
306     res->ctx.endpoint_len = peer.size();
307 }
308 
309 void Request::finish(lcb_STATUS error)
310 {
311     /* This is always safe to execute */
312     if (!(status & NOLCB)) {
313         maybe_refresh_config(error);
314     }
315 
316     /* And this one too */
317     if ((status & CBINVOKED) == 0) {
318         lcb_RESPHTTP resp{};
319         init_resp(&resp);
320         resp.rflags = LCB_RESP_F_FINAL;
321         resp.ctx.rc = error;
322 
323         status |= CBINVOKED;
324         callback(instance, LCB_CALLBACK_HTTP, (lcb_RESPBASE *)&resp);
325     }
326 
327     if (status & FINISHED) {
328         return;
329     }
330 
331     TRACE_HTTP_END(this, error, parser->get_cur_response().status);
332     status |= FINISHED;
333 
334     if (!(status & NOLCB)) {
335         /* Remove from wait queue */
336         lcb_aspend_del(&instance->pendops, LCB_PENDTYPE_HTTP, this);
337         /* Break out from the loop (must be called after aspend_del) */
338         lcb_maybe_breakout(instance);
339     }
340 
341     /* Cancel the timeout */
342     lcbio_timer_disarm(timer);
343 
344     /* Remove the initial refcount=1 (set from lcb_http3). Typically this will
345      * also free the request (though this is dependent on pending I/O operations) */
346     decref();
347 }
348 
349 void Request::add_to_preamble(const char *s)
350 {
351     preamble.insert(preamble.end(), s, s + strlen(s));
352 }
353 void Request::add_to_preamble(const std::string &s)
354 {
355     preamble.insert(preamble.end(), s.c_str(), s.c_str() + s.size());
356 }
357 void Request::add_to_preamble(const Header &header)
358 {
359     add_to_preamble(header.key);
360     add_to_preamble(": ");
361     add_to_preamble(header.value);
362     add_to_preamble("\r\n");
363 }
364 
365 lcb_STATUS Request::submit()
366 {
367     lcb_STATUS rc;
368     lcb_host_t reqhost = {"", "", 0};
369 
370     // Stop any pending socket/request
371     close_io();
372 
373     if (host.size() > sizeof reqhost.host || port.size() > sizeof reqhost.port) {
374         decref();
375         return LCB_ERR_VALUE_TOO_LARGE;
376     }
377 
378     preamble.clear();
379 
380     strncpy(reqhost.host, host.c_str(), host.size());
381     strncpy(reqhost.port, port.c_str(), port.size());
382     reqhost.host[host.size()] = '\0';
383     reqhost.port[port.size()] = '\0';
384     reqhost.ipv6 = ipv6;
385 
386     // Add the HTTP verb (e.g. "GET ") [note, the string contains a trailing space]
387     add_to_preamble(method_strings[method]);
388 
389     // Add the path
390     const char *url_s = url.c_str();
391     size_t path_off = url_info.field_data[UF_PATH].off;
392     size_t path_len = url.size() - path_off;
393     preamble.insert(preamble.end(), url_s + path_off, url_s + path_off + path_len);
394     lcb_log(LOGARGS(this, TRACE), LOGFMT "%s %s. Body=%lu bytes", LOGID(this), method_strings[method], url.c_str(),
395             (unsigned long int)body.size());
396 
397     add_to_preamble(" HTTP/1.1\r\n");
398 
399     // Add the Host: header manually. If redirected to a different host then
400     // we need to recalculate this, so don't make this part of the
401     // global headers (which are typically not cleared)
402     add_to_preamble("Host: ");
403     add_to_preamble(host);
404     add_to_preamble(":");
405     add_to_preamble(port);
406     add_to_preamble("\r\n");
407 
408     // Add the rest of the headers
409     std::vector<Header>::const_iterator ii = request_headers.begin();
410     for (; ii != request_headers.end(); ++ii) {
411         add_to_preamble(*ii);
412     }
413     add_to_preamble("\r\n");
414     // If there is a body, it is appended in the IO stage
415 
416     rc = start_io(reqhost);
417 
418     if (rc == LCB_SUCCESS) {
419         // Only wipe old parser/response information if current I/O request
420         // was a success
421         if (parser) {
422             parser->reset();
423         } else {
424             parser = new lcb::htparse::Parser(instance->settings);
425         }
426         response_headers.clear();
427         response_headers_clist.clear();
428         TRACE_HTTP_BEGIN(this);
429     }
430 
431     return rc;
432 }
433 
434 void Request::assign_from_urlfield(http_parser_url_fields field, std::string &target)
435 {
436     target = url.substr(url_info.field_data[field].off, url_info.field_data[field].len);
437 }
438 
439 lcb_STATUS Request::assign_url(const char *base, size_t nbase, const char *path, size_t npath)
440 {
441     const char *htscheme;
442     unsigned schemsize;
443 
444     if (LCBT_SETTING(instance, sslopts) & LCB_SSL_ENABLED) {
445         htscheme = "https://";
446         schemsize = sizeof("https://");
447     } else {
448         htscheme = "http://";
449         schemsize = sizeof("http://");
450     }
451 
452     schemsize--;
453     if (base) {
454         url.assign(htscheme, schemsize);
455         if (nbase > schemsize && memcmp(base, htscheme, schemsize) == 0) {
456             base += schemsize;
457             nbase -= schemsize;
458         }
459         url.append(base, nbase);
460         if (path) {
461             if (*path != '/' && url[url.size() - 1] != '/') {
462                 url.append("/");
463             }
464 
465             if (!lcb::strcodecs::urlencode(path, path + npath, url)) {
466                 return LCB_ERR_INVALID_CHAR;
467             }
468         }
469     }
470 
471     bool redir_checked = false;
472     static const unsigned required_fields = ((1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH));
473 
474 GT_REPARSE:
475     if (_lcb_http_parser_parse_url(url.c_str(), url.size(), 0, &url_info)) {
476         return LCB_ERR_INVALID_ARGUMENT;
477     }
478 
479     if ((url_info.field_set & required_fields) != required_fields) {
480         if (base == nullptr && path == nullptr && !redir_checked) {
481             redir_checked = true;
482             std::string first_part(htscheme, schemsize);
483             first_part += host;
484             first_part += ':';
485             first_part += port;
486             url = first_part + url;
487             goto GT_REPARSE;
488         }
489         return LCB_ERR_INVALID_ARGUMENT;
490     }
491 
492     assign_from_urlfield(UF_HOST, host);
493     assign_from_urlfield(UF_PORT, port);
494     ipv6 = host.find(':') != std::string::npos;
495     if (ipv6) {
496         peer = "[" + host + "]:" + port;
497     } else {
498         peer = host + ":" + port;
499     }
500     return LCB_SUCCESS;
501 }
502 
503 void Request::redirect()
504 {
505     lcb_STATUS rc;
506     lcb_assert(!pending_redirect.empty());
507     if (LCBT_SETTING(instance, max_redir) > -1) {
508         if (LCBT_SETTING(instance, max_redir) < ++redircount) {
509             finish(LCB_ERR_TOO_MANY_REDIRECTS);
510             return;
511         }
512     }
513 
514     memset(&url_info, 0, sizeof url_info);
515     url = pending_redirect;
516     pending_redirect.clear();
517 
518     if ((rc = assign_url(nullptr, 0, nullptr, 0)) != LCB_SUCCESS) {
519         lcb_log(LOGARGS(this, ERR), LOGFMT "Failed to add redirect URL (%s)", LOGID(this), url.c_str());
520         finish(rc);
521         return;
522     }
523 
524     if ((rc = submit()) != LCB_SUCCESS) {
525         finish(rc);
526     }
527 }
528 
529 static lcbvb_SVCTYPE httype2svctype(unsigned httype)
530 {
531     switch (httype) {
532         case LCB_HTTP_TYPE_VIEW:
533             return LCBVB_SVCTYPE_VIEWS;
534         case LCB_HTTP_TYPE_QUERY:
535             return LCBVB_SVCTYPE_QUERY;
536         case LCB_HTTP_TYPE_SEARCH:
537             return LCBVB_SVCTYPE_SEARCH;
538         case LCB_HTTP_TYPE_ANALYTICS:
539             return LCBVB_SVCTYPE_ANALYTICS;
540         case LCB_HTTP_TYPE_EVENTING:
541             return LCBVB_SVCTYPE_EVENTING;
542         default:
543             return LCBVB_SVCTYPE__MAX;
544     }
545 }
546 
547 const char *Request::get_api_node(lcb_STATUS &rc)
548 {
549     if (!is_data_request()) {
550         return lcb_get_node(instance, LCB_NODE_HTCONFIG, 0);
551     }
552 
553     if (!LCBT_VBCONFIG(instance)) {
554         rc = LCB_ERR_NO_CONFIGURATION;
555         return nullptr;
556     }
557 
558     const lcbvb_SVCTYPE svc = httype2svctype(reqtype);
559     const lcbvb_SVCMODE mode = LCBT_SETTING_SVCMODE(instance);
560 
561     lcbvb_CONFIG *vbc = LCBT_VBCONFIG(instance);
562 
563     if (last_vbcrev != vbc->revid) {
564         used_nodes.clear();
565         last_vbcrev = vbc->revid;
566     }
567     used_nodes.resize(LCBVB_NSERVERS(vbc));
568 
569     int ix = lcbvb_get_randhost_ex(vbc, svc, mode, &used_nodes[0]);
570     if (ix < 0) {
571         rc = LCB_ERR_UNSUPPORTED_OPERATION;
572         return nullptr;
573     }
574     used_nodes[ix] = 1;
575     return lcbvb_get_resturl(vbc, ix, svc, mode);
576 }
577 
578 static lcbauth_SERVICE http_type_to_service(lcb_HTTP_TYPE type)
579 {
580     switch (type) {
581         case LCB_HTTP_TYPE_VIEW:
582             return LCBAUTH_SERVICE_VIEWS;
583 
584         case LCB_HTTP_TYPE_MANAGEMENT:
585             return LCBAUTH_SERVICE_MANAGEMENT;
586 
587         case LCB_HTTP_TYPE_QUERY:
588             return LCBAUTH_SERVICE_QUERY;
589 
590         case LCB_HTTP_TYPE_SEARCH:
591             return LCBAUTH_SERVICE_SEARCH;
592 
593         case LCB_HTTP_TYPE_ANALYTICS:
594             return LCBAUTH_SERVICE_ANALYTICS;
595 
596         case LCB_HTTP_TYPE_EVENTING:
597             return LCBAUTH_SERVICE_EVENTING;
598 
599         default:
600             return LCBAUTH_SERVICE_UNSPECIFIED;
601     }
602 }
603 
604 lcb_STATUS Request::setup_inputs(const lcb_CMDHTTP *cmd)
605 {
606     std::string username, password;
607     const char *base = nullptr;
608     size_t nbase = 0;
609     lcb_STATUS rc = LCB_SUCCESS;
610 
611     if (method > LCB_HTTP_METHOD_MAX) {
612         return LCB_ERR_INVALID_ARGUMENT;
613     }
614 
615     if (cmd->username) {
616         username = cmd->username;
617     }
618     if (cmd->password) {
619         password = cmd->password;
620     }
621 
622     if (reqtype == LCB_HTTP_TYPE_RAW) {
623         if ((base = cmd->host) == nullptr) {
624             return LCB_ERR_INVALID_ARGUMENT;
625         }
626     } else {
627         if (cmd->host) {
628             switch (reqtype) {
629                 case LCB_HTTP_TYPE_QUERY:
630                 case LCB_HTTP_TYPE_ANALYTICS:
631                 case LCB_HTTP_TYPE_PING:
632                     base = cmd->host;
633                     break;
634                 default:
635                     return LCB_ERR_INVALID_ARGUMENT;
636             }
637         }
638         if (base == nullptr) {
639             base = get_api_node(rc);
640         }
641         if (base == nullptr || *base == '\0') {
642             if (rc == LCB_SUCCESS) {
643                 return LCB_ERR_SDK_INTERNAL;
644             } else {
645                 return rc;
646             }
647         }
648 
649         if ((cmd->cmdflags & LCB_CMDHTTP_F_NOUPASS) || instance->settings->keypath) {
650             // explicitly asked to skip Authorization header,
651             // or using SSL client certificate to authenticate
652             username.clear();
653             password.clear();
654         } else if (username.empty() && password.empty()) {
655             const Authenticator &auth = *LCBT_SETTING(instance, auth);
656             if (reqtype == LCB_HTTP_TYPE_MANAGEMENT) {
657                 username = auth.username();
658                 password = auth.password();
659             } else {
660                 auto service_type = http_type_to_service(reqtype);
661                 if (auth.mode() == LCBAUTH_MODE_DYNAMIC) {
662                     struct http_parser_url info = {};
663                     if (_lcb_http_parser_parse_url(base, strlen(base), 0, &info)) {
664                         lcb_log(LOGARGS(this, WARN), LOGFMT "Failed to parse API endpoint", LOGID(this));
665                         return LCB_ERR_SDK_INTERNAL;
666                     }
667                     std::string hh(base + info.field_data[UF_HOST].off, info.field_data[UF_HOST].len);
668                     std::string pp(base + info.field_data[UF_PORT].off, info.field_data[UF_PORT].len);
669                     auto creds = auth.credentials_for(service_type, LCBAUTH_REASON_NEW_OPERATION, hh.c_str(),
670                                                       pp.c_str(), LCBT_SETTING(instance, bucket));
671                     username = creds.username();
672                     password = creds.password();
673                 } else {
674                     auto creds = auth.credentials_for(service_type, LCBAUTH_REASON_NEW_OPERATION, nullptr, nullptr,
675                                                       LCBT_SETTING(instance, bucket));
676                     username = creds.username();
677                     password = creds.password();
678                 }
679             }
680         }
681     }
682 
683     if (base) {
684         nbase = strlen(base);
685     }
686 
687     rc = assign_url(base, nbase, reinterpret_cast<const char *>(cmd->key.contig.bytes), cmd->key.contig.nbytes);
688     if (rc != LCB_SUCCESS) {
689         return rc;
690     }
691 
692     std::string ua(LCB_CLIENT_ID);
693     if (instance->settings->client_string) {
694         ua.append(" ").append(instance->settings->client_string);
695     }
696     add_header("User-Agent", ua);
697 
698     if (instance->http_sockpool->get_options().maxidle == 0 || !is_data_request()) {
699         add_header("Connection", "close");
700     }
701 
702     add_header("Accept", "application/json");
703     if (!username.empty()) {
704         char auth[256];
705         std::string upassbuf;
706         upassbuf.append(username).append(":").append(password);
707         if (lcb_base64_encode(upassbuf.c_str(), upassbuf.size(), auth, sizeof(auth)) == -1) {
708             return LCB_ERR_INVALID_ARGUMENT;
709         }
710         add_header("Authorization", std::string("Basic ") + auth);
711     }
712 
713     if (!body.empty()) {
714         char lenbuf[64];
715         sprintf(lenbuf, "%lu", (unsigned long int)body.size());
716         add_header("Content-Length", lenbuf);
717         if (cmd->content_type) {
718             add_header("Content-Type", cmd->content_type);
719         }
720     }
721 
722     return LCB_SUCCESS;
723 }
724 
725 Request::Request(lcb_INSTANCE *instance_, const void *cookie, const lcb_CMDHTTP *cmd)
726     : instance(instance_), body(cmd->body, cmd->body + cmd->nbody), method(cmd->method),
727       chunked(cmd->cmdflags & LCB_CMDHTTP_F_STREAM), paused(false), command_cookie(cookie), refcount(1), redircount(0),
728       span(nullptr), passed_data(false), last_vbcrev(-1), reqtype(cmd->type), status(ONGOING),
729       callback(lcb_find_callback(instance, LCB_CALLBACK_HTTP)), io(instance->iotable), ioctx(nullptr), timer(nullptr),
730       parser(nullptr), user_timeout(cmd->cmdflags & LCB_CMDHTTP_F_CASTMO ? cmd->cas : 0)
731 {
732     for (const auto &pair : cmd->headers_) {
733         request_headers.emplace_back(Header(pair.first, pair.second));
734     }
735 }
736 
737 Request::~Request()
738 {
739     close_io();
740 
741     delete parser;
742 
743     if (timer) {
744         lcbio_timer_destroy(timer);
745         timer = nullptr;
746     }
747 }
748 
749 uint32_t Request::timeout() const
750 {
751     if (user_timeout) {
752         return user_timeout;
753     }
754     switch (reqtype) {
755         case LCB_HTTP_TYPE_QUERY:
756         case LCB_HTTP_TYPE_SEARCH:
757             return LCBT_SETTING(instance, n1ql_timeout);
758         case LCB_HTTP_TYPE_VIEW:
759             return LCBT_SETTING(instance, views_timeout);
760         default:
761             return LCBT_SETTING(instance, http_timeout);
762     }
763 }
764 
765 Request *Request::create(lcb_INSTANCE *instance, const void *cookie, const lcb_CMDHTTP *cmd, lcb_STATUS *rc)
766 {
767     auto *req = new lcb_HTTP_HANDLE_(instance, cookie, cmd);
768     req->start = gethrtime();
769     *rc = req->setup_inputs(cmd);
770     if (*rc != LCB_SUCCESS) {
771         delete req;
772         return nullptr;
773     }
774     req->span = cmd->pspan;
775     *rc = req->submit();
776     if (*rc == LCB_SUCCESS) {
777         if (cmd->reqhandle) {
778             *cmd->reqhandle = static_cast<lcb_HTTP_HANDLE *>(req);
779         }
780         lcb_aspend_add(&instance->pendops, LCB_PENDTYPE_HTTP, req);
781         return req;
782     } else {
783         // Do not call finish() as we don't want a callback
784         req->decref();
785         return nullptr;
786     }
787 }
788 
789 LIBCOUCHBASE_API
790 lcb_STATUS lcb_http(lcb_INSTANCE *instance, void *cookie, const lcb_CMDHTTP *cmd)
791 {
792     lcb_STATUS rc;
793     Request::create(instance, cookie, cmd, &rc);
794     return rc;
795 }
796 
797 void Request::cancel()
798 {
799     if (status & (FINISHED | CBINVOKED)) {
800         /* Nothing to cancel */
801         return;
802     }
803     status |= CBINVOKED;
804     finish(LCB_SUCCESS);
805 }
806 
807 LIBCOUCHBASE_API lcb_STATUS lcb_http_cancel(lcb_INSTANCE *, lcb_HTTP_HANDLE *handle)
808 {
809     handle->cancel();
810     return LCB_SUCCESS;
811 }
812