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 17 using 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 */ 36 static inline bool 37 validate_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 */ 52 static inline bool 53 iequals(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 */ 66 static inline std::string_view 67 trim(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 88 BHttpFields::InvalidInput::InvalidInput(const char* origin, BString input) 89 : 90 BError(origin), 91 input(std::move(input)) 92 { 93 } 94 95 96 const char* 97 BHttpFields::InvalidInput::Message() const noexcept 98 { 99 return "Invalid format or unsupported characters in input"; 100 } 101 102 103 BString 104 BHttpFields::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 115 BHttpFields::FieldName::FieldName() noexcept 116 : 117 fName(std::string_view()) 118 { 119 } 120 121 122 BHttpFields::FieldName::FieldName(const std::string_view& name) noexcept 123 : 124 fName(name) 125 { 126 } 127 128 129 /*! 130 \brief Copy constructor; 131 */ 132 BHttpFields::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 */ 142 BHttpFields::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 */ 153 BHttpFields::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 */ 164 BHttpFields::FieldName& 165 BHttpFields::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 173 bool 174 BHttpFields::FieldName::operator==(const BString& other) const noexcept 175 { 176 return iequals(fName, std::string_view(other.String())); 177 } 178 179 180 bool 181 BHttpFields::FieldName::operator==(const std::string_view& other) const noexcept 182 { 183 return iequals(fName, other); 184 } 185 186 187 bool 188 BHttpFields::FieldName::operator==(const BHttpFields::FieldName& other) const noexcept 189 { 190 return iequals(fName, other.fName); 191 } 192 193 194 BHttpFields::FieldName::operator std::string_view() const 195 { 196 return fName; 197 } 198 199 200 // #pragma mark -- BHttpFields::Field 201 202 203 BHttpFields::Field::Field() noexcept 204 : 205 fName(std::string_view()), 206 fValue(std::string_view()) 207 { 208 } 209 210 211 BHttpFields::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 228 BHttpFields::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 250 BHttpFields::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 269 BHttpFields::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 280 BHttpFields::Field& 281 BHttpFields::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 298 BHttpFields::Field& 299 BHttpFields::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 310 const BHttpFields::FieldName& 311 BHttpFields::Field::Name() const noexcept 312 { 313 return fName; 314 } 315 316 317 std::string_view 318 BHttpFields::Field::Value() const noexcept 319 { 320 return fValue; 321 } 322 323 324 std::string_view 325 BHttpFields::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 334 bool 335 BHttpFields::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 345 BHttpFields::BHttpFields() 346 { 347 } 348 349 350 BHttpFields::BHttpFields(std::initializer_list<BHttpFields::Field> fields) 351 { 352 AddFields(fields); 353 } 354 355 356 BHttpFields::BHttpFields(const BHttpFields& other) = default; 357 358 359 BHttpFields::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 369 BHttpFields::~BHttpFields() noexcept 370 { 371 } 372 373 374 BHttpFields& BHttpFields::operator=(const BHttpFields& other) = default; 375 376 377 BHttpFields& 378 BHttpFields::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 389 const BHttpFields::Field& 390 BHttpFields::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 400 void 401 BHttpFields::AddField(const std::string_view& name, const std::string_view& value) 402 { 403 fFields.emplace_back(name, value); 404 } 405 406 407 void 408 BHttpFields::AddField(BString& field) 409 { 410 fFields.emplace_back(field); 411 } 412 413 414 void 415 BHttpFields::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 424 void 425 BHttpFields::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 433 void 434 BHttpFields::RemoveField(ConstIterator it) noexcept 435 { 436 fFields.erase(it); 437 } 438 439 440 void 441 BHttpFields::MakeEmpty() noexcept 442 { 443 fFields.clear(); 444 } 445 446 447 BHttpFields::ConstIterator 448 BHttpFields::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 458 size_t 459 BHttpFields::CountFields() const noexcept 460 { 461 return fFields.size(); 462 } 463 464 465 size_t 466 BHttpFields::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 477 BHttpFields::ConstIterator 478 BHttpFields::begin() const noexcept 479 { 480 return fFields.cbegin(); 481 } 482 483 484 BHttpFields::ConstIterator 485 BHttpFields::end() const noexcept 486 { 487 return fFields.cend(); 488 } 489