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 <HttpFields.h> 10 11#include <algorithm> 12#include <ctype.h> 13#include <utility> 14 15#include "HttpPrivate.h" 16 17using namespace BPrivate::Network; 18 19 20// #pragma mark -- utilities 21 22 23/*! 24 \brief Validate whether the string is a valid HTTP header value 25 26 RFC 7230 section 3.2.6 determines that valid tokens for the header are: 27 HTAB ('\t'), SP (32), all visible ASCII characters (33-126), and all characters that 28 not control characters (in the case of a char, any value < 0) 29 30 \note When printing out the HTTP header, sometimes the string needs to be quoted and some 31 characters need to be escaped. This function is not checking for whether the string can 32 be transmitted as is. 33 34 \returns \c true if the string is valid, or \c false if it is not. 35*/ 36static inline bool 37validate_value_string(const std::string_view& string) 38{ 39 for (auto it = string.cbegin(); it < string.cend(); it++) { 40 if ((*it >= 0 && *it < 32) || *it == 127 || *it == '\t') 41 return false; 42 } 43 return true; 44} 45 46 47/*! 48 \brief Case insensitively compare two string_views. 49 50 Inspired by https://stackoverflow.com/a/4119881 51*/ 52static inline bool 53iequals(const std::string_view& a, const std::string_view& b) 54{ 55 return std::equal(a.begin(), a.end(), b.begin(), b.end(), 56 [](char a, char b) { return tolower(a) == tolower(b); }); 57} 58 59 60/*! 61 \brief Trim whitespace from the beginning and end of a string_view 62 63 Inspired by: 64 https://terrislinenbach.medium.com/trimming-whitespace-from-a-string-view-6795e18b108f 65*/ 66static inline std::string_view 67trim(std::string_view in) 68{ 69 auto left = in.begin(); 70 for (;; ++left) { 71 if (left == in.end()) 72 return std::string_view(); 73 if (!isspace(*left)) 74 break; 75 } 76 77 auto right = in.end() - 1; 78 for (; right > left && isspace(*right); --right) 79 ; 80 81 return std::string_view(left, std::distance(left, right) + 1); 82} 83 84 85// #pragma mark -- BHttpFields::InvalidHeader 86 87 88BHttpFields::InvalidInput::InvalidInput(const char* origin, BString input) 89 : 90 BError(origin), 91 input(std::move(input)) 92{ 93} 94 95 96const char* 97BHttpFields::InvalidInput::Message() const noexcept 98{ 99 return "Invalid format or unsupported characters in input"; 100} 101 102 103BString 104BHttpFields::InvalidInput::DebugMessage() const 105{ 106 BString output = BError::DebugMessage(); 107 output << "\t " << input << "\n"; 108 return output; 109} 110 111 112// #pragma mark -- BHttpFields::Name 113 114 115BHttpFields::FieldName::FieldName() noexcept 116 : 117 fName(std::string_view()) 118{ 119} 120 121 122BHttpFields::FieldName::FieldName(const std::string_view& name) noexcept 123 : 124 fName(name) 125{ 126} 127 128 129/*! 130 \brief Copy constructor; 131*/ 132BHttpFields::FieldName::FieldName(const FieldName& other) noexcept = default; 133 134 135/*! 136 \brief Move constructor 137 138 Moving leaves the other object in the empty state. It is implemented to satisfy the internal 139 requirements of BHttpFields and std::list<Field>. Once an object is moved from it must no 140 longer be used as an entry in a BHttpFields object. 141*/ 142BHttpFields::FieldName::FieldName(FieldName&& other) noexcept 143 : 144 fName(std::move(other.fName)) 145{ 146 other.fName = std::string_view(); 147} 148 149 150/*! 151 \brief Copy assignment; 152*/ 153BHttpFields::FieldName& BHttpFields::FieldName::operator=( 154 const BHttpFields::FieldName& other) noexcept = default; 155 156 157/*! 158 \brief Move assignment 159 160 Moving leaves the other object in the empty state. It is implemented to satisfy the internal 161 requirements of BHttpFields and std::list<Field>. Once an object is moved from it must no 162 longer be used as an entry in a BHttpFields object. 163*/ 164BHttpFields::FieldName& 165BHttpFields::FieldName::operator=(BHttpFields::FieldName&& other) noexcept 166{ 167 fName = std::move(other.fName); 168 other.fName = std::string_view(); 169 return *this; 170} 171 172 173bool 174BHttpFields::FieldName::operator==(const BString& other) const noexcept 175{ 176 return iequals(fName, std::string_view(other.String())); 177} 178 179 180bool 181BHttpFields::FieldName::operator==(const std::string_view& other) const noexcept 182{ 183 return iequals(fName, other); 184} 185 186 187bool 188BHttpFields::FieldName::operator==(const BHttpFields::FieldName& other) const noexcept 189{ 190 return iequals(fName, other.fName); 191} 192 193 194BHttpFields::FieldName::operator std::string_view() const 195{ 196 return fName; 197} 198 199 200// #pragma mark -- BHttpFields::Field 201 202 203BHttpFields::Field::Field() noexcept 204 : 205 fName(std::string_view()), 206 fValue(std::string_view()) 207{ 208} 209 210 211BHttpFields::Field::Field(const std::string_view& name, const std::string_view& value) 212{ 213 if (name.length() == 0 || !validate_http_token_string(name)) 214 throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, BString(name.data(), name.size())); 215 if (value.length() == 0 || !validate_value_string(value)) 216 throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, BString(value.data(), value.length())); 217 218 BString rawField(name.data(), name.size()); 219 rawField << ": "; 220 rawField.Append(value.data(), value.size()); 221 222 fName = std::string_view(rawField.String(), name.size()); 223 fValue = std::string_view(rawField.String() + name.size() + 2, value.size()); 224 fRawField = std::move(rawField); 225} 226 227 228BHttpFields::Field::Field(BString& field) 229{ 230 // Check if the input contains a key, a separator and a value. 231 auto separatorIndex = field.FindFirst(':'); 232 if (separatorIndex <= 0) 233 throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, field); 234 235 // Get the name and the value. Remove whitespace around the value. 236 auto name = std::string_view(field.String(), separatorIndex); 237 auto value = trim(std::string_view(field.String() + separatorIndex + 1)); 238 239 if (name.length() == 0 || !validate_http_token_string(name)) 240 throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, BString(name.data(), name.size())); 241 if (value.length() == 0 || !validate_value_string(value)) 242 throw BHttpFields::InvalidInput(__PRETTY_FUNCTION__, BString(value.data(), value.length())); 243 244 fRawField = std::move(field); 245 fName = name; 246 fValue = value; 247} 248 249 250BHttpFields::Field::Field(const BHttpFields::Field& other) 251 : 252 fName(std::string_view()), 253 fValue(std::string_view()) 254{ 255 if (other.IsEmpty()) { 256 fRawField = BString(); 257 fName = std::string_view(); 258 fValue = std::string_view(); 259 } else { 260 fRawField = other.fRawField; 261 auto nameSize = other.Name().fName.size(); 262 auto valueOffset = other.fValue.data() - other.fRawField.value().String(); 263 fName = std::string_view((*fRawField).String(), nameSize); 264 fValue = std::string_view((*fRawField).String() + valueOffset, other.fValue.size()); 265 } 266} 267 268 269BHttpFields::Field::Field(BHttpFields::Field&& other) noexcept 270 : 271 fRawField(std::move(other.fRawField)), 272 fName(std::move(other.fName)), 273 fValue(std::move(other.fValue)) 274{ 275 other.fName.fName = std::string_view(); 276 other.fValue = std::string_view(); 277} 278 279 280BHttpFields::Field& 281BHttpFields::Field::operator=(const BHttpFields::Field& other) 282{ 283 if (other.IsEmpty()) { 284 fRawField = BString(); 285 fName = std::string_view(); 286 fValue = std::string_view(); 287 } else { 288 fRawField = other.fRawField; 289 auto nameSize = other.Name().fName.size(); 290 auto valueOffset = other.fValue.data() - other.fRawField.value().String(); 291 fName = std::string_view((*fRawField).String(), nameSize); 292 fValue = std::string_view((*fRawField).String() + valueOffset, other.fValue.size()); 293 } 294 return *this; 295} 296 297 298BHttpFields::Field& 299BHttpFields::Field::operator=(BHttpFields::Field&& other) noexcept 300{ 301 fRawField = std::move(other.fRawField); 302 fName = std::move(other.fName); 303 other.fName.fName = std::string_view(); 304 fValue = std::move(other.fValue); 305 fValue = std::string_view(); 306 return *this; 307} 308 309 310const BHttpFields::FieldName& 311BHttpFields::Field::Name() const noexcept 312{ 313 return fName; 314} 315 316 317std::string_view 318BHttpFields::Field::Value() const noexcept 319{ 320 return fValue; 321} 322 323 324std::string_view 325BHttpFields::Field::RawField() const noexcept 326{ 327 if (fRawField) 328 return std::string_view((*fRawField).String(), (*fRawField).Length()); 329 else 330 return std::string_view(); 331} 332 333 334bool 335BHttpFields::Field::IsEmpty() const noexcept 336{ 337 // The object is either fully empty, or it has data, so we only have to check fValue. 338 return !fRawField.has_value(); 339} 340 341 342// #pragma mark -- BHttpFields 343 344 345BHttpFields::BHttpFields() 346{ 347} 348 349 350BHttpFields::BHttpFields(std::initializer_list<BHttpFields::Field> fields) 351{ 352 AddFields(fields); 353} 354 355 356BHttpFields::BHttpFields(const BHttpFields& other) = default; 357 358 359BHttpFields::BHttpFields(BHttpFields&& other) 360 : 361 fFields(std::move(other.fFields)) 362{ 363 // Explicitly clear the other list, as the C++ standard does not specify that the other list 364 // will be empty. 365 other.fFields.clear(); 366} 367 368 369BHttpFields::~BHttpFields() noexcept 370{ 371} 372 373 374BHttpFields& BHttpFields::operator=(const BHttpFields& other) = default; 375 376 377BHttpFields& 378BHttpFields::operator=(BHttpFields&& other) noexcept 379{ 380 fFields = std::move(other.fFields); 381 382 // Explicitly clear the other list, as the C++ standard does not specify that the other list 383 // will be empty. 384 other.fFields.clear(); 385 return *this; 386} 387 388 389const BHttpFields::Field& 390BHttpFields::operator[](size_t index) const 391{ 392 if (index >= fFields.size()) 393 throw BRuntimeError(__PRETTY_FUNCTION__, "Index out of bounds"); 394 auto it = fFields.cbegin(); 395 std::advance(it, index); 396 return *it; 397} 398 399 400void 401BHttpFields::AddField(const std::string_view& name, const std::string_view& value) 402{ 403 fFields.emplace_back(name, value); 404} 405 406 407void 408BHttpFields::AddField(BString& field) 409{ 410 fFields.emplace_back(field); 411} 412 413 414void 415BHttpFields::AddFields(std::initializer_list<Field> fields) 416{ 417 for (auto& field: fields) { 418 if (!field.IsEmpty()) 419 fFields.push_back(std::move(field)); 420 } 421} 422 423 424void 425BHttpFields::RemoveField(const std::string_view& name) noexcept 426{ 427 for (auto it = FindField(name); it != end(); it = FindField(name)) { 428 fFields.erase(it); 429 } 430} 431 432 433void 434BHttpFields::RemoveField(ConstIterator it) noexcept 435{ 436 fFields.erase(it); 437} 438 439 440void 441BHttpFields::MakeEmpty() noexcept 442{ 443 fFields.clear(); 444} 445 446 447BHttpFields::ConstIterator 448BHttpFields::FindField(const std::string_view& name) const noexcept 449{ 450 for (auto it = fFields.cbegin(); it != fFields.cend(); it++) { 451 if ((*it).Name() == name) 452 return it; 453 } 454 return fFields.cend(); 455} 456 457 458size_t 459BHttpFields::CountFields() const noexcept 460{ 461 return fFields.size(); 462} 463 464 465size_t 466BHttpFields::CountFields(const std::string_view& name) const noexcept 467{ 468 size_t count = 0; 469 for (auto it = fFields.cbegin(); it != fFields.cend(); it++) { 470 if ((*it).Name() == name) 471 count += 1; 472 } 473 return count; 474} 475 476 477BHttpFields::ConstIterator 478BHttpFields::begin() const noexcept 479{ 480 return fFields.cbegin(); 481} 482 483 484BHttpFields::ConstIterator 485BHttpFields::end() const noexcept 486{ 487 return fFields.cend(); 488} 489