1 /*
2  * Copyright 2020-present MongoDB, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "mongoc-http-private.h"
18 
19 #include "mongoc-client-private.h"
20 #include "mongoc-host-list-private.h"
21 #include "mongoc-stream-tls.h"
22 #include "mongoc-stream-private.h"
23 #include "mongoc-buffer-private.h"
24 
25 void
_mongoc_http_request_init(mongoc_http_request_t * request)26 _mongoc_http_request_init (mongoc_http_request_t *request)
27 {
28    memset (request, 0, sizeof (*request));
29 }
30 
31 void
_mongoc_http_response_init(mongoc_http_response_t * response)32 _mongoc_http_response_init (mongoc_http_response_t *response)
33 {
34    memset (response, 0, sizeof (*response));
35 }
36 
37 void
_mongoc_http_response_cleanup(mongoc_http_response_t * response)38 _mongoc_http_response_cleanup (mongoc_http_response_t *response)
39 {
40    if (!response) {
41       return;
42    }
43    bson_free (response->headers);
44    bson_free (response->body);
45 }
46 
47 bool
_mongoc_http_send(mongoc_http_request_t * req,int timeout_ms,bool use_tls,mongoc_ssl_opt_t * ssl_opts,mongoc_http_response_t * res,bson_error_t * error)48 _mongoc_http_send (mongoc_http_request_t *req,
49                    int timeout_ms,
50                    bool use_tls,
51                    mongoc_ssl_opt_t *ssl_opts,
52                    mongoc_http_response_t *res,
53                    bson_error_t *error)
54 {
55    mongoc_stream_t *stream = NULL;
56    mongoc_host_list_t host_list;
57    bool ret = false;
58    mongoc_iovec_t iovec;
59    ssize_t bytes_read;
60    char *path = NULL;
61    bson_string_t *http_request = NULL;
62    mongoc_buffer_t http_response_buf;
63    char *http_response_str;
64    char *ptr;
65    const char *header_delimiter = "\r\n\r\n";
66 
67    memset (res, 0, sizeof (*res));
68    _mongoc_buffer_init (&http_response_buf, NULL, 0, NULL, NULL);
69 
70    if (!_mongoc_host_list_from_hostport_with_err (
71           &host_list, req->host, (uint16_t) req->port, error)) {
72       goto fail;
73    }
74 
75    stream = mongoc_client_connect_tcp (timeout_ms, &host_list, error);
76    if (!stream) {
77       bson_set_error (error,
78                       MONGOC_ERROR_STREAM,
79                       MONGOC_ERROR_STREAM_SOCKET,
80                       "Failed to connect to: %s",
81                       req->host);
82       goto fail;
83    }
84 
85 #ifndef MONGOC_ENABLE_SSL
86    if (use_tls) {
87       bson_set_error (
88          error,
89          MONGOC_ERROR_STREAM,
90          MONGOC_ERROR_STREAM_SOCKET,
91          "Failed to connect to %s: libmongoc not built with TLS support",
92          req->host);
93       goto fail;
94    }
95 #else
96    if (use_tls) {
97       mongoc_stream_t *tls_stream;
98 
99       BSON_ASSERT (ssl_opts);
100       tls_stream = mongoc_stream_tls_new_with_hostname (
101          stream, req->host, ssl_opts, true);
102       if (!tls_stream) {
103          bson_set_error (error,
104                          MONGOC_ERROR_STREAM,
105                          MONGOC_ERROR_STREAM_SOCKET,
106                          "Failed create TLS stream to: %s",
107                          req->host);
108          goto fail;
109       }
110 
111       stream = tls_stream;
112       if (!mongoc_stream_tls_handshake_block (
113              stream, req->host, timeout_ms, error)) {
114          goto fail;
115       }
116    }
117 #endif
118 
119    if (!req->path) {
120       path = bson_strdup ("/");
121    } else if (req->path[0] != '/') {
122       path = bson_strdup_printf ("/%s", req->path);
123    } else {
124       path = bson_strdup (req->path);
125    }
126 
127    http_request = bson_string_new ("");
128    bson_string_append_printf (
129       http_request, "%s %s HTTP/1.0\r\n", req->method, path);
130    /* Always add Host header. */
131    bson_string_append_printf (http_request, "Host: %s\r\n", req->host);
132    /* Always add Connection: close header to ensure server closes connection. */
133    bson_string_append_printf (http_request, "Connection: close\r\n");
134    /* Add Content-Length if body included. */
135    if (req->body_len) {
136       bson_string_append_printf (
137          http_request, "Content-Length: %d\r\n", req->body_len);
138    }
139    if (req->extra_headers) {
140       bson_string_append (http_request, req->extra_headers);
141    }
142    bson_string_append (http_request, "\r\n");
143 
144    iovec.iov_base = http_request->str;
145    iovec.iov_len = http_request->len;
146 
147    if (!_mongoc_stream_writev_full (stream, &iovec, 1, timeout_ms, error)) {
148       goto fail;
149    }
150 
151    if (req->body) {
152       iovec.iov_base = (void *) req->body;
153       iovec.iov_len = req->body_len;
154       if (!_mongoc_stream_writev_full (stream, &iovec, 1, timeout_ms, error)) {
155          goto fail;
156       }
157    }
158 
159    /* Read until connection close. */
160    do {
161       bytes_read = _mongoc_buffer_try_append_from_stream (
162          &http_response_buf, stream, 512, timeout_ms);
163    } while (bytes_read > 0 || mongoc_stream_should_retry (stream));
164 
165    if (bytes_read < 0 && mongoc_stream_timed_out (stream)) {
166       bson_set_error (error,
167                       MONGOC_ERROR_STREAM,
168                       MONGOC_ERROR_STREAM_SOCKET,
169                       "Timeout reading from stream");
170       goto fail;
171    }
172 
173    if (http_response_buf.len == 0) {
174       bson_set_error (error,
175                       MONGOC_ERROR_STREAM,
176                       MONGOC_ERROR_STREAM_SOCKET,
177                       "No response received");
178       goto fail;
179    }
180 
181    http_response_str = (char *) http_response_buf.data;
182 
183    /* Find the end of the headers. */
184    ptr = strstr (http_response_str, header_delimiter);
185    if (NULL == ptr) {
186       bson_set_error (
187          error,
188          MONGOC_ERROR_STREAM,
189          MONGOC_ERROR_STREAM_SOCKET,
190          "Error occurred reading response: end of headers not found");
191       goto fail;
192    }
193 
194    res->headers_len = ptr - http_response_str;
195    res->headers = bson_strndup (http_response_str, res->headers_len);
196    res->body_len =
197       http_response_buf.len - res->headers_len - strlen (header_delimiter);
198    /* Add a NULL character in case caller assumes NULL terminated. */
199    res->body = bson_malloc0 (res->body_len + 1);
200    memcpy (res->body, ptr + strlen (header_delimiter), res->body_len);
201    ret = true;
202 
203 fail:
204    mongoc_stream_destroy (stream);
205    if (http_request) {
206       bson_string_free (http_request, true);
207    }
208    _mongoc_buffer_destroy (&http_response_buf);
209    bson_free (path);
210    return ret;
211 }
212