xref: /haiku/src/kits/network/libnetservices2/HttpBuffer.cpp (revision 3c9045fbbf341a6dbc09160d19b1590ef80bcdb3)
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 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(reinterpret_cast<const char*>(std::addressof(*offset)), std::distance(offset, result));
125 	fCurrentOffset = std::distance(fBuffer.cbegin(), result) + 2;
126 	return line;
127 }
128 
129 
130 /*!
131 	\brief Get the number of remaining bytes in this buffer.
132 */
133 size_t
134 HttpBuffer::RemainingBytes() const noexcept
135 {
136 	return fBuffer.size() - fCurrentOffset;
137 }
138 
139 
140 /*!
141 	\brief Move data to the beginning of the buffer to clear at the back.
142 
143 	The GetNextLine() increases the offset of the internal buffer. This call moves remaining data
144 	to the beginning of the buffer sets the correct size, making the remainder of the capacity
145 	available for further reading.
146 */
147 void
148 HttpBuffer::Flush() noexcept
149 {
150 	if (fCurrentOffset > 0) {
151 		auto end = fBuffer.cbegin() + fCurrentOffset;
152 		fBuffer.erase(fBuffer.cbegin(), end);
153 		fCurrentOffset = 0;
154 	}
155 }
156 
157 
158 /*!
159 	\brief Clear the internal buffer
160 */
161 void
162 HttpBuffer::Clear() noexcept
163 {
164 	fBuffer.clear();
165 	fCurrentOffset = 0;
166 }
167 
168 
169 /*!
170 	\brief Get a view over the current data
171 */
172 std::string_view
173 HttpBuffer::Data() const noexcept
174 {
175 	if (RemainingBytes() > 0) {
176 		return std::string_view(reinterpret_cast<const char*>(fBuffer.data()) + fCurrentOffset,
177 			RemainingBytes());
178 	} else
179 		return std::string_view();
180 }
181 
182 
183 /*!
184 	\brief Load data into the buffer
185 
186 	\exception BNetworkRequestError in case of a buffer overflow
187 */
188 HttpBuffer&
189 HttpBuffer::operator<<(const std::string_view& data)
190 {
191 	if (data.size() > (fBuffer.capacity() - fBuffer.size())) {
192 		throw BNetworkRequestError(__PRETTY_FUNCTION__, BNetworkRequestError::ProtocolError,
193 			"No capacity left in buffer to append data.");
194 	}
195 
196 	for (const auto& character: data)
197 		fBuffer.push_back(static_cast<const std::byte>(character));
198 
199 	return *this;
200 }
201