Public Access
lib/gemini: URLs split per RFC 3986 appendix B and resolved per section 5.2 (all 32 reference examples of 5.4 pass, with gemini:// for http://), the request form (no fragment, lower-case host, never an empty path), query encoding for input prompts, Saved Page paths; the response header (status, category, MIME type and parameters, 1024-byte meta limit); gemtext's line types; and display text for the Latin-1 fonts. 12 tests. Q86, decided after step 1: free heap stays above 40 KB in steady state and 20 KB during a handshake; a fetch won't start below 55 KB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
60 lines
1.9 KiB
C++
60 lines
1.9 KiB
C++
#include "gemini_response.h"
|
|
|
|
#include <cctype>
|
|
|
|
namespace roro::gemini {
|
|
|
|
namespace {
|
|
std::string trim(const std::string& s) {
|
|
size_t a = s.find_first_not_of(" \t"), b = s.find_last_not_of(" \t");
|
|
return a == std::string::npos ? "" : s.substr(a, b - a + 1);
|
|
}
|
|
std::string lower(std::string s) {
|
|
for (auto& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
return s;
|
|
}
|
|
} // namespace
|
|
|
|
bool parseHeader(const std::string& line, Header& out) {
|
|
if (line.size() < 2 || !std::isdigit(static_cast<unsigned char>(line[0])) ||
|
|
!std::isdigit(static_cast<unsigned char>(line[1])))
|
|
return false;
|
|
if (line.size() > 2 && line[2] != ' ') return false;
|
|
std::string meta = line.size() > 3 ? line.substr(3) : "";
|
|
if (meta.size() > 1024) return false;
|
|
out.status = (line[0] - '0') * 10 + (line[1] - '0');
|
|
out.meta = meta;
|
|
return true;
|
|
}
|
|
|
|
Category Header::category() const {
|
|
switch (status / 10) {
|
|
case 1: return Category::Input;
|
|
case 2: return Category::Success;
|
|
case 3: return Category::Redirect;
|
|
case 4: return Category::TemporaryFailure;
|
|
case 5: return Category::PermanentFailure;
|
|
case 6: return Category::ClientCertificate;
|
|
default: return Category::Unknown;
|
|
}
|
|
}
|
|
|
|
std::string Header::mimeType() const {
|
|
std::string type = lower(trim(meta.substr(0, meta.find(';'))));
|
|
return type.empty() ? "text/gemini" : type;
|
|
}
|
|
|
|
std::string Header::parameter(const std::string& name) const {
|
|
size_t pos = meta.find(';');
|
|
while (pos != std::string::npos) {
|
|
size_t next = meta.find(';', pos + 1);
|
|
std::string item = trim(meta.substr(pos + 1, next == std::string::npos ? std::string::npos : next - pos - 1));
|
|
size_t eq = item.find('=');
|
|
if (eq != std::string::npos && lower(trim(item.substr(0, eq))) == lower(name)) return trim(item.substr(eq + 1));
|
|
pos = next;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
} // namespace roro::gemini
|