xref: /haiku/src/kits/network/libnetservices2/HttpFields.cpp (revision 52c4471a3024d2eb81fe88e2c3982b9f8daa5e56)
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