diff --git a/examples/bindataformpost/bindataformpost.zig b/examples/bindataformpost/bindataformpost.zig index 6875f65..441bcc5 100644 --- a/examples/bindataformpost/bindataformpost.zig +++ b/examples/bindataformpost/bindataformpost.zig @@ -31,6 +31,7 @@ const Handler = struct { std.debug.print("\n", .{}); std.log.info("Param `{s}` in owned list is {any}\n", .{ kv.key.str, v }); switch (v) { + // single-file upload zap.HttpParam.Hash_Binfile => |*file| { const filename = file.filename orelse "(no filename)"; const mimetype = file.mimetype orelse "(no mimetype)"; @@ -40,6 +41,19 @@ const Handler = struct { std.log.debug(" mimetype: {s}\n", .{mimetype}); std.log.debug(" contents: {any}\n", .{data}); }, + // multi-file upload + zap.HttpParam.Array_Binfile => |*files| { + for (files.*.items) |file| { + const filename = file.filename orelse "(no filename)"; + const mimetype = file.mimetype orelse "(no mimetype)"; + const data = file.data orelse ""; + + std.log.debug(" filename: `{s}`\n", .{filename}); + std.log.debug(" mimetype: {s}\n", .{mimetype}); + std.log.debug(" contents: {any}\n", .{data}); + files.*.deinit(); + } + }, else => { // might be a string param, we don't care // let's just get it as string diff --git a/src/fio.zig b/src/fio.zig index 76d2733..b687017 100644 --- a/src/fio.zig +++ b/src/fio.zig @@ -133,7 +133,22 @@ pub extern fn fiobj_hash_pop(hash: FIOBJ, key: [*c]FIOBJ) FIOBJ; pub extern fn fiobj_hash_count(hash: FIOBJ) usize; pub extern fn fiobj_hash_key_in_loop() FIOBJ; pub extern fn fiobj_hash_haskey(hash: FIOBJ, key: FIOBJ) c_int; +pub extern fn fiobj_ary_new() FIOBJ; +pub extern fn fiobj_ary_new2(capa: usize) FIOBJ; +pub extern fn fiobj_ary_count(ary: FIOBJ) usize; +pub extern fn fiobj_ary_capa(ary: FIOBJ) usize; +pub extern fn fiobj_ary2ptr(ary: FIOBJ) [*c]FIOBJ; +pub extern fn fiobj_ary_index(ary: FIOBJ, pos: i64) FIOBJ; +pub extern fn fiobj_ary_set(ary: FIOBJ, obj: FIOBJ, pos: i64) void; pub extern fn fiobj_ary_push(ary: FIOBJ, obj: FIOBJ) void; +pub extern fn fiobj_ary_pop(ary: FIOBJ) FIOBJ; +pub extern fn fiobj_ary_unshift(ary: FIOBJ, obj: FIOBJ) void; +pub extern fn fiobj_ary_shift(ary: FIOBJ) FIOBJ; +pub extern fn fiobj_ary_replace(ary: FIOBJ, obj: FIOBJ, pos: i64) FIOBJ; +pub extern fn fiobj_ary_find(ary: FIOBJ, data: FIOBJ) i64; +pub extern fn fiobj_ary_remove(ary: FIOBJ, pos: i64) c_int; +pub extern fn fiobj_ary_remove2(ary: FIOBJ, data: FIOBJ) c_int; +pub extern fn fiobj_ary_compact(ary: FIOBJ) void; pub extern fn fiobj_float_new(num: f64) FIOBJ; pub extern fn fiobj_num_new_bignum(num: isize) FIOBJ; @@ -163,7 +178,6 @@ pub fn fiobj_true() callconv(.C) FIOBJ { pub fn fiobj_false() callconv(.C) FIOBJ { return @as(FIOBJ, @bitCast(@as(c_long, FIOBJ_T_FALSE))); } -pub extern fn fiobj_ary_new2(capa: usize) FIOBJ; pub extern fn fiobj_str_new(str: [*c]const u8, len: usize) FIOBJ; pub extern fn fiobj_str_buf(capa: usize) FIOBJ; @@ -200,7 +214,6 @@ pub inline fn FIOBJECT2HEAD(o: anytype) [*c]fiobj_object_header_s { pub inline fn fiobj_ary_entry(a: anytype, p: anytype) @TypeOf(fiobj_ary_index(a, p)) { return fiobj_ary_index(a, p); } -pub extern fn fiobj_ary_index(ary: FIOBJ, pos: i64) FIOBJ; pub const FIOBJ_T_NUMBER: c_int = 1; pub const FIOBJ_T_NULL: c_int = 6; pub const FIOBJ_T_TRUE: c_int = 22; diff --git a/src/zap.zig b/src/zap.zig index 9b37abc..4c21e3f 100644 --- a/src/zap.zig +++ b/src/zap.zig @@ -552,7 +552,7 @@ pub const HttpParamValueType = enum { String, Unsupported, Hash_Binfile, - Unsupported_Array, + Array_Binfile, }; pub const HttpParam = union(HttpParamValueType) { @@ -566,7 +566,7 @@ pub const HttpParam = union(HttpParamValueType) { /// we assume hashes are because of file transmissions Hash_Binfile: HttpParamBinaryFile, /// value will always be null - Unsupported_Array: ?void, + Array_Binfile: std.ArrayList(HttpParamBinaryFile), }; pub const HttpParamKV = struct { @@ -599,6 +599,118 @@ pub const HttpParamBinaryFile = struct { } }; +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 = .{} }; + } +} + pub fn Fiobj2HttpParam(o: fio.FIOBJ, a: std.mem.Allocator, dupe_string: bool) !?HttpParam { return switch (fio.fiobj_type(o)) { fio.FIOBJ_T_NULL => null, @@ -607,77 +719,12 @@ pub fn Fiobj2HttpParam(o: fio.FIOBJ, a: std.mem.Allocator, dupe_string: bool) !? 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(o, a, dupe_string) }, - fio.FIOBJ_T_ARRAY => .{ .Unsupported_Array = null }, + fio.FIOBJ_T_ARRAY => { + return .{ .Unsupported = null }; + }, fio.FIOBJ_T_HASH => { - 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 => { - std.log.warn("WARNING: HTTP param binary file as array object is not implemented\n", .{}); - return .{ .Unsupported = null }; - }, - else => { - // don't know what to do - return .{ .Unsupported = null }; - }, - } - - return .{ .Hash_Binfile = .{ - .filename = filename.data[0..filename.len], - .mimetype = mimetype.data[0..mimetype.len], - .data = data_slice, - } }; - } else { - return .{ .Hash_Binfile = .{} }; - } + const file = try parseBinfilesFrom(a, o); + return file; }, else => .{ .Unsupported = null }, };