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