xref: /haiku/src/kits/network/libnetservices2/HttpBuffer.cpp (revision 52c4471a3024d2eb81fe88e2c3982b9f8daa5e56)
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(
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 */
86 size_t
87 HttpBuffer::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 */
116 std::optional<BString>
117 HttpBuffer::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 */
134 size_t
135 HttpBuffer::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 */
148 void
149 HttpBuffer::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 */
162 void
163 HttpBuffer::Clear() noexcept
164 {
165 	fBuffer.clear();
166 	fCurrentOffset = 0;
167 }
168 
169 
170 /*!
171 	\brief Get a view over the current data
172 */
173 std::string_view
174 HttpBuffer::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 */
189 HttpBuffer&
190 HttpBuffer::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