fediglam/src/http/multipart.zig

155 lines
4.3 KiB
Zig
Raw Normal View History

2022-11-27 13:43:06 +00:00
const std = @import("std");
const max_boundary = 70;
const FormField = struct {
//name: []const u8,
//disposition: []const u8,
//filename: ?[]const u8,
//charset: ?[]const u8,
value: []const u8,
};
const FormFieldResult = struct {
field: FormField,
more: bool,
};
fn isFinalPart(peek_stream: anytype) !bool {
const reader = peek_stream.reader();
var buf: [2]u8 = undefined;
const end = try reader.readAll(&buf);
const end_line = buf[0..end];
const terminal = std.mem.eql(u8, end_line, "--");
if (!terminal) try peek_stream.putBack(end_line);
// Skip whitespace
while (true) {
const b = reader.readByte() catch |err| switch (err) {
error.EndOfStream => {
if (terminal) break else return error.InvalidMultipartBoundary;
},
else => return err,
};
if (std.mem.indexOfScalar(u8, " \r\n", b) == null) {
try peek_stream.putBackByte(b);
break;
}
}
return terminal;
}
fn parseFormField(boundary: []const u8, peek_stream: anytype, alloc: std.mem.Allocator) !FormFieldResult {
const reader = peek_stream.reader();
// TODO: refactor
var headers = try @import("./request/parser.zig").parseHeaders(alloc, reader);
defer headers.deinit();
std.debug.print("disposition: {?s}\n", .{headers.get("Content-Disposition")});
var value = std.ArrayList(u8).init(alloc);
errdefer value.deinit();
line_loop: while (true) {
// parse crlf--
var buf: [4]u8 = undefined;
try reader.readNoEof(&buf);
if (!std.mem.eql(u8, &buf, "\r\n--")) {
try value.append(buf[0]);
try peek_stream.putBack(buf[1..]);
var ch = try reader.readByte();
while (ch != '\r') : (ch = try reader.readByte()) try value.append(ch);
try peek_stream.putBackByte(ch);
continue;
}
for (boundary) |ch, i| {
const b = try reader.readByte();
if (b != ch) {
try value.appendSlice("\r\n--");
try value.appendSlice(boundary[0 .. i + 1]);
continue :line_loop;
}
}
// Boundary parsed. See if its a terminal or not
break;
}
const terminal = try isFinalPart(peek_stream);
return FormFieldResult{
.field = .{ .value = value.toOwnedSlice() },
.more = !terminal,
};
}
pub fn parseFormData(boundary: []const u8, reader: anytype, alloc: std.mem.Allocator) !void {
if (boundary.len > max_boundary) return error.BoundaryTooLarge;
var stream = std.io.peekStream(72, reader);
{
var buf: [72]u8 = undefined;
const count = try stream.reader().readAll(buf[0 .. boundary.len + 2]);
var line = buf[0..count];
if (line.len != boundary.len + 2) return error.InvalidMultipartBoundary;
if (!std.mem.startsWith(u8, line, "--")) return error.InvalidMultipartBoundary;
if (!std.mem.endsWith(u8, line, boundary)) return error.InvalidMultipartBoundary;
if (try isFinalPart(&stream)) return;
}
while (true) {
const field = try parseFormField(boundary, &stream, alloc);
alloc.free(field.field.value);
if (!field.more) return;
}
}
fn toCrlf(comptime str: []const u8) []const u8 {
comptime {
var buf: [str.len * 2]u8 = undefined;
@setEvalBranchQuota(@intCast(u32, str.len * 2)); // TODO: why does this need to be *2
var buf_len: usize = 0;
for (str) |ch| {
if (ch == '\n') {
buf[buf_len] = '\r';
buf_len += 1;
}
buf[buf_len] = ch;
buf_len += 1;
}
return buf[0..buf_len];
}
}
test "parseFormData" {
const body = toCrlf(
\\--abcd
\\Content-Disposition: form-data; name=first
\\
\\content
\\--abcd
\\content-Disposition: form-data; name=second
\\
\\no content
\\--abcd
\\content-disposition: form-data; name=third
\\
\\
\\--abcd--
\\
);
var stream = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
try parseFormData("abcd", stream.reader(), std.testing.allocator);
}