mirror of
https://github.com/zigzap/zap.git
synced 2025-10-20 15:14:08 +00:00
Refactored request, auth, endpoint: - zap.Request : refactored into its own file, along with supporting types and functions (e.g. http params related) - added setContentTypeFromFilename thx @hauleth. - zap.Auth : zap.Auth.Basic, zap.Auth.BearerSingle, ... - zap.Endpoint : zap.Endpoint, zap.Endpoint.Authenticating
This commit is contained in:
parent
7141318caf
commit
61fbbe8b08
12 changed files with 1157 additions and 1129 deletions
|
@ -4,23 +4,23 @@ Zap supports both Basic and Bearer authentication which are based on HTTP
|
||||||
headers.
|
headers.
|
||||||
|
|
||||||
For a cookie-based ("session token", not to mistake for "session cookie")
|
For a cookie-based ("session token", not to mistake for "session cookie")
|
||||||
authentication, see the [UserPassSessionAuth](../src/http_auth.zig#L319) and its
|
authentication, see the [UserPassSession](../src/http_auth.zig#L319) and its
|
||||||
[example](../examples/userpass_session_auth/).
|
[example](../examples/userpass_session_auth/).
|
||||||
|
|
||||||
For convenience, Authenticator types exist that can authenticate requests.
|
For convenience, Authenticator types exist that can authenticate requests.
|
||||||
|
|
||||||
Zap also provides an `AuthenticatingEndpoint` endpoint-wrapper. Have a look at the [example](../examples/endpoint_auth) and the [tests](../src/tests/test_auth.zig).
|
Zap also provides an `Endpoint.Authenticating` endpoint-wrapper. Have a look at the [example](../examples/endpoint_auth) and the [tests](../src/tests/test_auth.zig).
|
||||||
|
|
||||||
The following describes the Authenticator types. All of them provide the
|
The following describes the Authenticator types. All of them provide the
|
||||||
`authenticateRequest()` function, which takes a `zap.Request` and returns
|
`authenticateRequest()` function, which takes a `zap.Request` and returns
|
||||||
a bool value whether it could be authenticated or not.
|
a bool value whether it could be authenticated or not.
|
||||||
|
|
||||||
Further down, we show how to use the Authenticators, and also the
|
Further down, we show how to use the Authenticators, and also the
|
||||||
`AuthenticatingEndpoint`.
|
`Endpoint.Authenticating`.
|
||||||
|
|
||||||
## Basic Authentication
|
## Basic Authentication
|
||||||
|
|
||||||
The `zap.BasicAuth` Authenticator accepts 2 comptime values:
|
The `zap.Auth.Basic` Authenticator accepts 2 comptime values:
|
||||||
|
|
||||||
- `Lookup`: either a map to look up passwords for users or a set to lookup
|
- `Lookup`: either a map to look up passwords for users or a set to lookup
|
||||||
base64 encoded tokens (user:pass -> base64-encode = token)
|
base64 encoded tokens (user:pass -> base64-encode = token)
|
||||||
|
@ -35,11 +35,11 @@ support `contains([]const u8)`.
|
||||||
|
|
||||||
## Bearer Authentication
|
## Bearer Authentication
|
||||||
|
|
||||||
The `zap.BearerAuthSingle` Authenticator is a convenience-authenticator that
|
The `zap.Auth.BearerSingle` Authenticator is a convenience-authenticator that
|
||||||
takes a single auth token. If all you need is to protect your prototype with a
|
takes a single auth token. If all you need is to protect your prototype with a
|
||||||
token, this is the one you want to use.
|
token, this is the one you want to use.
|
||||||
|
|
||||||
`zap.BearerAuthMulti` accepts a map (`Lookup`) that needs to support
|
`zap.BearerMulti` accepts a map (`Lookup`) that needs to support
|
||||||
`contains([]const u8)`.
|
`contains([]const u8)`.
|
||||||
|
|
||||||
## Request Authentication
|
## Request Authentication
|
||||||
|
@ -56,7 +56,7 @@ const zap = @import("zap");
|
||||||
const allocator = std.heap.page_allocator;
|
const allocator = std.heap.page_allocator;
|
||||||
const token = "hello, world";
|
const token = "hello, world";
|
||||||
|
|
||||||
var auth = try zap.BearerAuthSingle.init(allocator, token, null);
|
var auth = try zap.Auth.BearerSingle.init(allocator, token, null);
|
||||||
defer auth.deinit();
|
defer auth.deinit();
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ defer set.deinit();
|
||||||
// insert auth tokens
|
// insert auth tokens
|
||||||
try set.put(token, {});
|
try set.put(token, {});
|
||||||
|
|
||||||
var auth = try zap.BearerAuthMulti(Set).init(allocator, &set, null);
|
var auth = try zap.Auth.BearerMulti(Set).init(allocator, &set, null);
|
||||||
defer auth.deinit();
|
defer auth.deinit();
|
||||||
|
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ const pass = "opensesame";
|
||||||
try map.put(user, pass);
|
try map.put(user, pass);
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = zap.BasicAuth(Map, .UserPass);
|
const Authenticator = zap.Auth.Basic(Map, .UserPass);
|
||||||
var auth = try Authenticator.init(a, &map, null);
|
var auth = try Authenticator.init(a, &map, null);
|
||||||
defer auth.deinit();
|
defer auth.deinit();
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ defer set.deinit();
|
||||||
try set.put(token, {});
|
try set.put(token, {});
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = zap.BasicAuth(Set, .Token68);
|
const Authenticator = zap.Auth.Basic(Set, .Token68);
|
||||||
var auth = try Authenticator.init(allocator, &set, null);
|
var auth = try Authenticator.init(allocator, &set, null);
|
||||||
defer auth.deinit();
|
defer auth.deinit();
|
||||||
|
|
||||||
|
@ -182,15 +182,15 @@ fn on_request(r: zap.Request) void {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## AuthenticatingEndpoint
|
## Endpoint.Authenticating
|
||||||
|
|
||||||
Here, we only show using one of the Authenticator types. See the tests for more
|
Here, we only show using one of the Authenticator types. See the tests for more
|
||||||
examples.
|
examples.
|
||||||
|
|
||||||
The `AuthenticatingEndpoint` honors `.unauthorized` in the endpoint settings, where you can pass in a callback to deal with unauthorized requests. If you leave it to `null`, the endpoint will automatically reply with a `401 - Unauthorized` response.
|
The `Endpoint.Authenticating` honors `.unauthorized` in the endpoint settings, where you can pass in a callback to deal with unauthorized requests. If you leave it to `null`, the endpoint will automatically reply with a `401 - Unauthorized` response.
|
||||||
|
|
||||||
The example below should make clear how to wrap an endpoint into an
|
The example below should make clear how to wrap an endpoint into an
|
||||||
`AuthenticatingEndpoint`:
|
`Endpoint.Authenticating`:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
@ -240,12 +240,12 @@ pub fn main() !void {
|
||||||
});
|
});
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = zap.BearerAuthSingle;
|
const Authenticator = zap.Auth.BearerSingle;
|
||||||
var authenticator = try Authenticator.init(a, token, null);
|
var authenticator = try Authenticator.init(a, token, null);
|
||||||
defer authenticator.deinit();
|
defer authenticator.deinit();
|
||||||
|
|
||||||
// create authenticating endpoint
|
// create authenticating endpoint
|
||||||
const BearerAuthEndpoint = zap.AuthenticatingEndpoint(Authenticator);
|
const BearerAuthEndpoint = zap.Endpoint.Authenticating(Authenticator);
|
||||||
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
||||||
|
|
||||||
try listener.register(auth_ep.endpoint());
|
try listener.register(auth_ep.endpoint());
|
||||||
|
|
|
@ -31,7 +31,7 @@ const Handler = struct {
|
||||||
std.log.info("Param `{s}` in owned list is {any}\n", .{ kv.key.str, v });
|
std.log.info("Param `{s}` in owned list is {any}\n", .{ kv.key.str, v });
|
||||||
switch (v) {
|
switch (v) {
|
||||||
// single-file upload
|
// single-file upload
|
||||||
zap.HttpParam.Hash_Binfile => |*file| {
|
zap.Request.HttpParam.Hash_Binfile => |*file| {
|
||||||
const filename = file.filename orelse "(no filename)";
|
const filename = file.filename orelse "(no filename)";
|
||||||
const mimetype = file.mimetype orelse "(no mimetype)";
|
const mimetype = file.mimetype orelse "(no mimetype)";
|
||||||
const data = file.data orelse "";
|
const data = file.data orelse "";
|
||||||
|
@ -41,7 +41,7 @@ const Handler = struct {
|
||||||
std.log.debug(" contents: {any}\n", .{data});
|
std.log.debug(" contents: {any}\n", .{data});
|
||||||
},
|
},
|
||||||
// multi-file upload
|
// multi-file upload
|
||||||
zap.HttpParam.Array_Binfile => |*files| {
|
zap.Request.HttpParam.Array_Binfile => |*files| {
|
||||||
for (files.*.items) |file| {
|
for (files.*.items) |file| {
|
||||||
const filename = file.filename orelse "(no filename)";
|
const filename = file.filename orelse "(no filename)";
|
||||||
const mimetype = file.mimetype orelse "(no mimetype)";
|
const mimetype = file.mimetype orelse "(no mimetype)";
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub fn main() !void {
|
||||||
// we scope everything that can allocate within this block for leak detection
|
// we scope everything that can allocate within this block for leak detection
|
||||||
{
|
{
|
||||||
// setup listener
|
// setup listener
|
||||||
var listener = zap.EndpointListener.init(
|
var listener = zap.Endpoint.Listener.init(
|
||||||
allocator,
|
allocator,
|
||||||
.{
|
.{
|
||||||
.port = 3000,
|
.port = 3000,
|
||||||
|
|
|
@ -25,7 +25,7 @@ fn endpoint_http_unauthorized(e: *zap.Endpoint, r: zap.Request) void {
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
// setup listener
|
// setup listener
|
||||||
var listener = zap.EndpointListener.init(
|
var listener = zap.Endpoint.Listener.init(
|
||||||
a,
|
a,
|
||||||
.{
|
.{
|
||||||
.port = 3000,
|
.port = 3000,
|
||||||
|
@ -45,12 +45,12 @@ pub fn main() !void {
|
||||||
});
|
});
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = zap.BearerAuthSingle;
|
const Authenticator = zap.Auth.BearerSingle;
|
||||||
var authenticator = try Authenticator.init(a, token, null);
|
var authenticator = try Authenticator.init(a, token, null);
|
||||||
defer authenticator.deinit();
|
defer authenticator.deinit();
|
||||||
|
|
||||||
// create authenticating endpoint
|
// create authenticating endpoint
|
||||||
const BearerAuthEndpoint = zap.AuthenticatingEndpoint(Authenticator);
|
const BearerAuthEndpoint = zap.Endpoint.Authenticating(Authenticator);
|
||||||
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
||||||
|
|
||||||
try listener.register(auth_ep.endpoint());
|
try listener.register(auth_ep.endpoint());
|
||||||
|
|
|
@ -5,7 +5,7 @@ const Lookup = std.StringHashMap([]const u8);
|
||||||
const auth_lock_pw_table = false;
|
const auth_lock_pw_table = false;
|
||||||
|
|
||||||
// see the source for more info
|
// see the source for more info
|
||||||
const Authenticator = zap.UserPassSessionAuth(
|
const Authenticator = zap.Auth.UserPassSession(
|
||||||
Lookup,
|
Lookup,
|
||||||
// we may set this to true if we expect our username -> password map
|
// we may set this to true if we expect our username -> password map
|
||||||
// to change. in that case the authenticator must lock the table for
|
// to change. in that case the authenticator must lock the table for
|
||||||
|
|
142
src/endpoint.zig
142
src/endpoint.zig
|
@ -2,16 +2,18 @@ const std = @import("std");
|
||||||
const zap = @import("zap.zig");
|
const zap = @import("zap.zig");
|
||||||
const auth = @import("http_auth.zig");
|
const auth = @import("http_auth.zig");
|
||||||
|
|
||||||
|
const Endpoint = @This();
|
||||||
|
|
||||||
// zap types
|
// zap types
|
||||||
const Request = zap.Request;
|
const Request = zap.Request;
|
||||||
const ListenerSettings = zap.HttpListenerSettings;
|
const ListenerSettings = zap.HttpListenerSettings;
|
||||||
const Listener = zap.HttpListener;
|
const HttpListener = zap.HttpListener;
|
||||||
|
|
||||||
/// Type of the request function callbacks.
|
/// Type of the request function callbacks.
|
||||||
pub const RequestFn = *const fn (self: *Endpoint, r: Request) void;
|
pub const RequestFn = *const fn (self: *Endpoint, r: Request) void;
|
||||||
|
|
||||||
/// Settings to initialize an Endpoint
|
/// Settings to initialize an Endpoint
|
||||||
pub const EndpointSettings = struct {
|
pub const Settings = struct {
|
||||||
/// path / slug of the endpoint
|
/// path / slug of the endpoint
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
/// callback to GET request handler
|
/// callback to GET request handler
|
||||||
|
@ -26,101 +28,57 @@ pub const EndpointSettings = struct {
|
||||||
patch: ?RequestFn = null,
|
patch: ?RequestFn = null,
|
||||||
/// callback to OPTIONS request handler
|
/// callback to OPTIONS request handler
|
||||||
options: ?RequestFn = null,
|
options: ?RequestFn = null,
|
||||||
/// Only applicable to AuthenticatingEndpoint: handler for unauthorized requests
|
/// Only applicable to Authenticating Endpoint: handler for unauthorized requests
|
||||||
unauthorized: ?RequestFn = null,
|
unauthorized: ?RequestFn = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The simple Endpoint struct. Create one and pass in your callbacks. Then,
|
settings: Settings,
|
||||||
/// pass it to a HttpListener's `register()` function to register with the
|
|
||||||
/// listener.
|
|
||||||
///
|
|
||||||
/// **NOTE**: A common endpoint pattern for zap is to create your own struct
|
|
||||||
/// that embeds an Endpoint, provides specific callbacks, and uses
|
|
||||||
/// `@fieldParentPtr` to get a reference to itself.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// A simple endpoint listening on the /stop route that shuts down zap.
|
|
||||||
/// The main thread usually continues at the instructions after the call to zap.start().
|
|
||||||
///
|
|
||||||
/// ```zig
|
|
||||||
/// const StopEndpoint = struct {
|
|
||||||
/// ep: zap.Endpoint = undefined,
|
|
||||||
///
|
|
||||||
/// pub fn init(
|
|
||||||
/// path: []const u8,
|
|
||||||
/// ) StopEndpoint {
|
|
||||||
/// return .{
|
|
||||||
/// .ep = zap.Endpoint.init(.{
|
|
||||||
/// .path = path,
|
|
||||||
/// .get = get,
|
|
||||||
/// }),
|
|
||||||
/// };
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// // access the internal Endpoint
|
|
||||||
/// pub fn endpoint(self: *StopEndpoint) *zap.Endpoint {
|
|
||||||
/// return &self.ep;
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn get(e: *zap.Endpoint, r: zap.Request) void {
|
|
||||||
/// const self: *StopEndpoint = @fieldParentPtr(StopEndpoint, "ep", e);
|
|
||||||
/// _ = self;
|
|
||||||
/// _ = r;
|
|
||||||
/// zap.stop();
|
|
||||||
/// }
|
|
||||||
/// };
|
|
||||||
/// ```
|
|
||||||
pub const Endpoint = struct {
|
|
||||||
settings: EndpointSettings,
|
|
||||||
|
|
||||||
const Self = @This();
|
/// Initialize the endpoint.
|
||||||
|
/// Set only the callbacks you need. Requests of HTTP methods without a
|
||||||
|
/// provided callback will be ignored.
|
||||||
|
pub fn init(s: Settings) Endpoint {
|
||||||
|
return .{
|
||||||
|
.settings = .{
|
||||||
|
.path = s.path,
|
||||||
|
.get = s.get orelse &nop,
|
||||||
|
.post = s.post orelse &nop,
|
||||||
|
.put = s.put orelse &nop,
|
||||||
|
.delete = s.delete orelse &nop,
|
||||||
|
.patch = s.patch orelse &nop,
|
||||||
|
.options = s.options orelse &nop,
|
||||||
|
.unauthorized = s.unauthorized orelse &nop,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Initialize the endpoint.
|
// no operation. Dummy handler function for ignoring unset request types.
|
||||||
/// Set only the callbacks you need. Requests of HTTP methods without a
|
fn nop(self: *Endpoint, r: Request) void {
|
||||||
/// provided callback will be ignored.
|
_ = self;
|
||||||
pub fn init(s: EndpointSettings) Self {
|
_ = r;
|
||||||
return .{
|
}
|
||||||
.settings = .{
|
|
||||||
.path = s.path,
|
/// The global request handler for this Endpoint, called by the listener.
|
||||||
.get = s.get orelse &nop,
|
pub fn onRequest(self: *Endpoint, r: zap.Request) void {
|
||||||
.post = s.post orelse &nop,
|
if (r.method) |m| {
|
||||||
.put = s.put orelse &nop,
|
if (std.mem.eql(u8, m, "GET"))
|
||||||
.delete = s.delete orelse &nop,
|
return self.settings.get.?(self, r);
|
||||||
.patch = s.patch orelse &nop,
|
if (std.mem.eql(u8, m, "POST"))
|
||||||
.options = s.options orelse &nop,
|
return self.settings.post.?(self, r);
|
||||||
.unauthorized = s.unauthorized orelse &nop,
|
if (std.mem.eql(u8, m, "PUT"))
|
||||||
},
|
return self.settings.put.?(self, r);
|
||||||
};
|
if (std.mem.eql(u8, m, "DELETE"))
|
||||||
|
return self.settings.delete.?(self, r);
|
||||||
|
if (std.mem.eql(u8, m, "PATCH"))
|
||||||
|
return self.settings.patch.?(self, r);
|
||||||
|
if (std.mem.eql(u8, m, "OPTIONS"))
|
||||||
|
return self.settings.options.?(self, r);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// no operation. Dummy handler function for ignoring unset request types.
|
|
||||||
fn nop(self: *Endpoint, r: Request) void {
|
|
||||||
_ = self;
|
|
||||||
_ = r;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The global request handler for this Endpoint, called by the listener.
|
|
||||||
pub fn onRequest(self: *Endpoint, r: zap.Request) void {
|
|
||||||
if (r.method) |m| {
|
|
||||||
if (std.mem.eql(u8, m, "GET"))
|
|
||||||
return self.settings.get.?(self, r);
|
|
||||||
if (std.mem.eql(u8, m, "POST"))
|
|
||||||
return self.settings.post.?(self, r);
|
|
||||||
if (std.mem.eql(u8, m, "PUT"))
|
|
||||||
return self.settings.put.?(self, r);
|
|
||||||
if (std.mem.eql(u8, m, "DELETE"))
|
|
||||||
return self.settings.delete.?(self, r);
|
|
||||||
if (std.mem.eql(u8, m, "PATCH"))
|
|
||||||
return self.settings.patch.?(self, r);
|
|
||||||
if (std.mem.eql(u8, m, "OPTIONS"))
|
|
||||||
return self.settings.options.?(self, r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Wrap an endpoint with an Authenticator -> new Endpoint of type Endpoint
|
/// Wrap an endpoint with an Authenticator -> new Endpoint of type Endpoint
|
||||||
/// is available via the `endpoint()` function.
|
/// is available via the `endpoint()` function.
|
||||||
pub fn AuthenticatingEndpoint(comptime Authenticator: type) type {
|
pub fn Authenticating(comptime Authenticator: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
authenticator: *Authenticator,
|
authenticator: *Authenticator,
|
||||||
ep: *Endpoint,
|
ep: *Endpoint,
|
||||||
|
@ -289,8 +247,8 @@ pub const EndpointListenerError = error{
|
||||||
/// The listener with ednpoint support
|
/// The listener with ednpoint support
|
||||||
///
|
///
|
||||||
/// NOTE: It switches on path.startsWith -> so use endpoints with distinctly starting names!!
|
/// NOTE: It switches on path.startsWith -> so use endpoints with distinctly starting names!!
|
||||||
pub const EndpointListener = struct {
|
pub const Listener = struct {
|
||||||
listener: Listener,
|
listener: HttpListener,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
@ -314,12 +272,12 @@ pub const EndpointListener = struct {
|
||||||
|
|
||||||
// override the settings with our internal, actul callback function
|
// override the settings with our internal, actul callback function
|
||||||
// so that "we" will be called on request
|
// so that "we" will be called on request
|
||||||
ls.on_request = onRequest;
|
ls.on_request = Listener.onRequest;
|
||||||
|
|
||||||
// store the settings-provided request callback for later use
|
// store the settings-provided request callback for later use
|
||||||
on_request = l.on_request;
|
on_request = l.on_request;
|
||||||
return .{
|
return .{
|
||||||
.listener = Listener.init(ls),
|
.listener = HttpListener.init(ls),
|
||||||
.allocator = a,
|
.allocator = a,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ pub const AuthResult = enum {
|
||||||
/// The authenticator handled the request that didn't pass authentication /
|
/// The authenticator handled the request that didn't pass authentication /
|
||||||
/// authorization.
|
/// authorization.
|
||||||
/// This is used to implement authenticators that redirect to a login
|
/// This is used to implement authenticators that redirect to a login
|
||||||
/// page. An AuthenticatingEndpoint will not do the default, which is trying
|
/// page. An Authenticating endpoint will not do the default, which is trying
|
||||||
/// to call the `unauthorized` callback if one exists orelse ignore the request.
|
/// to call the `unauthorized` callback if one exists orelse ignore the request.
|
||||||
Handled,
|
Handled,
|
||||||
};
|
};
|
||||||
|
@ -79,7 +79,7 @@ pub const AuthResult = enum {
|
||||||
/// WWW-Authenticate: Basic realm="this"
|
/// WWW-Authenticate: Basic realm="this"
|
||||||
///
|
///
|
||||||
/// Lookup : any kind of map that implements get([]const u8) -> []const u8
|
/// Lookup : any kind of map that implements get([]const u8) -> []const u8
|
||||||
pub fn BasicAuth(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
|
pub fn Basic(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
|
||||||
return struct {
|
return struct {
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
realm: ?[]const u8,
|
realm: ?[]const u8,
|
||||||
|
@ -219,7 +219,7 @@ pub fn BasicAuth(comptime Lookup: type, comptime kind: BasicAuthStrategy) type {
|
||||||
/// Errors:
|
/// Errors:
|
||||||
/// HTTP/1.1 401 Unauthorized
|
/// HTTP/1.1 401 Unauthorized
|
||||||
/// WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="..."
|
/// WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="..."
|
||||||
pub const BearerAuthSingle = struct {
|
pub const BearerSingle = struct {
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
token: []const u8,
|
token: []const u8,
|
||||||
realm: ?[]const u8,
|
realm: ?[]const u8,
|
||||||
|
@ -276,7 +276,7 @@ pub const BearerAuthSingle = struct {
|
||||||
/// Errors:
|
/// Errors:
|
||||||
/// HTTP/1.1 401 Unauthorized
|
/// HTTP/1.1 401 Unauthorized
|
||||||
/// WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="..."
|
/// WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="..."
|
||||||
pub fn BearerAuthMulti(comptime Lookup: type) type {
|
pub fn BearerMulti(comptime Lookup: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
lookup: *Lookup,
|
lookup: *Lookup,
|
||||||
|
@ -325,8 +325,8 @@ pub fn BearerAuthMulti(comptime Lookup: type) type {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Settings to initialize a UserPassSessionAuth authenticator.
|
/// Settings to initialize a UserPassSession authenticator.
|
||||||
pub const UserPassSessionAuthArgs = struct {
|
pub const UserPassSessionArgs = struct {
|
||||||
/// username body parameter
|
/// username body parameter
|
||||||
usernameParam: []const u8,
|
usernameParam: []const u8,
|
||||||
/// password body parameter
|
/// password body parameter
|
||||||
|
@ -341,7 +341,7 @@ pub const UserPassSessionAuthArgs = struct {
|
||||||
redirectCode: zap.StatusCode = .found,
|
redirectCode: zap.StatusCode = .found,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// UserPassSessionAuth supports the following use case:
|
/// UserPassSession supports the following use case:
|
||||||
///
|
///
|
||||||
/// - checks every request: is it going to the login page? -> let the request through.
|
/// - checks every request: is it going to the login page? -> let the request through.
|
||||||
/// - else:
|
/// - else:
|
||||||
|
@ -358,7 +358,7 @@ pub const UserPassSessionAuthArgs = struct {
|
||||||
/// mechanisms described above will still kick in. For that reason: please know what
|
/// mechanisms described above will still kick in. For that reason: please know what
|
||||||
/// you're doing.
|
/// you're doing.
|
||||||
///
|
///
|
||||||
/// See UserPassSessionAuthArgs:
|
/// See UserPassSessionArgs:
|
||||||
/// - username & password param names can be defined by you
|
/// - username & password param names can be defined by you
|
||||||
/// - session cookie name and max-age can be defined by you
|
/// - session cookie name and max-age can be defined by you
|
||||||
/// - login page and redirect code (.302) can be defined by you
|
/// - login page and redirect code (.302) can be defined by you
|
||||||
|
@ -376,11 +376,11 @@ pub const UserPassSessionAuthArgs = struct {
|
||||||
/// -> another browser program with the page still open would still be able to use
|
/// -> another browser program with the page still open would still be able to use
|
||||||
/// -> the session. Which is kindof OK, but not as cool as erasing the token
|
/// -> the session. Which is kindof OK, but not as cool as erasing the token
|
||||||
/// -> on the server side which immediately block all other browsers as well.
|
/// -> on the server side which immediately block all other browsers as well.
|
||||||
pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool) type {
|
pub fn UserPassSession(comptime Lookup: type, comptime lockedPwLookups: bool) type {
|
||||||
return struct {
|
return struct {
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
lookup: *Lookup,
|
lookup: *Lookup,
|
||||||
settings: UserPassSessionAuthArgs,
|
settings: UserPassSessionArgs,
|
||||||
|
|
||||||
// TODO: cookie store per user?
|
// TODO: cookie store per user?
|
||||||
sessionTokens: SessionTokenMap,
|
sessionTokens: SessionTokenMap,
|
||||||
|
@ -398,7 +398,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
lookup: *Lookup,
|
lookup: *Lookup,
|
||||||
args: UserPassSessionAuthArgs,
|
args: UserPassSessionArgs,
|
||||||
) !Self {
|
) !Self {
|
||||||
var ret: Self = .{
|
var ret: Self = .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
|
@ -464,7 +464,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else |err| {
|
} else |err| {
|
||||||
zap.debug("unreachable: UserPassSessionAuth.logout: {any}", .{err});
|
zap.debug("unreachable: UserPassSession.logout: {any}", .{err});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,7 +478,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
|
||||||
|
|
||||||
// parse body
|
// parse body
|
||||||
r.parseBody() catch {
|
r.parseBody() catch {
|
||||||
// zap.debug("warning: parseBody() failed in UserPassSessionAuth: {any}", .{err});
|
// zap.debug("warning: parseBody() failed in UserPassSession: {any}", .{err});
|
||||||
// this is not an error in case of e.g. gets with querystrings
|
// this is not an error in case of e.g. gets with querystrings
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -503,7 +503,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else |err| {
|
} else |err| {
|
||||||
zap.debug("unreachable: could not check for cookie in UserPassSessionAuth: {any}", .{err});
|
zap.debug("unreachable: could not check for cookie in UserPassSession: {any}", .{err});
|
||||||
}
|
}
|
||||||
|
|
||||||
// get params of username and password
|
// get params of username and password
|
||||||
|
@ -548,12 +548,12 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else |err| {
|
} else |err| {
|
||||||
zap.debug("getParamSt() for password failed in UserPassSessionAuth: {any}", .{err});
|
zap.debug("getParamSt() for password failed in UserPassSession: {any}", .{err});
|
||||||
return .AuthFailed;
|
return .AuthFailed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else |err| {
|
} else |err| {
|
||||||
zap.debug("getParamSt() for user failed in UserPassSessionAuth: {any}", .{err});
|
zap.debug("getParamSt() for user failed in UserPassSession: {any}", .{err});
|
||||||
return .AuthFailed;
|
return .AuthFailed;
|
||||||
}
|
}
|
||||||
return .AuthFailed;
|
return .AuthFailed;
|
||||||
|
@ -575,7 +575,7 @@ pub fn UserPassSessionAuth(comptime Lookup: type, comptime lockedPwLookups: bool
|
||||||
// we need to redirect and return .Handled
|
// we need to redirect and return .Handled
|
||||||
self.redirect(r) catch |err| {
|
self.redirect(r) catch |err| {
|
||||||
// we just give up
|
// we just give up
|
||||||
zap.debug("redirect() failed in UserPassSessionAuth: {any}", .{err});
|
zap.debug("redirect() failed in UserPassSession: {any}", .{err});
|
||||||
};
|
};
|
||||||
return .Handled;
|
return .Handled;
|
||||||
},
|
},
|
||||||
|
|
465
src/mustache.zig
465
src/mustache.zig
|
@ -2,252 +2,249 @@ const std = @import("std");
|
||||||
const fio = @import("fio.zig");
|
const fio = @import("fio.zig");
|
||||||
const util = @import("util.zig");
|
const util = @import("util.zig");
|
||||||
|
|
||||||
/// A struct to handle Mustache templating.
|
const Self = @This();
|
||||||
///
|
|
||||||
/// This is a wrapper around fiobj's mustache template handling.
|
|
||||||
/// See http://facil.io/0.7.x/fiobj_mustache for more information.
|
|
||||||
pub const Mustache = struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
const struct_mustache_s = opaque {};
|
const struct_mustache_s = opaque {};
|
||||||
const mustache_s = struct_mustache_s;
|
const mustache_s = struct_mustache_s;
|
||||||
const enum_mustache_error_en = c_uint;
|
const enum_mustache_error_en = c_uint;
|
||||||
const mustache_error_en = enum_mustache_error_en;
|
const mustache_error_en = enum_mustache_error_en;
|
||||||
|
|
||||||
extern fn fiobj_mustache_new(args: MustacheLoadArgsFio) ?*mustache_s;
|
extern fn fiobj_mustache_new(args: MustacheLoadArgsFio) ?*mustache_s;
|
||||||
extern fn fiobj_mustache_build(mustache: ?*mustache_s, data: fio.FIOBJ) fio.FIOBJ;
|
extern fn fiobj_mustache_build(mustache: ?*mustache_s, data: fio.FIOBJ) fio.FIOBJ;
|
||||||
extern fn fiobj_mustache_build2(dest: fio.FIOBJ, mustache: ?*mustache_s, data: fio.FIOBJ) fio.FIOBJ;
|
extern fn fiobj_mustache_build2(dest: fio.FIOBJ, mustache: ?*mustache_s, data: fio.FIOBJ) fio.FIOBJ;
|
||||||
extern fn fiobj_mustache_free(mustache: ?*mustache_s) void;
|
extern fn fiobj_mustache_free(mustache: ?*mustache_s) void;
|
||||||
|
|
||||||
/// Load arguments used when creating a new Mustache instance.
|
/// Load arguments used when creating a new Mustache instance.
|
||||||
pub const MustacheLoadArgs = struct {
|
pub const MustacheLoadArgs = struct {
|
||||||
/// Filename. This enables partial templates on filesystem.
|
/// Filename. This enables partial templates on filesystem.
|
||||||
filename: ?[]const u8 = null,
|
filename: ?[]const u8 = null,
|
||||||
|
|
||||||
/// String data. Should be used if no filename is specified.
|
/// String data. Should be used if no filename is specified.
|
||||||
data: ?[]const u8 = null,
|
data: ?[]const u8 = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Internal struct used for interfacing with fio.
|
||||||
|
const MustacheLoadArgsFio = extern struct {
|
||||||
|
filename: [*c]const u8,
|
||||||
|
filename_len: usize,
|
||||||
|
data: [*c]const u8,
|
||||||
|
data_len: usize,
|
||||||
|
err: [*c]mustache_error_en,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Handle to the underlying fiobj mustache instance.
|
||||||
|
handle: *mustache_s,
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
MUSTACHE_ERR_TOO_DEEP,
|
||||||
|
MUSTACHE_ERR_CLOSURE_MISMATCH,
|
||||||
|
MUSTACHE_ERR_FILE_NOT_FOUND,
|
||||||
|
MUSTACHE_ERR_FILE_TOO_BIG,
|
||||||
|
MUSTACHE_ERR_FILE_NAME_TOO_LONG,
|
||||||
|
MUSTACHE_ERR_FILE_NAME_TOO_SHORT,
|
||||||
|
MUSTACHE_ERR_EMPTY_TEMPLATE,
|
||||||
|
MUSTACHE_ERR_DELIMITER_TOO_LONG,
|
||||||
|
MUSTACHE_ERR_NAME_TOO_LONG,
|
||||||
|
MUSTACHE_ERR_UNKNOWN,
|
||||||
|
MUSTACHE_ERR_USER_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Create a new `Mustache` instance; `deinit()` should be called to free
|
||||||
|
/// the object after usage.
|
||||||
|
pub fn init(load_args: MustacheLoadArgs) Error!Self {
|
||||||
|
var err: mustache_error_en = undefined;
|
||||||
|
|
||||||
|
const args: MustacheLoadArgsFio = .{
|
||||||
|
.filename = filn: {
|
||||||
|
if (load_args.filename) |filn| break :filn filn.ptr else break :filn null;
|
||||||
|
},
|
||||||
|
.filename_len = filn_len: {
|
||||||
|
if (load_args.filename) |filn| break :filn_len filn.len else break :filn_len 0;
|
||||||
|
},
|
||||||
|
.data = data: {
|
||||||
|
if (load_args.data) |data| break :data data.ptr else break :data null;
|
||||||
|
},
|
||||||
|
.data_len = data_len: {
|
||||||
|
if (load_args.data) |data| break :data_len data.len else break :data_len 0;
|
||||||
|
},
|
||||||
|
.err = &err,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Internal struct used for interfacing with fio.
|
const ret = fiobj_mustache_new(args);
|
||||||
const MustacheLoadArgsFio = extern struct {
|
switch (err) {
|
||||||
filename: [*c]const u8,
|
0 => return Self{
|
||||||
filename_len: usize,
|
.handle = ret.?,
|
||||||
data: [*c]const u8,
|
},
|
||||||
data_len: usize,
|
1 => return Error.MUSTACHE_ERR_TOO_DEEP,
|
||||||
err: [*c]mustache_error_en,
|
2 => return Error.MUSTACHE_ERR_CLOSURE_MISMATCH,
|
||||||
};
|
3 => return Error.MUSTACHE_ERR_FILE_NOT_FOUND,
|
||||||
|
4 => return Error.MUSTACHE_ERR_FILE_TOO_BIG,
|
||||||
|
5 => return Error.MUSTACHE_ERR_FILE_NAME_TOO_LONG,
|
||||||
|
6 => return Error.MUSTACHE_ERR_FILE_NAME_TOO_SHORT,
|
||||||
|
7 => return Error.MUSTACHE_ERR_EMPTY_TEMPLATE,
|
||||||
|
8 => return Error.MUSTACHE_ERR_DELIMITER_TOO_LONG,
|
||||||
|
9 => return Error.MUSTACHE_ERR_NAME_TOO_LONG,
|
||||||
|
10 => return Error.MUSTACHE_ERR_UNKNOWN,
|
||||||
|
11 => return Error.MUSTACHE_ERR_USER_ERROR,
|
||||||
|
else => return Error.MUSTACHE_ERR_UNKNOWN,
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
handler: *mustache_s,
|
/// Convenience function to create a new `Mustache` instance with in-memory data loaded;
|
||||||
|
/// `deinit()` should be called to free the object after usage..
|
||||||
|
pub fn fromData(data: []const u8) Error!Self {
|
||||||
|
return Self.init(.{ .data = data });
|
||||||
|
}
|
||||||
|
|
||||||
pub const Status = enum(c_int) {};
|
/// Convenience function to create a new `Mustache` instance with file-based data loaded;
|
||||||
pub const Error = error{
|
/// `deinit()` should be called to free the object after usage..
|
||||||
MUSTACHE_ERR_TOO_DEEP,
|
pub fn fromFile(filename: []const u8) Error!Self {
|
||||||
MUSTACHE_ERR_CLOSURE_MISMATCH,
|
return Self.init(.{ .filename = filename });
|
||||||
MUSTACHE_ERR_FILE_NOT_FOUND,
|
}
|
||||||
MUSTACHE_ERR_FILE_TOO_BIG,
|
|
||||||
MUSTACHE_ERR_FILE_NAME_TOO_LONG,
|
|
||||||
MUSTACHE_ERR_FILE_NAME_TOO_SHORT,
|
|
||||||
MUSTACHE_ERR_EMPTY_TEMPLATE,
|
|
||||||
MUSTACHE_ERR_DELIMITER_TOO_LONG,
|
|
||||||
MUSTACHE_ERR_NAME_TOO_LONG,
|
|
||||||
MUSTACHE_ERR_UNKNOWN,
|
|
||||||
MUSTACHE_ERR_USER_ERROR,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Create a new `Mustache` instance; `deinit()` should be called to free
|
/// Free the data backing a `Mustache` instance.
|
||||||
/// the object after usage.
|
pub fn deinit(self: *Self) void {
|
||||||
pub fn init(load_args: MustacheLoadArgs) Error!Self {
|
fiobj_mustache_free(self.handle);
|
||||||
var err: mustache_error_en = undefined;
|
}
|
||||||
|
|
||||||
const args: MustacheLoadArgsFio = .{
|
// TODO: implement these - fiobj_mustache.c
|
||||||
.filename = filn: {
|
// pub extern fn fiobj_mustache_build(mustache: ?*mustache_s, data: FIOBJ) FIOBJ;
|
||||||
if (load_args.filename) |filn| break :filn filn.ptr else break :filn null;
|
// pub extern fn fiobj_mustache_build2(dest: FIOBJ, mustache: ?*mustache_s, data: FIOBJ) FIOBJ;
|
||||||
},
|
|
||||||
.filename_len = filn_len: {
|
|
||||||
if (load_args.filename) |filn| break :filn_len filn.len else break :filn_len 0;
|
|
||||||
},
|
|
||||||
.data = data: {
|
|
||||||
if (load_args.data) |data| break :data data.ptr else break :data null;
|
|
||||||
},
|
|
||||||
.data_len = data_len: {
|
|
||||||
if (load_args.data) |data| break :data_len data.len else break :data_len 0;
|
|
||||||
},
|
|
||||||
.err = &err,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ret = fiobj_mustache_new(args);
|
/// The result from calling `build`.
|
||||||
switch (err) {
|
const MustacheBuildResult = struct {
|
||||||
0 => return Self{
|
fiobj_result: fio.FIOBJ = 0,
|
||||||
.handler = ret.?,
|
|
||||||
},
|
/// Holds the context converted into a fiobj.
|
||||||
1 => return Error.MUSTACHE_ERR_TOO_DEEP,
|
/// This is used in `build`.
|
||||||
2 => return Error.MUSTACHE_ERR_CLOSURE_MISMATCH,
|
fiobj_context: fio.FIOBJ = 0,
|
||||||
3 => return Error.MUSTACHE_ERR_FILE_NOT_FOUND,
|
|
||||||
4 => return Error.MUSTACHE_ERR_FILE_TOO_BIG,
|
/// Free the data backing a `MustacheBuildResult` instance.
|
||||||
5 => return Error.MUSTACHE_ERR_FILE_NAME_TOO_LONG,
|
pub fn deinit(m: *const MustacheBuildResult) void {
|
||||||
6 => return Error.MUSTACHE_ERR_FILE_NAME_TOO_SHORT,
|
fio.fiobj_free_wrapped(m.fiobj_result);
|
||||||
7 => return Error.MUSTACHE_ERR_EMPTY_TEMPLATE,
|
fio.fiobj_free_wrapped(m.fiobj_context);
|
||||||
8 => return Error.MUSTACHE_ERR_DELIMITER_TOO_LONG,
|
|
||||||
9 => return Error.MUSTACHE_ERR_NAME_TOO_LONG,
|
|
||||||
10 => return Error.MUSTACHE_ERR_UNKNOWN,
|
|
||||||
11 => return Error.MUSTACHE_ERR_USER_ERROR,
|
|
||||||
else => return Error.MUSTACHE_ERR_UNKNOWN,
|
|
||||||
}
|
|
||||||
unreachable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function to create a new `Mustache` instance with in-memory data loaded;
|
/// Retrieve a string representation of the built template.
|
||||||
/// `deinit()` should be called to free the object after usage..
|
pub fn str(m: *const MustacheBuildResult) ?[]const u8 {
|
||||||
pub fn fromData(data: []const u8) Error!Mustache {
|
return util.fio2str(m.fiobj_result);
|
||||||
return Self.init(.{ .data = data });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convenience function to create a new `Mustache` instance with file-based data loaded;
|
|
||||||
/// `deinit()` should be called to free the object after usage..
|
|
||||||
pub fn fromFile(filename: []const u8) Error!Mustache {
|
|
||||||
return Self.init(.{ .filename = filename });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Free the data backing a `Mustache` instance.
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
fiobj_mustache_free(self.handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: implement these - fiobj_mustache.c
|
|
||||||
// pub extern fn fiobj_mustache_build(mustache: ?*mustache_s, data: FIOBJ) FIOBJ;
|
|
||||||
// pub extern fn fiobj_mustache_build2(dest: FIOBJ, mustache: ?*mustache_s, data: FIOBJ) FIOBJ;
|
|
||||||
|
|
||||||
/// The result from calling `build`.
|
|
||||||
const MustacheBuildResult = struct {
|
|
||||||
fiobj_result: fio.FIOBJ = 0,
|
|
||||||
|
|
||||||
/// Holds the context converted into a fiobj.
|
|
||||||
/// This is used in `build`.
|
|
||||||
fiobj_context: fio.FIOBJ = 0,
|
|
||||||
|
|
||||||
/// Free the data backing a `MustacheBuildResult` instance.
|
|
||||||
pub fn deinit(m: *const MustacheBuildResult) void {
|
|
||||||
fio.fiobj_free_wrapped(m.fiobj_result);
|
|
||||||
fio.fiobj_free_wrapped(m.fiobj_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve a string representation of the built template.
|
|
||||||
pub fn str(m: *const MustacheBuildResult) ?[]const u8 {
|
|
||||||
return util.fio2str(m.fiobj_result);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Build the Mustache template; `deinit()` should be called on the build
|
|
||||||
/// result to free the data.
|
|
||||||
/// TODO: This build is slow because it needs to translate to a FIOBJ data
|
|
||||||
/// object - FIOBJ_T_HASH
|
|
||||||
pub fn build(self: *Self, data: anytype) MustacheBuildResult {
|
|
||||||
const T = @TypeOf(data);
|
|
||||||
if (@typeInfo(T) != .Struct) {
|
|
||||||
@compileError("No struct: '" ++ @typeName(T) ++ "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
var result: MustacheBuildResult = .{};
|
|
||||||
|
|
||||||
result.fiobj_context = fiobjectify(data);
|
|
||||||
result.fiobj_result = fiobj_mustache_build(self.handler, result.fiobj_context);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fiobjectify(
|
|
||||||
value: anytype,
|
|
||||||
) fio.FIOBJ {
|
|
||||||
const T = @TypeOf(value);
|
|
||||||
switch (@typeInfo(T)) {
|
|
||||||
.Float, .ComptimeFloat => {
|
|
||||||
return fio.fiobj_float_new(value);
|
|
||||||
},
|
|
||||||
.Int, .ComptimeInt => {
|
|
||||||
return fio.fiobj_num_new_bignum(value);
|
|
||||||
},
|
|
||||||
.Bool => {
|
|
||||||
return if (value) fio.fiobj_true() else fio.fiobj_false();
|
|
||||||
},
|
|
||||||
.Null => {
|
|
||||||
return 0;
|
|
||||||
},
|
|
||||||
.Optional => {
|
|
||||||
if (value) |payload| {
|
|
||||||
return fiobjectify(payload);
|
|
||||||
} else {
|
|
||||||
return fiobjectify(null);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.Enum => {
|
|
||||||
return fio.fiobj_num_new_bignum(@intFromEnum(value));
|
|
||||||
},
|
|
||||||
.Union => {
|
|
||||||
const info = @typeInfo(T).Union;
|
|
||||||
if (info.tag_type) |UnionTagType| {
|
|
||||||
inline for (info.fields) |u_field| {
|
|
||||||
if (value == @field(UnionTagType, u_field.name)) {
|
|
||||||
return fiobjectify(@field(value, u_field.name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
@compileError("Unable to fiobjectify untagged union '" ++ @typeName(T) ++ "'");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.Struct => |S| {
|
|
||||||
// create a new fio hashmap
|
|
||||||
const m = fio.fiobj_hash_new();
|
|
||||||
// std.debug.print("new struct\n", .{});
|
|
||||||
inline for (S.fields) |Field| {
|
|
||||||
// don't include void fields
|
|
||||||
if (Field.type == void) continue;
|
|
||||||
|
|
||||||
// std.debug.print(" new field: {s}\n", .{Field.name});
|
|
||||||
const fname = fio.fiobj_str_new(util.toCharPtr(Field.name), Field.name.len);
|
|
||||||
// std.debug.print(" fiobj name : {any}\n", .{fname});
|
|
||||||
const v = @field(value, Field.name);
|
|
||||||
// std.debug.print(" value: {any}\n", .{v});
|
|
||||||
const fvalue = fiobjectify(v);
|
|
||||||
// std.debug.print(" fiobj value: {any}\n", .{fvalue});
|
|
||||||
_ = fio.fiobj_hash_set(m, fname, fvalue);
|
|
||||||
fio.fiobj_free_wrapped(fname);
|
|
||||||
}
|
|
||||||
return m;
|
|
||||||
},
|
|
||||||
.ErrorSet => return fiobjectify(@as([]const u8, @errorName(value))),
|
|
||||||
.Pointer => |ptr_info| switch (ptr_info.size) {
|
|
||||||
.One => switch (@typeInfo(ptr_info.child)) {
|
|
||||||
.Array => {
|
|
||||||
const Slice = []const std.meta.Elem(ptr_info.child);
|
|
||||||
return fiobjectify(@as(Slice, value));
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
// TODO: avoid loops?
|
|
||||||
return fiobjectify(value.*);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972)
|
|
||||||
.Slice => {
|
|
||||||
// std.debug.print("new slice\n", .{});
|
|
||||||
if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) {
|
|
||||||
return fio.fiobj_str_new(util.toCharPtr(value), value.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
const arr = fio.fiobj_ary_new2(value.len);
|
|
||||||
for (value) |x| {
|
|
||||||
const v = fiobjectify(x);
|
|
||||||
fio.fiobj_ary_push(arr, v);
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
},
|
|
||||||
else => @compileError("Unable to fiobjectify type '" ++ @typeName(T) ++ "'"),
|
|
||||||
},
|
|
||||||
.Array => return fiobjectify(&value),
|
|
||||||
.Vector => |info| {
|
|
||||||
const array: [info.len]info.child = value;
|
|
||||||
return fiobjectify(&array);
|
|
||||||
},
|
|
||||||
else => @compileError("Unable to fiobjectify type '" ++ @typeName(T) ++ "'"),
|
|
||||||
}
|
|
||||||
unreachable;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Build the Mustache template; `deinit()` should be called on the build
|
||||||
|
/// result to free the data.
|
||||||
|
// TODO: The build may be slow because it needs to convert zig types to facil.io
|
||||||
|
// types. However, this needs to be investigated into.
|
||||||
|
// See `fiobjectify` for more information.
|
||||||
|
pub fn build(self: *Self, data: anytype) MustacheBuildResult {
|
||||||
|
const T = @TypeOf(data);
|
||||||
|
if (@typeInfo(T) != .Struct) {
|
||||||
|
@compileError("No struct: '" ++ @typeName(T) ++ "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
var result: MustacheBuildResult = .{};
|
||||||
|
|
||||||
|
result.fiobj_context = fiobjectify(data);
|
||||||
|
result.fiobj_result = fiobj_mustache_build(self.handle, result.fiobj_context);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal function used to convert zig types to facil.io types.
|
||||||
|
/// Used when providing the context to `fiobj_mustache_build`.
|
||||||
|
fn fiobjectify(
|
||||||
|
value: anytype,
|
||||||
|
) fio.FIOBJ {
|
||||||
|
const T = @TypeOf(value);
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Float, .ComptimeFloat => {
|
||||||
|
return fio.fiobj_float_new(value);
|
||||||
|
},
|
||||||
|
.Int, .ComptimeInt => {
|
||||||
|
return fio.fiobj_num_new_bignum(value);
|
||||||
|
},
|
||||||
|
.Bool => {
|
||||||
|
return if (value) fio.fiobj_true() else fio.fiobj_false();
|
||||||
|
},
|
||||||
|
.Null => {
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
.Optional => {
|
||||||
|
if (value) |payload| {
|
||||||
|
return fiobjectify(payload);
|
||||||
|
} else {
|
||||||
|
return fiobjectify(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Enum => {
|
||||||
|
return fio.fiobj_num_new_bignum(@intFromEnum(value));
|
||||||
|
},
|
||||||
|
.Union => {
|
||||||
|
const info = @typeInfo(T).Union;
|
||||||
|
if (info.tag_type) |UnionTagType| {
|
||||||
|
inline for (info.fields) |u_field| {
|
||||||
|
if (value == @field(UnionTagType, u_field.name)) {
|
||||||
|
return fiobjectify(@field(value, u_field.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@compileError("Unable to fiobjectify untagged union '" ++ @typeName(T) ++ "'");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Struct => |S| {
|
||||||
|
// create a new fio hashmap
|
||||||
|
const m = fio.fiobj_hash_new();
|
||||||
|
// std.debug.print("new struct\n", .{});
|
||||||
|
inline for (S.fields) |Field| {
|
||||||
|
// don't include void fields
|
||||||
|
if (Field.type == void) continue;
|
||||||
|
|
||||||
|
// std.debug.print(" new field: {s}\n", .{Field.name});
|
||||||
|
const fname = fio.fiobj_str_new(util.toCharPtr(Field.name), Field.name.len);
|
||||||
|
// std.debug.print(" fiobj name : {any}\n", .{fname});
|
||||||
|
const v = @field(value, Field.name);
|
||||||
|
// std.debug.print(" value: {any}\n", .{v});
|
||||||
|
const fvalue = fiobjectify(v);
|
||||||
|
// std.debug.print(" fiobj value: {any}\n", .{fvalue});
|
||||||
|
_ = fio.fiobj_hash_set(m, fname, fvalue);
|
||||||
|
fio.fiobj_free_wrapped(fname);
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
},
|
||||||
|
.ErrorSet => return fiobjectify(@as([]const u8, @errorName(value))),
|
||||||
|
.Pointer => |ptr_info| switch (ptr_info.size) {
|
||||||
|
.One => switch (@typeInfo(ptr_info.child)) {
|
||||||
|
.Array => {
|
||||||
|
const Slice = []const std.meta.Elem(ptr_info.child);
|
||||||
|
return fiobjectify(@as(Slice, value));
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
// TODO: avoid loops?
|
||||||
|
return fiobjectify(value.*);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972)
|
||||||
|
.Slice => {
|
||||||
|
// std.debug.print("new slice\n", .{});
|
||||||
|
if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) {
|
||||||
|
return fio.fiobj_str_new(util.toCharPtr(value), value.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
const arr = fio.fiobj_ary_new2(value.len);
|
||||||
|
for (value) |x| {
|
||||||
|
const v = fiobjectify(x);
|
||||||
|
fio.fiobj_ary_push(arr, v);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
},
|
||||||
|
else => @compileError("Unable to fiobjectify type '" ++ @typeName(T) ++ "'"),
|
||||||
|
},
|
||||||
|
.Array => return fiobjectify(&value),
|
||||||
|
.Vector => |info| {
|
||||||
|
const array: [info.len]info.child = value;
|
||||||
|
return fiobjectify(&array);
|
||||||
|
},
|
||||||
|
else => @compileError("Unable to fiobjectify type '" ++ @typeName(T) ++ "'"),
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
738
src/request.zig
Normal file
738
src/request.zig
Normal file
|
@ -0,0 +1,738 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const Log = @import("log.zig");
|
||||||
|
const http = @import("http.zig");
|
||||||
|
const fio = @import("fio.zig");
|
||||||
|
|
||||||
|
const util = @import("util.zig");
|
||||||
|
const zap = @import("zap.zig");
|
||||||
|
|
||||||
|
pub const HttpError = error{
|
||||||
|
HttpSendBody,
|
||||||
|
HttpSetContentType,
|
||||||
|
HttpSetHeader,
|
||||||
|
HttpParseBody,
|
||||||
|
HttpIterParams,
|
||||||
|
SetCookie,
|
||||||
|
SendFile,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Http Content Type enum.
|
||||||
|
/// Needs some love.
|
||||||
|
pub const ContentType = enum {
|
||||||
|
TEXT,
|
||||||
|
HTML,
|
||||||
|
JSON,
|
||||||
|
// TODO: more content types
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Key value pair of strings from HTTP parameters
|
||||||
|
pub const HttpParamStrKV = struct {
|
||||||
|
key: util.FreeOrNot,
|
||||||
|
value: util.FreeOrNot,
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.key.deinit();
|
||||||
|
self.value.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// List of key value pairs of Http param strings.
|
||||||
|
pub const HttpParamStrKVList = struct {
|
||||||
|
items: []HttpParamStrKV,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
for (self.items) |*item| {
|
||||||
|
item.deinit();
|
||||||
|
}
|
||||||
|
self.allocator.free(self.items);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// List of key value pairs of Http params (might be of different types).
|
||||||
|
pub const HttpParamKVList = struct {
|
||||||
|
items: []HttpParamKV,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
pub fn deinit(self: *const @This()) void {
|
||||||
|
for (self.items) |*item| {
|
||||||
|
item.deinit();
|
||||||
|
}
|
||||||
|
self.allocator.free(self.items);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Enum for HttpParam tagged union
|
||||||
|
pub const HttpParamValueType = enum {
|
||||||
|
// Null,
|
||||||
|
Bool,
|
||||||
|
Int,
|
||||||
|
Float,
|
||||||
|
String,
|
||||||
|
Unsupported,
|
||||||
|
Hash_Binfile,
|
||||||
|
Array_Binfile,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Tagged union holding a typed Http param
|
||||||
|
pub const HttpParam = union(HttpParamValueType) {
|
||||||
|
Bool: bool,
|
||||||
|
Int: isize,
|
||||||
|
Float: f64,
|
||||||
|
/// we don't do writable strings here
|
||||||
|
String: util.FreeOrNot,
|
||||||
|
/// value will always be null
|
||||||
|
Unsupported: ?void,
|
||||||
|
/// we assume hashes are because of file transmissions
|
||||||
|
Hash_Binfile: HttpParamBinaryFile,
|
||||||
|
/// value will always be null
|
||||||
|
Array_Binfile: std.ArrayList(HttpParamBinaryFile),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Key value pair of one typed Http param
|
||||||
|
pub const HttpParamKV = struct {
|
||||||
|
key: util.FreeOrNot,
|
||||||
|
value: ?HttpParam,
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.key.deinit();
|
||||||
|
if (self.value) |p| {
|
||||||
|
switch (p) {
|
||||||
|
.String => |*s| s.deinit(),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Struct representing an uploaded file.
|
||||||
|
pub const HttpParamBinaryFile = struct {
|
||||||
|
/// file contents
|
||||||
|
data: ?[]const u8 = null,
|
||||||
|
/// mimetype
|
||||||
|
mimetype: ?[]const u8 = null,
|
||||||
|
/// filename
|
||||||
|
filename: ?[]const u8 = null,
|
||||||
|
|
||||||
|
/// format function for printing file upload data
|
||||||
|
pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) std.os.WriteError!void {
|
||||||
|
const d = value.data orelse "\\0";
|
||||||
|
const m = value.mimetype orelse "null";
|
||||||
|
const f = value.filename orelse "null";
|
||||||
|
return writer.print("<{s} ({s}): {any}>", .{ f, m, d });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn parseBinfilesFrom(a: std.mem.Allocator, o: fio.FIOBJ) !HttpParam {
|
||||||
|
const key_name = fio.fiobj_str_new("name", 4);
|
||||||
|
const key_data = fio.fiobj_str_new("data", 4);
|
||||||
|
const key_type = fio.fiobj_str_new("type", 4);
|
||||||
|
defer {
|
||||||
|
fio.fiobj_free_wrapped(key_name);
|
||||||
|
fio.fiobj_free_wrapped(key_data);
|
||||||
|
fio.fiobj_free_wrapped(key_type);
|
||||||
|
} // files: they should have "data", "type", and "filename" keys
|
||||||
|
if (fio.fiobj_hash_haskey(o, key_data) == 1 and fio.fiobj_hash_haskey(o, key_type) == 1 and fio.fiobj_hash_haskey(o, key_name) == 1) {
|
||||||
|
const filename = fio.fiobj_obj2cstr(fio.fiobj_hash_get(o, key_name));
|
||||||
|
const mimetype = fio.fiobj_obj2cstr(fio.fiobj_hash_get(o, key_type));
|
||||||
|
const data = fio.fiobj_hash_get(o, key_data);
|
||||||
|
|
||||||
|
var data_slice: ?[]const u8 = null;
|
||||||
|
|
||||||
|
switch (fio.fiobj_type(data)) {
|
||||||
|
fio.FIOBJ_T_DATA => {
|
||||||
|
if (fio.is_invalid(data) == 1) {
|
||||||
|
data_slice = "(zap: invalid data)";
|
||||||
|
std.log.warn("WARNING: HTTP param binary file is not a data object\n", .{});
|
||||||
|
} else {
|
||||||
|
// the data
|
||||||
|
const data_len = fio.fiobj_data_len(data);
|
||||||
|
var data_buf = fio.fiobj_data_read(data, data_len);
|
||||||
|
|
||||||
|
if (data_len < 0) {
|
||||||
|
std.log.warn("WARNING: HTTP param binary file size negative: {d}\n", .{data_len});
|
||||||
|
std.log.warn("FIOBJ_TYPE of data is: {d}\n", .{fio.fiobj_type(data)});
|
||||||
|
} else {
|
||||||
|
if (data_buf.len != data_len) {
|
||||||
|
std.log.warn("WARNING: HTTP param binary file size mismatch: should {d}, is: {d}\n", .{ data_len, data_buf.len });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_buf.len > 0) {
|
||||||
|
data_slice = data_buf.data[0..data_buf.len];
|
||||||
|
} else {
|
||||||
|
std.log.warn("WARNING: HTTP param binary file buffer size negative: {d}\n", .{data_buf.len});
|
||||||
|
data_slice = "(zap: invalid data: negative BUFFER size)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fio.FIOBJ_T_STRING => {
|
||||||
|
const fiostr = fio.fiobj_obj2cstr(data);
|
||||||
|
if (fiostr.len == 0) {
|
||||||
|
data_slice = "(zap: empty string data)";
|
||||||
|
std.log.warn("WARNING: HTTP param binary file has empty string object\n", .{});
|
||||||
|
} else {
|
||||||
|
data_slice = fiostr.data[0..fiostr.len];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fio.FIOBJ_T_ARRAY => {
|
||||||
|
// OK, data is an array
|
||||||
|
const len = fio.fiobj_ary_count(data);
|
||||||
|
const fn_ary = fio.fiobj_hash_get(o, key_name);
|
||||||
|
const mt_ary = fio.fiobj_hash_get(o, key_type);
|
||||||
|
|
||||||
|
if (fio.fiobj_ary_count(fn_ary) == len and fio.fiobj_ary_count(mt_ary) == len) {
|
||||||
|
var i: isize = 0;
|
||||||
|
var ret = std.ArrayList(HttpParamBinaryFile).init(a);
|
||||||
|
while (i < len) : (i += 1) {
|
||||||
|
const file_data_obj = fio.fiobj_ary_entry(data, i);
|
||||||
|
const file_name_obj = fio.fiobj_ary_entry(fn_ary, i);
|
||||||
|
const file_mimetype_obj = fio.fiobj_ary_entry(mt_ary, i);
|
||||||
|
var has_error: bool = false;
|
||||||
|
if (fio.is_invalid(file_data_obj) == 1) {
|
||||||
|
std.log.debug("file data invalid in array", .{});
|
||||||
|
has_error = true;
|
||||||
|
}
|
||||||
|
if (fio.is_invalid(file_name_obj) == 1) {
|
||||||
|
std.log.debug("file name invalid in array", .{});
|
||||||
|
has_error = true;
|
||||||
|
}
|
||||||
|
if (fio.is_invalid(file_mimetype_obj) == 1) {
|
||||||
|
std.log.debug("file mimetype invalid in array", .{});
|
||||||
|
has_error = true;
|
||||||
|
}
|
||||||
|
if (has_error) {
|
||||||
|
return error.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file_data = fio.fiobj_obj2cstr(file_data_obj);
|
||||||
|
const file_name = fio.fiobj_obj2cstr(file_name_obj);
|
||||||
|
const file_mimetype = fio.fiobj_obj2cstr(file_mimetype_obj);
|
||||||
|
try ret.append(.{
|
||||||
|
.data = file_data.data[0..file_data.len],
|
||||||
|
.mimetype = file_mimetype.data[0..file_mimetype.len],
|
||||||
|
.filename = file_name.data[0..file_name.len],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return .{ .Array_Binfile = ret };
|
||||||
|
} else {
|
||||||
|
return error.ArrayLenMismatch;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
// don't know what to do
|
||||||
|
return error.Unsupported;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .Hash_Binfile = .{
|
||||||
|
.filename = filename.data[0..filename.len],
|
||||||
|
.mimetype = mimetype.data[0..mimetype.len],
|
||||||
|
.data = data_slice,
|
||||||
|
} };
|
||||||
|
} else {
|
||||||
|
return .{ .Hash_Binfile = .{} };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse FIO object into a typed Http param. Supports file uploads.
|
||||||
|
pub fn Fiobj2HttpParam(a: std.mem.Allocator, o: fio.FIOBJ, dupe_string: bool) !?HttpParam {
|
||||||
|
return switch (fio.fiobj_type(o)) {
|
||||||
|
fio.FIOBJ_T_NULL => null,
|
||||||
|
fio.FIOBJ_T_TRUE => .{ .Bool = true },
|
||||||
|
fio.FIOBJ_T_FALSE => .{ .Bool = false },
|
||||||
|
fio.FIOBJ_T_NUMBER => .{ .Int = fio.fiobj_obj2num(o) },
|
||||||
|
fio.FIOBJ_T_FLOAT => .{ .Float = fio.fiobj_obj2float(o) },
|
||||||
|
fio.FIOBJ_T_STRING => .{ .String = try util.fio2strAllocOrNot(a, o, dupe_string) },
|
||||||
|
fio.FIOBJ_T_ARRAY => {
|
||||||
|
return .{ .Unsupported = null };
|
||||||
|
},
|
||||||
|
fio.FIOBJ_T_HASH => {
|
||||||
|
const file = try parseBinfilesFrom(a, o);
|
||||||
|
return file;
|
||||||
|
},
|
||||||
|
else => .{ .Unsupported = null },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Args for setting a cookie
|
||||||
|
pub const CookieArgs = struct {
|
||||||
|
name: []const u8,
|
||||||
|
value: []const u8,
|
||||||
|
domain: ?[]const u8 = null,
|
||||||
|
path: ?[]const u8 = null,
|
||||||
|
/// max age in seconds. 0 -> session
|
||||||
|
max_age_s: c_int = 0,
|
||||||
|
secure: bool = true,
|
||||||
|
http_only: bool = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
path: ?[]const u8,
|
||||||
|
query: ?[]const u8,
|
||||||
|
body: ?[]const u8,
|
||||||
|
method: ?[]const u8,
|
||||||
|
h: [*c]fio.http_s,
|
||||||
|
|
||||||
|
/// NEVER touch this field!!!!
|
||||||
|
/// if you absolutely MUST, then you may provide context here
|
||||||
|
/// via setUserContext and getUserContext
|
||||||
|
_user_context: *UserContext,
|
||||||
|
/// NEVER touch this field!!!!
|
||||||
|
/// use markAsFinished() and isFinished() instead
|
||||||
|
/// this is a hack: the listener will put a pointer to this into the udata
|
||||||
|
/// field of `h`. So copies of the Request will all have way to the
|
||||||
|
/// same instance of this field.
|
||||||
|
_is_finished_request_global: bool,
|
||||||
|
/// NEVER touch this field!!!!
|
||||||
|
/// this is part of the hack.
|
||||||
|
_is_finished: *bool = undefined,
|
||||||
|
|
||||||
|
pub const UserContext = struct {
|
||||||
|
user_context: ?*anyopaque = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
/// mark the current request as finished. Important for middleware-style
|
||||||
|
/// request handler chaining. Called when sending a body, redirecting, etc.
|
||||||
|
pub fn markAsFinished(self: *const Self, finished: bool) void {
|
||||||
|
// we might be a copy
|
||||||
|
self._is_finished.* = finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// tell whether request processing has finished. (e.g. response sent,
|
||||||
|
/// redirected, ...)
|
||||||
|
pub fn isFinished(self: *const Self) bool {
|
||||||
|
// we might be a copy
|
||||||
|
return self._is_finished.*;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// if you absolutely must, you can set any context on the request here
|
||||||
|
// (note, this line is linked to from the readme) -- TODO: sync
|
||||||
|
pub fn setUserContext(self: *const Self, context: *anyopaque) void {
|
||||||
|
self._user_context.*.user_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get the associated user context of the request.
|
||||||
|
pub fn getUserContext(self: *const Self, comptime Context: type) ?*Context {
|
||||||
|
if (self._user_context.*.user_context) |ptr| {
|
||||||
|
return @as(*Context, @ptrCast(@alignCast(ptr)));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to send an error stack trace.
|
||||||
|
pub fn sendError(self: *const Self, err: anyerror, errorcode_num: usize) void {
|
||||||
|
// TODO: query accept headers
|
||||||
|
if (self._internal_sendError(err, errorcode_num)) {
|
||||||
|
return;
|
||||||
|
} else |_| {
|
||||||
|
self.sendBody(@errorName(err)) catch return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used internally. Probably does not need to be public.
|
||||||
|
pub fn _internal_sendError(self: *const Self, err: anyerror, errorcode_num: usize) !void {
|
||||||
|
// TODO: query accept headers
|
||||||
|
// TODO: let's hope 20k is enough. Maybe just really allocate here
|
||||||
|
self.h.*.status = errorcode_num;
|
||||||
|
var buf: [20 * 1024]u8 = undefined;
|
||||||
|
var fba = std.heap.FixedBufferAllocator.init(&buf);
|
||||||
|
var string = std.ArrayList(u8).init(fba.allocator());
|
||||||
|
var writer = string.writer();
|
||||||
|
try writer.print("ERROR: {any}\n\n", .{err});
|
||||||
|
|
||||||
|
const debugInfo = try std.debug.getSelfDebugInfo();
|
||||||
|
const ttyConfig: std.io.tty.Config = .no_color;
|
||||||
|
try std.debug.writeCurrentStackTrace(writer, debugInfo, ttyConfig, null);
|
||||||
|
try self.sendBody(string.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send body.
|
||||||
|
pub fn sendBody(self: *const Self, body: []const u8) HttpError!void {
|
||||||
|
const ret = fio.http_send_body(self.h, @as(
|
||||||
|
*anyopaque,
|
||||||
|
@ptrFromInt(@intFromPtr(body.ptr)),
|
||||||
|
), body.len);
|
||||||
|
zap.debug("Request.sendBody(): ret = {}\n", .{ret});
|
||||||
|
if (ret == -1) return error.HttpSendBody;
|
||||||
|
self.markAsFinished(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set content type and send json buffer.
|
||||||
|
pub fn sendJson(self: *const Self, json: []const u8) HttpError!void {
|
||||||
|
if (self.setContentType(.JSON)) {
|
||||||
|
if (fio.http_send_body(self.h, @as(
|
||||||
|
*anyopaque,
|
||||||
|
@ptrFromInt(@intFromPtr(json.ptr)),
|
||||||
|
), json.len) != 0) return error.HttpSendBody;
|
||||||
|
self.markAsFinished(true);
|
||||||
|
} else |err| return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set content type.
|
||||||
|
pub fn setContentType(self: *const Self, c: ContentType) HttpError!void {
|
||||||
|
const s = switch (c) {
|
||||||
|
.TEXT => "text/plain",
|
||||||
|
.JSON => "application/json",
|
||||||
|
else => "text/html",
|
||||||
|
};
|
||||||
|
zap.debug("setting content-type to {s}\n", .{s});
|
||||||
|
return self.setHeader("content-type", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// redirect to path with status code 302 by default
|
||||||
|
pub fn redirectTo(self: *const Self, path: []const u8, code: ?http.StatusCode) HttpError!void {
|
||||||
|
self.setStatus(if (code) |status| status else .found);
|
||||||
|
try self.setHeader("Location", path);
|
||||||
|
try self.sendBody("moved");
|
||||||
|
self.markAsFinished(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// shows how to use the logger
|
||||||
|
pub fn setContentTypeWithLogger(
|
||||||
|
self: *const Self,
|
||||||
|
c: ContentType,
|
||||||
|
logger: *const Log,
|
||||||
|
) HttpError!void {
|
||||||
|
const s = switch (c) {
|
||||||
|
.TEXT => "text/plain",
|
||||||
|
.JSON => "application/json",
|
||||||
|
else => "text/html",
|
||||||
|
};
|
||||||
|
logger.log("setting content-type to {s}\n", .{s});
|
||||||
|
return self.setHeader("content-type", s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to determine the content type by file extension of request path, and sets it.
|
||||||
|
pub fn setContentTypeFromPath(self: *const Self) !void {
|
||||||
|
const t = fio.http_mimetype_find2(self.h.*.path);
|
||||||
|
if (fio.is_invalid(t) == 1) return error.HttpSetContentType;
|
||||||
|
const ret = fio.fiobj_hash_set(
|
||||||
|
self.h.*.private_data.out_headers,
|
||||||
|
fio.HTTP_HEADER_CONTENT_TYPE,
|
||||||
|
t,
|
||||||
|
);
|
||||||
|
if (ret == -1) return error.HttpSetContentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to determine the content type by filename extension, and sets it.
|
||||||
|
/// If the extension cannot be determined, NoExtensionInFilename error is
|
||||||
|
/// returned.
|
||||||
|
pub fn setContentTypeFromFilename(self: *const Self, filename: []const u8) !void {
|
||||||
|
const ext = std.fs.path.extension(filename);
|
||||||
|
|
||||||
|
if (ext.len > 1) {
|
||||||
|
const e = ext[1..];
|
||||||
|
const obj = fio.http_mimetype_find(@constCast(e.ptr), e.len);
|
||||||
|
|
||||||
|
if (util.fio2str(obj)) |mime_str| {
|
||||||
|
try self.setHeader("content-type", mime_str);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return error.NoExtensionInFilename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the header value of given key name. Returned mem is temp.
|
||||||
|
/// Do not free it.
|
||||||
|
pub fn getHeader(self: *const Self, name: []const u8) ?[]const u8 {
|
||||||
|
const hname = fio.fiobj_str_new(util.toCharPtr(name), name.len);
|
||||||
|
defer fio.fiobj_free_wrapped(hname);
|
||||||
|
return util.fio2str(fio.fiobj_hash_get(self.h.*.headers, hname));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set header.
|
||||||
|
pub fn setHeader(self: *const Self, name: []const u8, value: []const u8) HttpError!void {
|
||||||
|
const hname: fio.fio_str_info_s = .{
|
||||||
|
.data = util.toCharPtr(name),
|
||||||
|
.len = name.len,
|
||||||
|
.capa = name.len,
|
||||||
|
};
|
||||||
|
|
||||||
|
zap.debug("setHeader: hname = {s}\n", .{name});
|
||||||
|
const vname: fio.fio_str_info_s = .{
|
||||||
|
.data = util.toCharPtr(value),
|
||||||
|
.len = value.len,
|
||||||
|
.capa = value.len,
|
||||||
|
};
|
||||||
|
zap.debug("setHeader: vname = {s}\n", .{value});
|
||||||
|
const ret = fio.http_set_header2(self.h, hname, vname);
|
||||||
|
|
||||||
|
// FIXME without the following if, we get errors in release builds
|
||||||
|
// at least we don't have to log unconditionally
|
||||||
|
if (ret == -1) {
|
||||||
|
std.debug.print("***************** zap.zig:274\n", .{});
|
||||||
|
}
|
||||||
|
zap.debug("setHeader: ret = {}\n", .{ret});
|
||||||
|
|
||||||
|
if (ret == 0) return;
|
||||||
|
return error.HttpSetHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set status by numeric value.
|
||||||
|
pub fn setStatusNumeric(self: *const Self, status: usize) void {
|
||||||
|
self.h.*.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set status by enum.
|
||||||
|
pub fn setStatus(self: *const Self, status: http.StatusCode) void {
|
||||||
|
self.h.*.status = @as(usize, @intCast(@intFromEnum(status)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a file if present in the filesystem orelse returns an error.
|
||||||
|
///
|
||||||
|
/// - efficiently sends a file using gzip compression
|
||||||
|
/// - also handles range requests if `Range` or `If-Range` headers are present in the request.
|
||||||
|
/// - sends the response headers and the specified file (the response's body).
|
||||||
|
///
|
||||||
|
/// On success, the `self.h` handle will be consumed and invalid.
|
||||||
|
/// On error, the handle will still be valid and should be used to send an error response
|
||||||
|
///
|
||||||
|
/// Important: sets last-modified and cache-control headers with a max-age value of 1 hour!
|
||||||
|
/// You can override that by setting those headers yourself, e.g.: setHeader("Cache-Control", "no-cache")
|
||||||
|
pub fn sendFile(self: *const Self, file_path: []const u8) !void {
|
||||||
|
if (fio.http_sendfile2(self.h, util.toCharPtr(file_path), file_path.len, null, 0) != 0)
|
||||||
|
return error.SendFile;
|
||||||
|
self.markAsFinished(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to decode the request's body.
|
||||||
|
/// This should be called BEFORE parseQuery
|
||||||
|
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice()
|
||||||
|
///
|
||||||
|
/// Supported body types:
|
||||||
|
/// - application/x-www-form-urlencoded
|
||||||
|
/// - application/json
|
||||||
|
/// - multipart/form-data
|
||||||
|
pub fn parseBody(self: *const Self) HttpError!void {
|
||||||
|
if (fio.http_parse_body(self.h) == -1) return error.HttpParseBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the query part of an HTTP request
|
||||||
|
/// This should be called AFTER parseBody(), just in case the body is a JSON
|
||||||
|
/// object that doesn't have a hash map at its root.
|
||||||
|
///
|
||||||
|
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice()
|
||||||
|
pub fn parseQuery(self: *const Self) void {
|
||||||
|
fio.http_parse_query(self.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse received cookie headers
|
||||||
|
pub fn parseCookies(self: *const Self, url_encoded: bool) void {
|
||||||
|
fio.http_parse_cookies(self.h, if (url_encoded) 1 else 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a response cookie
|
||||||
|
pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void {
|
||||||
|
const c: fio.http_cookie_args_s = .{
|
||||||
|
.name = util.toCharPtr(args.name),
|
||||||
|
.name_len = @as(isize, @intCast(args.name.len)),
|
||||||
|
.value = util.toCharPtr(args.value),
|
||||||
|
.value_len = @as(isize, @intCast(args.value.len)),
|
||||||
|
.domain = if (args.domain) |p| util.toCharPtr(p) else null,
|
||||||
|
.domain_len = if (args.domain) |p| @as(isize, @intCast(p.len)) else 0,
|
||||||
|
.path = if (args.path) |p| util.toCharPtr(p) else null,
|
||||||
|
.path_len = if (args.path) |p| @as(isize, @intCast(p.len)) else 0,
|
||||||
|
.max_age = args.max_age_s,
|
||||||
|
.secure = if (args.secure) 1 else 0,
|
||||||
|
.http_only = if (args.http_only) 1 else 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO WAT?
|
||||||
|
// if we:
|
||||||
|
// if(fio.http_set_cookie(...) == -1)
|
||||||
|
// instead of capturing it in `ret` first and then checking it,
|
||||||
|
// all ReleaseXXX builds return an error!
|
||||||
|
// TODO: still happening?
|
||||||
|
const ret = fio.http_set_cookie(self.h, c);
|
||||||
|
if (ret == -1) {
|
||||||
|
std.log.err("fio.http_set_cookie returned: {}\n", .{ret});
|
||||||
|
return error.SetCookie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns named cookie. Works like getParamStr().
|
||||||
|
pub fn getCookieStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot {
|
||||||
|
if (self.h.*.cookies == 0) return null;
|
||||||
|
const key = fio.fiobj_str_new(name.ptr, name.len);
|
||||||
|
defer fio.fiobj_free_wrapped(key);
|
||||||
|
const value = fio.fiobj_hash_get(self.h.*.cookies, key);
|
||||||
|
if (value == fio.FIOBJ_INVALID) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return try util.fio2strAllocOrNot(a, value, always_alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of cookies after parsing.
|
||||||
|
///
|
||||||
|
/// Parse with parseCookies()
|
||||||
|
pub fn getCookiesCount(self: *const Self) isize {
|
||||||
|
if (self.h.*.cookies == 0) return 0;
|
||||||
|
return fio.fiobj_obj2num(self.h.*.cookies);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of parameters after parsing.
|
||||||
|
///
|
||||||
|
/// Parse with parseBody() and / or parseQuery()
|
||||||
|
pub fn getParamCount(self: *const Self) isize {
|
||||||
|
if (self.h.*.params == 0) return 0;
|
||||||
|
return fio.fiobj_obj2num(self.h.*.params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as parametersToOwnedStrList() but for cookies
|
||||||
|
pub fn cookiesToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_alloc: bool) anyerror!HttpParamStrKVList {
|
||||||
|
var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @as(usize, @intCast(self.getCookiesCount())));
|
||||||
|
var context: _parametersToOwnedStrSliceContext = .{
|
||||||
|
.params = ¶ms,
|
||||||
|
.allocator = a,
|
||||||
|
.always_alloc = always_alloc,
|
||||||
|
};
|
||||||
|
const howmany = fio.fiobj_each1(self.h.*.cookies, 0, _each_nextParamStr, &context);
|
||||||
|
if (howmany != self.getCookiesCount()) {
|
||||||
|
return error.HttpIterParams;
|
||||||
|
}
|
||||||
|
return .{ .items = try params.toOwnedSlice(), .allocator = a };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as parametersToOwnedList() but for cookies
|
||||||
|
pub fn cookiesToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings: bool) !HttpParamKVList {
|
||||||
|
var params = try std.ArrayList(HttpParamKV).initCapacity(a, @as(usize, @intCast(self.getCookiesCount())));
|
||||||
|
var context: _parametersToOwnedSliceContext = .{ .params = ¶ms, .allocator = a, .dupe_strings = dupe_strings };
|
||||||
|
const howmany = fio.fiobj_each1(self.h.*.cookies, 0, _each_nextParam, &context);
|
||||||
|
if (howmany != self.getCookiesCount()) {
|
||||||
|
return error.HttpIterParams;
|
||||||
|
}
|
||||||
|
return .{ .items = try params.toOwnedSlice(), .allocator = a };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the query / body parameters as key/value pairs, as strings.
|
||||||
|
/// Supported param types that will be converted:
|
||||||
|
///
|
||||||
|
/// - Bool
|
||||||
|
/// - Int
|
||||||
|
/// - Float
|
||||||
|
/// - String
|
||||||
|
///
|
||||||
|
/// At the moment, no fio ARRAYs are supported as well as HASH maps.
|
||||||
|
/// So, for JSON body payloads: parse the body instead.
|
||||||
|
///
|
||||||
|
/// Requires parseBody() and/or parseQuery() have been called.
|
||||||
|
/// Returned list needs to be deinited.
|
||||||
|
pub fn parametersToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_alloc: bool) anyerror!HttpParamStrKVList {
|
||||||
|
var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @as(usize, @intCast(self.getParamCount())));
|
||||||
|
var context: _parametersToOwnedStrSliceContext = .{
|
||||||
|
.params = ¶ms,
|
||||||
|
.allocator = a,
|
||||||
|
.always_alloc = always_alloc,
|
||||||
|
};
|
||||||
|
const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParamStr, &context);
|
||||||
|
if (howmany != self.getParamCount()) {
|
||||||
|
return error.HttpIterParams;
|
||||||
|
}
|
||||||
|
return .{ .items = try params.toOwnedSlice(), .allocator = a };
|
||||||
|
}
|
||||||
|
|
||||||
|
const _parametersToOwnedStrSliceContext = struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
params: *std.ArrayList(HttpParamStrKV),
|
||||||
|
last_error: ?anyerror = null,
|
||||||
|
always_alloc: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn _each_nextParamStr(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_int {
|
||||||
|
const ctx: *_parametersToOwnedStrSliceContext = @as(*_parametersToOwnedStrSliceContext, @ptrCast(@alignCast(context)));
|
||||||
|
// this is thread-safe, guaranteed by fio
|
||||||
|
const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
|
||||||
|
ctx.params.append(.{
|
||||||
|
.key = util.fio2strAllocOrNot(ctx.allocator, fiobj_key, ctx.always_alloc) catch |err| {
|
||||||
|
ctx.last_error = err;
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
.value = util.fio2strAllocOrNot(ctx.allocator, fiobj_value, ctx.always_alloc) catch |err| {
|
||||||
|
ctx.last_error = err;
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
}) catch |err| {
|
||||||
|
// what to do?
|
||||||
|
// signal the caller that an error occured by returning -1
|
||||||
|
// also, set the error
|
||||||
|
ctx.last_error = err;
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the query / body parameters as key/value pairs
|
||||||
|
/// Supported param types that will be converted:
|
||||||
|
///
|
||||||
|
/// - Bool
|
||||||
|
/// - Int
|
||||||
|
/// - Float
|
||||||
|
/// - String
|
||||||
|
///
|
||||||
|
/// At the moment, no fio ARRAYs are supported as well as HASH maps.
|
||||||
|
/// So, for JSON body payloads: parse the body instead.
|
||||||
|
///
|
||||||
|
/// Requires parseBody() and/or parseQuery() have been called.
|
||||||
|
/// Returned slice needs to be freed.
|
||||||
|
pub fn parametersToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings: bool) !HttpParamKVList {
|
||||||
|
var params = try std.ArrayList(HttpParamKV).initCapacity(a, @as(usize, @intCast(self.getParamCount())));
|
||||||
|
var context: _parametersToOwnedSliceContext = .{ .params = ¶ms, .allocator = a, .dupe_strings = dupe_strings };
|
||||||
|
const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParam, &context);
|
||||||
|
if (howmany != self.getParamCount()) {
|
||||||
|
return error.HttpIterParams;
|
||||||
|
}
|
||||||
|
return .{ .items = try params.toOwnedSlice(), .allocator = a };
|
||||||
|
}
|
||||||
|
|
||||||
|
const _parametersToOwnedSliceContext = struct {
|
||||||
|
params: *std.ArrayList(HttpParamKV),
|
||||||
|
last_error: ?anyerror = null,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
dupe_strings: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn _each_nextParam(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_int {
|
||||||
|
const ctx: *_parametersToOwnedSliceContext = @as(*_parametersToOwnedSliceContext, @ptrCast(@alignCast(context)));
|
||||||
|
// this is thread-safe, guaranteed by fio
|
||||||
|
const fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
|
||||||
|
ctx.params.append(.{
|
||||||
|
.key = util.fio2strAllocOrNot(ctx.allocator, fiobj_key, ctx.dupe_strings) catch |err| {
|
||||||
|
ctx.last_error = err;
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
.value = Fiobj2HttpParam(ctx.allocator, fiobj_value, ctx.dupe_strings) catch |err| {
|
||||||
|
ctx.last_error = err;
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
}) catch |err| {
|
||||||
|
// what to do?
|
||||||
|
// signal the caller that an error occured by returning -1
|
||||||
|
// also, set the error
|
||||||
|
ctx.last_error = err;
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get named parameter as string
|
||||||
|
/// Supported param types that will be converted:
|
||||||
|
///
|
||||||
|
/// - Bool
|
||||||
|
/// - Int
|
||||||
|
/// - Float
|
||||||
|
/// - String
|
||||||
|
///
|
||||||
|
/// At the moment, no fio ARRAYs are supported as well as HASH maps.
|
||||||
|
/// So, for JSON body payloads: parse the body instead.
|
||||||
|
///
|
||||||
|
/// Requires parseBody() and/or parseQuery() have been called.
|
||||||
|
/// The returned string needs to be deinited with .deinit()
|
||||||
|
pub fn getParamStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot {
|
||||||
|
if (self.h.*.params == 0) return null;
|
||||||
|
const key = fio.fiobj_str_new(name.ptr, name.len);
|
||||||
|
defer fio.fiobj_free_wrapped(key);
|
||||||
|
const value = fio.fiobj_hash_get(self.h.*.params, key);
|
||||||
|
if (value == fio.FIOBJ_INVALID) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return try util.fio2strAllocOrNot(a, value, always_alloc);
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const zap = @import("zap");
|
const zap = @import("zap");
|
||||||
// const Authenticators = @import("http_auth.zig");
|
// const Authenticators = @import("http_auth.zig");
|
||||||
const Authenticators = zap;
|
const Authenticators = zap.Auth;
|
||||||
const Endpoints = zap;
|
const Endpoint = zap.Endpoint;
|
||||||
// const Endpoints = @import("endpoint.zig");
|
|
||||||
const fio = zap;
|
const fio = zap;
|
||||||
// const fio = @import("fio.zig");
|
// const fio = @import("fio.zig");
|
||||||
const util = zap;
|
const util = zap;
|
||||||
|
@ -13,7 +12,7 @@ test "BearerAuthSingle authenticate" {
|
||||||
const a = std.testing.allocator;
|
const a = std.testing.allocator;
|
||||||
const token = "hello, world";
|
const token = "hello, world";
|
||||||
|
|
||||||
var auth = try Authenticators.BearerAuthSingle.init(a, token, null);
|
var auth = try Authenticators.BearerSingle.init(a, token, null);
|
||||||
defer auth.deinit();
|
defer auth.deinit();
|
||||||
|
|
||||||
// invalid auth header
|
// invalid auth header
|
||||||
|
@ -33,7 +32,7 @@ test "BearerAuthMulti authenticate" {
|
||||||
|
|
||||||
try set.put(token, {});
|
try set.put(token, {});
|
||||||
|
|
||||||
var auth = try Authenticators.BearerAuthMulti(Set).init(a, &set, null);
|
var auth = try Authenticators.BearerMulti(Set).init(a, &set, null);
|
||||||
defer auth.deinit();
|
defer auth.deinit();
|
||||||
|
|
||||||
// invalid auth header
|
// invalid auth header
|
||||||
|
@ -54,7 +53,7 @@ test "BasicAuth Token68" {
|
||||||
try set.put(token, {});
|
try set.put(token, {});
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = Authenticators.BasicAuth(Set, .Token68);
|
const Authenticator = Authenticators.Basic(Set, .Token68);
|
||||||
var auth = try Authenticator.init(a, &set, null);
|
var auth = try Authenticator.init(a, &set, null);
|
||||||
defer auth.deinit();
|
defer auth.deinit();
|
||||||
|
|
||||||
|
@ -85,7 +84,7 @@ test "BasicAuth UserPass" {
|
||||||
const encoded = encoder.encode(&buffer, token);
|
const encoded = encoder.encode(&buffer, token);
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = Authenticators.BasicAuth(Map, .UserPass);
|
const Authenticator = Authenticators.Basic(Map, .UserPass);
|
||||||
var auth = try Authenticator.init(a, &map, null);
|
var auth = try Authenticator.init(a, &map, null);
|
||||||
defer auth.deinit();
|
defer auth.deinit();
|
||||||
|
|
||||||
|
@ -106,7 +105,7 @@ const HTTP_RESPONSE: []const u8 =
|
||||||
;
|
;
|
||||||
var received_response: []const u8 = "null";
|
var received_response: []const u8 = "null";
|
||||||
|
|
||||||
fn endpoint_http_get(e: *Endpoints.Endpoint, r: zap.Request) void {
|
fn endpoint_http_get(e: *Endpoint, r: zap.Request) void {
|
||||||
_ = e;
|
_ = e;
|
||||||
r.sendBody(HTTP_RESPONSE) catch return;
|
r.sendBody(HTTP_RESPONSE) catch return;
|
||||||
received_response = HTTP_RESPONSE;
|
received_response = HTTP_RESPONSE;
|
||||||
|
@ -114,7 +113,7 @@ fn endpoint_http_get(e: *Endpoints.Endpoint, r: zap.Request) void {
|
||||||
zap.stop();
|
zap.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn endpoint_http_unauthorized(e: *Endpoints.Endpoint, r: zap.Request) void {
|
fn endpoint_http_unauthorized(e: *Endpoint, r: zap.Request) void {
|
||||||
_ = e;
|
_ = e;
|
||||||
r.setStatus(.unauthorized);
|
r.setStatus(.unauthorized);
|
||||||
r.sendBody("UNAUTHORIZED ACCESS") catch return;
|
r.sendBody("UNAUTHORIZED ACCESS") catch return;
|
||||||
|
@ -181,7 +180,7 @@ test "BearerAuthSingle authenticateRequest OK" {
|
||||||
const token = "ABCDEFG";
|
const token = "ABCDEFG";
|
||||||
|
|
||||||
// setup listener
|
// setup listener
|
||||||
var listener = zap.EndpointListener.init(
|
var listener = zap.Endpoint.Listener.init(
|
||||||
a,
|
a,
|
||||||
.{
|
.{
|
||||||
.port = 3000,
|
.port = 3000,
|
||||||
|
@ -194,19 +193,19 @@ test "BearerAuthSingle authenticateRequest OK" {
|
||||||
defer listener.deinit();
|
defer listener.deinit();
|
||||||
|
|
||||||
// create mini endpoint
|
// create mini endpoint
|
||||||
var ep = Endpoints.Endpoint.init(.{
|
var ep = Endpoint.init(.{
|
||||||
.path = "/test",
|
.path = "/test",
|
||||||
.get = endpoint_http_get,
|
.get = endpoint_http_get,
|
||||||
.unauthorized = endpoint_http_unauthorized,
|
.unauthorized = endpoint_http_unauthorized,
|
||||||
});
|
});
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = Authenticators.BearerAuthSingle;
|
const Authenticator = Authenticators.BearerSingle;
|
||||||
var authenticator = try Authenticator.init(a, token, null);
|
var authenticator = try Authenticator.init(a, token, null);
|
||||||
defer authenticator.deinit();
|
defer authenticator.deinit();
|
||||||
|
|
||||||
// create authenticating endpoint
|
// create authenticating endpoint
|
||||||
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
|
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator);
|
||||||
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
||||||
|
|
||||||
try listener.register(auth_ep.endpoint());
|
try listener.register(auth_ep.endpoint());
|
||||||
|
@ -234,7 +233,7 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" {
|
||||||
const token = "ABCDEFG";
|
const token = "ABCDEFG";
|
||||||
|
|
||||||
// setup listener
|
// setup listener
|
||||||
var listener = zap.EndpointListener.init(
|
var listener = zap.Endpoint.Listener.init(
|
||||||
a,
|
a,
|
||||||
.{
|
.{
|
||||||
.port = 3000,
|
.port = 3000,
|
||||||
|
@ -247,7 +246,7 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" {
|
||||||
defer listener.deinit();
|
defer listener.deinit();
|
||||||
|
|
||||||
// create mini endpoint
|
// create mini endpoint
|
||||||
var ep = Endpoints.Endpoint.init(.{
|
var ep = Endpoint.init(.{
|
||||||
.path = "/test",
|
.path = "/test",
|
||||||
.get = endpoint_http_get,
|
.get = endpoint_http_get,
|
||||||
.unauthorized = endpoint_http_unauthorized,
|
.unauthorized = endpoint_http_unauthorized,
|
||||||
|
@ -260,12 +259,12 @@ test "BearerAuthSingle authenticateRequest test-unauthorized" {
|
||||||
// insert auth tokens
|
// insert auth tokens
|
||||||
try set.put(token, {});
|
try set.put(token, {});
|
||||||
|
|
||||||
const Authenticator = Authenticators.BearerAuthMulti(Set);
|
const Authenticator = Authenticators.BearerMulti(Set);
|
||||||
var authenticator = try Authenticator.init(a, &set, null);
|
var authenticator = try Authenticator.init(a, &set, null);
|
||||||
defer authenticator.deinit();
|
defer authenticator.deinit();
|
||||||
|
|
||||||
// create authenticating endpoint
|
// create authenticating endpoint
|
||||||
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
|
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator);
|
||||||
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
||||||
|
|
||||||
try listener.register(auth_ep.endpoint());
|
try listener.register(auth_ep.endpoint());
|
||||||
|
@ -291,7 +290,7 @@ test "BearerAuthMulti authenticateRequest OK" {
|
||||||
const token = "ABCDEFG";
|
const token = "ABCDEFG";
|
||||||
|
|
||||||
// setup listener
|
// setup listener
|
||||||
var listener = zap.EndpointListener.init(
|
var listener = zap.Endpoint.Listener.init(
|
||||||
a,
|
a,
|
||||||
.{
|
.{
|
||||||
.port = 3000,
|
.port = 3000,
|
||||||
|
@ -304,19 +303,19 @@ test "BearerAuthMulti authenticateRequest OK" {
|
||||||
defer listener.deinit();
|
defer listener.deinit();
|
||||||
|
|
||||||
// create mini endpoint
|
// create mini endpoint
|
||||||
var ep = Endpoints.Endpoint.init(.{
|
var ep = Endpoint.init(.{
|
||||||
.path = "/test",
|
.path = "/test",
|
||||||
.get = endpoint_http_get,
|
.get = endpoint_http_get,
|
||||||
.unauthorized = endpoint_http_unauthorized,
|
.unauthorized = endpoint_http_unauthorized,
|
||||||
});
|
});
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = Authenticators.BearerAuthSingle;
|
const Authenticator = Authenticators.BearerSingle;
|
||||||
var authenticator = try Authenticator.init(a, token, null);
|
var authenticator = try Authenticator.init(a, token, null);
|
||||||
defer authenticator.deinit();
|
defer authenticator.deinit();
|
||||||
|
|
||||||
// create authenticating endpoint
|
// create authenticating endpoint
|
||||||
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
|
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator);
|
||||||
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
||||||
|
|
||||||
try listener.register(auth_ep.endpoint());
|
try listener.register(auth_ep.endpoint());
|
||||||
|
@ -342,7 +341,7 @@ test "BearerAuthMulti authenticateRequest test-unauthorized" {
|
||||||
const token = "invalid";
|
const token = "invalid";
|
||||||
|
|
||||||
// setup listener
|
// setup listener
|
||||||
var listener = zap.EndpointListener.init(
|
var listener = zap.Endpoint.Listener.init(
|
||||||
a,
|
a,
|
||||||
.{
|
.{
|
||||||
.port = 3000,
|
.port = 3000,
|
||||||
|
@ -355,19 +354,19 @@ test "BearerAuthMulti authenticateRequest test-unauthorized" {
|
||||||
defer listener.deinit();
|
defer listener.deinit();
|
||||||
|
|
||||||
// create mini endpoint
|
// create mini endpoint
|
||||||
var ep = Endpoints.Endpoint.init(.{
|
var ep = Endpoint.init(.{
|
||||||
.path = "/test",
|
.path = "/test",
|
||||||
.get = endpoint_http_get,
|
.get = endpoint_http_get,
|
||||||
.unauthorized = endpoint_http_unauthorized,
|
.unauthorized = endpoint_http_unauthorized,
|
||||||
});
|
});
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = Authenticators.BearerAuthSingle;
|
const Authenticator = Authenticators.BearerSingle;
|
||||||
var authenticator = try Authenticator.init(a, token, null);
|
var authenticator = try Authenticator.init(a, token, null);
|
||||||
defer authenticator.deinit();
|
defer authenticator.deinit();
|
||||||
|
|
||||||
// create authenticating endpoint
|
// create authenticating endpoint
|
||||||
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
|
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator);
|
||||||
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
||||||
|
|
||||||
try listener.register(auth_ep.endpoint());
|
try listener.register(auth_ep.endpoint());
|
||||||
|
@ -393,7 +392,7 @@ test "BasicAuth Token68 authenticateRequest" {
|
||||||
const token = "QWxhZGRpbjpvcGVuIHNlc2FtZQ==";
|
const token = "QWxhZGRpbjpvcGVuIHNlc2FtZQ==";
|
||||||
|
|
||||||
// setup listener
|
// setup listener
|
||||||
var listener = zap.EndpointListener.init(
|
var listener = zap.Endpoint.Listener.init(
|
||||||
a,
|
a,
|
||||||
.{
|
.{
|
||||||
.port = 3000,
|
.port = 3000,
|
||||||
|
@ -406,7 +405,7 @@ test "BasicAuth Token68 authenticateRequest" {
|
||||||
defer listener.deinit();
|
defer listener.deinit();
|
||||||
|
|
||||||
// create mini endpoint
|
// create mini endpoint
|
||||||
var ep = Endpoints.Endpoint.init(.{
|
var ep = Endpoint.init(.{
|
||||||
.path = "/test",
|
.path = "/test",
|
||||||
.get = endpoint_http_get,
|
.get = endpoint_http_get,
|
||||||
.unauthorized = endpoint_http_unauthorized,
|
.unauthorized = endpoint_http_unauthorized,
|
||||||
|
@ -418,12 +417,12 @@ test "BasicAuth Token68 authenticateRequest" {
|
||||||
try set.put(token, {});
|
try set.put(token, {});
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = Authenticators.BasicAuth(Set, .Token68);
|
const Authenticator = Authenticators.Basic(Set, .Token68);
|
||||||
var authenticator = try Authenticator.init(a, &set, null);
|
var authenticator = try Authenticator.init(a, &set, null);
|
||||||
defer authenticator.deinit();
|
defer authenticator.deinit();
|
||||||
|
|
||||||
// create authenticating endpoint
|
// create authenticating endpoint
|
||||||
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
|
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator);
|
||||||
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
||||||
|
|
||||||
try listener.register(auth_ep.endpoint());
|
try listener.register(auth_ep.endpoint());
|
||||||
|
@ -449,7 +448,7 @@ test "BasicAuth Token68 authenticateRequest test-unauthorized" {
|
||||||
const token = "QWxhZGRpbjpvcGVuIHNlc2FtZQ==";
|
const token = "QWxhZGRpbjpvcGVuIHNlc2FtZQ==";
|
||||||
|
|
||||||
// setup listener
|
// setup listener
|
||||||
var listener = zap.EndpointListener.init(
|
var listener = zap.Endpoint.Listener.init(
|
||||||
a,
|
a,
|
||||||
.{
|
.{
|
||||||
.port = 3000,
|
.port = 3000,
|
||||||
|
@ -462,7 +461,7 @@ test "BasicAuth Token68 authenticateRequest test-unauthorized" {
|
||||||
defer listener.deinit();
|
defer listener.deinit();
|
||||||
|
|
||||||
// create mini endpoint
|
// create mini endpoint
|
||||||
var ep = Endpoints.Endpoint.init(.{
|
var ep = Endpoint.init(.{
|
||||||
.path = "/test",
|
.path = "/test",
|
||||||
.get = endpoint_http_get,
|
.get = endpoint_http_get,
|
||||||
.unauthorized = endpoint_http_unauthorized,
|
.unauthorized = endpoint_http_unauthorized,
|
||||||
|
@ -474,12 +473,12 @@ test "BasicAuth Token68 authenticateRequest test-unauthorized" {
|
||||||
try set.put(token, {});
|
try set.put(token, {});
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = Authenticators.BasicAuth(Set, .Token68);
|
const Authenticator = Authenticators.Basic(Set, .Token68);
|
||||||
var authenticator = try Authenticator.init(a, &set, null);
|
var authenticator = try Authenticator.init(a, &set, null);
|
||||||
defer authenticator.deinit();
|
defer authenticator.deinit();
|
||||||
|
|
||||||
// create authenticating endpoint
|
// create authenticating endpoint
|
||||||
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
|
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator);
|
||||||
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
||||||
|
|
||||||
try listener.register(auth_ep.endpoint());
|
try listener.register(auth_ep.endpoint());
|
||||||
|
@ -504,7 +503,7 @@ test "BasicAuth UserPass authenticateRequest" {
|
||||||
const a = std.testing.allocator;
|
const a = std.testing.allocator;
|
||||||
|
|
||||||
// setup listener
|
// setup listener
|
||||||
var listener = zap.EndpointListener.init(
|
var listener = zap.Endpoint.Listener.init(
|
||||||
a,
|
a,
|
||||||
.{
|
.{
|
||||||
.port = 3000,
|
.port = 3000,
|
||||||
|
@ -517,7 +516,7 @@ test "BasicAuth UserPass authenticateRequest" {
|
||||||
defer listener.deinit();
|
defer listener.deinit();
|
||||||
|
|
||||||
// create mini endpoint
|
// create mini endpoint
|
||||||
var ep = Endpoints.Endpoint.init(.{
|
var ep = Endpoint.init(.{
|
||||||
.path = "/test",
|
.path = "/test",
|
||||||
.get = endpoint_http_get,
|
.get = endpoint_http_get,
|
||||||
.unauthorized = endpoint_http_unauthorized,
|
.unauthorized = endpoint_http_unauthorized,
|
||||||
|
@ -540,12 +539,12 @@ test "BasicAuth UserPass authenticateRequest" {
|
||||||
const encoded = encoder.encode(&buffer, token);
|
const encoded = encoder.encode(&buffer, token);
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = Authenticators.BasicAuth(Map, .UserPass);
|
const Authenticator = Authenticators.Basic(Map, .UserPass);
|
||||||
var authenticator = try Authenticator.init(a, &map, null);
|
var authenticator = try Authenticator.init(a, &map, null);
|
||||||
defer authenticator.deinit();
|
defer authenticator.deinit();
|
||||||
|
|
||||||
// create authenticating endpoint
|
// create authenticating endpoint
|
||||||
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
|
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator);
|
||||||
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
||||||
|
|
||||||
try listener.register(auth_ep.endpoint());
|
try listener.register(auth_ep.endpoint());
|
||||||
|
@ -570,7 +569,7 @@ test "BasicAuth UserPass authenticateRequest test-unauthorized" {
|
||||||
const a = std.testing.allocator;
|
const a = std.testing.allocator;
|
||||||
|
|
||||||
// setup listener
|
// setup listener
|
||||||
var listener = zap.EndpointListener.init(
|
var listener = zap.Endpoint.Listener.init(
|
||||||
a,
|
a,
|
||||||
.{
|
.{
|
||||||
.port = 3000,
|
.port = 3000,
|
||||||
|
@ -583,7 +582,7 @@ test "BasicAuth UserPass authenticateRequest test-unauthorized" {
|
||||||
defer listener.deinit();
|
defer listener.deinit();
|
||||||
|
|
||||||
// create mini endpoint
|
// create mini endpoint
|
||||||
var ep = Endpoints.Endpoint.init(.{
|
var ep = Endpoint.init(.{
|
||||||
.path = "/test",
|
.path = "/test",
|
||||||
.get = endpoint_http_get,
|
.get = endpoint_http_get,
|
||||||
.unauthorized = endpoint_http_unauthorized,
|
.unauthorized = endpoint_http_unauthorized,
|
||||||
|
@ -607,12 +606,12 @@ test "BasicAuth UserPass authenticateRequest test-unauthorized" {
|
||||||
_ = encoded;
|
_ = encoded;
|
||||||
|
|
||||||
// create authenticator
|
// create authenticator
|
||||||
const Authenticator = Authenticators.BasicAuth(Map, .UserPass);
|
const Authenticator = Authenticators.Basic(Map, .UserPass);
|
||||||
var authenticator = try Authenticator.init(a, &map, null);
|
var authenticator = try Authenticator.init(a, &map, null);
|
||||||
defer authenticator.deinit();
|
defer authenticator.deinit();
|
||||||
|
|
||||||
// create authenticating endpoint
|
// create authenticating endpoint
|
||||||
const BearerAuthEndpoint = Endpoints.AuthenticatingEndpoint(Authenticator);
|
const BearerAuthEndpoint = Endpoint.Authenticating(Authenticator);
|
||||||
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
var auth_ep = BearerAuthEndpoint.init(&ep, &authenticator);
|
||||||
|
|
||||||
try listener.register(auth_ep.endpoint());
|
try listener.register(auth_ep.endpoint());
|
||||||
|
|
|
@ -30,8 +30,8 @@ test "http parameters" {
|
||||||
var ran: bool = false;
|
var ran: bool = false;
|
||||||
var param_count: isize = 0;
|
var param_count: isize = 0;
|
||||||
|
|
||||||
var strParams: ?zap.HttpParamStrKVList = null;
|
var strParams: ?zap.Request.HttpParamStrKVList = null;
|
||||||
var params: ?zap.HttpParamKVList = null;
|
var params: ?zap.Request.HttpParamKVList = null;
|
||||||
var paramOneStr: ?zap.FreeOrNot = null;
|
var paramOneStr: ?zap.FreeOrNot = null;
|
||||||
|
|
||||||
pub fn on_request(r: zap.Request) void {
|
pub fn on_request(r: zap.Request) void {
|
||||||
|
|
778
src/zap.zig
778
src/zap.zig
|
@ -9,12 +9,63 @@ pub const fio = @import("fio.zig");
|
||||||
/// Server-Side TLS function wrapper
|
/// Server-Side TLS function wrapper
|
||||||
pub const Tls = @import("tls.zig");
|
pub const Tls = @import("tls.zig");
|
||||||
|
|
||||||
// pub usingnamespace @import("fio.zig");
|
/// Endpoint and supporting types.
|
||||||
pub usingnamespace @import("endpoint.zig");
|
/// Create one and pass in your callbacks. Then,
|
||||||
|
/// pass it to a HttpListener's `register()` function to register with the
|
||||||
|
/// listener.
|
||||||
|
///
|
||||||
|
/// **NOTE**: A common endpoint pattern for zap is to create your own struct
|
||||||
|
/// that embeds an Endpoint, provides specific callbacks, and uses
|
||||||
|
/// `@fieldParentPtr` to get a reference to itself.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// A simple endpoint listening on the /stop route that shuts down zap.
|
||||||
|
/// The main thread usually continues at the instructions after the call to zap.start().
|
||||||
|
///
|
||||||
|
/// ```zig
|
||||||
|
/// const StopEndpoint = struct {
|
||||||
|
/// ep: zap.Endpoint = undefined,
|
||||||
|
///
|
||||||
|
/// pub fn init(
|
||||||
|
/// path: []const u8,
|
||||||
|
/// ) StopEndpoint {
|
||||||
|
/// return .{
|
||||||
|
/// .ep = zap.Endpoint.init(.{
|
||||||
|
/// .path = path,
|
||||||
|
/// .get = get,
|
||||||
|
/// }),
|
||||||
|
/// };
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // access the internal Endpoint
|
||||||
|
/// pub fn endpoint(self: *StopEndpoint) *zap.Endpoint {
|
||||||
|
/// return &self.ep;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn get(e: *zap.Endpoint, r: zap.Request) void {
|
||||||
|
/// const self: *StopEndpoint = @fieldParentPtr(StopEndpoint, "ep", e);
|
||||||
|
/// _ = self;
|
||||||
|
/// _ = r;
|
||||||
|
/// zap.stop();
|
||||||
|
/// }
|
||||||
|
/// };
|
||||||
|
/// ```
|
||||||
|
pub const Endpoint = @import("endpoint.zig");
|
||||||
|
|
||||||
pub usingnamespace @import("util.zig");
|
pub usingnamespace @import("util.zig");
|
||||||
pub usingnamespace @import("http.zig");
|
pub usingnamespace @import("http.zig");
|
||||||
pub usingnamespace @import("mustache.zig");
|
|
||||||
pub usingnamespace @import("http_auth.zig");
|
/// A struct to handle Mustache templating.
|
||||||
|
///
|
||||||
|
/// This is a wrapper around fiobj's mustache template handling.
|
||||||
|
/// See http://facil.io/0.7.x/fiobj_mustache for more information.
|
||||||
|
pub const Mustache = @import("mustache.zig");
|
||||||
|
|
||||||
|
/// Authenticators
|
||||||
|
pub const Auth = @import("http_auth.zig");
|
||||||
|
|
||||||
|
/// Http request and supporting types.
|
||||||
|
pub const Request = @import("request.zig");
|
||||||
|
|
||||||
/// Middleware support.
|
/// Middleware support.
|
||||||
/// Contains a special Listener and a Handler struct that support chaining
|
/// Contains a special Listener and a Handler struct that support chaining
|
||||||
|
@ -93,721 +144,6 @@ pub const ContentType = enum {
|
||||||
// TODO: more content types
|
// TODO: more content types
|
||||||
};
|
};
|
||||||
|
|
||||||
/// HttpRequest passed to request callback functions.
|
|
||||||
pub const Request = struct {
|
|
||||||
path: ?[]const u8,
|
|
||||||
query: ?[]const u8,
|
|
||||||
body: ?[]const u8,
|
|
||||||
method: ?[]const u8,
|
|
||||||
h: [*c]fio.http_s,
|
|
||||||
|
|
||||||
/// NEVER touch this field!!!!
|
|
||||||
/// if you absolutely MUST, then you may provide context here
|
|
||||||
/// via setUserContext and getUserContext
|
|
||||||
_user_context: *UserContext,
|
|
||||||
/// NEVER touch this field!!!!
|
|
||||||
/// use markAsFinished() and isFinished() instead
|
|
||||||
/// this is a hack: the listener will put a pointer to this into the udata
|
|
||||||
/// field of `h`. So copies of the Request will all have way to the
|
|
||||||
/// same instance of this field.
|
|
||||||
_is_finished_request_global: bool,
|
|
||||||
/// NEVER touch this field!!!!
|
|
||||||
/// this is part of the hack.
|
|
||||||
_is_finished: *bool = undefined,
|
|
||||||
|
|
||||||
const UserContext = struct {
|
|
||||||
user_context: ?*anyopaque = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
/// mark the current request as finished. Important for middleware-style
|
|
||||||
/// request handler chaining. Called when sending a body, redirecting, etc.
|
|
||||||
pub fn markAsFinished(self: *const Self, finished: bool) void {
|
|
||||||
// we might be a copy
|
|
||||||
self._is_finished.* = finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// tell whether request processing has finished. (e.g. response sent,
|
|
||||||
/// redirected, ...)
|
|
||||||
pub fn isFinished(self: *const Self) bool {
|
|
||||||
// we might be a copy
|
|
||||||
return self._is_finished.*;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// if you absolutely must, you can set any context on the request here
|
|
||||||
// (note, this line is linked to from the readme) -- TODO: sync
|
|
||||||
pub fn setUserContext(self: *const Self, context: *anyopaque) void {
|
|
||||||
self._user_context.*.user_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get the associated user context of the request.
|
|
||||||
pub fn getUserContext(self: *const Self, comptime Context: type) ?*Context {
|
|
||||||
if (self._user_context.*.user_context) |ptr| {
|
|
||||||
return @as(*Context, @ptrCast(@alignCast(ptr)));
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to send an error stack trace.
|
|
||||||
pub fn sendError(self: *const Self, err: anyerror, errorcode_num: usize) void {
|
|
||||||
// TODO: query accept headers
|
|
||||||
if (self._internal_sendError(err, errorcode_num)) {
|
|
||||||
return;
|
|
||||||
} else |_| {
|
|
||||||
self.sendBody(@errorName(err)) catch return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used internally. Probably does not need to be public.
|
|
||||||
pub fn _internal_sendError(self: *const Self, err: anyerror, errorcode_num: usize) !void {
|
|
||||||
// TODO: query accept headers
|
|
||||||
// TODO: let's hope 20k is enough. Maybe just really allocate here
|
|
||||||
self.h.*.status = errorcode_num;
|
|
||||||
var buf: [20 * 1024]u8 = undefined;
|
|
||||||
var fba = std.heap.FixedBufferAllocator.init(&buf);
|
|
||||||
var string = std.ArrayList(u8).init(fba.allocator());
|
|
||||||
var writer = string.writer();
|
|
||||||
try writer.print("ERROR: {any}\n\n", .{err});
|
|
||||||
|
|
||||||
var debugInfo = try std.debug.getSelfDebugInfo();
|
|
||||||
var ttyConfig: std.io.tty.Config = .no_color;
|
|
||||||
try std.debug.writeCurrentStackTrace(writer, debugInfo, ttyConfig, null);
|
|
||||||
try self.sendBody(string.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send body.
|
|
||||||
pub fn sendBody(self: *const Self, body: []const u8) HttpError!void {
|
|
||||||
const ret = fio.http_send_body(self.h, @as(
|
|
||||||
*anyopaque,
|
|
||||||
@ptrFromInt(@intFromPtr(body.ptr)),
|
|
||||||
), body.len);
|
|
||||||
debug("Request.sendBody(): ret = {}\n", .{ret});
|
|
||||||
if (ret == -1) return error.HttpSendBody;
|
|
||||||
self.markAsFinished(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set content type and send json buffer.
|
|
||||||
pub fn sendJson(self: *const Self, json: []const u8) HttpError!void {
|
|
||||||
if (self.setContentType(.JSON)) {
|
|
||||||
if (fio.http_send_body(self.h, @as(
|
|
||||||
*anyopaque,
|
|
||||||
@ptrFromInt(@intFromPtr(json.ptr)),
|
|
||||||
), json.len) != 0) return error.HttpSendBody;
|
|
||||||
self.markAsFinished(true);
|
|
||||||
} else |err| return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set content type.
|
|
||||||
pub fn setContentType(self: *const Self, c: ContentType) HttpError!void {
|
|
||||||
const s = switch (c) {
|
|
||||||
.TEXT => "text/plain",
|
|
||||||
.JSON => "application/json",
|
|
||||||
else => "text/html",
|
|
||||||
};
|
|
||||||
debug("setting content-type to {s}\n", .{s});
|
|
||||||
return self.setHeader("content-type", s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// redirect to path with status code 302 by default
|
|
||||||
pub fn redirectTo(self: *const Self, path: []const u8, code: ?http.StatusCode) HttpError!void {
|
|
||||||
self.setStatus(if (code) |status| status else .found);
|
|
||||||
try self.setHeader("Location", path);
|
|
||||||
try self.sendBody("moved");
|
|
||||||
self.markAsFinished(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// shows how to use the logger
|
|
||||||
pub fn setContentTypeWithLogger(
|
|
||||||
self: *const Self,
|
|
||||||
c: ContentType,
|
|
||||||
logger: *const Log,
|
|
||||||
) HttpError!void {
|
|
||||||
const s = switch (c) {
|
|
||||||
.TEXT => "text/plain",
|
|
||||||
.JSON => "application/json",
|
|
||||||
else => "text/html",
|
|
||||||
};
|
|
||||||
logger.log("setting content-type to {s}\n", .{s});
|
|
||||||
return self.setHeader("content-type", s);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to determine the content type by file extension of request path, and sets it.
|
|
||||||
pub fn setContentTypeFromPath(self: *const Self) !void {
|
|
||||||
const t = fio.http_mimetype_find2(self.h.*.path);
|
|
||||||
if (fio.is_invalid(t) == 1) return error.HttpSetContentType;
|
|
||||||
const ret = fio.fiobj_hash_set(
|
|
||||||
self.h.*.private_data.out_headers,
|
|
||||||
fio.HTTP_HEADER_CONTENT_TYPE,
|
|
||||||
t,
|
|
||||||
);
|
|
||||||
if (ret == -1) return error.HttpSetContentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to determine the content type by filename extension, and sets it.
|
|
||||||
/// If the extension cannot be determined, NoExtensionInFilename error is
|
|
||||||
/// returned.
|
|
||||||
pub fn setContentTypeFromFilename(self: *const Self, filename: []const u8) !void {
|
|
||||||
const ext = std.fs.path.extension(filename);
|
|
||||||
|
|
||||||
if (ext.len > 1) {
|
|
||||||
const e = ext[1..];
|
|
||||||
const obj = fio.http_mimetype_find(@constCast(e.ptr), e.len);
|
|
||||||
|
|
||||||
if (util.fio2str(obj)) |mime_str| {
|
|
||||||
try self.setHeader("content-type", mime_str);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return error.NoExtensionInFilename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the header value of given key name. Returned mem is temp.
|
|
||||||
/// Do not free it.
|
|
||||||
pub fn getHeader(self: *const Self, name: []const u8) ?[]const u8 {
|
|
||||||
const hname = fio.fiobj_str_new(util.toCharPtr(name), name.len);
|
|
||||||
defer fio.fiobj_free_wrapped(hname);
|
|
||||||
return util.fio2str(fio.fiobj_hash_get(self.h.*.headers, hname));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set header.
|
|
||||||
pub fn setHeader(self: *const Self, name: []const u8, value: []const u8) HttpError!void {
|
|
||||||
const hname: fio.fio_str_info_s = .{
|
|
||||||
.data = util.toCharPtr(name),
|
|
||||||
.len = name.len,
|
|
||||||
.capa = name.len,
|
|
||||||
};
|
|
||||||
|
|
||||||
debug("setHeader: hname = {s}\n", .{name});
|
|
||||||
const vname: fio.fio_str_info_s = .{
|
|
||||||
.data = util.toCharPtr(value),
|
|
||||||
.len = value.len,
|
|
||||||
.capa = value.len,
|
|
||||||
};
|
|
||||||
debug("setHeader: vname = {s}\n", .{value});
|
|
||||||
const ret = fio.http_set_header2(self.h, hname, vname);
|
|
||||||
|
|
||||||
// FIXME without the following if, we get errors in release builds
|
|
||||||
// at least we don't have to log unconditionally
|
|
||||||
if (ret == -1) {
|
|
||||||
std.debug.print("***************** zap.zig:274\n", .{});
|
|
||||||
}
|
|
||||||
debug("setHeader: ret = {}\n", .{ret});
|
|
||||||
|
|
||||||
if (ret == 0) return;
|
|
||||||
return error.HttpSetHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set status by numeric value.
|
|
||||||
pub fn setStatusNumeric(self: *const Self, status: usize) void {
|
|
||||||
self.h.*.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set status by enum.
|
|
||||||
pub fn setStatus(self: *const Self, status: http.StatusCode) void {
|
|
||||||
self.h.*.status = @as(usize, @intCast(@intFromEnum(status)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sends a file if present in the filesystem orelse returns an error.
|
|
||||||
///
|
|
||||||
/// - efficiently sends a file using gzip compression
|
|
||||||
/// - also handles range requests if `Range` or `If-Range` headers are present in the request.
|
|
||||||
/// - sends the response headers and the specified file (the response's body).
|
|
||||||
///
|
|
||||||
/// On success, the `self.h` handle will be consumed and invalid.
|
|
||||||
/// On error, the handle will still be valid and should be used to send an error response
|
|
||||||
///
|
|
||||||
/// Important: sets last-modified and cache-control headers with a max-age value of 1 hour!
|
|
||||||
/// You can override that by setting those headers yourself, e.g.: setHeader("Cache-Control", "no-cache")
|
|
||||||
pub fn sendFile(self: *const Self, file_path: []const u8) !void {
|
|
||||||
if (fio.http_sendfile2(self.h, util.toCharPtr(file_path), file_path.len, null, 0) != 0)
|
|
||||||
return error.SendFile;
|
|
||||||
self.markAsFinished(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempts to decode the request's body.
|
|
||||||
/// This should be called BEFORE parseQuery
|
|
||||||
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice()
|
|
||||||
///
|
|
||||||
/// Supported body types:
|
|
||||||
/// - application/x-www-form-urlencoded
|
|
||||||
/// - application/json
|
|
||||||
/// - multipart/form-data
|
|
||||||
pub fn parseBody(self: *const Self) HttpError!void {
|
|
||||||
if (fio.http_parse_body(self.h) == -1) return error.HttpParseBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses the query part of an HTTP request
|
|
||||||
/// This should be called AFTER parseBody(), just in case the body is a JSON
|
|
||||||
/// object that doesn't have a hash map at its root.
|
|
||||||
///
|
|
||||||
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice()
|
|
||||||
pub fn parseQuery(self: *const Self) void {
|
|
||||||
fio.http_parse_query(self.h);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse received cookie headers
|
|
||||||
pub fn parseCookies(self: *const Self, url_encoded: bool) void {
|
|
||||||
fio.http_parse_cookies(self.h, if (url_encoded) 1 else 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a response cookie
|
|
||||||
pub fn setCookie(self: *const Self, args: CookieArgs) HttpError!void {
|
|
||||||
var c: fio.http_cookie_args_s = .{
|
|
||||||
.name = util.toCharPtr(args.name),
|
|
||||||
.name_len = @as(isize, @intCast(args.name.len)),
|
|
||||||
.value = util.toCharPtr(args.value),
|
|
||||||
.value_len = @as(isize, @intCast(args.value.len)),
|
|
||||||
.domain = if (args.domain) |p| util.toCharPtr(p) else null,
|
|
||||||
.domain_len = if (args.domain) |p| @as(isize, @intCast(p.len)) else 0,
|
|
||||||
.path = if (args.path) |p| util.toCharPtr(p) else null,
|
|
||||||
.path_len = if (args.path) |p| @as(isize, @intCast(p.len)) else 0,
|
|
||||||
.max_age = args.max_age_s,
|
|
||||||
.secure = if (args.secure) 1 else 0,
|
|
||||||
.http_only = if (args.http_only) 1 else 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO WAT?
|
|
||||||
// if we:
|
|
||||||
// if(fio.http_set_cookie(...) == -1)
|
|
||||||
// instead of capturing it in `ret` first and then checking it,
|
|
||||||
// all ReleaseXXX builds return an error!
|
|
||||||
// TODO: still happening?
|
|
||||||
const ret = fio.http_set_cookie(self.h, c);
|
|
||||||
if (ret == -1) {
|
|
||||||
std.log.err("fio.http_set_cookie returned: {}\n", .{ret});
|
|
||||||
return error.SetCookie;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns named cookie. Works like getParamStr().
|
|
||||||
pub fn getCookieStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot {
|
|
||||||
if (self.h.*.cookies == 0) return null;
|
|
||||||
const key = fio.fiobj_str_new(name.ptr, name.len);
|
|
||||||
defer fio.fiobj_free_wrapped(key);
|
|
||||||
const value = fio.fiobj_hash_get(self.h.*.cookies, key);
|
|
||||||
if (value == fio.FIOBJ_INVALID) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return try util.fio2strAllocOrNot(a, value, always_alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of cookies after parsing.
|
|
||||||
///
|
|
||||||
/// Parse with parseCookies()
|
|
||||||
pub fn getCookiesCount(self: *const Self) isize {
|
|
||||||
if (self.h.*.cookies == 0) return 0;
|
|
||||||
return fio.fiobj_obj2num(self.h.*.cookies);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of parameters after parsing.
|
|
||||||
///
|
|
||||||
/// Parse with parseBody() and / or parseQuery()
|
|
||||||
pub fn getParamCount(self: *const Self) isize {
|
|
||||||
if (self.h.*.params == 0) return 0;
|
|
||||||
return fio.fiobj_obj2num(self.h.*.params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same as parametersToOwnedStrList() but for cookies
|
|
||||||
pub fn cookiesToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_alloc: bool) anyerror!HttpParamStrKVList {
|
|
||||||
var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @as(usize, @intCast(self.getCookiesCount())));
|
|
||||||
var context: _parametersToOwnedStrSliceContext = .{
|
|
||||||
.params = ¶ms,
|
|
||||||
.allocator = a,
|
|
||||||
.always_alloc = always_alloc,
|
|
||||||
};
|
|
||||||
const howmany = fio.fiobj_each1(self.h.*.cookies, 0, _each_nextParamStr, &context);
|
|
||||||
if (howmany != self.getCookiesCount()) {
|
|
||||||
return error.HttpIterParams;
|
|
||||||
}
|
|
||||||
return .{ .items = try params.toOwnedSlice(), .allocator = a };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same as parametersToOwnedList() but for cookies
|
|
||||||
pub fn cookiesToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings: bool) !HttpParamKVList {
|
|
||||||
var params = try std.ArrayList(HttpParamKV).initCapacity(a, @as(usize, @intCast(self.getCookiesCount())));
|
|
||||||
var context: _parametersToOwnedSliceContext = .{ .params = ¶ms, .allocator = a, .dupe_strings = dupe_strings };
|
|
||||||
const howmany = fio.fiobj_each1(self.h.*.cookies, 0, _each_nextParam, &context);
|
|
||||||
if (howmany != self.getCookiesCount()) {
|
|
||||||
return error.HttpIterParams;
|
|
||||||
}
|
|
||||||
return .{ .items = try params.toOwnedSlice(), .allocator = a };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the query / body parameters as key/value pairs, as strings.
|
|
||||||
/// Supported param types that will be converted:
|
|
||||||
///
|
|
||||||
/// - Bool
|
|
||||||
/// - Int
|
|
||||||
/// - Float
|
|
||||||
/// - String
|
|
||||||
///
|
|
||||||
/// At the moment, no fio ARRAYs are supported as well as HASH maps.
|
|
||||||
/// So, for JSON body payloads: parse the body instead.
|
|
||||||
///
|
|
||||||
/// Requires parseBody() and/or parseQuery() have been called.
|
|
||||||
/// Returned list needs to be deinited.
|
|
||||||
pub fn parametersToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_alloc: bool) anyerror!HttpParamStrKVList {
|
|
||||||
var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @as(usize, @intCast(self.getParamCount())));
|
|
||||||
var context: _parametersToOwnedStrSliceContext = .{
|
|
||||||
.params = ¶ms,
|
|
||||||
.allocator = a,
|
|
||||||
.always_alloc = always_alloc,
|
|
||||||
};
|
|
||||||
const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParamStr, &context);
|
|
||||||
if (howmany != self.getParamCount()) {
|
|
||||||
return error.HttpIterParams;
|
|
||||||
}
|
|
||||||
return .{ .items = try params.toOwnedSlice(), .allocator = a };
|
|
||||||
}
|
|
||||||
|
|
||||||
const _parametersToOwnedStrSliceContext = struct {
|
|
||||||
allocator: std.mem.Allocator,
|
|
||||||
params: *std.ArrayList(HttpParamStrKV),
|
|
||||||
last_error: ?anyerror = null,
|
|
||||||
always_alloc: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn _each_nextParamStr(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_int {
|
|
||||||
const ctx: *_parametersToOwnedStrSliceContext = @as(*_parametersToOwnedStrSliceContext, @ptrCast(@alignCast(context)));
|
|
||||||
// this is thread-safe, guaranteed by fio
|
|
||||||
var fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
|
|
||||||
ctx.params.append(.{
|
|
||||||
.key = util.fio2strAllocOrNot(ctx.allocator, fiobj_key, ctx.always_alloc) catch |err| {
|
|
||||||
ctx.last_error = err;
|
|
||||||
return -1;
|
|
||||||
},
|
|
||||||
.value = util.fio2strAllocOrNot(ctx.allocator, fiobj_value, ctx.always_alloc) catch |err| {
|
|
||||||
ctx.last_error = err;
|
|
||||||
return -1;
|
|
||||||
},
|
|
||||||
}) catch |err| {
|
|
||||||
// what to do?
|
|
||||||
// signal the caller that an error occured by returning -1
|
|
||||||
// also, set the error
|
|
||||||
ctx.last_error = err;
|
|
||||||
return -1;
|
|
||||||
};
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the query / body parameters as key/value pairs
|
|
||||||
/// Supported param types that will be converted:
|
|
||||||
///
|
|
||||||
/// - Bool
|
|
||||||
/// - Int
|
|
||||||
/// - Float
|
|
||||||
/// - String
|
|
||||||
///
|
|
||||||
/// At the moment, no fio ARRAYs are supported as well as HASH maps.
|
|
||||||
/// So, for JSON body payloads: parse the body instead.
|
|
||||||
///
|
|
||||||
/// Requires parseBody() and/or parseQuery() have been called.
|
|
||||||
/// Returned slice needs to be freed.
|
|
||||||
pub fn parametersToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings: bool) !HttpParamKVList {
|
|
||||||
var params = try std.ArrayList(HttpParamKV).initCapacity(a, @as(usize, @intCast(self.getParamCount())));
|
|
||||||
var context: _parametersToOwnedSliceContext = .{ .params = ¶ms, .allocator = a, .dupe_strings = dupe_strings };
|
|
||||||
const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParam, &context);
|
|
||||||
if (howmany != self.getParamCount()) {
|
|
||||||
return error.HttpIterParams;
|
|
||||||
}
|
|
||||||
return .{ .items = try params.toOwnedSlice(), .allocator = a };
|
|
||||||
}
|
|
||||||
|
|
||||||
const _parametersToOwnedSliceContext = struct {
|
|
||||||
params: *std.ArrayList(HttpParamKV),
|
|
||||||
last_error: ?anyerror = null,
|
|
||||||
allocator: std.mem.Allocator,
|
|
||||||
dupe_strings: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn _each_nextParam(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_int {
|
|
||||||
const ctx: *_parametersToOwnedSliceContext = @as(*_parametersToOwnedSliceContext, @ptrCast(@alignCast(context)));
|
|
||||||
// this is thread-safe, guaranteed by fio
|
|
||||||
var fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
|
|
||||||
ctx.params.append(.{
|
|
||||||
.key = util.fio2strAllocOrNot(ctx.allocator, fiobj_key, ctx.dupe_strings) catch |err| {
|
|
||||||
ctx.last_error = err;
|
|
||||||
return -1;
|
|
||||||
},
|
|
||||||
.value = Fiobj2HttpParam(ctx.allocator, fiobj_value, ctx.dupe_strings) catch |err| {
|
|
||||||
ctx.last_error = err;
|
|
||||||
return -1;
|
|
||||||
},
|
|
||||||
}) catch |err| {
|
|
||||||
// what to do?
|
|
||||||
// signal the caller that an error occured by returning -1
|
|
||||||
// also, set the error
|
|
||||||
ctx.last_error = err;
|
|
||||||
return -1;
|
|
||||||
};
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get named parameter as string
|
|
||||||
/// Supported param types that will be converted:
|
|
||||||
///
|
|
||||||
/// - Bool
|
|
||||||
/// - Int
|
|
||||||
/// - Float
|
|
||||||
/// - String
|
|
||||||
///
|
|
||||||
/// At the moment, no fio ARRAYs are supported as well as HASH maps.
|
|
||||||
/// So, for JSON body payloads: parse the body instead.
|
|
||||||
///
|
|
||||||
/// Requires parseBody() and/or parseQuery() have been called.
|
|
||||||
/// The returned string needs to be deinited with .deinit()
|
|
||||||
pub fn getParamStr(self: *const Self, a: std.mem.Allocator, name: []const u8, always_alloc: bool) !?util.FreeOrNot {
|
|
||||||
if (self.h.*.params == 0) return null;
|
|
||||||
const key = fio.fiobj_str_new(name.ptr, name.len);
|
|
||||||
defer fio.fiobj_free_wrapped(key);
|
|
||||||
const value = fio.fiobj_hash_get(self.h.*.params, key);
|
|
||||||
if (value == fio.FIOBJ_INVALID) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return try util.fio2strAllocOrNot(a, value, always_alloc);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Key value pair of strings from HTTP parameters
|
|
||||||
pub const HttpParamStrKV = struct {
|
|
||||||
key: util.FreeOrNot,
|
|
||||||
value: util.FreeOrNot,
|
|
||||||
pub fn deinit(self: *@This()) void {
|
|
||||||
self.key.deinit();
|
|
||||||
self.value.deinit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// List of key value pairs of Http param strings.
|
|
||||||
pub const HttpParamStrKVList = struct {
|
|
||||||
items: []HttpParamStrKV,
|
|
||||||
allocator: std.mem.Allocator,
|
|
||||||
pub fn deinit(self: *@This()) void {
|
|
||||||
for (self.items) |*item| {
|
|
||||||
item.deinit();
|
|
||||||
}
|
|
||||||
self.allocator.free(self.items);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// List of key value pairs of Http params (might be of different types).
|
|
||||||
pub const HttpParamKVList = struct {
|
|
||||||
items: []HttpParamKV,
|
|
||||||
allocator: std.mem.Allocator,
|
|
||||||
pub fn deinit(self: *const @This()) void {
|
|
||||||
for (self.items) |*item| {
|
|
||||||
item.deinit();
|
|
||||||
}
|
|
||||||
self.allocator.free(self.items);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Enum for HttpParam tagged union
|
|
||||||
pub const HttpParamValueType = enum {
|
|
||||||
// Null,
|
|
||||||
Bool,
|
|
||||||
Int,
|
|
||||||
Float,
|
|
||||||
String,
|
|
||||||
Unsupported,
|
|
||||||
Hash_Binfile,
|
|
||||||
Array_Binfile,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Tagged union holding a typed Http param
|
|
||||||
pub const HttpParam = union(HttpParamValueType) {
|
|
||||||
Bool: bool,
|
|
||||||
Int: isize,
|
|
||||||
Float: f64,
|
|
||||||
/// we don't do writable strings here
|
|
||||||
String: util.FreeOrNot,
|
|
||||||
/// value will always be null
|
|
||||||
Unsupported: ?void,
|
|
||||||
/// we assume hashes are because of file transmissions
|
|
||||||
Hash_Binfile: HttpParamBinaryFile,
|
|
||||||
/// value will always be null
|
|
||||||
Array_Binfile: std.ArrayList(HttpParamBinaryFile),
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Key value pair of one typed Http param
|
|
||||||
pub const HttpParamKV = struct {
|
|
||||||
key: util.FreeOrNot,
|
|
||||||
value: ?HttpParam,
|
|
||||||
pub fn deinit(self: *@This()) void {
|
|
||||||
self.key.deinit();
|
|
||||||
if (self.value) |p| {
|
|
||||||
switch (p) {
|
|
||||||
.String => |*s| s.deinit(),
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Struct representing an uploaded file.
|
|
||||||
pub const HttpParamBinaryFile = struct {
|
|
||||||
/// file contents
|
|
||||||
data: ?[]const u8 = null,
|
|
||||||
/// mimetype
|
|
||||||
mimetype: ?[]const u8 = null,
|
|
||||||
/// filename
|
|
||||||
filename: ?[]const u8 = null,
|
|
||||||
|
|
||||||
/// format function for printing file upload data
|
|
||||||
pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) std.os.WriteError!void {
|
|
||||||
const d = value.data orelse "\\0";
|
|
||||||
const m = value.mimetype orelse "null";
|
|
||||||
const f = value.filename orelse "null";
|
|
||||||
return writer.print("<{s} ({s}): {any}>", .{ f, m, d });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn parseBinfilesFrom(a: std.mem.Allocator, o: fio.FIOBJ) !HttpParam {
|
|
||||||
const key_name = fio.fiobj_str_new("name", 4);
|
|
||||||
const key_data = fio.fiobj_str_new("data", 4);
|
|
||||||
const key_type = fio.fiobj_str_new("type", 4);
|
|
||||||
defer {
|
|
||||||
fio.fiobj_free_wrapped(key_name);
|
|
||||||
fio.fiobj_free_wrapped(key_data);
|
|
||||||
fio.fiobj_free_wrapped(key_type);
|
|
||||||
} // files: they should have "data", "type", and "filename" keys
|
|
||||||
if (fio.fiobj_hash_haskey(o, key_data) == 1 and fio.fiobj_hash_haskey(o, key_type) == 1 and fio.fiobj_hash_haskey(o, key_name) == 1) {
|
|
||||||
const filename = fio.fiobj_obj2cstr(fio.fiobj_hash_get(o, key_name));
|
|
||||||
const mimetype = fio.fiobj_obj2cstr(fio.fiobj_hash_get(o, key_type));
|
|
||||||
const data = fio.fiobj_hash_get(o, key_data);
|
|
||||||
|
|
||||||
var data_slice: ?[]const u8 = null;
|
|
||||||
|
|
||||||
switch (fio.fiobj_type(data)) {
|
|
||||||
fio.FIOBJ_T_DATA => {
|
|
||||||
if (fio.is_invalid(data) == 1) {
|
|
||||||
data_slice = "(zap: invalid data)";
|
|
||||||
std.log.warn("WARNING: HTTP param binary file is not a data object\n", .{});
|
|
||||||
} else {
|
|
||||||
// the data
|
|
||||||
const data_len = fio.fiobj_data_len(data);
|
|
||||||
var data_buf = fio.fiobj_data_read(data, data_len);
|
|
||||||
|
|
||||||
if (data_len < 0) {
|
|
||||||
std.log.warn("WARNING: HTTP param binary file size negative: {d}\n", .{data_len});
|
|
||||||
std.log.warn("FIOBJ_TYPE of data is: {d}\n", .{fio.fiobj_type(data)});
|
|
||||||
} else {
|
|
||||||
if (data_buf.len != data_len) {
|
|
||||||
std.log.warn("WARNING: HTTP param binary file size mismatch: should {d}, is: {d}\n", .{ data_len, data_buf.len });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data_buf.len > 0) {
|
|
||||||
data_slice = data_buf.data[0..data_buf.len];
|
|
||||||
} else {
|
|
||||||
std.log.warn("WARNING: HTTP param binary file buffer size negative: {d}\n", .{data_buf.len});
|
|
||||||
data_slice = "(zap: invalid data: negative BUFFER size)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fio.FIOBJ_T_STRING => {
|
|
||||||
const fiostr = fio.fiobj_obj2cstr(data);
|
|
||||||
if (fiostr.len == 0) {
|
|
||||||
data_slice = "(zap: empty string data)";
|
|
||||||
std.log.warn("WARNING: HTTP param binary file has empty string object\n", .{});
|
|
||||||
} else {
|
|
||||||
data_slice = fiostr.data[0..fiostr.len];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fio.FIOBJ_T_ARRAY => {
|
|
||||||
// OK, data is an array
|
|
||||||
const len = fio.fiobj_ary_count(data);
|
|
||||||
const fn_ary = fio.fiobj_hash_get(o, key_name);
|
|
||||||
const mt_ary = fio.fiobj_hash_get(o, key_type);
|
|
||||||
|
|
||||||
if (fio.fiobj_ary_count(fn_ary) == len and fio.fiobj_ary_count(mt_ary) == len) {
|
|
||||||
var i: isize = 0;
|
|
||||||
var ret = std.ArrayList(HttpParamBinaryFile).init(a);
|
|
||||||
while (i < len) : (i += 1) {
|
|
||||||
const file_data_obj = fio.fiobj_ary_entry(data, i);
|
|
||||||
const file_name_obj = fio.fiobj_ary_entry(fn_ary, i);
|
|
||||||
const file_mimetype_obj = fio.fiobj_ary_entry(mt_ary, i);
|
|
||||||
var has_error: bool = false;
|
|
||||||
if (fio.is_invalid(file_data_obj) == 1) {
|
|
||||||
std.log.debug("file data invalid in array", .{});
|
|
||||||
has_error = true;
|
|
||||||
}
|
|
||||||
if (fio.is_invalid(file_name_obj) == 1) {
|
|
||||||
std.log.debug("file name invalid in array", .{});
|
|
||||||
has_error = true;
|
|
||||||
}
|
|
||||||
if (fio.is_invalid(file_mimetype_obj) == 1) {
|
|
||||||
std.log.debug("file mimetype invalid in array", .{});
|
|
||||||
has_error = true;
|
|
||||||
}
|
|
||||||
if (has_error) {
|
|
||||||
return error.Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
const file_data = fio.fiobj_obj2cstr(file_data_obj);
|
|
||||||
const file_name = fio.fiobj_obj2cstr(file_name_obj);
|
|
||||||
const file_mimetype = fio.fiobj_obj2cstr(file_mimetype_obj);
|
|
||||||
try ret.append(.{
|
|
||||||
.data = file_data.data[0..file_data.len],
|
|
||||||
.mimetype = file_mimetype.data[0..file_mimetype.len],
|
|
||||||
.filename = file_name.data[0..file_name.len],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return .{ .Array_Binfile = ret };
|
|
||||||
} else {
|
|
||||||
return error.ArrayLenMismatch;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
// don't know what to do
|
|
||||||
return error.Unsupported;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{ .Hash_Binfile = .{
|
|
||||||
.filename = filename.data[0..filename.len],
|
|
||||||
.mimetype = mimetype.data[0..mimetype.len],
|
|
||||||
.data = data_slice,
|
|
||||||
} };
|
|
||||||
} else {
|
|
||||||
return .{ .Hash_Binfile = .{} };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse FIO object into a typed Http param. Supports file uploads.
|
|
||||||
pub fn Fiobj2HttpParam(a: std.mem.Allocator, o: fio.FIOBJ, dupe_string: bool) !?HttpParam {
|
|
||||||
return switch (fio.fiobj_type(o)) {
|
|
||||||
fio.FIOBJ_T_NULL => null,
|
|
||||||
fio.FIOBJ_T_TRUE => .{ .Bool = true },
|
|
||||||
fio.FIOBJ_T_FALSE => .{ .Bool = false },
|
|
||||||
fio.FIOBJ_T_NUMBER => .{ .Int = fio.fiobj_obj2num(o) },
|
|
||||||
fio.FIOBJ_T_FLOAT => .{ .Float = fio.fiobj_obj2float(o) },
|
|
||||||
fio.FIOBJ_T_STRING => .{ .String = try util.fio2strAllocOrNot(a, o, dupe_string) },
|
|
||||||
fio.FIOBJ_T_ARRAY => {
|
|
||||||
return .{ .Unsupported = null };
|
|
||||||
},
|
|
||||||
fio.FIOBJ_T_HASH => {
|
|
||||||
const file = try parseBinfilesFrom(a, o);
|
|
||||||
return file;
|
|
||||||
},
|
|
||||||
else => .{ .Unsupported = null },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Args for setting a cookie
|
|
||||||
pub const CookieArgs = struct {
|
|
||||||
name: []const u8,
|
|
||||||
value: []const u8,
|
|
||||||
domain: ?[]const u8 = null,
|
|
||||||
path: ?[]const u8 = null,
|
|
||||||
/// max age in seconds. 0 -> session
|
|
||||||
max_age_s: c_int = 0,
|
|
||||||
secure: bool = true,
|
|
||||||
http_only: bool = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Used internally: facilio Http request callback function type
|
/// Used internally: facilio Http request callback function type
|
||||||
pub const FioHttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void;
|
pub const FioHttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void;
|
||||||
|
|
||||||
|
@ -921,7 +257,7 @@ pub const HttpListener = struct {
|
||||||
._is_finished_request_global = false,
|
._is_finished_request_global = false,
|
||||||
._user_context = undefined,
|
._user_context = undefined,
|
||||||
};
|
};
|
||||||
var zigtarget: []u8 = target[0..target_len];
|
const zigtarget: []u8 = target[0..target_len];
|
||||||
req._is_finished = &req._is_finished_request_global;
|
req._is_finished = &req._is_finished_request_global;
|
||||||
|
|
||||||
var user_context: Request.UserContext = .{};
|
var user_context: Request.UserContext = .{};
|
||||||
|
@ -949,7 +285,7 @@ pub const HttpListener = struct {
|
||||||
pfolder = pf.ptr;
|
pfolder = pf.ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
var x: fio.http_settings_s = .{
|
const x: fio.http_settings_s = .{
|
||||||
.on_request = if (self.settings.on_request) |_| Self.theOneAndOnlyRequestCallBack else null,
|
.on_request = if (self.settings.on_request) |_| Self.theOneAndOnlyRequestCallBack else null,
|
||||||
.on_upgrade = if (self.settings.on_upgrade) |_| Self.theOneAndOnlyUpgradeCallBack else null,
|
.on_upgrade = if (self.settings.on_upgrade) |_| Self.theOneAndOnlyUpgradeCallBack else null,
|
||||||
.on_response = if (self.settings.on_response) |_| Self.theOneAndOnlyResponseCallBack else null,
|
.on_response = if (self.settings.on_response) |_| Self.theOneAndOnlyResponseCallBack else null,
|
||||||
|
|
Loading…
Add table
Reference in a new issue