xref: /haiku/src/kits/network/libnetservices2/HttpBuffer.cpp (revision 9cb56a4881c054d5c0947e7657eda6fd71765796)
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 
15 using namespace BPrivate::Network;
16 
17 
18 /*!
19 	\brief Newline sequence
20 
21 	As per the RFC, defined as \r\n
22 */
23 static 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 */
29 HttpBuffer::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 */
43 ssize_t
44 HttpBuffer::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("BDataIO::Read()", BNetworkRequestError::NetworkError,
67 			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 Use BDataIO::WriteExactly() on target to write the contents of the buffer.
79 */
80 void
81 HttpBuffer::WriteExactlyTo(BDataIO* target)
82 {
83 	if (RemainingBytes() == 0)
84 		return;
85 
86 	auto status = target->WriteExactly(fBuffer.data() + fCurrentOffset, RemainingBytes());
87 	if (status != B_OK) {
88 		throw BNetworkRequestError("BDataIO::WriteExactly()", BNetworkRequestError::SystemError,
89 			status);
90 	}
91 
92 	// Entire buffer is written; reset internal buffer
93 	Clear();
94 }
95 
96 
97 /*!
98 	\brief Write the contents of the buffer through the helper \a func.
99 
100 	\param func Handle the actual writing. The function accepts a pointer and a size as inputs
101 		and should return the number of actual written bytes, which may be fewer than the number
102 		of available bytes.
103 */
104 void
105 HttpBuffer::WriteTo(HttpTransferFunction func , std::optional<size_t> maxSize)
106 {
107 	if (RemainingBytes() == 0)
108 		return;
109 
110 	auto size = RemainingBytes();
111 	if (maxSize.has_value() && *maxSize < size)
112 		size = *maxSize;
113 
114 	auto bytesWritten = func(fBuffer.data() + fCurrentOffset, size);
115 	if (bytesWritten > size)
116 		throw BRuntimeError(__PRETTY_FUNCTION__, "More bytes written than were made available");
117 
118 	fCurrentOffset += bytesWritten;
119 }
120 
121 
122 /*!
123 	\brief Get the next line from this buffer.
124 
125 	This can be called iteratively until all lines in the current data are read. After using this
126 	method, you should use Flush() to make sure that the read lines are cleared from the beginning
127 	of the buffer.
128 
129 	\retval std::nullopt There are no more lines in the buffer.
130 	\retval BString The next line.
131 */
132 std::optional<BString>
133 HttpBuffer::GetNextLine()
134 {
135 	auto offset = fBuffer.cbegin() + fCurrentOffset;
136 	auto result = std::search(offset, fBuffer.cend(), kNewLine.cbegin(), kNewLine.cend());
137 	if (result == fBuffer.cend())
138 		return std::nullopt;
139 
140 	BString line(reinterpret_cast<const char*>(std::addressof(*offset)), std::distance(offset, result));
141 	fCurrentOffset = std::distance(fBuffer.cbegin(), result) + 2;
142 	return line;
143 }
144 
145 
146 /*!
147 	\brief Get the number of remaining bytes in this buffer.
148 */
149 size_t
150 HttpBuffer::RemainingBytes() const noexcept
151 {
152 	return fBuffer.size() - fCurrentOffset;
153 }
154 
155 
156 /*!
157 	\brief Move data to the beginning of the buffer to clear at the back.
158 
159 	The GetNextLine() increases the offset of the internal buffer. This call moves remaining data
160 	to the beginning of the buffer sets the correct size, making the remainder of the capacity
161 	available for further reading.
162 */
163 void
164 HttpBuffer::Flush() noexcept
165 {
166 	if (fCurrentOffset > 0) {
167 		auto end = fBuffer.cbegin() + fCurrentOffset;
168 		fBuffer.erase(fBuffer.cbegin(), end);
169 		fCurrentOffset = 0;
170 	}
171 }
172 
173 
174 /*!
175 	\brief Clear the internal buffer
176 */
177 void
178 HttpBuffer::Clear() noexcept
179 {
180 	fBuffer.clear();
181 	fCurrentOffset = 0;
182 }
183 
184 
185 /*!
186 	\brief Get a view over the current data
187 */
188 std::string_view
189 HttpBuffer::Data() const noexcept
190 {
191 	if (RemainingBytes() > 0) {
192 		return std::string_view(reinterpret_cast<const char*>(fBuffer.data()) + fCurrentOffset,
193 			RemainingBytes());
194 	} else
195 		return std::string_view();
196 }
197 
198 
199 /*!
200 	\brief Load data into the buffer
201 
202 	\exception BNetworkRequestError in case of a buffer overflow
203 */
204 HttpBuffer&
205 HttpBuffer::operator<<(const std::string_view& data)
206 {
207 	if (data.size() > (fBuffer.capacity() - fBuffer.size())) {
208 		throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError,
209 			"No capacity left in buffer to append data.");
210 	}
211 
212 	for (const auto& character: data)
213 		fBuffer.push_back(static_cast<const std::byte>(character));
214 
215 	return *this;
216 }
217