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