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 "HttpBuffer.h"
10
11#include <DataIO.h>
12#include <NetServicesDefs.h>
13#include <String.h>
14
15using namespace BPrivate::Network;
16
17
18/*!
19	\brief Newline sequence
20
21	As per the RFC, defined as \r\n
22*/
23static constexpr std::array<std::byte, 2> kNewLine = {std::byte('\r'), std::byte('\n')};
24
25
26/*!
27	\brief Create a new HTTP buffer with \a capacity.
28*/
29HttpBuffer::HttpBuffer(size_t capacity)
30{
31	fBuffer.reserve(capacity);
32};
33
34
35/*!
36	\brief Load data from \a source into the spare capacity of this buffer.
37
38	\exception BNetworkRequestError When BDataIO::Read() returns any error other than B_WOULD_BLOCK
39
40	\retval B_WOULD_BLOCK The read call on the \a source was unsuccessful because it would block.
41	\retval >=0 The actual number of bytes read.
42*/
43ssize_t
44HttpBuffer::ReadFrom(BDataIO* source, std::optional<size_t> maxSize)
45{
46	// Remove any unused bytes at the beginning of the buffer
47	Flush();
48
49	auto currentSize = fBuffer.size();
50	auto remainingBufferSize = fBuffer.capacity() - currentSize;
51
52	if (maxSize && maxSize.value() < remainingBufferSize)
53		remainingBufferSize = maxSize.value();
54
55	// Adjust the buffer to the maximum size
56	fBuffer.resize(fBuffer.capacity());
57
58	ssize_t bytesRead = B_INTERRUPTED;
59	while (bytesRead == B_INTERRUPTED)
60		bytesRead = source->Read(fBuffer.data() + currentSize, remainingBufferSize);
61
62	if (bytesRead == B_WOULD_BLOCK || bytesRead == 0) {
63		fBuffer.resize(currentSize);
64		return bytesRead;
65	} else if (bytesRead < 0) {
66		throw BNetworkRequestError(
67			"BDataIO::Read()", BNetworkRequestError::NetworkError, bytesRead);
68	}
69
70	// Adjust the buffer to the current size
71	fBuffer.resize(currentSize + bytesRead);
72
73	return bytesRead;
74}
75
76
77/*!
78	\brief Write the contents of the buffer through the helper \a func.
79
80	\param func Handle the actual writing. The function accepts a pointer and a size as inputs
81		and should return the number of actual written bytes, which may be fewer than the number
82		of available bytes.
83
84	\returns the actual number of bytes written to the \a func.
85*/
86size_t
87HttpBuffer::WriteTo(HttpTransferFunction func, std::optional<size_t> maxSize)
88{
89	if (RemainingBytes() == 0)
90		return 0;
91
92	auto size = RemainingBytes();
93	if (maxSize.has_value() && *maxSize < size)
94		size = *maxSize;
95
96	auto bytesWritten = func(fBuffer.data() + fCurrentOffset, size);
97	if (bytesWritten > size)
98		throw BRuntimeError(__PRETTY_FUNCTION__, "More bytes written than were made available");
99
100	fCurrentOffset += bytesWritten;
101
102	return bytesWritten;
103}
104
105
106/*!
107	\brief Get the next line from this buffer.
108
109	This can be called iteratively until all lines in the current data are read. After using this
110	method, you should use Flush() to make sure that the read lines are cleared from the beginning
111	of the buffer.
112
113	\retval std::nullopt There are no more lines in the buffer.
114	\retval BString The next line.
115*/
116std::optional<BString>
117HttpBuffer::GetNextLine()
118{
119	auto offset = fBuffer.cbegin() + fCurrentOffset;
120	auto result = std::search(offset, fBuffer.cend(), kNewLine.cbegin(), kNewLine.cend());
121	if (result == fBuffer.cend())
122		return std::nullopt;
123
124	BString line(
125		reinterpret_cast<const char*>(std::addressof(*offset)), std::distance(offset, result));
126	fCurrentOffset = std::distance(fBuffer.cbegin(), result) + 2;
127	return line;
128}
129
130
131/*!
132	\brief Get the number of remaining bytes in this buffer.
133*/
134size_t
135HttpBuffer::RemainingBytes() const noexcept
136{
137	return fBuffer.size() - fCurrentOffset;
138}
139
140
141/*!
142	\brief Move data to the beginning of the buffer to clear at the back.
143
144	The GetNextLine() increases the offset of the internal buffer. This call moves remaining data
145	to the beginning of the buffer sets the correct size, making the remainder of the capacity
146	available for further reading.
147*/
148void
149HttpBuffer::Flush() noexcept
150{
151	if (fCurrentOffset > 0) {
152		auto end = fBuffer.cbegin() + fCurrentOffset;
153		fBuffer.erase(fBuffer.cbegin(), end);
154		fCurrentOffset = 0;
155	}
156}
157
158
159/*!
160	\brief Clear the internal buffer
161*/
162void
163HttpBuffer::Clear() noexcept
164{
165	fBuffer.clear();
166	fCurrentOffset = 0;
167}
168
169
170/*!
171	\brief Get a view over the current data
172*/
173std::string_view
174HttpBuffer::Data() const noexcept
175{
176	if (RemainingBytes() > 0) {
177		return std::string_view(
178			reinterpret_cast<const char*>(fBuffer.data()) + fCurrentOffset, RemainingBytes());
179	} else
180		return std::string_view();
181}
182
183
184/*!
185	\brief Load data into the buffer
186
187	\exception BNetworkRequestError in case of a buffer overflow
188*/
189HttpBuffer&
190HttpBuffer::operator<<(const std::string_view& data)
191{
192	if (data.size() > (fBuffer.capacity() - fBuffer.size())) {
193		throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError,
194			"No capacity left in buffer to append data.");
195	}
196
197	for (const auto& character: data)
198		fBuffer.push_back(static_cast<const std::byte>(character));
199
200	return *this;
201}
202