13using namespace Qt::Literals::StringLiterals;
15inline QByteArray decodeBasicAuth(
const QByteArray &auth);
19QVector<Headers::HeaderKeyValue>::const_iterator
20 findHeaderConst(
const QVector<Headers::HeaderKeyValue> &headers, QAnyStringView key)
noexcept
23 return QAnyStringView::compare(key, entry.key, Qt::CaseInsensitive) == 0;
25 return std::find_if(headers.cbegin(), headers.cend(), matchKey);
30 : m_data(other.m_data)
36 return header(
"Content-Disposition");
51 if (filename.isEmpty()) {
60 return header(
"Content-Encoding");
65 setHeader(
"Content-Encoding"_ba, encoding);
70 QByteArray ret =
header(
"Content-Type");
72 ret = ret.mid(0, ret.indexOf(
';')).toLower();
85 const QByteArray _contentType =
header(
"Content-Type");
86 if (!_contentType.isEmpty()) {
87 int pos = _contentType.indexOf(
"charset=", 0);
89 int endPos = _contentType.indexOf(u
';', pos);
90 ret = _contentType.mid(pos + 8, endPos).trimmed().toUpper();
99 auto result = findHeaderConst(m_data,
"Content-Type");
100 if (result == m_data.end() || (result->value.isEmpty() && !charset.isEmpty())) {
105 QByteArray _contentType = result->value;
106 int pos = _contentType.indexOf(
"charset=", 0);
108 int endPos = _contentType.indexOf(
';', pos);
110 if (charset.isEmpty()) {
111 int lastPos = _contentType.lastIndexOf(
';', pos);
116 _contentType.remove(lastPos, _contentType.length() - lastPos);
119 _contentType.replace(pos + 8, _contentType.length() - pos + 8, charset);
122 _contentType.replace(pos + 8, endPos, charset);
124 }
else if (!charset.isEmpty()) {
125 _contentType.append(
"; charset=" + charset);
132 return header(
"Content-Type").startsWith(
"text/");
138 return ct.compare(
"text/html") == 0 || ct.compare(
"application/xhtml+xml") == 0 ||
139 ct.compare(
"application/vnd.wap.xhtml+xml") == 0;
145 return ct.compare(
"application/xhtml+xml") == 0 ||
146 ct.compare(
"application/vnd.wap.xhtml+xml") == 0;
152 return ct.compare(
"text/xml") == 0 || ct.compare(
"application/xml") == 0 || ct.endsWith(
"xml");
157 auto value =
header(
"Content-Type");
158 if (!value.isEmpty()) {
159 return value.compare(
"application/json") == 0;
166 auto value =
header(
"Content-Length");
167 if (!value.isEmpty()) {
168 return value.toLongLong();
175 setHeader(
"Content-Length"_ba, QByteArray::number(value));
183 QLocale::c().toString(
date.toUTC(), u
"ddd, dd MMM yyyy hh:mm:ss 'GMT").toLatin1();
191 auto value =
header(
"Date");
192 if (!value.isEmpty()) {
193 if (value.endsWith(
" GMT")) {
194 ret = QLocale::c().toDateTime(QString::fromLatin1(value.left(value.size() - 4)),
195 QStringLiteral(
"ddd, dd MMM yyyy hh:mm:ss"));
197 ret = QLocale::c().toDateTime(QString::fromLatin1(value),
198 QStringLiteral(
"ddd, dd MMM yyyy hh:mm:ss"));
200 ret.setTimeSpec(Qt::UTC);
208 return header(
"If-Modified-Since");
214 auto value =
header(
"If-Modified-Since");
215 if (!value.isEmpty()) {
216 if (value.endsWith(
" GMT")) {
217 ret = QLocale::c().toDateTime(QString::fromLatin1(value.left(value.size() - 4)),
218 QStringLiteral(
"ddd, dd MMM yyyy hh:mm:ss"));
220 ret = QLocale::c().toDateTime(QString::fromLatin1(value),
221 QStringLiteral(
"ddd, dd MMM yyyy hh:mm:ss"));
223 ret.setTimeSpec(Qt::UTC);
231 auto value =
header(
"If-Modified-Since");
232 if (!value.isEmpty()) {
233 return value != QLocale::c()
234 .toString(
lastModified.toUTC(), u
"ddd, dd MMM yyyy hh:mm:ss 'GMT")
242 auto value =
header(
"If-Match");
243 if (!value.isEmpty()) {
244 const auto clientETag = QByteArrayView(value);
245 return clientETag.sliced(1, clientETag.size() - 2) == etag ||
246 clientETag.sliced(3, clientETag.size() - 4) == etag;
253 auto value =
header(
"If-None-Match");
254 if (!value.isEmpty()) {
255 const auto clientETag = QByteArrayView(value);
256 return clientETag.sliced(1, clientETag.size() - 2) == etag ||
257 clientETag.sliced(3, clientETag.size() - 4) == etag;
269 return header(
"Last-Modified");
281 auto dt = QLocale::c().toString(
lastModified.toUTC(), u
"ddd, dd MMM yyyy hh:mm:ss 'GMT");
298 return header(
"Connection");
308 return header(
"User-Agent");
318 int fragmentPos = uri.indexOf(
'#');
319 if (fragmentPos != -1) {
321 setHeader(
"Referer"_ba, uri.mid(0, fragmentPos));
334 setHeader(
"Proxy-Authenticate"_ba, value);
339 return header(
"Authorization");
346 int pos = auth.indexOf(
"Bearer ");
349 ret = auth.mid(pos, auth.indexOf(
',', pos) - pos);
367 if (username.contains(u
':')) {
368 qCWarning(CUTELYST_CORE) <<
"Headers::Basic authorization user name can't contain ':'";
372 const QString result = username + u
':' + password;
373 ret =
"Basic " + result.toLatin1().toBase64();
380 return header(
"Proxy-Authorization");
395 if (
auto result = findHeaderConst(m_data, key); result != m_data.end()) {
396 return result->value;
403 return QString::fromLatin1(
header(key));
406QByteArray
Headers::header(QAnyStringView key,
const QByteArray &defaultValue)
const noexcept
408 if (
auto result = findHeaderConst(m_data, key); result != m_data.end()) {
409 return result->value;
416 if (
auto result = findHeaderConst(m_data, key); result != m_data.end()) {
417 return QString::fromLatin1(result->value);
425 for (
auto result = findHeaderConst(m_data, key); result != m_data.end(); ++result) {
426 ret.append(result->value);
434 for (
auto result = findHeaderConst(m_data, key); result != m_data.end(); ++result) {
435 ret.append(QString::fromLatin1(result->value));
443 return key.compare(entry.key, Qt::CaseInsensitive) == 0;
446 if (
auto result = std::find_if(m_data.begin(), m_data.end(), matchKey);
447 result != m_data.end()) {
448 result->value = value;
451 QVector<HeaderKeyValue>::ConstIterator begin =
452 std::remove_if(result, m_data.end(), matchKey);
453 m_data.erase(begin, m_data.cend());
477 return QAnyStringView::compare(key, entry.key, Qt::CaseInsensitive) == 0;
483 auto result = findHeaderConst(m_data, key);
484 return result != m_data.end();
487QByteArrayList Headers::keys()
const
491 for (
const auto &_header : m_data) {
492 const bool exists = std::ranges::any_of(ret, [&](
const QByteArray &key) {
493 return _header.key.compare(key, Qt::CaseInsensitive) == 0;
497 ret.append(_header.key);
511 const auto otherData = other.data();
512 if (m_data.size() != otherData.size()) {
516 return std::ranges::all_of(
517 m_data, [otherData](
const auto &myValue) {
return otherData.contains(myValue); });
520QByteArray decodeBasicAuth(
const QByteArray &auth)
523 int pos = auth.indexOf(
"Basic ");
526 ret = auth.mid(pos, auth.indexOf(
',', pos) - pos);
527 ret = QByteArray::fromBase64(ret);
535 const QByteArray authorization = decodeBasicAuth(auth);
536 if (!authorization.isEmpty()) {
537 int pos = authorization.indexOf(
':');
539 ret.user = QString::fromLatin1(authorization);
541 ret.user = QString::fromLatin1(authorization.left(pos));
542 ret.password = QString::fromLatin1(authorization.mid(pos + 1));
548QDebug operator<<(QDebug debug,
const Headers &headers)
550 const auto data = headers.
data();
551 const bool oldSetting = debug.autoInsertSpaces();
552 debug.nospace() <<
"Headers[";
553 for (
auto it = data.begin(); it != data.end(); ++it) {
554 debug <<
'(' << it->key +
'=' + it->value <<
')';
557 debug.setAutoInsertSpaces(oldSetting);
558 return debug.maybeSpace();
The Cutelyst namespace holds all public Cutelyst API.