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