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 "HttpSerializer.h"
10
11#include <DataIO.h>
12#include <HttpRequest.h>
13#include <NetServicesDefs.h>
14
15#include "HttpBuffer.h"
16
17using namespace std::literals;
18using namespace BPrivate::Network;
19
20
21/*!
22	\brief Set the \a request to serialize, and load the initial data into the \a buffer.
23*/
24void
25HttpSerializer::SetTo(HttpBuffer& buffer, const BHttpRequest& request)
26{
27	buffer.Clear();
28	request.SerializeHeaderTo(buffer);
29	fState = HttpSerializerState::Header;
30
31	if (auto requestBody = request.RequestBody()) {
32		fBody = requestBody->input.get();
33		if (requestBody->size) {
34			fBodySize = *(requestBody->size);
35		}
36	}
37}
38
39
40/*!
41	\brief Transfer the HTTP request to \a target while using \a buffer for intermediate storage.
42
43	\returns The number of body bytes written during the call.
44*/
45size_t
46HttpSerializer::Serialize(HttpBuffer& buffer, BDataIO* target)
47{
48	bool finishing = false;
49	size_t bodyBytesWritten = 0;
50	while (!finishing) {
51		switch (fState) {
52			case HttpSerializerState::Uninitialized:
53				throw BRuntimeError(__PRETTY_FUNCTION__, "Invalid state: Uninitialized");
54
55			case HttpSerializerState::Header:
56				_WriteToTarget(buffer, target);
57				if (buffer.RemainingBytes() > 0) {
58					// There are more bytes to be processed; wait for the next iteration
59					return 0;
60				}
61
62				if (fBody == nullptr) {
63					fState = HttpSerializerState::Done;
64					return 0;
65				} else if (_IsChunked())
66					// fState = HttpSerializerState::ChunkHeader;
67					throw BRuntimeError(
68						__PRETTY_FUNCTION__, "Chunked serialization not implemented");
69				else
70					fState = HttpSerializerState::Body;
71				break;
72
73			case HttpSerializerState::Body:
74			{
75				auto bytesWritten = _WriteToTarget(buffer, target);
76				bodyBytesWritten += bytesWritten;
77				fTransferredBodySize += bytesWritten;
78				if (buffer.RemainingBytes() > 0) {
79					// did not manage to write all the bytes in the buffer; continue in the next
80					// round
81					finishing = true;
82					break;
83				}
84
85				if (fBodySize && fBodySize.value() == fTransferredBodySize) {
86					fState = HttpSerializerState::Done;
87					finishing = true;
88				}
89				break;
90			}
91
92			case HttpSerializerState::Done:
93			default:
94				finishing = true;
95				continue;
96		}
97
98		// Load more data into the buffer
99		std::optional<size_t> maxReadSize = std::nullopt;
100		if (fBodySize)
101			maxReadSize = fBodySize.value() - fTransferredBodySize;
102		buffer.ReadFrom(fBody, maxReadSize);
103	}
104
105	return bodyBytesWritten;
106}
107
108
109bool
110HttpSerializer::_IsChunked() const noexcept
111{
112	return fBodySize == std::nullopt;
113}
114
115
116size_t
117HttpSerializer::_WriteToTarget(HttpBuffer& buffer, BDataIO* target) const
118{
119	size_t bytesWritten = 0;
120	buffer.WriteTo([target, &bytesWritten](const std::byte* buffer, size_t size) {
121		ssize_t result = B_INTERRUPTED;
122		while (result == B_INTERRUPTED) {
123			result = target->Write(buffer, size);
124		}
125
126		if (result <= 0 && result != B_WOULD_BLOCK) {
127			throw BNetworkRequestError(
128				__PRETTY_FUNCTION__, BNetworkRequestError::NetworkError, result);
129		} else if (result > 0) {
130			bytesWritten += result;
131			return size_t(result);
132		} else {
133			return size_t(0);
134		}
135	});
136
137	return bytesWritten;
138}
139