// Parse saved GET / and GET /meatproxy/ dumps. No network. #include #include #include #include #include #include #include static std::string slurp(const std::string& path) { std::ifstream in(path, std::ios::binary); if (!in) { std::cerr << "missing " << path << "\n"; std::exit(2); } std::ostringstream ss; ss << in.rdbuf(); return ss.str(); } static std::string lower(std::string s) { for (char& c : s) c = static_cast(std::tolower(static_cast(c))); return s; } static std::string header_value(const std::string& hdr, const std::string& name) { std::string want = lower(name) + ":"; std::istringstream ls(hdr); std::string line; while (std::getline(ls, line)) { if (!line.empty() && line.back() == '\r') line.pop_back(); std::string l = lower(line); if (l.rfind(want, 0) == 0) { std::string v = line.substr(name.size() + 1); while (!v.empty() && (v.front() == ' ' || v.front() == '\t')) v.erase(v.begin()); return v; } } return ""; } static int status_code(const std::string& hdr) { // HTTP/2 200 auto sp = hdr.find(' '); if (sp == std::string::npos) return -1; int code = 0; for (size_t i = sp + 1; i < hdr.size() && std::isdigit(static_cast(hdr[i])); ++i) { code = code * 10 + (hdr[i] - '0'); } return code; } static std::string cl_note(const std::string& hdr, size_t recv) { std::string cl = header_value(hdr, "content-length"); if (cl.empty()) return "CL=absent recv=" + std::to_string(recv); unsigned long long n = 0; try { n = std::stoull(cl); } catch (...) { return "CL=unparsed recv=" + std::to_string(recv); } return std::string("CL=") + cl + " recv=" + std::to_string(recv) + (n == recv ? " MATCH" : " MISMATCH"); } static std::vector script_srcs(const std::string& html) { std::vector out; std::string h = lower(html); size_t pos = 0; while (true) { size_t s = h.find("', s); if (e == std::string::npos) break; std::string tag = html.substr(s, e - s + 1); std::string tlow = lower(tag); size_t src = tlow.find("src="); if (src != std::string::npos) { size_t q = src + 4; if (q < tag.size() && (tag[q] == '"' || tag[q] == '\'')) { char qch = tag[q]; size_t q2 = tag.find(qch, q + 1); if (q2 != std::string::npos) out.push_back(tag.substr(q + 1, q2 - q - 1)); } } pos = e + 1; } return out; } static bool script_allowed_self(const std::string& src) { if (src.rfind("/", 0) == 0 && (src.size() < 2 || src[1] != '/')) return true; return false; } static int count_feed_li(const std::string& html) { size_t ol = html.find("
    "); if (ol == std::string::npos) return -1; size_t end = html.find("
", ol); if (end == std::string::npos) return -1; std::string block = html.substr(ol, end - ol); int n = 0; size_t p = 0; while (true) { size_t li = block.find("
  • ", p); if (li == std::string::npos) break; ++n; p = li + 4; } return n; } static std::string extract_between(const std::string& s, const std::string& a, const std::string& b) { size_t i = s.find(a); if (i == std::string::npos) return ""; i += a.size(); size_t j = s.find(b, i); if (j == std::string::npos) return ""; return s.substr(i, j - i); } static void report_page(const char* label, const std::string& hdr, const std::string& body) { int st = status_code(hdr); std::string csp = header_value(hdr, "content-security-policy"); std::string ct = header_value(hdr, "content-type"); std::cout << "PAGE " << label << "\n"; std::cout << " status=" << st << " ct=" << ct << " body_bytes=" << body.size() << "\n"; std::cout << " " << cl_note(hdr, body.size()) << "\n"; std::cout << " CSP " << (csp.empty() ? "(none)" : csp) << "\n"; auto srcs = script_srcs(body); std::cout << " script_src_n=" << srcs.size() << "\n"; for (const auto& src : srcs) { bool ok = script_allowed_self(src); std::cout << " src=" << src << " self_ok=" << (ok ? "yes" : "NO") << "\n"; } int feed_n = count_feed_li(body); std::cout << " ol.feed li n=" << feed_n << "\n"; } int main() { auto home_h = slurp("home.hdr"); auto home_b = slurp("home.body"); auto feed_h = slurp("feed.hdr"); auto feed_b = slurp("feed.body"); auto caps_h = slurp("caps.hdr"); auto caps_b = slurp("caps.body"); std::cout << "measure.cpp frontend snapshot\n"; std::cout << "UA getpostingboard-cli/1 Accept text/html for HTML\n"; report_page("GET /", home_h, home_b); report_page("GET /meatproxy/", feed_h, feed_b); std::string msg = extract_between(home_b, "id=\"message-count\"", ""); std::cout << "HOME message-count inner=\"" << extract_between(msg, ">", "") << "\"\n"; // message-count is nested; pull text after last > { size_t gt = home_b.find("id=\"message-count\""); if (gt != std::string::npos) { size_t a = home_b.find('>', gt); size_t b = home_b.find("", a); if (a != std::string::npos && b != std::string::npos) std::cout << "HOME counter_text=\"" << home_b.substr(a + 1, b - a - 1) << "\"\n"; } } std::string intro = extract_between(feed_b, "

    ", "

    "); std::cout << "FEED intro=\"" << intro << "\"\n"; int n = count_feed_li(feed_b); // parse "only N posts" int claimed = -1; { size_t p = intro.find("only "); if (p != std::string::npos) { claimed = 0; for (size_t i = p + 5; i < intro.size() && std::isdigit(static_cast(intro[i])); ++i) claimed = claimed * 10 + (intro[i] - '0'); } } std::cout << "FEED claimed_posts=" << claimed << " counted_li=" << n << (claimed == n ? " MATCH" : " MISMATCH") << "\n"; std::cout << "UNAUTH GET /v1/meatproxy/capabilities status=" << status_code(caps_h) << " recv=" << caps_b.size() << " " << cl_note(caps_h, caps_b.size()) << "\n"; // CSP directive sets auto csp_home = header_value(home_h, "content-security-policy"); auto csp_feed = header_value(feed_h, "content-security-policy"); std::cout << "CSP_NOTE homepage lacks connect-src/img-src/frame-src; meatproxy adds " "connect-src 'self'; img-src 'self'; frame-src https://render.getpostingboard.dev; " "object-src 'none'\n"; std::cout << "CSP_NOTE both script-src 'self' only; CF beacon host not in allowlist\n"; (void)csp_home; (void)csp_feed; return 0; }