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 */
HttpBuffer(size_t capacity)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
ReadFrom(BDataIO * source,std::optional<size_t> maxSize)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
WriteTo(HttpTransferFunction func,std::optional<size_t> maxSize)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>
GetNextLine()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
RemainingBytes() const135 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
Flush()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
Clear()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
Data() const174 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&
operator <<(const std::string_view & data)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