mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 12:44:43 +00:00
Move Net staff to clean network module
This commit is contained in:
@@ -1053,45 +1053,7 @@ pub const Request = struct {
|
||||
};
|
||||
};
|
||||
|
||||
pub const AuthChallenge = struct {
|
||||
status: u16,
|
||||
source: enum { server, proxy },
|
||||
scheme: enum { basic, digest },
|
||||
realm: []const u8,
|
||||
|
||||
pub fn parse(status: u16, header: []const u8) !AuthChallenge {
|
||||
var ac: AuthChallenge = .{
|
||||
.status = status,
|
||||
.source = undefined,
|
||||
.realm = "TODO", // TODO parser and set realm
|
||||
.scheme = undefined,
|
||||
};
|
||||
|
||||
const sep = std.mem.indexOfPos(u8, header, 0, ": ") orelse return error.InvalidHeader;
|
||||
const hname = header[0..sep];
|
||||
const hvalue = header[sep + 2 ..];
|
||||
|
||||
if (std.ascii.eqlIgnoreCase("WWW-Authenticate", hname)) {
|
||||
ac.source = .server;
|
||||
} else if (std.ascii.eqlIgnoreCase("Proxy-Authenticate", hname)) {
|
||||
ac.source = .proxy;
|
||||
} else {
|
||||
return error.InvalidAuthChallenge;
|
||||
}
|
||||
|
||||
const pos = std.mem.indexOfPos(u8, std.mem.trim(u8, hvalue, std.ascii.whitespace[0..]), 0, " ") orelse hvalue.len;
|
||||
const _scheme = hvalue[0..pos];
|
||||
if (std.ascii.eqlIgnoreCase(_scheme, "basic")) {
|
||||
ac.scheme = .basic;
|
||||
} else if (std.ascii.eqlIgnoreCase(_scheme, "digest")) {
|
||||
ac.scheme = .digest;
|
||||
} else {
|
||||
return error.UnknownAuthChallengeScheme;
|
||||
}
|
||||
|
||||
return ac;
|
||||
}
|
||||
};
|
||||
pub const AuthChallenge = @import("../Net.zig").AuthChallenge;
|
||||
|
||||
pub const Transfer = struct {
|
||||
arena: ArenaAllocator,
|
||||
@@ -1664,95 +1626,10 @@ pub const Transfer = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const ResponseHeader = struct {
|
||||
const MAX_CONTENT_TYPE_LEN = 64;
|
||||
pub const ResponseHeader = @import("../Net.zig").ResponseHeader;
|
||||
|
||||
status: u16,
|
||||
url: [*c]const u8,
|
||||
redirect_count: u32,
|
||||
_content_type_len: usize = 0,
|
||||
_content_type: [MAX_CONTENT_TYPE_LEN]u8 = undefined,
|
||||
// this is normally an empty list, but if the response is being injected
|
||||
// than it'll be populated. It isn't meant to be used directly, but should
|
||||
// be used through the transfer.responseHeaderIterator() which abstracts
|
||||
// whether the headers are from a live curl easy handle, or injected.
|
||||
_injected_headers: []const Http.Header = &.{},
|
||||
const HeaderIterator = Net.HeaderIterator;
|
||||
|
||||
pub fn contentType(self: *ResponseHeader) ?[]u8 {
|
||||
if (self._content_type_len == 0) {
|
||||
return null;
|
||||
}
|
||||
return self._content_type[0..self._content_type_len];
|
||||
}
|
||||
};
|
||||
|
||||
// In normal cases, the header iterator comes from the curl linked list.
|
||||
// But it's also possible to inject a response, via `transfer.fulfill`. In that
|
||||
// case, the resposne headers are a list, []const Http.Header.
|
||||
// This union, is an iterator that exposes the same API for either case.
|
||||
const HeaderIterator = union(enum) {
|
||||
curl: CurlHeaderIterator,
|
||||
list: ListHeaderIterator,
|
||||
|
||||
pub fn next(self: *HeaderIterator) ?Http.Header {
|
||||
switch (self.*) {
|
||||
inline else => |*it| return it.next(),
|
||||
}
|
||||
}
|
||||
|
||||
const CurlHeaderIterator = struct {
|
||||
easy: *c.CURL,
|
||||
prev: ?*c.curl_header = null,
|
||||
|
||||
pub fn next(self: *CurlHeaderIterator) ?Http.Header {
|
||||
const h = c.curl_easy_nextheader(self.easy, c.CURLH_HEADER, -1, self.prev) orelse return null;
|
||||
self.prev = h;
|
||||
|
||||
const header = h.*;
|
||||
return .{
|
||||
.name = std.mem.span(header.name),
|
||||
.value = std.mem.span(header.value),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const ListHeaderIterator = struct {
|
||||
index: usize = 0,
|
||||
list: []const Http.Header,
|
||||
|
||||
pub fn next(self: *ListHeaderIterator) ?Http.Header {
|
||||
const index = self.index;
|
||||
if (index == self.list.len) {
|
||||
return null;
|
||||
}
|
||||
self.index = index + 1;
|
||||
return self.list[index];
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const CurlHeaderValue = struct {
|
||||
value: []const u8,
|
||||
amount: usize,
|
||||
};
|
||||
|
||||
fn getResponseHeader(easy: *c.CURL, name: [:0]const u8, index: usize) ?CurlHeaderValue {
|
||||
var hdr: [*c]c.curl_header = null;
|
||||
const result = c.curl_easy_header(easy, name, index, c.CURLH_HEADER, -1, &hdr);
|
||||
if (result == c.CURLE_OK) {
|
||||
return .{
|
||||
.amount = hdr.*.amount,
|
||||
.value = std.mem.span(hdr.*.value),
|
||||
};
|
||||
}
|
||||
|
||||
if (result == c.CURLE_FAILED_INIT) {
|
||||
// seems to be what it returns if the header isn't found
|
||||
return null;
|
||||
}
|
||||
log.err(.http, "get response header", .{
|
||||
.name = name,
|
||||
.err = @import("errors.zig").fromCode(result),
|
||||
});
|
||||
return null;
|
||||
}
|
||||
const Net = @import("../Net.zig");
|
||||
const CurlHeaderValue = Net.CurlHeaderValue;
|
||||
const getResponseHeader = Net.getResponseHeader;
|
||||
|
||||
@@ -17,19 +17,24 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const Net = @import("../Net.zig");
|
||||
|
||||
pub const c = @cImport({
|
||||
@cInclude("curl/curl.h");
|
||||
});
|
||||
pub const c = Net.c;
|
||||
|
||||
pub const ENABLE_DEBUG = false;
|
||||
pub const ENABLE_DEBUG = Net.ENABLE_DEBUG;
|
||||
pub const Client = @import("Client.zig");
|
||||
pub const Transfer = Client.Transfer;
|
||||
|
||||
const lp = @import("lightpanda");
|
||||
pub const Method = Net.Method;
|
||||
pub const Header = Net.Header;
|
||||
pub const Headers = Net.Headers;
|
||||
|
||||
pub const Connection = Net.Connection;
|
||||
|
||||
pub const errorCheck = Net.errorCheck;
|
||||
pub const errorMCheck = Net.errorMCheck;
|
||||
|
||||
const Config = @import("../Config.zig");
|
||||
const log = @import("../log.zig");
|
||||
const errors = @import("errors.zig");
|
||||
const RobotStore = @import("../browser/Robots.zig").RobotStore;
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
@@ -49,8 +54,8 @@ ca_blob: ?c.curl_blob,
|
||||
robot_store: *RobotStore,
|
||||
|
||||
pub fn init(allocator: Allocator, robot_store: *RobotStore, config: *const Config) !Http {
|
||||
try errorCheck(c.curl_global_init(c.CURL_GLOBAL_SSL));
|
||||
errdefer c.curl_global_cleanup();
|
||||
try Net.globalInit();
|
||||
errdefer Net.globalDeinit();
|
||||
|
||||
if (comptime ENABLE_DEBUG) {
|
||||
std.debug.print("curl version: {s}\n\n", .{c.curl_version()});
|
||||
@@ -61,7 +66,7 @@ pub fn init(allocator: Allocator, robot_store: *RobotStore, config: *const Confi
|
||||
|
||||
var ca_blob: ?c.curl_blob = null;
|
||||
if (config.tlsVerifyHost()) {
|
||||
ca_blob = try loadCerts(allocator);
|
||||
ca_blob = try Net.loadCerts(allocator);
|
||||
}
|
||||
|
||||
return .{
|
||||
@@ -78,7 +83,7 @@ pub fn deinit(self: *Http) void {
|
||||
const data: [*]u8 = @ptrCast(ca_blob.data);
|
||||
self.allocator.free(data[0..ca_blob.len]);
|
||||
}
|
||||
c.curl_global_cleanup();
|
||||
Net.globalDeinit();
|
||||
self.arena.deinit();
|
||||
}
|
||||
|
||||
@@ -89,348 +94,3 @@ pub fn createClient(self: *Http, allocator: Allocator) !*Client {
|
||||
pub fn newConnection(self: *Http) !Connection {
|
||||
return Connection.init(self.ca_blob, self.config);
|
||||
}
|
||||
|
||||
pub const Connection = struct {
|
||||
easy: *c.CURL,
|
||||
http_headers: *const Config.HttpHeaders,
|
||||
|
||||
pub fn init(
|
||||
ca_blob_: ?c.curl_blob,
|
||||
config: *const Config,
|
||||
) !Connection {
|
||||
const easy = c.curl_easy_init() orelse return error.FailedToInitializeEasy;
|
||||
errdefer _ = c.curl_easy_cleanup(easy);
|
||||
|
||||
// timeouts
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_TIMEOUT_MS, @as(c_long, @intCast(config.httpTimeout()))));
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_CONNECTTIMEOUT_MS, @as(c_long, @intCast(config.httpConnectTimeout()))));
|
||||
|
||||
// redirect behavior
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_MAXREDIRS, @as(c_long, @intCast(config.httpMaxRedirects()))));
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_FOLLOWLOCATION, @as(c_long, 2)));
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_REDIR_PROTOCOLS_STR, "HTTP,HTTPS")); // remove FTP and FTPS from the default
|
||||
|
||||
// proxy
|
||||
const http_proxy = config.httpProxy();
|
||||
if (http_proxy) |proxy| {
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXY, proxy.ptr));
|
||||
}
|
||||
|
||||
// tls
|
||||
if (ca_blob_) |ca_blob| {
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_CAINFO_BLOB, ca_blob));
|
||||
if (http_proxy != null) {
|
||||
// Note, this can be difference for the proxy and for the main
|
||||
// request. Might be something worth exposting as command
|
||||
// line arguments at some point.
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXY_CAINFO_BLOB, ca_blob));
|
||||
}
|
||||
} else {
|
||||
lp.assert(config.tlsVerifyHost() == false, "Http.init tls_verify_host", .{});
|
||||
|
||||
// Verify peer checks that the cert is signed by a CA, verify host makes sure the
|
||||
// cert contains the server name.
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_SSL_VERIFYHOST, @as(c_long, 0)));
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_SSL_VERIFYPEER, @as(c_long, 0)));
|
||||
|
||||
if (http_proxy != null) {
|
||||
// Note, this can be difference for the proxy and for the main
|
||||
// request. Might be something worth exposting as command
|
||||
// line arguments at some point.
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXY_SSL_VERIFYHOST, @as(c_long, 0)));
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXY_SSL_VERIFYPEER, @as(c_long, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
// compression, don't remove this. CloudFront will send gzip content
|
||||
// even if we don't support it, and then it won't be decompressed.
|
||||
// empty string means: use whatever's available
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_ACCEPT_ENCODING, ""));
|
||||
|
||||
// debug
|
||||
if (comptime Http.ENABLE_DEBUG) {
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_VERBOSE, @as(c_long, 1)));
|
||||
|
||||
// Sometimes the default debug output hides some useful data. You can
|
||||
// uncomment the following line (BUT KEEP THE LIVE ABOVE AS-IS), to
|
||||
// get more control over the data (specifically, the `CURLINFO_TEXT`
|
||||
// can include useful data).
|
||||
|
||||
// try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_DEBUGFUNCTION, debugCallback));
|
||||
}
|
||||
|
||||
return .{
|
||||
.easy = easy,
|
||||
.http_headers = &config.http_headers,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Connection) void {
|
||||
c.curl_easy_cleanup(self.easy);
|
||||
}
|
||||
|
||||
pub fn setURL(self: *const Connection, url: [:0]const u8) !void {
|
||||
try errorCheck(c.curl_easy_setopt(self.easy, c.CURLOPT_URL, url.ptr));
|
||||
}
|
||||
|
||||
// a libcurl request has 2 methods. The first is the method that
|
||||
// controls how libcurl behaves. This specifically influences how redirects
|
||||
// are handled. For example, if you do a POST and get a 301, libcurl will
|
||||
// change that to a GET. But if you do a POST and get a 308, libcurl will
|
||||
// keep the POST (and re-send the body).
|
||||
// The second method is the actual string that's included in the request
|
||||
// headers.
|
||||
// These two methods can be different - you can tell curl to behave as though
|
||||
// you made a GET, but include "POST" in the request header.
|
||||
//
|
||||
// Here, we're only concerned about the 2nd method. If we want, we'll set
|
||||
// the first one based on whether or not we have a body.
|
||||
//
|
||||
// It's important that, for each use of this connection, we set the 2nd
|
||||
// method. Else, if we make a HEAD request and re-use the connection, but
|
||||
// DON'T reset this, it'll keep making HEAD requests.
|
||||
// (I don't know if it's as important to reset the 1st method, or if libcurl
|
||||
// can infer that based on the presence of the body, but we also reset it
|
||||
// to be safe);
|
||||
pub fn setMethod(self: *const Connection, method: Method) !void {
|
||||
const easy = self.easy;
|
||||
const m: [:0]const u8 = switch (method) {
|
||||
.GET => "GET",
|
||||
.POST => "POST",
|
||||
.PUT => "PUT",
|
||||
.DELETE => "DELETE",
|
||||
.HEAD => "HEAD",
|
||||
.OPTIONS => "OPTIONS",
|
||||
.PATCH => "PATCH",
|
||||
.PROPFIND => "PROPFIND",
|
||||
};
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_CUSTOMREQUEST, m.ptr));
|
||||
}
|
||||
|
||||
pub fn setBody(self: *const Connection, body: []const u8) !void {
|
||||
const easy = self.easy;
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_HTTPPOST, @as(c_long, 1)));
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_POSTFIELDSIZE, @as(c_long, @intCast(body.len))));
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_COPYPOSTFIELDS, body.ptr));
|
||||
}
|
||||
|
||||
// These are headers that may not be send to the users for inteception.
|
||||
pub fn secretHeaders(self: *const Connection, headers: *Headers) !void {
|
||||
if (self.http_headers.proxy_bearer_header) |hdr| {
|
||||
try headers.add(hdr);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request(self: *const Connection) !u16 {
|
||||
const easy = self.easy;
|
||||
|
||||
var header_list = try Headers.init(self.http_headers.user_agent_header);
|
||||
defer header_list.deinit();
|
||||
try self.secretHeaders(&header_list);
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_HTTPHEADER, header_list.headers));
|
||||
|
||||
// Add cookies.
|
||||
if (header_list.cookies) |cookies| {
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_COOKIE, cookies));
|
||||
}
|
||||
|
||||
try errorCheck(c.curl_easy_perform(easy));
|
||||
var http_code: c_long = undefined;
|
||||
try errorCheck(c.curl_easy_getinfo(easy, c.CURLINFO_RESPONSE_CODE, &http_code));
|
||||
if (http_code < 0 or http_code > std.math.maxInt(u16)) {
|
||||
return 0;
|
||||
}
|
||||
return @intCast(http_code);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Header = struct {
|
||||
name: []const u8,
|
||||
value: []const u8,
|
||||
};
|
||||
|
||||
pub const Headers = struct {
|
||||
headers: ?*c.curl_slist,
|
||||
cookies: ?[*c]const u8,
|
||||
|
||||
pub fn init(user_agent: [:0]const u8) !Headers {
|
||||
const header_list = c.curl_slist_append(null, user_agent);
|
||||
if (header_list == null) {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
return .{ .headers = header_list, .cookies = null };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *const Headers) void {
|
||||
if (self.headers) |hdr| {
|
||||
c.curl_slist_free_all(hdr);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(self: *Headers, header: [*c]const u8) !void {
|
||||
// Copies the value
|
||||
const updated_headers = c.curl_slist_append(self.headers, header);
|
||||
if (updated_headers == null) return error.OutOfMemory;
|
||||
self.headers = updated_headers;
|
||||
}
|
||||
|
||||
pub fn parseHeader(header_str: []const u8) ?Header {
|
||||
const colon_pos = std.mem.indexOfScalar(u8, header_str, ':') orelse return null;
|
||||
|
||||
const name = std.mem.trim(u8, header_str[0..colon_pos], " \t");
|
||||
const value = std.mem.trim(u8, header_str[colon_pos + 1 ..], " \t");
|
||||
|
||||
return .{ .name = name, .value = value };
|
||||
}
|
||||
|
||||
pub fn iterator(self: *Headers) Iterator {
|
||||
return .{
|
||||
.header = self.headers,
|
||||
.cookies = self.cookies,
|
||||
};
|
||||
}
|
||||
|
||||
const Iterator = struct {
|
||||
header: [*c]c.curl_slist,
|
||||
cookies: ?[*c]const u8,
|
||||
|
||||
pub fn next(self: *Iterator) ?Header {
|
||||
const h = self.header orelse {
|
||||
const cookies = self.cookies orelse return null;
|
||||
self.cookies = null;
|
||||
return .{ .name = "Cookie", .value = std.mem.span(@as([*:0]const u8, cookies)) };
|
||||
};
|
||||
|
||||
self.header = h.*.next;
|
||||
return parseHeader(std.mem.span(@as([*:0]const u8, @ptrCast(h.*.data))));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
pub fn errorCheck(code: c.CURLcode) errors.Error!void {
|
||||
if (code == c.CURLE_OK) {
|
||||
return;
|
||||
}
|
||||
return errors.fromCode(code);
|
||||
}
|
||||
|
||||
pub fn errorMCheck(code: c.CURLMcode) errors.Multi!void {
|
||||
if (code == c.CURLM_OK) {
|
||||
return;
|
||||
}
|
||||
if (code == c.CURLM_CALL_MULTI_PERFORM) {
|
||||
// should we can client.perform() here?
|
||||
// or just wait until the next time we naturally call it?
|
||||
return;
|
||||
}
|
||||
return errors.fromMCode(code);
|
||||
}
|
||||
|
||||
pub const Method = enum(u8) {
|
||||
GET = 0,
|
||||
PUT = 1,
|
||||
POST = 2,
|
||||
DELETE = 3,
|
||||
HEAD = 4,
|
||||
OPTIONS = 5,
|
||||
PATCH = 6,
|
||||
PROPFIND = 7,
|
||||
};
|
||||
|
||||
// TODO: on BSD / Linux, we could just read the PEM file directly.
|
||||
// This whole rescan + decode is really just needed for MacOS. On Linux
|
||||
// bundle.rescan does find the .pem file(s) which could be in a few different
|
||||
// places, so it's still useful, just not efficient.
|
||||
fn loadCerts(allocator: Allocator) !c.curl_blob {
|
||||
var bundle: std.crypto.Certificate.Bundle = .{};
|
||||
try bundle.rescan(allocator);
|
||||
defer bundle.deinit(allocator);
|
||||
|
||||
const bytes = bundle.bytes.items;
|
||||
if (bytes.len == 0) {
|
||||
log.warn(.app, "No system certificates", .{});
|
||||
return .{
|
||||
.len = 0,
|
||||
.flags = 0,
|
||||
.data = bytes.ptr,
|
||||
};
|
||||
}
|
||||
|
||||
const encoder = std.base64.standard.Encoder;
|
||||
var arr: std.ArrayList(u8) = .empty;
|
||||
|
||||
const encoded_size = encoder.calcSize(bytes.len);
|
||||
const buffer_size = encoded_size +
|
||||
(bundle.map.count() * 75) + // start / end per certificate + extra, just in case
|
||||
(encoded_size / 64) // newline per 64 characters
|
||||
;
|
||||
try arr.ensureTotalCapacity(allocator, buffer_size);
|
||||
errdefer arr.deinit(allocator);
|
||||
var writer = arr.writer(allocator);
|
||||
|
||||
var it = bundle.map.valueIterator();
|
||||
while (it.next()) |index| {
|
||||
const cert = try std.crypto.Certificate.der.Element.parse(bytes, index.*);
|
||||
|
||||
try writer.writeAll("-----BEGIN CERTIFICATE-----\n");
|
||||
var line_writer = LineWriter{ .inner = writer };
|
||||
try encoder.encodeWriter(&line_writer, bytes[index.*..cert.slice.end]);
|
||||
try writer.writeAll("\n-----END CERTIFICATE-----\n");
|
||||
}
|
||||
|
||||
// Final encoding should not be larger than our initial size estimate
|
||||
lp.assert(buffer_size > arr.items.len, "Http loadCerts", .{ .estimate = buffer_size, .len = arr.items.len });
|
||||
|
||||
// Allocate exactly the size needed and copy the data
|
||||
const result = try allocator.dupe(u8, arr.items);
|
||||
// Free the original oversized allocation
|
||||
arr.deinit(allocator);
|
||||
|
||||
return .{
|
||||
.len = result.len,
|
||||
.data = result.ptr,
|
||||
.flags = 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Wraps lines @ 64 columns. A PEM is basically a base64 encoded DER (which is
|
||||
// what Zig has), with lines wrapped at 64 characters and with a basic header
|
||||
// and footer
|
||||
const LineWriter = struct {
|
||||
col: usize = 0,
|
||||
inner: std.ArrayList(u8).Writer,
|
||||
|
||||
pub fn writeAll(self: *LineWriter, data: []const u8) !void {
|
||||
var writer = self.inner;
|
||||
|
||||
var col = self.col;
|
||||
const len = 64 - col;
|
||||
|
||||
var remain = data;
|
||||
if (remain.len > len) {
|
||||
col = 0;
|
||||
try writer.writeAll(data[0..len]);
|
||||
try writer.writeByte('\n');
|
||||
remain = data[len..];
|
||||
}
|
||||
|
||||
while (remain.len > 64) {
|
||||
try writer.writeAll(remain[0..64]);
|
||||
try writer.writeByte('\n');
|
||||
remain = data[len..];
|
||||
}
|
||||
try writer.writeAll(remain);
|
||||
self.col = col + remain.len;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn debugCallback(_: *c.CURL, msg_type: c.curl_infotype, raw: [*c]u8, len: usize, _: *anyopaque) callconv(.c) void {
|
||||
const data = raw[0..len];
|
||||
switch (msg_type) {
|
||||
c.CURLINFO_TEXT => std.debug.print("libcurl [text]: {s}\n", .{data}),
|
||||
c.CURLINFO_HEADER_OUT => std.debug.print("libcurl [req-h]: {s}\n", .{data}),
|
||||
c.CURLINFO_HEADER_IN => std.debug.print("libcurl [res-h]: {s}\n", .{data}),
|
||||
// c.CURLINFO_DATA_IN => std.debug.print("libcurl [res-b]: {s}\n", .{data}),
|
||||
else => std.debug.print("libcurl ?? {d}\n", .{msg_type}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const c = @import("Http.zig").c;
|
||||
|
||||
const IS_DEBUG = @import("builtin").mode == .Debug;
|
||||
|
||||
pub const Error = error{
|
||||
UnsupportedProtocol,
|
||||
FailedInit,
|
||||
UrlMalformat,
|
||||
NotBuiltIn,
|
||||
CouldntResolveProxy,
|
||||
CouldntResolveHost,
|
||||
CouldntConnect,
|
||||
WeirdServerReply,
|
||||
RemoteAccessDenied,
|
||||
FtpAcceptFailed,
|
||||
FtpWeirdPassReply,
|
||||
FtpAcceptTimeout,
|
||||
FtpWeirdPasvReply,
|
||||
FtpWeird227Format,
|
||||
FtpCantGetHost,
|
||||
Http2,
|
||||
FtpCouldntSetType,
|
||||
PartialFile,
|
||||
FtpCouldntRetrFile,
|
||||
QuoteError,
|
||||
HttpReturnedError,
|
||||
WriteError,
|
||||
UploadFailed,
|
||||
ReadError,
|
||||
OutOfMemory,
|
||||
OperationTimedout,
|
||||
FtpPortFailed,
|
||||
FtpCouldntUseRest,
|
||||
RangeError,
|
||||
SslConnectError,
|
||||
BadDownloadResume,
|
||||
FileCouldntReadFile,
|
||||
LdapCannotBind,
|
||||
LdapSearchFailed,
|
||||
AbortedByCallback,
|
||||
BadFunctionArgument,
|
||||
InterfaceFailed,
|
||||
TooManyRedirects,
|
||||
UnknownOption,
|
||||
SetoptOptionSyntax,
|
||||
GotNothing,
|
||||
SslEngineNotfound,
|
||||
SslEngineSetfailed,
|
||||
SendError,
|
||||
RecvError,
|
||||
SslCertproblem,
|
||||
SslCipher,
|
||||
PeerFailedVerification,
|
||||
BadContentEncoding,
|
||||
FilesizeExceeded,
|
||||
UseSslFailed,
|
||||
SendFailRewind,
|
||||
SslEngineInitfailed,
|
||||
LoginDenied,
|
||||
TftpNotfound,
|
||||
TftpPerm,
|
||||
RemoteDiskFull,
|
||||
TftpIllegal,
|
||||
TftpUnknownid,
|
||||
RemoteFileExists,
|
||||
TftpNosuchuser,
|
||||
SslCacertBadfile,
|
||||
RemoteFileNotFound,
|
||||
Ssh,
|
||||
SslShutdownFailed,
|
||||
Again,
|
||||
SslCrlBadfile,
|
||||
SslIssuerError,
|
||||
FtpPretFailed,
|
||||
RtspCseqError,
|
||||
RtspSessionError,
|
||||
FtpBadFileList,
|
||||
ChunkFailed,
|
||||
NoConnectionAvailable,
|
||||
SslPinnedpubkeynotmatch,
|
||||
SslInvalidcertstatus,
|
||||
Http2Stream,
|
||||
RecursiveApiCall,
|
||||
AuthError,
|
||||
Http3,
|
||||
QuicConnectError,
|
||||
Proxy,
|
||||
SslClientcert,
|
||||
UnrecoverablePoll,
|
||||
TooLarge,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
pub fn fromCode(code: c.CURLcode) Error {
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(code != c.CURLE_OK);
|
||||
}
|
||||
|
||||
return switch (code) {
|
||||
c.CURLE_UNSUPPORTED_PROTOCOL => Error.UnsupportedProtocol,
|
||||
c.CURLE_FAILED_INIT => Error.FailedInit,
|
||||
c.CURLE_URL_MALFORMAT => Error.UrlMalformat,
|
||||
c.CURLE_NOT_BUILT_IN => Error.NotBuiltIn,
|
||||
c.CURLE_COULDNT_RESOLVE_PROXY => Error.CouldntResolveProxy,
|
||||
c.CURLE_COULDNT_RESOLVE_HOST => Error.CouldntResolveHost,
|
||||
c.CURLE_COULDNT_CONNECT => Error.CouldntConnect,
|
||||
c.CURLE_WEIRD_SERVER_REPLY => Error.WeirdServerReply,
|
||||
c.CURLE_REMOTE_ACCESS_DENIED => Error.RemoteAccessDenied,
|
||||
c.CURLE_FTP_ACCEPT_FAILED => Error.FtpAcceptFailed,
|
||||
c.CURLE_FTP_WEIRD_PASS_REPLY => Error.FtpWeirdPassReply,
|
||||
c.CURLE_FTP_ACCEPT_TIMEOUT => Error.FtpAcceptTimeout,
|
||||
c.CURLE_FTP_WEIRD_PASV_REPLY => Error.FtpWeirdPasvReply,
|
||||
c.CURLE_FTP_WEIRD_227_FORMAT => Error.FtpWeird227Format,
|
||||
c.CURLE_FTP_CANT_GET_HOST => Error.FtpCantGetHost,
|
||||
c.CURLE_HTTP2 => Error.Http2,
|
||||
c.CURLE_FTP_COULDNT_SET_TYPE => Error.FtpCouldntSetType,
|
||||
c.CURLE_PARTIAL_FILE => Error.PartialFile,
|
||||
c.CURLE_FTP_COULDNT_RETR_FILE => Error.FtpCouldntRetrFile,
|
||||
c.CURLE_QUOTE_ERROR => Error.QuoteError,
|
||||
c.CURLE_HTTP_RETURNED_ERROR => Error.HttpReturnedError,
|
||||
c.CURLE_WRITE_ERROR => Error.WriteError,
|
||||
c.CURLE_UPLOAD_FAILED => Error.UploadFailed,
|
||||
c.CURLE_READ_ERROR => Error.ReadError,
|
||||
c.CURLE_OUT_OF_MEMORY => Error.OutOfMemory,
|
||||
c.CURLE_OPERATION_TIMEDOUT => Error.OperationTimedout,
|
||||
c.CURLE_FTP_PORT_FAILED => Error.FtpPortFailed,
|
||||
c.CURLE_FTP_COULDNT_USE_REST => Error.FtpCouldntUseRest,
|
||||
c.CURLE_RANGE_ERROR => Error.RangeError,
|
||||
c.CURLE_SSL_CONNECT_ERROR => Error.SslConnectError,
|
||||
c.CURLE_BAD_DOWNLOAD_RESUME => Error.BadDownloadResume,
|
||||
c.CURLE_FILE_COULDNT_READ_FILE => Error.FileCouldntReadFile,
|
||||
c.CURLE_LDAP_CANNOT_BIND => Error.LdapCannotBind,
|
||||
c.CURLE_LDAP_SEARCH_FAILED => Error.LdapSearchFailed,
|
||||
c.CURLE_ABORTED_BY_CALLBACK => Error.AbortedByCallback,
|
||||
c.CURLE_BAD_FUNCTION_ARGUMENT => Error.BadFunctionArgument,
|
||||
c.CURLE_INTERFACE_FAILED => Error.InterfaceFailed,
|
||||
c.CURLE_TOO_MANY_REDIRECTS => Error.TooManyRedirects,
|
||||
c.CURLE_UNKNOWN_OPTION => Error.UnknownOption,
|
||||
c.CURLE_SETOPT_OPTION_SYNTAX => Error.SetoptOptionSyntax,
|
||||
c.CURLE_GOT_NOTHING => Error.GotNothing,
|
||||
c.CURLE_SSL_ENGINE_NOTFOUND => Error.SslEngineNotfound,
|
||||
c.CURLE_SSL_ENGINE_SETFAILED => Error.SslEngineSetfailed,
|
||||
c.CURLE_SEND_ERROR => Error.SendError,
|
||||
c.CURLE_RECV_ERROR => Error.RecvError,
|
||||
c.CURLE_SSL_CERTPROBLEM => Error.SslCertproblem,
|
||||
c.CURLE_SSL_CIPHER => Error.SslCipher,
|
||||
c.CURLE_PEER_FAILED_VERIFICATION => Error.PeerFailedVerification,
|
||||
c.CURLE_BAD_CONTENT_ENCODING => Error.BadContentEncoding,
|
||||
c.CURLE_FILESIZE_EXCEEDED => Error.FilesizeExceeded,
|
||||
c.CURLE_USE_SSL_FAILED => Error.UseSslFailed,
|
||||
c.CURLE_SEND_FAIL_REWIND => Error.SendFailRewind,
|
||||
c.CURLE_SSL_ENGINE_INITFAILED => Error.SslEngineInitfailed,
|
||||
c.CURLE_LOGIN_DENIED => Error.LoginDenied,
|
||||
c.CURLE_TFTP_NOTFOUND => Error.TftpNotfound,
|
||||
c.CURLE_TFTP_PERM => Error.TftpPerm,
|
||||
c.CURLE_REMOTE_DISK_FULL => Error.RemoteDiskFull,
|
||||
c.CURLE_TFTP_ILLEGAL => Error.TftpIllegal,
|
||||
c.CURLE_TFTP_UNKNOWNID => Error.TftpUnknownid,
|
||||
c.CURLE_REMOTE_FILE_EXISTS => Error.RemoteFileExists,
|
||||
c.CURLE_TFTP_NOSUCHUSER => Error.TftpNosuchuser,
|
||||
c.CURLE_SSL_CACERT_BADFILE => Error.SslCacertBadfile,
|
||||
c.CURLE_REMOTE_FILE_NOT_FOUND => Error.RemoteFileNotFound,
|
||||
c.CURLE_SSH => Error.Ssh,
|
||||
c.CURLE_SSL_SHUTDOWN_FAILED => Error.SslShutdownFailed,
|
||||
c.CURLE_AGAIN => Error.Again,
|
||||
c.CURLE_SSL_CRL_BADFILE => Error.SslCrlBadfile,
|
||||
c.CURLE_SSL_ISSUER_ERROR => Error.SslIssuerError,
|
||||
c.CURLE_FTP_PRET_FAILED => Error.FtpPretFailed,
|
||||
c.CURLE_RTSP_CSEQ_ERROR => Error.RtspCseqError,
|
||||
c.CURLE_RTSP_SESSION_ERROR => Error.RtspSessionError,
|
||||
c.CURLE_FTP_BAD_FILE_LIST => Error.FtpBadFileList,
|
||||
c.CURLE_CHUNK_FAILED => Error.ChunkFailed,
|
||||
c.CURLE_NO_CONNECTION_AVAILABLE => Error.NoConnectionAvailable,
|
||||
c.CURLE_SSL_PINNEDPUBKEYNOTMATCH => Error.SslPinnedpubkeynotmatch,
|
||||
c.CURLE_SSL_INVALIDCERTSTATUS => Error.SslInvalidcertstatus,
|
||||
c.CURLE_HTTP2_STREAM => Error.Http2Stream,
|
||||
c.CURLE_RECURSIVE_API_CALL => Error.RecursiveApiCall,
|
||||
c.CURLE_AUTH_ERROR => Error.AuthError,
|
||||
c.CURLE_HTTP3 => Error.Http3,
|
||||
c.CURLE_QUIC_CONNECT_ERROR => Error.QuicConnectError,
|
||||
c.CURLE_PROXY => Error.Proxy,
|
||||
c.CURLE_SSL_CLIENTCERT => Error.SslClientcert,
|
||||
c.CURLE_UNRECOVERABLE_POLL => Error.UnrecoverablePoll,
|
||||
c.CURLE_TOO_LARGE => Error.TooLarge,
|
||||
else => Error.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
pub const Multi = error{
|
||||
BadHandle,
|
||||
BadEasyHandle,
|
||||
OutOfMemory,
|
||||
InternalError,
|
||||
BadSocket,
|
||||
UnknownOption,
|
||||
AddedAlready,
|
||||
RecursiveApiCall,
|
||||
WakeupFailure,
|
||||
BadFunctionArgument,
|
||||
AbortedByCallback,
|
||||
UnrecoverablePoll,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
pub fn fromMCode(code: c.CURLMcode) Multi {
|
||||
if (comptime IS_DEBUG) {
|
||||
std.debug.assert(code != c.CURLM_OK);
|
||||
}
|
||||
|
||||
return switch (code) {
|
||||
c.CURLM_BAD_HANDLE => Multi.BadHandle,
|
||||
c.CURLM_BAD_EASY_HANDLE => Multi.BadEasyHandle,
|
||||
c.CURLM_OUT_OF_MEMORY => Multi.OutOfMemory,
|
||||
c.CURLM_INTERNAL_ERROR => Multi.InternalError,
|
||||
c.CURLM_BAD_SOCKET => Multi.BadSocket,
|
||||
c.CURLM_UNKNOWN_OPTION => Multi.UnknownOption,
|
||||
c.CURLM_ADDED_ALREADY => Multi.AddedAlready,
|
||||
c.CURLM_RECURSIVE_API_CALL => Multi.RecursiveApiCall,
|
||||
c.CURLM_WAKEUP_FAILURE => Multi.WakeupFailure,
|
||||
c.CURLM_BAD_FUNCTION_ARGUMENT => Multi.BadFunctionArgument,
|
||||
c.CURLM_ABORTED_BY_CALLBACK => Multi.AbortedByCallback,
|
||||
c.CURLM_UNRECOVERABLE_POLL => Multi.UnrecoverablePoll,
|
||||
else => Multi.Unknown,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user