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

realm / realm-core / github_pull_request_319175

19 May 2025 08:16PM UTC coverage: 91.119% (-0.02%) from 91.143%
github_pull_request_319175

Pull #8082

Evergreen

web-flow
Bump setuptools from 70.0.0 to 78.1.1 in /evergreen/hang_analyzer

Bumps [setuptools](https://github.com/pypa/setuptools) from 70.0.0 to 78.1.1.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v70.0.0...v78.1.1)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-version: 78.1.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #8082: Bump setuptools from 70.0.0 to 78.1.1 in /evergreen/hang_analyzer

102788 of 181548 branches covered (56.62%)

217441 of 238634 relevant lines covered (91.12%)

5497200.53 hits per line

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

82.27
/src/realm/sync/network/http.hpp
1
/*************************************************************************
2
 *
3
 * Copyright 2022 Realm 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

19
#pragma once
20

21
#include <cstdint>
22
#include <cstdlib>
23
#include <type_traits>
24
#include <map>
25
#include <system_error>
26
#include <iosfwd>
27
#include <locale>
28
#include <sstream>
29

30
#include <realm/util/optional.hpp>
31
#include <realm/util/basic_system_errors.hpp>
32
#include <realm/util/logger.hpp>
33
#include <realm/string_data.hpp>
34

35
namespace realm::sync {
36
enum class HTTPParserError {
37
    None = 0,
38
    ContentTooLong,
39
    HeaderLineTooLong,
40
    MalformedResponse,
41
    MalformedRequest,
42
    BadRequest,
43
};
44
std::error_code make_error_code(HTTPParserError);
45
} // namespace realm::sync
46

47
namespace std {
48
template <>
49
struct is_error_code_enum<realm::sync::HTTPParserError> : std::true_type {};
50
} // namespace std
51

52
namespace realm::sync {
53

54
/// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
55
///
56
/// It is guaranteed that the backing integer value of this enum corresponds
57
/// to the numerical code representing the status.
58
enum class HTTPStatus {
59
    Unknown = 0,
60

61
    Continue = 100,
62
    SwitchingProtocols = 101,
63

64
    Ok = 200,
65
    Created = 201,
66
    Accepted = 202,
67
    NonAuthoritative = 203,
68
    NoContent = 204,
69
    ResetContent = 205,
70
    PartialContent = 206,
71

72
    MultipleChoices = 300,
73
    MovedPermanently = 301,
74
    Found = 302,
75
    SeeOther = 303,
76
    NotModified = 304,
77
    UseProxy = 305,
78
    SwitchProxy = 306,
79
    TemporaryRedirect = 307,
80
    PermanentRedirect = 308,
81

82
    BadRequest = 400,
83
    Unauthorized = 401,
84
    PaymentRequired = 402,
85
    Forbidden = 403,
86
    NotFound = 404,
87
    MethodNotAllowed = 405,
88
    NotAcceptable = 406,
89
    ProxyAuthenticationRequired = 407,
90
    RequestTimeout = 408,
91
    Conflict = 409,
92
    Gone = 410,
93
    LengthRequired = 411,
94
    PreconditionFailed = 412,
95
    PayloadTooLarge = 413,
96
    UriTooLong = 414,
97
    UnsupportedMediaType = 415,
98
    RangeNotSatisfiable = 416,
99
    ExpectationFailed = 417,
100
    ImATeapot = 418,
101
    MisdirectedRequest = 421,
102
    UpgradeRequired = 426,
103
    PreconditionRequired = 428,
104
    TooManyRequests = 429,
105
    RequestHeaderFieldsTooLarge = 431,
106
    UnavailableForLegalReasons = 451,
107

108
    InternalServerError = 500,
109
    NotImplemented = 501,
110
    BadGateway = 502,
111
    ServiceUnavailable = 503,
112
    GatewayTimeout = 504,
113
    HttpVersionNotSupported = 505,
114
    VariantAlsoNegotiates = 506,
115
    NotExtended = 510,
116
    NetworkAuthenticationRequired = 511,
117
};
118

119
bool valid_http_status_code(unsigned int code);
120

121
/// See: https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
122
enum class HTTPMethod {
123
    Options,
124
    Get,
125
    Head,
126
    Post,
127
    Put,
128
    Patch,
129
    Delete,
130
    Trace,
131
    Connect,
132
};
133

134
struct HTTPAuthorization {
135
    std::string scheme;
136
    std::map<std::string, std::string> values;
137
};
138

139
HTTPAuthorization parse_authorization(const std::string&);
140

141
class HeterogeneousCaseInsensitiveCompare {
142
public:
143
    using is_transparent = std::true_type;
144
    template <class A, class B>
145
    bool operator()(const A& a, const B& b) const noexcept
146
    {
464,304✔
147
        return comp(std::string_view(a), std::string_view(b));
464,304✔
148
    }
464,304✔
149

150
private:
151
    bool comp(std::string_view a, std::string_view b) const noexcept
152
    {
464,282✔
153
        auto cmp = [](char lhs, char rhs) {
4,102,660✔
154
            return std::tolower(lhs, std::locale::classic()) < std::tolower(rhs, std::locale::classic());
4,102,660✔
155
        };
4,102,660✔
156
        return std::lexicographical_compare(begin(a), end(a), begin(b), end(b), cmp);
464,282✔
157
    }
464,282✔
158
};
159

160
/// Case-insensitive map suitable for storing HTTP headers.
161
using HTTPHeaders = std::map<std::string, std::string, HeterogeneousCaseInsensitiveCompare>;
162

163
struct HTTPRequest {
164
    HTTPMethod method = HTTPMethod::Get;
165
    HTTPHeaders headers;
166
    std::string path;
167

168
    /// If the request object has a body, the Content-Length header MUST be
169
    /// set to a string representation of the number of bytes in the body.
170
    /// FIXME: Relax this restriction, and also support Transfer-Encoding
171
    /// and other HTTP/1.1 features.
172
    util::Optional<std::string> body;
173
};
174

175
struct HTTPResponse {
176
    HTTPStatus status = HTTPStatus::Unknown;
177
    std::string reason;
178
    HTTPHeaders headers;
179

180
    // A body is only read from the response stream if the server sent the
181
    // Content-Length header.
182
    // FIXME: Support other transfer methods, including Transfer-Encoding and
183
    // HTTP/1.1 features.
184
    util::Optional<std::string> body;
185
};
186

187

188
/// Serialize HTTP request to output stream.
189
std::ostream& operator<<(std::ostream&, const HTTPRequest&);
190
/// Serialize HTTP response to output stream.
191
std::ostream& operator<<(std::ostream&, const HTTPResponse&);
192
/// Serialize HTTP method to output stream ("GET", "POST", etc.).
193
std::ostream& operator<<(std::ostream&, HTTPMethod);
194
/// Serialize HTTP status to output stream, include reason string ("200 OK" etc.)
195
std::ostream& operator<<(std::ostream&, HTTPStatus);
196

197

198
struct HTTPParserBase {
199
    const std::shared_ptr<util::Logger> logger_ptr;
200
    util::Logger& network_logger;
201

202
    // FIXME: Generally useful?
203
    struct CallocDeleter {
204
        void operator()(void* ptr)
205
        {
7,624✔
206
            std::free(ptr);
7,624✔
207
        }
7,624✔
208
    };
209

210
    HTTPParserBase(const std::shared_ptr<util::Logger>& logger_ptr)
211
        : logger_ptr{std::make_shared<util::CategoryLogger>(util::LogCategory::network, logger_ptr)}
3,750✔
212
        , network_logger{*logger_ptr}
3,750✔
213
    {
7,608✔
214
        // Allocating read buffer with calloc to avoid accidentally spilling
215
        // data from other sessions in case of a buffer overflow exploit.
216
        m_read_buffer.reset(static_cast<char*>(std::calloc(read_buffer_size, 1)));
7,608✔
217
    }
7,608✔
218
    virtual ~HTTPParserBase() {}
7,608✔
219

220
    std::string m_write_buffer;
221
    std::unique_ptr<char[], CallocDeleter> m_read_buffer;
222
    util::Optional<size_t> m_found_content_length;
223
    bool m_has_chunked_encoding = false;
224
    std::optional<std::stringstream> m_chunked_encoding_ss;
225
    static const size_t read_buffer_size = 8192;
226
    static const size_t max_header_line_length = read_buffer_size;
227

228
    /// Parses the contents of m_read_buffer as a HTTP header line,
229
    /// and calls on_header() as appropriate. on_header() will be called at
230
    /// most once per invocation.
231
    /// Returns false if the contents of m_read_buffer is not a valid HTTP
232
    /// header line.
233
    bool parse_header_line(size_t len);
234

235
    virtual std::error_code on_first_line(StringData line) = 0;
236
    virtual void on_header(StringData key, StringData value) = 0;
237
    virtual void on_body(StringData body) = 0;
238
    virtual void on_complete(std::error_code = std::error_code{}) = 0;
239

240
    /// If the input matches a known HTTP method string, return the appropriate
241
    /// HTTPMethod enum value. Otherwise, returns none.
242
    static util::Optional<HTTPMethod> parse_method_string(StringData method);
243

244
    /// Interpret line as the first line of an HTTP request. If the return value
245
    /// is true, out_method and out_uri have been assigned the appropriate
246
    /// values found in the request line.
247
    static bool parse_first_line_of_request(StringData line, HTTPMethod& out_method, StringData& out_uri);
248

249
    /// Interpret line as the first line of an HTTP response. If the return
250
    /// value is true, out_status and out_reason have been assigned the
251
    /// appropriate values found in the response line.
252
    static bool parse_first_line_of_response(StringData line, HTTPStatus& out_status, StringData& out_reason,
253
                                             util::Logger& logger);
254

255
    void set_write_buffer(const HTTPRequest&);
256
    void set_write_buffer(const HTTPResponse&);
257
};
258

259

260
template <class Socket>
261
struct HTTPParser : protected HTTPParserBase {
262
    explicit HTTPParser(Socket& socket, const std::shared_ptr<util::Logger>& logger_ptr)
263
        : HTTPParserBase(logger_ptr)
3,472✔
264
        , m_socket(socket)
3,472✔
265
    {
7,321✔
266
    }
7,321✔
267

268
    void read_first_line()
269
    {
6,090✔
270
        auto handler = [this](std::error_code ec, size_t n) {
6,090✔
271
            if (ec == util::error::operation_aborted) {
6,090✔
272
                return;
54✔
273
            }
54✔
274
            if (ec) {
6,036✔
275
                on_complete(ec);
8✔
276
                return;
8✔
277
            }
8✔
278
            ec = on_first_line(StringData(m_read_buffer.get(), n));
6,028✔
279
            if (ec) {
6,028✔
280
                on_complete(ec);
×
281
                return;
×
282
            }
×
283
            read_headers();
6,028✔
284
        };
6,028✔
285
        m_socket.async_read_until(m_read_buffer.get(), max_header_line_length, '\n', std::move(handler));
6,090✔
286
    }
6,090✔
287

288
    void read_headers()
289
    {
47,393✔
290
        auto handler = [this](std::error_code ec, size_t n) {
47,393✔
291
            if (ec == util::error::operation_aborted) {
47,393✔
292
                return;
×
293
            }
×
294
            if (ec) {
47,393✔
295
                on_complete(ec);
×
296
                return;
×
297
            }
×
298
            if (n <= 2) {
47,393✔
299
                read_body();
6,020✔
300
                return;
6,020✔
301
            }
6,020✔
302
            if (!parse_header_line(n)) {
41,373✔
303
                on_complete(HTTPParserError::BadRequest);
8✔
304
                return;
8✔
305
            }
8✔
306

307
            // FIXME: Limit the total size of headers. Apache uses 8K.
308
            read_headers();
41,365✔
309
        };
41,365✔
310
        m_socket.async_read_until(m_read_buffer.get(), max_header_line_length, '\n', std::move(handler));
47,393✔
311
    }
47,393✔
312

313
    void read_body()
314
    {
6,040✔
315
        if (m_found_content_length) {
6,040✔
316
            // FIXME: Support longer bodies.
317
            // FIXME: Support multipart and other body types (no body shaming).
318
            if (*m_found_content_length > read_buffer_size) {
12!
319
                on_complete(HTTPParserError::ContentTooLong);
×
320
                return;
×
321
            }
×
322

323
            auto handler = [this](std::error_code ec, size_t n) {
12✔
324
                if (ec == util::error::operation_aborted) {
12!
325
                    return;
×
326
                }
×
327
                if (!ec) {
12!
328
                    on_body(std::string_view(m_read_buffer.get(), n));
12✔
329
                }
12✔
330
                on_complete(ec);
12✔
331
            };
12✔
332
            m_socket.async_read(m_read_buffer.get(), *m_found_content_length, std::move(handler));
12✔
333
        }
12✔
334
        else if (m_has_chunked_encoding) {
6,028!
335
            auto content_length_handler = [this](std::error_code ec, size_t chunk_start_index) {
20✔
336
                if (ec == util::error::operation_aborted) {
20!
337
                    on_complete(ec);
×
338
                    return;
×
339
                }
×
340

341
                auto content_length =
20✔
342
                    std::strtoul(std::string(m_read_buffer.get(), chunk_start_index - 2).c_str(), nullptr, 16);
20✔
343

344
                if (content_length == 0) {
20!
345
                    on_body(m_chunked_encoding_ss->str());
8✔
346
                    on_complete(ec);
8✔
347
                    return;
8✔
348
                }
8✔
349

350
                auto handler = [this](std::error_code ec, size_t n) {
12✔
351
                    if (ec == util::error::operation_aborted) {
12!
352
                        on_complete(ec);
×
353
                        return;
×
354
                    }
×
355
                    auto chunk_data = std::string_view(m_read_buffer.get(), n - 2); // -2 to strip \r\n
12✔
356
                    *m_chunked_encoding_ss << chunk_data;
12✔
357
                    read_body();
12✔
358
                };
12✔
359
                m_socket.async_read(m_read_buffer.get(), content_length + 2,
12✔
360
                                    std::move(handler)); // +2 to account for \r\n
12✔
361
            };
12✔
362

363
            // First get the content-length
364
            m_socket.async_read_until(m_read_buffer.get(), 8, '\n',
20✔
365
                                      content_length_handler); // buffer of 8 is enough to read hex value
20✔
366
        }
20✔
367
        else {
6,008✔
368
            // No body, just finish.
369
            on_complete();
6,008✔
370
        }
6,008✔
371
    }
6,040✔
372

373
    void write_buffer(util::UniqueFunction<void(std::error_code, size_t)> handler)
374
    {
6,080✔
375
        m_socket.async_write(m_write_buffer.data(), m_write_buffer.size(), std::move(handler));
6,080✔
376
    }
6,080✔
377

378
    Socket& m_socket;
379
};
380

381

382
template <class Socket>
383
struct HTTPClient : protected HTTPParser<Socket> {
384
    using Handler = void(HTTPResponse, std::error_code);
385

386
    explicit HTTPClient(Socket& socket, const std::shared_ptr<util::Logger>& logger_ptr)
387
        : HTTPParser<Socket>(socket, logger_ptr)
1,720✔
388
    {
3,730✔
389
    }
3,730✔
390

391
    /// Serialize and send \a request over the connected socket asynchronously.
392
    ///
393
    /// When the response has been received, or an error occurs, \a handler will
394
    /// be invoked with the appropriate parameters. The HTTPResponse object
395
    /// passed to \a handler will only be complete in non-error conditions, but
396
    /// may be partially populated.
397
    ///
398
    /// It is an error to start a request before the \a handler of a previous
399
    /// request has been invoked. It is permitted to call async_request() from
400
    /// the handler, unless an error has been reported representing a condition
401
    /// where the underlying socket is no longer able to communicate (for
402
    /// example, if it has been closed).
403
    ///
404
    /// If a request is already in progress, an exception will be thrown.
405
    ///
406
    /// This method is *NOT* thread-safe.
407
    void async_request(const HTTPRequest& request, util::UniqueFunction<Handler> handler)
408
    {
3,722✔
409
        if (REALM_UNLIKELY(m_handler)) {
3,722✔
410
            throw LogicError(ErrorCodes::LogicError, "Request already in progress.");
×
411
        }
×
412
        this->set_write_buffer(request);
3,722✔
413
        m_handler = std::move(handler);
3,722✔
414
        this->write_buffer([this](std::error_code ec, size_t bytes_written) {
3,722✔
415
            static_cast<void>(bytes_written);
3,722✔
416
            if (ec == util::error::operation_aborted) {
3,722✔
417
                return;
×
418
            }
×
419
            if (ec) {
3,722!
420
                this->on_complete(ec);
×
421
                return;
×
422
            }
×
423
            this->read_first_line();
3,722✔
424
        });
3,722✔
425
    }
3,722✔
426

427
private:
428
    util::UniqueFunction<Handler> m_handler;
429
    HTTPResponse m_response;
430

431
    std::error_code on_first_line(StringData line) override final
432
    {
3,668✔
433
        HTTPStatus status;
3,668✔
434
        StringData reason;
3,668✔
435
        if (this->parse_first_line_of_response(line, status, reason, this->network_logger)) {
3,668!
436
            m_response.status = status;
3,668✔
437
            m_response.reason = reason;
3,668✔
438
            return std::error_code{};
3,668✔
439
        }
3,668✔
440
        return HTTPParserError::MalformedResponse;
×
441
    }
3,668✔
442

443
    void on_header(StringData key, StringData value) override final
444
    {
26,320✔
445
        // FIXME: Multiple headers with the same key should show up as a
446
        // comma-separated list of their values, rather than overwriting.
447
        m_response.headers[std::string(key)] = std::string(value);
26,320✔
448
    }
26,320✔
449

450
    void on_body(StringData body) override final
451
    {
4✔
452
        m_response.body = std::string(body);
4✔
453
    }
4✔
454

455
    void on_complete(std::error_code ec) override final
456
    {
3,668✔
457
        auto handler = std::move(m_handler);
3,668✔
458
        m_handler = nullptr;
3,668✔
459
        handler(std::move(m_response), ec);
3,668✔
460
    }
3,668✔
461
};
462

463

464
template <class Socket>
465
struct HTTPServer : protected HTTPParser<Socket> {
466
    using RequestHandler = void(HTTPRequest, std::error_code);
467
    using RespondHandler = void(std::error_code);
468

469
    explicit HTTPServer(Socket& socket, const std::shared_ptr<util::Logger>& logger_ptr)
470
        : HTTPParser<Socket>(socket, logger_ptr)
1,748✔
471
    {
3,583✔
472
    }
3,583✔
473

474
    /// Receive a request on the underlying socket asynchronously.
475
    ///
476
    /// This function starts an asynchronous read operation and keeps reading
477
    /// until an HTTP request has been received. \a handler is invoked when a
478
    /// request has been received, or an error occurs.
479
    ///
480
    /// After a request is received, callers MUST invoke async_send_response()
481
    /// to provide the client with a valid HTTP response, unless the error
482
    /// passed to the handler represents a condition where the underlying socket
483
    /// is no longer able to communicate (for example, if it has been closed).
484
    ///
485
    /// It is an error to attempt to receive a request before any previous
486
    /// requests have been fully responded to, i.e. the \a handler argument of
487
    /// async_send_response() must have been invoked before attempting to
488
    /// receive the next request.
489
    ///
490
    /// This function is *NOT* thread-safe.
491
    void async_receive_request(util::UniqueFunction<RequestHandler> handler)
492
    {
2,368✔
493
        if (REALM_UNLIKELY(m_request_handler)) {
2,368✔
494
            throw LogicError(ErrorCodes::LogicError, "Request already in progress");
×
495
        }
×
496
        m_request_handler = std::move(handler);
2,368✔
497
        this->read_first_line();
2,368✔
498
    }
2,368✔
499

500
    /// Send an HTTP response to a client asynchronously.
501
    ///
502
    /// This function starts an asynchronous write operation on the underlying
503
    /// socket. \a handler is invoked when the response has been written to the
504
    /// socket, or an error occurs.
505
    ///
506
    /// It is an error to call async_receive_request() again before \a handler
507
    /// has been invoked, and it is an error to call async_send_response()
508
    /// before the \a handler of a previous invocation has been invoked.
509
    ///
510
    /// This function is *NOT* thread-safe.
511
    void async_send_response(const HTTPResponse& response, util::UniqueFunction<RespondHandler> handler)
512
    {
2,360✔
513
        if (REALM_UNLIKELY(!m_request_handler)) {
2,360✔
514
            throw LogicError(ErrorCodes::LogicError, "No request in progress");
×
515
        }
×
516
        if (m_respond_handler) {
2,360✔
517
            // FIXME: Proper exception type.
518
            throw LogicError(ErrorCodes::LogicError, "Already responding to request");
×
519
        }
×
520
        m_respond_handler = std::move(handler);
2,360✔
521
        this->set_write_buffer(response);
2,360✔
522
        this->write_buffer([this](std::error_code ec, size_t) {
2,360✔
523
            if (ec == util::error::operation_aborted) {
2,360✔
524
                return;
×
525
            }
×
526
            m_request_handler = nullptr;
2,360✔
527
            auto handler = std::move(m_respond_handler);
2,360✔
528
            handler(ec);
2,360✔
529
        });
2,360✔
530
        ;
2,360✔
531
    }
2,360✔
532

533
private:
534
    util::UniqueFunction<RequestHandler> m_request_handler;
535
    util::UniqueFunction<RespondHandler> m_respond_handler;
536
    HTTPRequest m_request;
537

538
    std::error_code on_first_line(StringData line) override final
539
    {
2,360✔
540
        HTTPMethod method;
2,360✔
541
        StringData uri;
2,360✔
542
        if (this->parse_first_line_of_request(line, method, uri)) {
2,360✔
543
            m_request.method = method;
2,360✔
544
            m_request.path = uri;
2,360✔
545
            return std::error_code{};
2,360✔
546
        }
2,360✔
547
        return HTTPParserError::MalformedRequest;
×
548
    }
2,360✔
549

550
    void on_header(StringData key, StringData value) override final
551
    {
15,045✔
552
        // FIXME: Multiple headers with the same key should show up as a
553
        // comma-separated list of their values, rather than overwriting.
554
        m_request.headers[std::string(key)] = std::string(value);
15,045✔
555
    }
15,045✔
556

557
    void on_body(StringData body) override final
558
    {
8✔
559
        m_request.body = std::string(body);
8✔
560
    }
8✔
561

562
    void on_complete(std::error_code ec) override final
563
    {
2,368✔
564
        // Deliberately not nullifying m_request_handler so that we can
565
        // check for invariants in async_send_response.
566
        m_request_handler(std::move(m_request), ec);
2,368✔
567
    }
2,368✔
568
};
569

570
} // namespace realm::sync
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