libcurl with CURLAUTH_ANY answers a 401/407 by resending the request with an Authorization header. Two spots in net/ cut that short: - http_client's header callback delivered the response head exactly once and latched `head_delivered`, so after an auth challenge the caller only ever saw the 401 — never the 2xx of the authenticated resend. Reset the latch when a fresh status line follows a delivered 401/407 (redirects never reach that path — their head is suppressed). - the prober's head callbacks return DataAction::abort to skip the body, which also aborts the transfer mid-handshake. Return `proceed` for a 401/407 when credentials were supplied, so curl's resend can run; the real status lands on the next header block. Also give ProbeRequest an `auth` field (default scheme == none) and pass it through base_request(), so a re-probe after a 401 can present the credentials the user just entered. No behaviour change when no auth is configured. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
92 lines
3.3 KiB
C++
92 lines
3.3 KiB
C++
// vdm/net/probe.hpp — "what is at this URL?" without downloading it.
|
|
//
|
|
// Feeds the File Info dialog (docs/04 §2). HEAD first; a ranged GET `bytes=0-0` follows to
|
|
// PROVE resumability (a 206 with a matching Content-Range) rather than trust
|
|
// `Accept-Ranges`, which servers lie about (docs/06 R4). The ranged GET is also the
|
|
// fallback when HEAD is refused (403/405/501).
|
|
//
|
|
// Runs on its own small worker pool, sized outside the segment budget (ADR 0011 §5) so a
|
|
// burst of probes can't starve transfers and capture.offer's 750 ms path never waits on
|
|
// one.
|
|
//
|
|
// This header compiles standalone.
|
|
|
|
#ifndef VDM_NET_PROBE_HPP
|
|
#define VDM_NET_PROBE_HPP
|
|
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
#include "vdm/net/content_disposition.hpp"
|
|
#include "vdm/net/http_types.hpp"
|
|
#include "vdm/util/result.hpp"
|
|
|
|
namespace vdm::net {
|
|
|
|
struct ProbeRequest {
|
|
std::string url;
|
|
std::vector<HeaderField> headers; // browser headers, verbatim
|
|
std::vector<Cookie> cookies;
|
|
std::string user_agent;
|
|
std::string referrer;
|
|
ProxyConfig proxy;
|
|
AuthConfig auth; // credentials for a re-probe after a 401 (leave scheme == none otherwise)
|
|
|
|
long connect_timeout_ms = 15000;
|
|
long overall_timeout_ms = 25000; // download.probe deadline is 30 s
|
|
};
|
|
|
|
struct ProbeResult {
|
|
std::string effective_url;
|
|
std::vector<std::string> redirect_chain; // requested URL first, effective_url last
|
|
long http_status = 0;
|
|
|
|
std::optional<std::uint64_t> total_size; // full-resource size, if known
|
|
std::string mime; // Content-Type value (params kept)
|
|
std::string etag;
|
|
std::string last_modified;
|
|
|
|
bool accept_ranges = false; // server advertised Accept-Ranges: bytes
|
|
bool resumable = false; // PROVEN: ranged GET -> 206 + matching Content-Range,
|
|
// and a validator (ETag or Last-Modified) is present
|
|
bool requires_auth = false; // a 401/407 was seen
|
|
|
|
// Decoded, path-stripped; NOT filesystem-sanitized (rules/ owns that, stage 9).
|
|
std::string filename_from_disposition;
|
|
std::string filename_from_url;
|
|
ContentDisposition::Type disposition_type = ContentDisposition::Type::none;
|
|
};
|
|
|
|
// Resolution order (docs/04 §2.5): explicit user name -> Content-Disposition -> URL path
|
|
// segment -> "download.bin". Only a light path/control strip here; rules/ does the
|
|
// authoritative sanitize, byte cap, and collision handling.
|
|
[[nodiscard]] std::string suggest_filename(const ProbeResult &r,
|
|
std::string_view explicit_name = {});
|
|
|
|
class Prober {
|
|
public:
|
|
// max_concurrent bounds outstanding probe transfers; the rest queue.
|
|
explicit Prober(unsigned max_concurrent = 4);
|
|
~Prober();
|
|
Prober(const Prober &) = delete;
|
|
Prober &operator=(const Prober &) = delete;
|
|
|
|
// Async. `done` runs on an internal worker thread, exactly once. A 401/407 is a
|
|
// SUCCESS result with requires_auth = true (the GUI collects credentials), not an
|
|
// error; transport failures and hard HTTP errors (404/410/5xx) are errors.
|
|
void probe(ProbeRequest req, std::function<void(Result<ProbeResult>)> done);
|
|
|
|
private:
|
|
struct Impl;
|
|
std::unique_ptr<Impl> impl_;
|
|
};
|
|
|
|
} // namespace vdm::net
|
|
|
|
#endif // VDM_NET_PROBE_HPP
|