14using namespace Qt::Literals::StringLiterals;
16inline static QByteArray decodeBasicAuth(
const QByteArray &auth)
19 int pos = auth.indexOf(
"Basic ");
22 ret = auth.mid(pos, auth.indexOf(
',', pos) - pos);
23 ret = QByteArray::fromBase64(ret);
31 const QByteArray authorization = decodeBasicAuth(auth);
32 if (!authorization.isEmpty()) {
33 int pos = authorization.indexOf(
':');
35 ret.user = QString::fromLatin1(authorization);
37 ret.user = QString::fromLatin1(authorization.left(pos));
38 ret.password = QString::fromLatin1(authorization.mid(pos + 1));
44static QVector<Headers::HeaderKeyValue>::const_iterator
45 findHeaderConst(
const QVector<Headers::HeaderKeyValue> &headers, QAnyStringView key)
noexcept
48 return QAnyStringView::compare(key, entry.key, Qt::CaseInsensitive) == 0;
53 : m_data(other.m_data)
59 return header(
"Content-Disposition");
74 if (filename.isEmpty()) {
83 return header(
"Content-Encoding");
88 setHeader(
"Content-Encoding"_ba, encoding);
93 QByteArray ret =
header(
"Content-Type");
95 ret = ret.mid(0, ret.indexOf(
';')).toLower();
108 const QByteArray _contentType =
header(
"Content-Type");
109 if (!_contentType.isEmpty()) {
110 int pos = _contentType.indexOf(
"charset=", 0);
112 int endPos = _contentType.indexOf(u
';', pos);
113 ret = _contentType.mid(pos + 8, endPos).trimmed().toUpper();
122 auto result = findHeaderConst(m_data,
"Content-Type");
123 if (result == m_data.end() || (result->value.isEmpty() && !charset.isEmpty())) {
128 QByteArray _contentType = result->value;
129 int pos = _contentType.indexOf(
"charset=", 0);
131 int endPos = _contentType.indexOf(
';', pos);
133 if (charset.isEmpty()) {
134 int lastPos = _contentType.lastIndexOf(
';', pos);
139 _contentType.remove(lastPos, _contentType.length() - lastPos);
142 _contentType.replace(pos + 8, _contentType.length() - pos + 8, charset);
145 _contentType.replace(pos + 8, endPos, charset);
147 }
else if (!charset.isEmpty()) {
148 _contentType.append(
"; charset=" + charset);
155 return header(
"Content-Type").startsWith(
"text/");
161 return ct.compare(
"text/html") == 0 || ct.compare(
"application/xhtml+xml") == 0 ||
162 ct.compare(
"application/vnd.wap.xhtml+xml") == 0;
168 return ct.compare(
"application/xhtml+xml") == 0 ||
169 ct.compare(
"application/vnd.wap.xhtml+xml") == 0;
175 return ct.compare(
"text/xml") == 0 || ct.compare(
"application/xml") == 0 || ct.endsWith(
"xml");
180 auto value =
header(
"Content-Type");
181 if (!value.isEmpty()) {
182 return value.compare(
"application/json") == 0;
189 auto value =
header(
"Content-Length");
190 if (!value.isEmpty()) {
191 return value.toLongLong();
198 setHeader(
"Content-Length"_ba, QByteArray::number(value));
206 QLocale::c().toString(
date.toUTC(), u
"ddd, dd MMM yyyy hh:mm:ss 'GMT").toLatin1();
214 auto value =
header(
"Date");
215 if (!value.isEmpty()) {
216 if (value.endsWith(
" GMT")) {
217 ret = QLocale::c().toDateTime(QString::fromLatin1(value.left(value.size() - 4)),
218 u
"ddd, dd MMM yyyy hh:mm:ss"_s);
221 QLocale::c().toDateTime(QString::fromLatin1(value), u
"ddd, dd MMM yyyy hh:mm:ss"_s);
223 ret.setTimeZone(QTimeZone::UTC);
231 return header(
"If-Modified-Since");
237 auto value =
header(
"If-Modified-Since");
238 if (!value.isEmpty()) {
239 if (value.endsWith(
" GMT")) {
240 ret = QLocale::c().toDateTime(QString::fromLatin1(value.left(value.size() - 4)),
241 u
"ddd, dd MMM yyyy hh:mm:ss"_s);
244 QLocale::c().toDateTime(QString::fromLatin1(value), u
"ddd, dd MMM yyyy hh:mm:ss"_s);
246 ret.setTimeZone(QTimeZone::UTC);
254 auto value =
header(
"If-Modified-Since");
255 if (!value.isEmpty()) {
256 return value != QLocale::c()
257 .toString(
lastModified.toUTC(), u
"ddd, dd MMM yyyy hh:mm:ss 'GMT")
265 auto value =
header(
"If-Match");
266 if (!value.isEmpty()) {
267 const auto clientETag = QByteArrayView(value);
268 return clientETag.sliced(1, clientETag.size() - 2) == etag ||
269 clientETag.sliced(3, clientETag.size() - 4) == etag;
276 auto value =
header(
"If-None-Match");
277 if (!value.isEmpty()) {
278 const auto clientETag = QByteArrayView(value);
279 return clientETag.sliced(1, clientETag.size() - 2) == etag ||
280 clientETag.sliced(3, clientETag.size() - 4) == etag;
292 return header(
"Last-Modified");
304 auto dt = QLocale::c().toString(
lastModified.toUTC(), u
"ddd, dd MMM yyyy hh:mm:ss 'GMT");
321 return header(
"Connection");
331 return header(
"User-Agent");
341 int fragmentPos = uri.indexOf(
'#');
342 if (fragmentPos != -1) {
344 setHeader(
"Referer"_ba, uri.mid(0, fragmentPos));
357 setHeader(
"Proxy-Authenticate"_ba, value);
362 return header(
"Authorization");
369 int pos = auth.indexOf(
"Bearer ");
372 ret = auth.mid(pos, auth.indexOf(
',', pos) - pos);
390 if (username.contains(u
':')) {
391 qCWarning(CUTELYST_CORE) <<
"Headers::Basic authorization user name can't contain ':'";
395 const QString result = username + u
':' + password;
396 ret =
"Basic " + result.toLatin1().toBase64();
403 return header(
"Proxy-Authorization");
418 if (
auto result = findHeaderConst(m_data, key); result != m_data.end()) {
419 return result->value;
426 return QString::fromLatin1(
header(key));
429QByteArray
Headers::header(QAnyStringView key,
const QByteArray &defaultValue)
const noexcept
431 if (
auto result = findHeaderConst(m_data, key); result != m_data.end()) {
432 return result->value;
439 if (
auto result = findHeaderConst(m_data, key); result != m_data.end()) {
440 return QString::fromLatin1(result->value);
448 for (
auto result = findHeaderConst(m_data, key); result != m_data.end(); ++result) {
449 ret.append(result->value);
457 for (
auto result = findHeaderConst(m_data, key); result != m_data.end(); ++result) {
458 ret.append(QString::fromLatin1(result->value));
466 return key.compare(entry.key, Qt::CaseInsensitive) == 0;
469 if (
auto result = std::ranges::find_if(m_data, matchKey); result != m_data.end()) {
470 result->value = value;
473 QVector<HeaderKeyValue>::ConstIterator begin =
474 std::remove_if(result, m_data.end(), matchKey);
475 m_data.erase(begin, m_data.cend());
493 m_data.emplace_back(
HeaderKeyValue{.key = key, .value = values.join(
", ")});
499 return QAnyStringView::compare(key, entry.key, Qt::CaseInsensitive) == 0;
505 auto result = findHeaderConst(m_data, key);
506 return result != m_data.end();
509QByteArrayList Headers::keys()
const
513 for (
const auto &_header : m_data) {
514 const bool exists = std::ranges::any_of(ret, [&](
const QByteArray &key) {
515 return _header.key.compare(key, Qt::CaseInsensitive) == 0;
519 ret.append(_header.key);
533 const auto otherData = other.data();
534 if (m_data.size() != otherData.size()) {
538 return std::ranges::all_of(
539 m_data, [otherData](
const auto &myValue) {
return otherData.contains(myValue); });
542QDebug operator<<(QDebug debug,
const Headers &headers)
544 const auto data = headers.
data();
545 const bool oldSetting = debug.autoInsertSpaces();
546 debug.nospace() <<
"Headers[";
547 for (
auto it = data.begin(); it != data.end(); ++it) {
548 debug <<
'(' << it->key +
'=' + it->value <<
')';
551 debug.setAutoInsertSpaces(oldSetting);
552 return debug.maybeSpace();
The Cutelyst namespace holds all public Cutelyst API.