261 lines
5.0 KiB
C++
Executable File
261 lines
5.0 KiB
C++
Executable File
// TJP COPYRIGHT HEADER
|
|
|
|
#ifdef TJP_CORE_HEADER_ONLY
|
|
#pragma once
|
|
#endif
|
|
#include <tjp/core/header_only/compile.h>
|
|
|
|
#include "Headers.hpp"
|
|
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
|
|
#include <cstring>
|
|
|
|
#include <tjp/core/io/memstream.hpp>
|
|
#include <tjp/core/string/from_string.hpp>
|
|
#include <tjp/core/string/ToString.hpp>
|
|
#include <tjp/core/string/cmp_case.hpp>
|
|
|
|
namespace tjp::core::internet {
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
const String *getHeader(const Headers &headers, StringView name)
|
|
{
|
|
for (auto &[key, value]: headers)
|
|
if (core::cmp_case(key, name) == 0)
|
|
return &value;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<std::time_t> parseDate(const String &s)
|
|
{
|
|
std::tm tm = {};
|
|
core::io::imemstream ss(s.data(), s.size());
|
|
|
|
// Example format: "Fri, 02 Jun 2023 18:24:37 GMT"
|
|
ss >> std::get_time(&tm, "%a, %d %b %Y %H:%M:%S GMT");
|
|
if (ss.fail()) {
|
|
return {};
|
|
}
|
|
|
|
// Convert to time_t in UTC
|
|
#if defined(_WIN32)
|
|
time_t t = _mkgmtime(&tm);
|
|
#else
|
|
time_t t = timegm(&tm);
|
|
#endif
|
|
|
|
return static_cast<std::time_t>(t);
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<std::time_t> getLastModified(const Headers &headers)
|
|
{
|
|
if (auto value = getHeader(headers, "Last-Modified"))
|
|
return parseDate(*value);
|
|
|
|
return {};
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<std::time_t> getDate(const Headers &headers)
|
|
{
|
|
if (auto value = getHeader(headers, "Date"))
|
|
return parseDate(*value);
|
|
|
|
return {};
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<String> getType(const Headers &headers)
|
|
{
|
|
if (auto value = getHeader(headers, "Content-Type"))
|
|
return *value;
|
|
|
|
return {};
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<String> getCacheControl(const Headers &headers)
|
|
{
|
|
if (auto value = getHeader(headers, "Cache-Control"))
|
|
return *value;
|
|
|
|
return {};
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<String> getContentLocation(const Headers &headers)
|
|
{
|
|
if (auto value = getHeader(headers, "Content-Location"))
|
|
return *value;
|
|
|
|
return {};
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<String> getLocation(const Headers &headers)
|
|
{
|
|
if (auto value = getHeader(headers, "Location"))
|
|
return *value;
|
|
|
|
return {};
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<String> getCookie(const Headers &headers)
|
|
{
|
|
if (auto value = getHeader(headers, "Set-Cookie"))
|
|
return *value;
|
|
|
|
return {};
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<s64> parseStatusCode(const String &s)
|
|
{
|
|
if (s.rfind("HTTP/", 0) == 0)
|
|
{
|
|
int code = 0;
|
|
if (sscanf((char *)s.data(), "HTTP/%*s %d", &code) != 1)
|
|
return {};
|
|
|
|
return code;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<Range> parseRange(const String &s)
|
|
{
|
|
long long int start = -1, end = -1;
|
|
|
|
int matched = std::sscanf(s.data(), "bytes=%lld-%lld", &start, &end);
|
|
if (matched != 2)
|
|
return {};
|
|
|
|
return Range { start, end };
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
String makeRange(Range range)
|
|
{
|
|
ToString string;
|
|
|
|
auto [begin, end] = range;
|
|
string << "bytes=" << begin << '-' << end;
|
|
|
|
return string;
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<ContentRange> parseContentRange(const String &s)
|
|
{
|
|
char unit[16];
|
|
char totalStr[32];
|
|
long long int start = -1, end = -1, total = -1;
|
|
|
|
int matched = std::sscanf(s.data(), "%15s %lld-%lld/%31s", unit, &start, &end, totalStr);
|
|
if (matched != 4 || std::strcmp(unit, "bytes") != 0)
|
|
return {};
|
|
|
|
if (std::strcmp(totalStr, "*") == 0)
|
|
total = -1; // unknown total
|
|
else
|
|
total = std::atoll(totalStr);
|
|
|
|
return ContentRange { start, end, total };
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<s64> getCacheControlMaxAge(const String &s_)
|
|
{
|
|
StringView s(s_);
|
|
const StringView prefix = "max-age=";
|
|
auto maxAgeP = s.find(prefix);
|
|
if (maxAgeP == s.npos)
|
|
return {};
|
|
|
|
auto maxAgeB = maxAgeP + prefix.size();
|
|
auto semi = s.find(';', maxAgeB);
|
|
if (semi == s.npos)
|
|
semi = s.size();
|
|
|
|
auto ageS = s.substr(maxAgeB, semi - maxAgeB);
|
|
return core::from_string<s64>(ageS);
|
|
}
|
|
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
String makeContentRange(ContentRange range)
|
|
{
|
|
ToString string;
|
|
|
|
auto [begin, end, of] = range;
|
|
string << "bytes " << begin << '-' << end << '/';
|
|
|
|
if (of >= 0)
|
|
string << of;
|
|
else
|
|
string << '*';
|
|
|
|
return string;
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<ContentRange> getContentRange(const Headers &headers)
|
|
{
|
|
if (auto value = getHeader(headers, "Content-Range"))
|
|
return parseContentRange(*value);
|
|
|
|
return {};
|
|
}
|
|
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<s64> parseContentSize(const String &s)
|
|
{
|
|
long long int size = 0;
|
|
if (sscanf(s.c_str(), "%lld", &size) != 1)
|
|
return {};
|
|
|
|
return size;
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
Optional<s64> getContentSize(const Headers &headers)
|
|
{
|
|
if (auto value = getHeader(headers, "Content-Length"))
|
|
return parseContentSize(*value);
|
|
|
|
return {};
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
String makeDateHeader(std::time_t t)
|
|
{
|
|
std::tm* gm = std::gmtime(&t); // convert to UTC
|
|
if (!gm) return {};
|
|
|
|
char buffer[128];
|
|
// Format: "Fri, 02 Jun 2023 18:24:37 GMT"
|
|
if (std::strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT", gm) == 0)
|
|
return {};
|
|
|
|
return String(buffer);
|
|
}
|
|
|
|
TJP_CORE_HEADER_ONLY_INLINE
|
|
String makeLastModifiedHeader(std::time_t t)
|
|
{
|
|
return makeDateHeader(t);
|
|
}
|
|
|
|
} // namespace
|