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

realm / realm-core / 1820

07 Nov 2023 10:50AM UTC coverage: 91.665% (+0.02%) from 91.648%
1820

push

Evergreen

web-flow
Test FLX sync geospatial queries (#7042)

* add tests for FLX geospatial

* update tests for new BAAS error messages

* fix several hangs in tests

download/upload/advance may have already happened
by the time the test code gets to the wait_for_upload() line
which results in the test hanging forever because there are no further
changes. A fix is to do a timed wait for the expected state which will
succeed immediately if the same race occurs.

* revert polling waits in tests

* fix an error if recovering schema changes in dev mode

* add additional tests

* don't overcomplicate the test

* lint

---------

Co-authored-by: Daniel Tabacaru <96778637+danieltabacaru@users.noreply.github.com>

92104 of 168878 branches covered (0.0%)

129 of 129 new or added lines in 4 files covered. (100.0%)

60 existing lines in 11 files now uncovered.

230809 of 251797 relevant lines covered (91.66%)

6701489.7 hits per line

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

63.29
/src/realm/sync/network/http.cpp
1
#include <algorithm>
2
#include <cctype>
3
#include <sstream>
4
#include <ostream>
5

6
#include <realm/sync/network/http.hpp>
7

8
using namespace realm;
9

10
namespace {
11

12
StringData trim_whitespace(StringData str)
13
{
166,172✔
14
    auto p0 = str.data();
166,172✔
15
    auto p1 = str.data() + str.size();
166,172✔
16
    while (p1 > p0 && std::isspace(*(p1 - 1)))
260,038✔
17
        --p1;
93,866✔
18
    while (p0 < p1 && std::isspace(*p0))
201,910✔
19
        ++p0;
35,738✔
20
    return StringData(p0, p1 - p0);
166,172✔
21
}
166,172✔
22

23

24
struct HTTPParserErrorCategory : std::error_category {
25
    HTTPParserErrorCategory() {}
12✔
26

27
    const char* name() const noexcept override
28
    {
×
29
        return "HTTP Parser Error";
×
30
    }
×
31

32
    std::string message(int condition) const override
33
    {
×
34
        using sync::HTTPParserError;
×
35
        switch (HTTPParserError(condition)) {
×
36
            case HTTPParserError::None:
×
37
                return "None";
×
38
            case HTTPParserError::ContentTooLong:
×
39
                return "Content too long";
×
40
            case HTTPParserError::HeaderLineTooLong:
×
41
                return "Header line too long";
×
42
            case HTTPParserError::MalformedResponse:
×
43
                return "Malformed response";
×
44
            case HTTPParserError::MalformedRequest:
×
45
                return "Malformed request";
×
46
            default:
×
47
                REALM_TERMINATE("Invalid HTTP Parser Error");
48
        }
×
49
    }
×
50
};
51

52
const HTTPParserErrorCategory g_http_parser_error_category;
53

54
} // unnamed namespace
55

56

57
namespace realm::sync {
58

59
bool valid_http_status_code(unsigned int code)
60
{
3,358✔
61
    if (code < 100)
3,358✔
62
        return false;
×
63
    if (code > 101 && code < 200)
3,358✔
64
        return false;
×
65
    if (code > 206 && code < 300)
3,358✔
66
        return false;
×
67
    if (code > 308 && code < 400)
3,358✔
68
        return false;
×
69
    if (code > 451 && code < 500)
3,358✔
70
        return false;
×
71
    if (code > 511)
3,358✔
72
        return false;
×
73

1,668✔
74
    return true;
3,358✔
75
}
3,358✔
76

77

78
HTTPAuthorization parse_authorization(const std::string& header_value)
79
{
40✔
80
    // StringData line{header_value.c_str(), header_value.length()};
20✔
81

20✔
82
    HTTPAuthorization auth;
40✔
83
    auto p = header_value.begin();
40✔
84
    auto end = header_value.end();
40✔
85
    auto space = std::find(p, end, ' ');
40✔
86

20✔
87
    auth.scheme = std::string(p, space);
40✔
88

20✔
89
    while (space != end) {
156✔
90
        p = space + 1;
116✔
91
        space = std::find(p, end, ' ');
116✔
92
        auto eq = std::find(p, space, '=');
116✔
93

58✔
94
        if (eq == space) {
116✔
95
            continue;
88✔
96
        }
88✔
97

14✔
98
        auto key_begin = p;
28✔
99
        auto key_end = eq;
28✔
100
        auto value_begin = eq + 1;
28✔
101
        auto value_end = space;
28✔
102

14✔
103
        std::string key(key_begin, key_end);
28✔
104
        std::string value(value_begin, value_end);
28✔
105

14✔
106
        if (key.size() == 0)
28✔
107
            continue;
×
108

14✔
109
        auth.values[key] = value;
28✔
110
    }
28✔
111

20✔
112
    return auth;
40✔
113
}
40✔
114

115

116
void HTTPParserBase::set_write_buffer(const HTTPRequest& req)
117
{
3,388✔
118
    std::stringstream ss;
3,388✔
119
    ss << req;
3,388✔
120
    m_write_buffer = ss.str();
3,388✔
121
}
3,388✔
122

123

124
void HTTPParserBase::set_write_buffer(const HTTPResponse& res)
125
{
2,004✔
126
    std::stringstream ss;
2,004✔
127
    ss << res;
2,004✔
128
    m_write_buffer = ss.str();
2,004✔
129
}
2,004✔
130

131

132
bool HTTPParserBase::parse_header_line(size_t len)
133
{
37,408✔
134
    StringData line{m_read_buffer.get(), len};
37,408✔
135
    auto p = line.data();
37,408✔
136
    auto end = line.data() + line.size();
37,408✔
137
    auto colon = std::find(p, end, ':');
37,408✔
138

18,598✔
139
    if (colon == end) {
37,408✔
140
        logger.error("Bad header line in HTTP message:\n%1", line);
4✔
141
        return false;
4✔
142
    }
4✔
143

18,596✔
144
    auto key_begin = p;
37,404✔
145
    auto key_end = colon;
37,404✔
146
    auto value_begin = colon + 1;
37,404✔
147
    auto value_end = end;
37,404✔
148

18,596✔
149
    StringData key(key_begin, key_end - key_begin);
37,404✔
150
    StringData value(value_begin, value_end - value_begin);
37,404✔
151

18,596✔
152
    key = trim_whitespace(key);
37,404✔
153
    value = trim_whitespace(value);
37,404✔
154

18,596✔
155
    if (key.size() == 0) {
37,404✔
156
        logger.error("Bad header line in HTTP message:\n%1", line);
8✔
157
        return false;
8✔
158
    }
8✔
159

18,592✔
160
    if (key == "Content-Length" || key == "content-length") {
37,396✔
161
        if (value.size() == 0) {
24✔
162
            // We consider the empty Content-Length to mean 0.
2✔
163
            // A warning is logged.
2✔
164
            logger.warn("Empty Content-Length header in HTTP message:\n%1", line);
4✔
165
            m_found_content_length = 0;
4✔
166
        }
4✔
167
        else {
20✔
168
            std::stringstream ss;
20✔
169
            ss.str(value);
20✔
170
            size_t content_length;
20✔
171
            if (ss >> content_length && ss.eof()) {
20✔
172
                m_found_content_length = content_length;
12✔
173
            }
12✔
174
            else {
8✔
175
                logger.error("Bad Content-Length header in HTTP message:\n%1", line);
8✔
176
                return false;
8✔
177
            }
8✔
178
        }
37,388✔
179
    }
24✔
180

18,588✔
181
    this->on_header(key, value);
37,388✔
182
    return true;
37,388✔
183
}
37,388✔
184

185

186
util::Optional<HTTPMethod> HTTPParserBase::parse_method_string(StringData method)
187
{
2,020✔
188
    if (method == "OPTIONS")
2,020✔
189
        return HTTPMethod::Options;
×
190
    if (method == "GET")
2,020✔
191
        return HTTPMethod::Get;
2,000✔
192
    if (method == "HEAD")
20✔
193
        return HTTPMethod::Head;
×
194
    if (method == "POST")
20✔
195
        return HTTPMethod::Post;
16✔
196
    if (method == "PUT")
4✔
197
        return HTTPMethod::Put;
×
198
    if (method == "DELETE")
4✔
199
        return HTTPMethod::Delete;
×
200
    if (method == "TRACE")
4✔
201
        return HTTPMethod::Trace;
×
202
    if (method == "CONNECT")
4✔
203
        return HTTPMethod::Connect;
×
204
    return none;
4✔
205
}
4✔
206

207

208
bool HTTPParserBase::parse_first_line_of_request(StringData line, HTTPMethod& out_method, StringData& out_uri)
209
{
2,048✔
210
    line = trim_whitespace(line);
2,048✔
211
    auto p = line.data();
2,048✔
212
    auto end = line.data() + line.size();
2,048✔
213
    auto sp = std::find(p, end, ' ');
2,048✔
214
    if (sp == end)
2,048✔
215
        return false;
4✔
216
    StringData method(p, sp - p);
2,044✔
217
    auto request_uri_begin = sp + 1;
2,044✔
218
    sp = std::find(request_uri_begin, end, ' ');
2,044✔
219
    if (sp == end)
2,044✔
220
        return false;
8✔
221
    out_uri = StringData(request_uri_begin, sp - request_uri_begin);
2,036✔
222
    auto http_version_begin = sp + 1;
2,036✔
223
    StringData http_version(http_version_begin, end - http_version_begin);
2,036✔
224
    if (http_version != "HTTP/1.1") {
2,036✔
225
        return false;
16✔
226
    }
16✔
227
    auto parsed_method = HTTPParserBase::parse_method_string(method);
2,020✔
228
    if (!parsed_method)
2,020✔
229
        return false;
4✔
230
    out_method = *parsed_method;
2,016✔
231
    return true;
2,016✔
232
}
2,016✔
233

234

235
bool HTTPParserBase::parse_first_line_of_response(StringData line, HTTPStatus& out_status, StringData& out_reason,
236
                                                  util::Logger& logger)
237
{
3,370✔
238
    line = trim_whitespace(line);
3,370✔
239
    auto p = line.data();
3,370✔
240
    auto end = line.data() + line.size();
3,370✔
241
    auto sp = std::find(p, end, ' ');
3,370✔
242
    if (sp == end) {
3,370✔
243
        logger.error("Invalid HTTP response:\n%1", line);
4✔
244
        return false;
4✔
245
    }
4✔
246
    StringData http_version(p, sp - p);
3,366✔
247
    if (http_version != "HTTP/1.1") {
3,366✔
248
        logger.error("Invalid version in HTTP response:\n%1", line);
4✔
249
        return false;
4✔
250
    }
4✔
251
    auto status_code_begin = sp + 1;
3,362✔
252
    sp = std::find(status_code_begin, end, ' ');
3,362✔
253
    auto status_code_end = sp;
3,362✔
254
    if (status_code_end != end) {
3,362✔
255
        // Some proxies don't give a "Reason-Phrase". This is not valid
1,668✔
256
        // according to the HTTP/1.1 standard, but what are we gonna do...
1,668✔
257
        auto reason_begin = sp + 1;
3,358✔
258
        auto reason_end = end;
3,358✔
259
        out_reason = StringData(reason_begin, reason_end - reason_begin);
3,358✔
260
    }
3,358✔
261

1,670✔
262
    StringData status_code_str(status_code_begin, status_code_end - status_code_begin);
3,362✔
263
    std::stringstream ss;
3,362✔
264
    ss << status_code_str;
3,362✔
265
    unsigned int code;
3,362✔
266
    if (ss >> code && valid_http_status_code(code)) {
3,362✔
267
        out_status = static_cast<HTTPStatus>(code);
3,358✔
268
    }
3,358✔
269
    else {
4✔
270
        logger.error("Invalid status code in HTTP response:\n%1", line);
4✔
271
        return false;
4✔
272
    }
4✔
273
    return true;
3,358✔
274
}
3,358✔
275

276

277
std::ostream& operator<<(std::ostream& os, HTTPMethod method)
278
{
8,752✔
279
    switch (method) {
8,752✔
280
        case HTTPMethod::Options:
✔
281
            return os << "OPTIONS";
×
282
        case HTTPMethod::Get:
8,742✔
283
            return os << "GET";
8,742✔
284
        case HTTPMethod::Head:
✔
285
            return os << "HEAD";
×
286
        case HTTPMethod::Post:
8✔
287
            return os << "POST";
8✔
288
        case HTTPMethod::Put:
✔
289
            return os << "PUT";
×
290
        case HTTPMethod::Delete:
✔
291
            return os << "DELETE";
×
292
        case HTTPMethod::Trace:
✔
293
            return os << "TRACE";
×
294
        case HTTPMethod::Connect:
✔
295
            return os << "CONNECT";
×
296
    }
×
297
    REALM_TERMINATE("Invalid HTTPRequest object.");
298
}
×
299

300

301
std::ostream& operator<<(std::ostream& os, HTTPStatus status)
302
{
5,354✔
303
    os << int(status) << ' ';
5,354✔
304
    switch (status) {
5,354✔
305
        case HTTPStatus::Unknown:
✔
306
            return os << "Unknown Status";
×
307
        case HTTPStatus::Continue:
✔
308
            return os << "Continue";
×
309
        case HTTPStatus::SwitchingProtocols:
5,330✔
310
            return os << "Switching Protocols";
5,330✔
311
        case HTTPStatus::Ok:
4✔
312
            return os << "OK";
4✔
313
        case HTTPStatus::Created:
✔
314
            return os << "Created";
×
315
        case HTTPStatus::Accepted:
✔
316
            return os << "Accepted";
×
317
        case HTTPStatus::NonAuthoritative:
✔
318
            return os << "Non-Authoritative Information";
×
319
        case HTTPStatus::NoContent:
✔
320
            return os << "No Content";
×
321
        case HTTPStatus::ResetContent:
✔
322
            return os << "Reset Content";
×
323
        case HTTPStatus::PartialContent:
✔
324
            return os << "Partial Content";
×
325
        case HTTPStatus::MultipleChoices:
✔
326
            return os << "Multiple Choices";
×
327
        case HTTPStatus::MovedPermanently:
✔
328
            return os << "Moved Permanently";
×
329
        case HTTPStatus::Found:
✔
330
            return os << "Found";
×
331
        case HTTPStatus::SeeOther:
✔
332
            return os << "See Other";
×
333
        case HTTPStatus::NotModified:
✔
334
            return os << "Not Modified";
×
335
        case HTTPStatus::UseProxy:
✔
336
            return os << "Use Proxy";
×
337
        case HTTPStatus::SwitchProxy:
✔
338
            return os << "Switch Proxy";
×
339
        case HTTPStatus::TemporaryRedirect:
✔
340
            return os << "Temporary Redirect";
×
341
        case HTTPStatus::PermanentRedirect:
✔
342
            return os << "Permanent Redirect";
×
343
        case HTTPStatus::BadRequest:
8✔
344
            return os << "Bad Request";
8✔
UNCOV
345
        case HTTPStatus::Unauthorized:
✔
UNCOV
346
            return os << "Unauthorized";
×
347
        case HTTPStatus::PaymentRequired:
✔
348
            return os << "Payment Required";
×
349
        case HTTPStatus::Forbidden:
✔
350
            return os << "Forbidden";
×
351
        case HTTPStatus::NotFound:
12✔
352
            return os << "Not Found";
12✔
353
        case HTTPStatus::MethodNotAllowed:
✔
354
            return os << "Method Not Allowed";
×
355
        case HTTPStatus::NotAcceptable:
✔
356
            return os << "Not Acceptable";
×
357
        case HTTPStatus::ProxyAuthenticationRequired:
✔
358
            return os << "Proxy Authentication Required";
×
359
        case HTTPStatus::RequestTimeout:
✔
360
            return os << "Request Timeout";
×
361
        case HTTPStatus::Conflict:
✔
362
            return os << "Conflict";
×
363
        case HTTPStatus::Gone:
✔
364
            return os << "Gone";
×
365
        case HTTPStatus::LengthRequired:
✔
366
            return os << "Length Required";
×
367
        case HTTPStatus::PreconditionFailed:
✔
368
            return os << "Precondition Failed";
×
369
        case HTTPStatus::PayloadTooLarge:
✔
370
            return os << "Payload Too Large";
×
371
        case HTTPStatus::UriTooLong:
✔
372
            return os << "URI Too Long";
×
373
        case HTTPStatus::UnsupportedMediaType:
✔
374
            return os << "Unsupported Media Type";
×
375
        case HTTPStatus::RangeNotSatisfiable:
✔
376
            return os << "Range Not Satisfiable";
×
377
        case HTTPStatus::ExpectationFailed:
✔
378
            return os << "Expectation Failed";
×
379
        case HTTPStatus::ImATeapot:
✔
380
            return os << "I'm A Teapot";
×
381
        case HTTPStatus::MisdirectedRequest:
✔
382
            return os << "Misdirected Request";
×
383
        case HTTPStatus::UpgradeRequired:
✔
384
            return os << "Upgrade Required";
×
385
        case HTTPStatus::PreconditionRequired:
✔
386
            return os << "Precondition Required";
×
387
        case HTTPStatus::TooManyRequests:
✔
388
            return os << "Too Many Requests";
×
389
        case HTTPStatus::RequestHeaderFieldsTooLarge:
✔
390
            return os << "Request Header Fields Too Large";
×
391
        case HTTPStatus::UnavailableForLegalReasons:
✔
392
            return os << "Unavailable For Legal Reasons";
×
393
        case HTTPStatus::InternalServerError:
✔
394
            return os << "Internal Server Error";
×
395
        case HTTPStatus::NotImplemented:
✔
396
            return os << "Not Implemented";
×
397
        case HTTPStatus::BadGateway:
✔
398
            return os << "Bad Gateway";
×
399
        case HTTPStatus::ServiceUnavailable:
✔
400
            return os << "Service Unavailable";
×
401
        case HTTPStatus::GatewayTimeout:
✔
402
            return os << "Gateway Timeout";
×
403
        case HTTPStatus::HttpVersionNotSupported:
✔
404
            return os << "HTTP Version not supported";
×
405
        case HTTPStatus::VariantAlsoNegotiates:
✔
406
            return os << "Variant Also Negotiates";
×
407
        case HTTPStatus::NotExtended:
✔
408
            return os << "Not Extended";
×
409
        case HTTPStatus::NetworkAuthenticationRequired:
✔
410
            return os << "Network Authentication Required";
×
411
    }
×
412
    return os;
×
413
}
×
414

415

416
std::ostream& operator<<(std::ostream& os, const HTTPRequest& request)
417
{
8,748✔
418
    auto host = request.headers.find("Host");
8,748✔
419

4,352✔
420
    os << request.method << ' ';
8,748✔
421

4,352✔
422
    if (request.method == HTTPMethod::Connect) {
8,748✔
423
        REALM_ASSERT_RELEASE(host != request.headers.end());
×
424
        os << host->second;
×
425
    }
×
426
    else if (request.path.size() == 0) {
8,748✔
427
        os << '/';
×
428
    }
×
429
    else {
8,748✔
430
        os << request.path;
8,748✔
431
    }
8,748✔
432
    os << " HTTP/1.1\r\n";
8,748✔
433

4,352✔
434
    {
8,748✔
435
        os << "Host:";
8,748✔
436
        if (host != request.headers.end())
8,748✔
437
            os << " " << host->second;
8,736✔
438
        os << "\r\n";
8,748✔
439
    }
8,748✔
440

4,352✔
441
    for (auto& pair : request.headers) {
61,136✔
442
        if (pair.first == "Host")
61,136✔
443
            continue;
8,736✔
444
        // FIXME: No need for trimming here. There should be extra white space
26,058✔
445
        // when, and only when the application specifies it.
26,058✔
446
        StringData trimmed_value = trim_whitespace(pair.second);
52,400✔
447
        os << pair.first << ": " << trimmed_value << "\r\n";
52,400✔
448
    }
52,400✔
449
    os << "\r\n";
8,748✔
450
    if (request.body)
8,748✔
451
        os.write(request.body->data(), request.body->size());
8✔
452

4,352✔
453
    bool content_length_exists = request.headers.find("Content-Length") != request.headers.end();
8,748✔
454
    bool body_exists = bool(request.body);
8,748✔
455
    REALM_ASSERT(content_length_exists == body_exists);
8,748✔
456

4,352✔
457
    return os;
8,748✔
458
}
8,748✔
459

460

461
std::ostream& operator<<(std::ostream& os, const HTTPResponse& response)
462
{
5,354✔
463
    os << "HTTP/1.1 " << response.status;
5,354✔
464
    os << "\r\n";
5,354✔
465

2,668✔
466
    for (auto& pair : response.headers) {
33,566✔
467
        StringData trimmed_value = trim_whitespace(pair.second);
33,566✔
468
        os << pair.first << ": " << trimmed_value << "\r\n";
33,566✔
469
    }
33,566✔
470
    os << "\r\n";
5,354✔
471
    if (response.body) {
5,354✔
472
        os.write(response.body->data(), response.body->size());
24✔
473
    }
24✔
474

2,668✔
475
    return os;
5,354✔
476
}
5,354✔
477

478

479
std::error_code make_error_code(HTTPParserError error)
480
{
7,334✔
481
    return std::error_code(static_cast<int>(error), g_http_parser_error_category);
7,334✔
482
}
7,334✔
483

484
} // 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

© 2026 Coveralls, Inc