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