xref: /haiku/src/kits/network/libnetservices2/HttpBuffer.cpp (revision e68284565a5338098b6985a66fa485662cd1bc14)
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)
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 	// Adjust the buffer to the maximum size
53 	fBuffer.resize(fBuffer.capacity());
54 
55 	ssize_t bytesRead = B_INTERRUPTED;
56 	while (bytesRead == B_INTERRUPTED)
57 		bytesRead = source->Read(fBuffer.data() + currentSize, remainingBufferSize);
58 
59 	if (bytesRead == B_WOULD_BLOCK || bytesRead == 0) {
60 		fBuffer.resize(currentSize);
61 		return bytesRead;
62 	} else if (bytesRead < 0) {
63 		throw BNetworkRequestError("BDataIO::Read()", BNetworkRequestError::NetworkError,
64 			bytesRead);
65 	}
66 
67 	// Adjust the buffer to the current size
68 	fBuffer.resize(currentSize + bytesRead);
69 
70 	return bytesRead;
71 }
72 
73 
74 /*!
75 	\brief Use BDataIO::WriteExactly() on target to write the contents of the buffer.
76 */
77 void
78 HttpBuffer::WriteExactlyTo(BDataIO* target)
79 {
80 	if (RemainingBytes() == 0)
81 		return;
82 
83 	auto status = target->WriteExactly(fBuffer.data() + fCurrentOffset, RemainingBytes());
84 	if (status != B_OK) {
85 		throw BNetworkRequestError("BDataIO::WriteExactly()", BNetworkRequestError::SystemError,
86 			status);
87 	}
88 
89 	// Entire buffer is written; reset internal buffer
90 	Clear();
91 }
92 
93 
94 /*!
95 	\brief Write the contents of the buffer through the helper \a func.
96 
97 	\param func Handle the actual writing. The function accepts a pointer and a size as inputs
98 		and should return the number of actual written bytes, which may be fewer than the number
99 		of available bytes.
100 */
101 void
102 HttpBuffer::WriteTo(HttpTransferFunction func , std::optional<size_t> maxSize)
103 {
104 	if (RemainingBytes() == 0)
105 		return;
106 
107 	auto size = RemainingBytes();
108 	if (maxSize.has_value() && *maxSize < size)
109 		size = *maxSize;
110 
111 	auto bytesWritten = func(fBuffer.data() + fCurrentOffset, size);
112 	if (bytesWritten > size)
113 		throw BRuntimeError(__PRETTY_FUNCTION__, "More bytes written than were made available");
114 
115 	fCurrentOffset += bytesWritten;
116 }
117 
118 
119 /*!
120 	\brief Get the next line from this buffer.
121 
122 	This can be called iteratively until all lines in the current data are read. After using this
123 	method, you should use Flush() to make sure that the read lines are cleared from the beginning
124 	of the buffer.
125 
126 	\retval std::nullopt There are no more lines in the buffer.
127 	\retval BString The next line.
128 */
129 std::optional<BString>
130 HttpBuffer::GetNextLine()
131 {
132 	auto offset = fBuffer.cbegin() + fCurrentOffset;
133 	auto result = std::search(offset, fBuffer.cend(), kNewLine.cbegin(), kNewLine.cend());
134 	if (result == fBuffer.cend())
135 		return std::nullopt;
136 
137 	BString line(reinterpret_cast<const char*>(std::addressof(*offset)), std::distance(offset, result));
138 	fCurrentOffset = std::distance(fBuffer.cbegin(), result) + 2;
139 	return line;
140 }
141 
142 
143 /*!
144 	\brief Get the number of remaining bytes in this buffer.
145 */
146 size_t
147 HttpBuffer::RemainingBytes() noexcept
148 {
149 	return fBuffer.size() - fCurrentOffset;
150 }
151 
152 
153 /*!
154 	\brief Move data to the beginning of the buffer to clear at the back.
155 
156 	The GetNextLine() increases the offset of the internal buffer. This call moves remaining data
157 	to the beginning of the buffer sets the correct size, making the remainder of the capacity
158 	available for further reading.
159 */
160 void
161 HttpBuffer::Flush() noexcept
162 {
163 	if (fCurrentOffset > 0) {
164 		auto end = fBuffer.cbegin() + fCurrentOffset;
165 		fBuffer.erase(fBuffer.cbegin(), end);
166 		fCurrentOffset = 0;
167 	}
168 }
169 
170 
171 /*!
172 	\brief Clear the internal buffer
173 */
174 void
175 HttpBuffer::Clear() noexcept
176 {
177 	fBuffer.clear();
178 	fCurrentOffset = 0;
179 }
180