1 /*
2 * Copyright 2022 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Niels Sascha Reedijk, niels.reedijk@gmail.com
7 */
8
9 #include <HttpRequest.h>
10
11 #include <algorithm>
12 #include <ctype.h>
13 #include <sstream>
14 #include <utility>
15
16 #include <DataIO.h>
17 #include <HttpFields.h>
18 #include <MimeType.h>
19 #include <NetServicesDefs.h>
20 #include <Url.h>
21
22 #include "HttpBuffer.h"
23 #include "HttpPrivate.h"
24
25 using namespace std::literals;
26 using namespace BPrivate::Network;
27
28
29 // #pragma mark -- BHttpMethod::InvalidMethod
30
31
InvalidMethod(const char * origin,BString input)32 BHttpMethod::InvalidMethod::InvalidMethod(const char* origin, BString input)
33 :
34 BError(origin),
35 input(std::move(input))
36 {
37 }
38
39
40 const char*
Message() const41 BHttpMethod::InvalidMethod::Message() const noexcept
42 {
43 if (input.IsEmpty())
44 return "The HTTP method cannot be empty";
45 else
46 return "Unsupported characters in the HTTP method";
47 }
48
49
50 BString
DebugMessage() const51 BHttpMethod::InvalidMethod::DebugMessage() const
52 {
53 BString output = BError::DebugMessage();
54 if (!input.IsEmpty())
55 output << ":\t " << input << "\n";
56 return output;
57 }
58
59
60 // #pragma mark -- BHttpMethod
61
62
BHttpMethod(Verb verb)63 BHttpMethod::BHttpMethod(Verb verb) noexcept
64 :
65 fMethod(verb)
66 {
67 }
68
69
BHttpMethod(const std::string_view & verb)70 BHttpMethod::BHttpMethod(const std::string_view& verb)
71 :
72 fMethod(BString(verb.data(), verb.length()))
73 {
74 if (verb.size() == 0 || !validate_http_token_string(verb))
75 throw BHttpMethod::InvalidMethod(
76 __PRETTY_FUNCTION__, std::move(std::get<BString>(fMethod)));
77 }
78
79
80 BHttpMethod::BHttpMethod(const BHttpMethod& other) = default;
81
82
BHttpMethod(BHttpMethod && other)83 BHttpMethod::BHttpMethod(BHttpMethod&& other) noexcept
84 :
85 fMethod(std::move(other.fMethod))
86 {
87 other.fMethod = Get;
88 }
89
90
91 BHttpMethod::~BHttpMethod() = default;
92
93
94 BHttpMethod& BHttpMethod::operator=(const BHttpMethod& other) = default;
95
96
97 BHttpMethod&
operator =(BHttpMethod && other)98 BHttpMethod::operator=(BHttpMethod&& other) noexcept
99 {
100 fMethod = std::move(other.fMethod);
101 other.fMethod = Get;
102 return *this;
103 }
104
105
106 bool
operator ==(const BHttpMethod::Verb & other) const107 BHttpMethod::operator==(const BHttpMethod::Verb& other) const noexcept
108 {
109 if (std::holds_alternative<Verb>(fMethod)) {
110 return std::get<Verb>(fMethod) == other;
111 } else {
112 BHttpMethod otherMethod(other);
113 auto otherMethodSv = otherMethod.Method();
114 return std::get<BString>(fMethod).Compare(otherMethodSv.data(), otherMethodSv.size()) == 0;
115 }
116 }
117
118
119 bool
operator !=(const BHttpMethod::Verb & other) const120 BHttpMethod::operator!=(const BHttpMethod::Verb& other) const noexcept
121 {
122 return !operator==(other);
123 }
124
125
126 const std::string_view
Method() const127 BHttpMethod::Method() const noexcept
128 {
129 if (std::holds_alternative<Verb>(fMethod)) {
130 switch (std::get<Verb>(fMethod)) {
131 case Get:
132 return "GET"sv;
133 case Head:
134 return "HEAD"sv;
135 case Post:
136 return "POST"sv;
137 case Put:
138 return "PUT"sv;
139 case Delete:
140 return "DELETE"sv;
141 case Connect:
142 return "CONNECT"sv;
143 case Options:
144 return "OPTIONS"sv;
145 case Trace:
146 return "TRACE"sv;
147 default:
148 // should never be reached
149 std::abort();
150 }
151 } else {
152 const auto& methodString = std::get<BString>(fMethod);
153 // the following constructor is not noexcept, but we know we pass in valid data
154 return std::string_view(methodString.String());
155 }
156 }
157
158
159 // #pragma mark -- BHttpRequest::Data
160 static const BUrl kDefaultUrl = BUrl();
161 static const BHttpMethod kDefaultMethod = BHttpMethod::Get;
162 static const BHttpFields kDefaultOptionalFields = BHttpFields();
163
164 struct BHttpRequest::Data {
165 BUrl url = kDefaultUrl;
166 BHttpMethod method = kDefaultMethod;
167 uint8 maxRedirections = 8;
168 BHttpFields optionalFields;
169 std::optional<BHttpAuthentication> authentication;
170 bool stopOnError = false;
171 bigtime_t timeout = B_INFINITE_TIMEOUT;
172 std::optional<Body> requestBody;
173 };
174
175
176 // #pragma mark -- BHttpRequest helper functions
177
178
179 /*!
180 \brief Build basic authentication header
181 */
182 static inline BString
build_basic_http_header(const BString & username,const BString & password)183 build_basic_http_header(const BString& username, const BString& password)
184 {
185 BString basicEncode, result;
186 basicEncode << username << ":" << password;
187 result << "Basic " << encode_to_base64(basicEncode);
188 return result;
189 }
190
191
192 // #pragma mark -- BHttpRequest
193
194
BHttpRequest()195 BHttpRequest::BHttpRequest()
196 :
197 fData(std::make_unique<Data>())
198 {
199 }
200
201
BHttpRequest(const BUrl & url)202 BHttpRequest::BHttpRequest(const BUrl& url)
203 :
204 fData(std::make_unique<Data>())
205 {
206 SetUrl(url);
207 }
208
209
210 BHttpRequest::BHttpRequest(BHttpRequest&& other) noexcept = default;
211
212
213 BHttpRequest::~BHttpRequest() = default;
214
215
216 BHttpRequest& BHttpRequest::operator=(BHttpRequest&&) noexcept = default;
217
218
219 bool
IsEmpty() const220 BHttpRequest::IsEmpty() const noexcept
221 {
222 return (!fData || !fData->url.IsValid());
223 }
224
225
226 const BHttpAuthentication*
Authentication() const227 BHttpRequest::Authentication() const noexcept
228 {
229 if (fData && fData->authentication)
230 return std::addressof(*fData->authentication);
231 return nullptr;
232 }
233
234
235 const BHttpFields&
Fields() const236 BHttpRequest::Fields() const noexcept
237 {
238 if (!fData)
239 return kDefaultOptionalFields;
240 return fData->optionalFields;
241 }
242
243
244 uint8
MaxRedirections() const245 BHttpRequest::MaxRedirections() const noexcept
246 {
247 if (!fData)
248 return 8;
249 return fData->maxRedirections;
250 }
251
252
253 const BHttpMethod&
Method() const254 BHttpRequest::Method() const noexcept
255 {
256 if (!fData)
257 return kDefaultMethod;
258 return fData->method;
259 }
260
261
262 const BHttpRequest::Body*
RequestBody() const263 BHttpRequest::RequestBody() const noexcept
264 {
265 if (fData && fData->requestBody)
266 return std::addressof(*fData->requestBody);
267 return nullptr;
268 }
269
270
271 bool
StopOnError() const272 BHttpRequest::StopOnError() const noexcept
273 {
274 if (!fData)
275 return false;
276 return fData->stopOnError;
277 }
278
279
280 bigtime_t
Timeout() const281 BHttpRequest::Timeout() const noexcept
282 {
283 if (!fData)
284 return B_INFINITE_TIMEOUT;
285 return fData->timeout;
286 }
287
288
289 const BUrl&
Url() const290 BHttpRequest::Url() const noexcept
291 {
292 if (!fData)
293 return kDefaultUrl;
294 return fData->url;
295 }
296
297
298 void
SetAuthentication(const BHttpAuthentication & authentication)299 BHttpRequest::SetAuthentication(const BHttpAuthentication& authentication)
300 {
301 if (!fData)
302 fData = std::make_unique<Data>();
303
304 fData->authentication = authentication;
305 }
306
307
308 static constexpr std::array<std::string_view, 6> fReservedOptionalFieldNames
309 = {"Host"sv, "Accept-Encoding"sv, "Connection"sv, "Content-Type"sv, "Content-Length"sv};
310
311
312 void
SetFields(const BHttpFields & fields)313 BHttpRequest::SetFields(const BHttpFields& fields)
314 {
315 if (!fData)
316 fData = std::make_unique<Data>();
317
318 for (auto& field: fields) {
319 if (std::find(fReservedOptionalFieldNames.begin(), fReservedOptionalFieldNames.end(),
320 field.Name())
321 != fReservedOptionalFieldNames.end()) {
322 std::string_view fieldName = field.Name();
323 throw BHttpFields::InvalidInput(
324 __PRETTY_FUNCTION__, BString(fieldName.data(), fieldName.size()));
325 }
326 }
327 fData->optionalFields = fields;
328 }
329
330
331 void
SetMaxRedirections(uint8 maxRedirections)332 BHttpRequest::SetMaxRedirections(uint8 maxRedirections)
333 {
334 if (!fData)
335 fData = std::make_unique<Data>();
336 fData->maxRedirections = maxRedirections;
337 }
338
339
340 void
SetMethod(const BHttpMethod & method)341 BHttpRequest::SetMethod(const BHttpMethod& method)
342 {
343 if (!fData)
344 fData = std::make_unique<Data>();
345 fData->method = method;
346 }
347
348
349 void
SetRequestBody(std::unique_ptr<BDataIO> input,BString mimeType,std::optional<off_t> size)350 BHttpRequest::SetRequestBody(
351 std::unique_ptr<BDataIO> input, BString mimeType, std::optional<off_t> size)
352 {
353 if (input == nullptr)
354 throw std::invalid_argument("input cannot be null");
355
356 // TODO: support optional mimetype arguments like type/subtype;parameter=value
357 if (!BMimeType::IsValid(mimeType.String()))
358 throw std::invalid_argument("mimeType must be a valid mimetype");
359
360 // TODO: review if there should be complex validation between the method and whether or not
361 // there is a request body. The current implementation does the validation at the request
362 // generation stage, where GET, HEAD, OPTIONS, CONNECT and TRACE will not submit a body.
363
364 if (!fData)
365 fData = std::make_unique<Data>();
366 fData->requestBody = {std::move(input), std::move(mimeType), size};
367
368 // Check if the input is a BPositionIO, and if so, store the current position, so that it can
369 // be rewinded in case of a redirect.
370 auto inputPositionIO = dynamic_cast<BPositionIO*>(fData->requestBody->input.get());
371 if (inputPositionIO != nullptr)
372 fData->requestBody->startPosition = inputPositionIO->Position();
373 }
374
375
376 void
SetStopOnError(bool stopOnError)377 BHttpRequest::SetStopOnError(bool stopOnError)
378 {
379 if (!fData)
380 fData = std::make_unique<Data>();
381 fData->stopOnError = stopOnError;
382 }
383
384
385 void
SetTimeout(bigtime_t timeout)386 BHttpRequest::SetTimeout(bigtime_t timeout)
387 {
388 if (!fData)
389 fData = std::make_unique<Data>();
390 fData->timeout = timeout;
391 }
392
393
394 void
SetUrl(const BUrl & url)395 BHttpRequest::SetUrl(const BUrl& url)
396 {
397 if (!fData)
398 fData = std::make_unique<Data>();
399
400 if (!url.IsValid())
401 throw BInvalidUrl(__PRETTY_FUNCTION__, BUrl(url));
402 if (url.Protocol() != "http" && url.Protocol() != "https") {
403 // TODO: optimize BStringList with modern language features
404 BStringList list;
405 list.Add("http");
406 list.Add("https");
407 throw BUnsupportedProtocol(__PRETTY_FUNCTION__, BUrl(url), list);
408 }
409 fData->url = url;
410 }
411
412
413 void
ClearAuthentication()414 BHttpRequest::ClearAuthentication() noexcept
415 {
416 if (fData)
417 fData->authentication = std::nullopt;
418 }
419
420
421 std::unique_ptr<BDataIO>
ClearRequestBody()422 BHttpRequest::ClearRequestBody() noexcept
423 {
424 if (fData && fData->requestBody) {
425 auto body = std::move(fData->requestBody->input);
426 fData->requestBody = std::nullopt;
427 return body;
428 }
429 return nullptr;
430 }
431
432
433 BString
HeaderToString() const434 BHttpRequest::HeaderToString() const
435 {
436 HttpBuffer buffer;
437 SerializeHeaderTo(buffer);
438
439 return BString(static_cast<const char*>(buffer.Data().data()), buffer.RemainingBytes());
440 }
441
442
443 /*!
444 \brief Private method used by BHttpSession::Request to rewind the content in case of redirect
445
446 \retval true Content was rewinded successfully. Also the case if there is no content
447 \retval false Cannot/could not rewind content.
448 */
449 bool
RewindBody()450 BHttpRequest::RewindBody() noexcept
451 {
452 if (fData && fData->requestBody && fData->requestBody->startPosition) {
453 auto inputData = dynamic_cast<BPositionIO*>(fData->requestBody->input.get());
454 return *fData->requestBody->startPosition
455 == inputData->Seek(*fData->requestBody->startPosition, SEEK_SET);
456 }
457 return true;
458 }
459
460
461 /*!
462 \brief Private method used by HttpSerializer::SetTo() to serialize the header data into a
463 buffer.
464 */
465 void
SerializeHeaderTo(HttpBuffer & buffer) const466 BHttpRequest::SerializeHeaderTo(HttpBuffer& buffer) const
467 {
468 // Method & URL
469 // TODO: proxy
470 buffer << fData->method.Method() << " "sv;
471 if (fData->url.HasPath() && fData->url.Path().Length() > 0)
472 buffer << std::string_view(fData->url.Path().String());
473 else
474 buffer << "/"sv;
475
476 if (fData->url.HasRequest())
477 buffer << "?"sv << Url().Request().String();
478
479 // TODO: switch between HTTP 1.0 and 1.1 based on configuration
480 buffer << " HTTP/1.1\r\n"sv;
481
482 BHttpFields outputFields;
483 if (true /* http == 1.1 */) {
484 BString host = fData->url.Host();
485 int defaultPort = fData->url.Protocol() == "http" ? 80 : 443;
486 if (fData->url.HasPort() && fData->url.Port() != defaultPort)
487 host << ':' << fData->url.Port();
488
489 outputFields.AddFields({
490 {"Host"sv, std::string_view(host.String())}, {"Accept-Encoding"sv, "gzip"sv},
491 // Allows the server to compress data using the "gzip" format.
492 // "deflate" is not supported, because there are two interpretations
493 // of what it means (the RFC and Microsoft products), and we don't
494 // want to handle this. Very few websites support only deflate,
495 // and most of them will send gzip, or at worst, uncompressed data.
496 {"Connection"sv, "close"sv}
497 // Let the remote server close the connection after response since
498 // we don't handle multiple request on a single connection
499 });
500 }
501
502 if (fData->authentication) {
503 // This request will add a Basic authorization header
504 BString authorization = build_basic_http_header(
505 fData->authentication->username, fData->authentication->password);
506 outputFields.AddField("Authorization"sv, std::string_view(authorization.String()));
507 }
508
509 if (fData->requestBody) {
510 outputFields.AddField(
511 "Content-Type"sv, std::string_view(fData->requestBody->mimeType.String()));
512 if (fData->requestBody->size)
513 outputFields.AddField("Content-Length"sv, std::to_string(*fData->requestBody->size));
514 else
515 throw BRuntimeError(__PRETTY_FUNCTION__,
516 "Transfer body with unknown content length; chunked transfer not supported");
517 }
518
519 for (const auto& field: outputFields)
520 buffer << field.RawField() << "\r\n"sv;
521
522 for (const auto& field: fData->optionalFields)
523 buffer << field.RawField() << "\r\n"sv;
524
525 buffer << "\r\n"sv;
526 }
527