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