Multipart/form-data
This commit is contained in:
parent
0b13f210c7
commit
6e56775d61
2 changed files with 106 additions and 100 deletions
src/http
|
@ -18,6 +18,7 @@ const util = @import("util");
|
|||
const http = @import("./lib.zig");
|
||||
const urlencode = @import("./urlencode.zig");
|
||||
const json_utils = @import("./json.zig");
|
||||
const fields = @import("./fields.zig");
|
||||
|
||||
/// Takes an iterable of middlewares and chains them together.
|
||||
pub fn apply(middlewares: anytype) Apply(@TypeOf(middlewares)) {
|
||||
|
@ -29,20 +30,20 @@ pub fn Apply(comptime Middlewares: type) type {
|
|||
return ApplyInternal(std.meta.fields(Middlewares));
|
||||
}
|
||||
|
||||
fn ApplyInternal(comptime fields: []const std.builtin.Type.StructField) type {
|
||||
if (fields.len == 0) return void;
|
||||
fn ApplyInternal(comptime which: []const std.builtin.Type.StructField) type {
|
||||
if (which.len == 0) return void;
|
||||
|
||||
return HandlerList(
|
||||
fields[0].field_type,
|
||||
ApplyInternal(fields[1..]),
|
||||
which[0].field_type,
|
||||
ApplyInternal(which[1..]),
|
||||
);
|
||||
}
|
||||
|
||||
fn applyInternal(middlewares: anytype, comptime fields: []const std.builtin.Type.StructField) ApplyInternal(fields) {
|
||||
if (fields.len == 0) return {};
|
||||
fn applyInternal(middlewares: anytype, comptime which: []const std.builtin.Type.StructField) ApplyInternal(which) {
|
||||
if (which.len == 0) return {};
|
||||
return .{
|
||||
.first = @field(middlewares, fields[0].name),
|
||||
.next = applyInternal(middlewares, fields[1..]),
|
||||
.first = @field(middlewares, which[0].name),
|
||||
.next = applyInternal(middlewares, which[1..]),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -648,32 +649,27 @@ fn parseBodyFromRequest(comptime T: type, content_type: ?[]const u8, reader: any
|
|||
// Use json by default for now for testing purposes
|
||||
const eff_type = content_type orelse "application/json";
|
||||
const parser_type = matchContentType(eff_type);
|
||||
const buf = try reader.readAllAlloc(alloc, 1 << 16);
|
||||
defer alloc.free(buf);
|
||||
|
||||
switch (parser_type) {
|
||||
.octet_stream, .json => {
|
||||
const buf = try reader.readAllAlloc(alloc, 1 << 16);
|
||||
defer alloc.free(buf);
|
||||
const body = try json_utils.parse(T, buf, alloc);
|
||||
defer json_utils.parseFree(body, alloc);
|
||||
|
||||
return try util.deepClone(alloc, body);
|
||||
},
|
||||
.url_encoded => return urlencode.parse(alloc, T, buf) catch |err| switch (err) {
|
||||
//error.NoQuery => error.NoBody,
|
||||
else => err,
|
||||
},
|
||||
.multipart_formdata => {
|
||||
const param_string = std.mem.split(u8, eff_type, ";").rest();
|
||||
const params = urlencode.parse(alloc, struct {
|
||||
boundary: []const u8,
|
||||
}, param_string) catch |err| return switch (err) {
|
||||
//error.NoQuery => error.MissingBoundary,
|
||||
.url_encoded => {
|
||||
const buf = try reader.readAllAlloc(alloc, 1 << 16);
|
||||
defer alloc.free(buf);
|
||||
return urlencode.parse(alloc, T, buf) catch |err| switch (err) {
|
||||
//error.NoQuery => error.NoBody,
|
||||
else => err,
|
||||
};
|
||||
defer util.deepFree(alloc, params);
|
||||
|
||||
unreachable;
|
||||
//try @import("./multipart.zig").parseFormData(params.boundary, reader, alloc);
|
||||
},
|
||||
.multipart_formdata => {
|
||||
const boundary = fields.getParam(eff_type, "boundary") orelse return error.MissingBoundary;
|
||||
return try @import("./multipart.zig").parseFormData(T, boundary, reader, alloc);
|
||||
},
|
||||
else => return error.UnsupportedMediaType,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const std = @import("std");
|
||||
const util = @import("util");
|
||||
const http = @import("./lib.zig");
|
||||
const fields = @import("./fields.zig");
|
||||
|
||||
const max_boundary = 70;
|
||||
const read_ahead = max_boundary + 4;
|
||||
|
@ -37,11 +37,11 @@ pub fn MultipartStream(comptime ReaderType: type) type {
|
|||
|
||||
pub const Part = struct {
|
||||
base: ?*Multipart,
|
||||
fields: http.Fields,
|
||||
fields: fields.Fields,
|
||||
|
||||
pub fn open(base: *Multipart, alloc: std.mem.Allocator) !Part {
|
||||
var fields = try @import("./request/parser.zig").parseHeaders(alloc, base.stream.reader());
|
||||
return .{ .base = base, .fields = fields };
|
||||
var parsed_fields = try @import("./request/parser.zig").parseHeaders(alloc, base.stream.reader());
|
||||
return .{ .base = base, .fields = parsed_fields };
|
||||
}
|
||||
|
||||
pub fn reader(self: *Part) PartReader {
|
||||
|
@ -101,61 +101,26 @@ pub fn openMultipart(boundary: []const u8, reader: anytype) !MultipartStream(@Ty
|
|||
return stream;
|
||||
}
|
||||
|
||||
const ParamIter = struct {
|
||||
str: []const u8,
|
||||
index: usize = 0,
|
||||
|
||||
const Param = struct {
|
||||
name: []const u8,
|
||||
value: []const u8,
|
||||
};
|
||||
|
||||
pub fn from(str: []const u8) ParamIter {
|
||||
return .{ .str = str, .index = std.mem.indexOfScalar(u8, str, ';') orelse str.len };
|
||||
}
|
||||
|
||||
pub fn fieldValue(self: *ParamIter) []const u8 {
|
||||
return std.mem.sliceTo(self.str, ';');
|
||||
}
|
||||
|
||||
pub fn next(self: *ParamIter) ?Param {
|
||||
if (self.index >= self.str.len) return null;
|
||||
|
||||
const start = self.index + 1;
|
||||
const new_start = std.mem.indexOfScalarPos(u8, self.str, start, ';') orelse self.str.len;
|
||||
self.index = new_start;
|
||||
|
||||
const param = std.mem.trim(u8, self.str[start..new_start], " \t");
|
||||
var split = std.mem.split(u8, param, "=");
|
||||
const name = split.first();
|
||||
const value = std.mem.trimLeft(u8, split.rest(), " \t");
|
||||
// TODO: handle quoted values
|
||||
// TODO: handle parse errors
|
||||
|
||||
return Param{
|
||||
.name = name,
|
||||
.value = value,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn parseFormData(comptime T: type, boundary: []const u8, reader: anytype, alloc: std.mem.Allocator) !T {
|
||||
var multipart = try openMultipart(boundary, reader);
|
||||
|
||||
var ds = util.Deserializer(T){};
|
||||
defer {
|
||||
var iter = ds.iterator();
|
||||
while (iter.next()) |pair| {
|
||||
alloc.free(pair.value);
|
||||
}
|
||||
}
|
||||
while (true) {
|
||||
var part = (try multipart.next(alloc)) orelse break;
|
||||
defer part.close();
|
||||
|
||||
const disposition = part.fields.get("Content-Disposition") orelse return error.InvalidForm;
|
||||
var iter = ParamIter.from(disposition);
|
||||
if (!std.ascii.eqlIgnoreCase("form-data", iter.fieldValue())) return error.InvalidForm;
|
||||
const name = while (iter.next()) |param| {
|
||||
if (!std.ascii.eqlIgnoreCase("name", param.name)) @panic("Not implemented");
|
||||
break param.value;
|
||||
} else return error.InvalidForm;
|
||||
|
||||
const name = fields.getParam(disposition, "name") orelse return error.InvalidForm;
|
||||
|
||||
const value = try part.reader().readAllAlloc(alloc, 1 << 32);
|
||||
errdefer alloc.free(value);
|
||||
try ds.setSerializedField(name, value);
|
||||
}
|
||||
|
||||
|
@ -164,34 +129,79 @@ pub fn parseFormData(comptime T: type, boundary: []const u8, reader: anytype, al
|
|||
|
||||
// TODO: Fix these tests
|
||||
test "MultipartStream" {
|
||||
const body = util.comptimeToCrlf(
|
||||
\\--abcd
|
||||
\\Content-Disposition: form-data; name=first; charset=utf8
|
||||
\\
|
||||
\\content
|
||||
\\--abcd
|
||||
\\content-Disposition: form-data; name=second
|
||||
\\
|
||||
\\no content
|
||||
\\--abcd
|
||||
\\content-disposition: form-data; name=third
|
||||
\\
|
||||
\\
|
||||
\\--abcd--
|
||||
\\
|
||||
const ExpectedPart = struct {
|
||||
disposition: []const u8,
|
||||
value: []const u8,
|
||||
};
|
||||
const testCase = struct {
|
||||
fn case(comptime body: []const u8, boundary: []const u8, expected_parts: []const ExpectedPart) !void {
|
||||
var src = std.io.StreamSource{
|
||||
.const_buffer = std.io.fixedBufferStream(body),
|
||||
};
|
||||
|
||||
var stream = try openMultipart(boundary, src.reader());
|
||||
|
||||
for (expected_parts) |expected| {
|
||||
var part = try stream.next(std.testing.allocator) orelse return error.TestExpectedEqual;
|
||||
defer part.close();
|
||||
|
||||
const dispo = part.fields.get("Content-Disposition") orelse return error.TestExpectedEqual;
|
||||
try std.testing.expectEqualStrings(expected.disposition, dispo);
|
||||
|
||||
var buf: [128]u8 = undefined;
|
||||
const count = try part.reader().read(&buf);
|
||||
try std.testing.expectEqualStrings(expected.value, buf[0..count]);
|
||||
}
|
||||
|
||||
try std.testing.expect(try stream.next(std.testing.allocator) == null);
|
||||
}
|
||||
}.case;
|
||||
|
||||
try testCase("--abc--\r\n", "abc", &.{});
|
||||
try testCase(
|
||||
util.comptimeToCrlf(
|
||||
\\------abcd
|
||||
\\Content-Disposition: form-data; name=first; charset=utf8
|
||||
\\
|
||||
\\content
|
||||
\\------abcd
|
||||
\\content-Disposition: form-data; name=second
|
||||
\\
|
||||
\\no content
|
||||
\\------abcd
|
||||
\\content-disposition: form-data; name=third
|
||||
\\
|
||||
\\
|
||||
\\------abcd--
|
||||
\\
|
||||
),
|
||||
"----abcd",
|
||||
&.{
|
||||
.{ .disposition = "form-data; name=first; charset=utf8", .value = "content" },
|
||||
.{ .disposition = "form-data; name=second", .value = "no content" },
|
||||
.{ .disposition = "form-data; name=third", .value = "" },
|
||||
},
|
||||
);
|
||||
|
||||
var src = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
|
||||
|
||||
if (true) return error.SkipZigTest;
|
||||
var stream = try openMultipart("abcd", src.reader());
|
||||
while (try stream.next(std.testing.allocator)) |p| {
|
||||
var part = p;
|
||||
defer part.close();
|
||||
std.debug.print("\n{?s}\n", .{part.fields.get("content-disposition")});
|
||||
var buf: [64]u8 = undefined;
|
||||
std.debug.print("\"{s}\"\n", .{buf[0..try part.reader().readAll(&buf)]});
|
||||
}
|
||||
try testCase(
|
||||
util.comptimeToCrlf(
|
||||
\\--xyz
|
||||
\\Content-Disposition: uhh
|
||||
\\
|
||||
\\xyz
|
||||
\\--xyz
|
||||
\\Content-disposition: ok
|
||||
\\
|
||||
\\ --xyz
|
||||
\\--xyz--
|
||||
\\
|
||||
),
|
||||
"xyz",
|
||||
&.{
|
||||
.{ .disposition = "uhh", .value = "xyz" },
|
||||
.{ .disposition = "ok", .value = " --xyz" },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test "parseFormData" {
|
||||
|
@ -203,10 +213,10 @@ test "parseFormData" {
|
|||
\\--abcd--
|
||||
\\
|
||||
);
|
||||
if (true) return error.SkipZigTest;
|
||||
if (true) return error.SkipZigTest; // TODO
|
||||
var src = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
|
||||
const val = try parseFormData(struct {
|
||||
foo: []const u8,
|
||||
}, "abcd", src.reader(), std.testing.allocator);
|
||||
std.debug.print("\n\n\n\"{any}\"\n\n\n", .{val});
|
||||
_ = val;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue