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