diff --git a/src/main.zig b/src/main.zig index bf19559..9b81ecb 100644 --- a/src/main.zig +++ b/src/main.zig @@ -149,7 +149,7 @@ const Multipart = struct { var reader = self.stream.reader(); // first self.boundary.len+2 bytes MUST be boundary + \r + \n var boundary_buffer: [512]u8 = undefined; - const maybe_boundary_raw = (try reader.readUntilDelimiterOrEof(&boundary_buffer, '\n')).?; + const maybe_boundary_raw = (try reader.readUntilDelimiterOrEof(&boundary_buffer, '\n')) orelse return null; const maybe_boundary_strip1 = std.mem.trimRight(u8, maybe_boundary_raw, "\n"); const maybe_boundary_strip2 = std.mem.trimRight(u8, maybe_boundary_strip1, "\r"); @@ -228,21 +228,46 @@ const Multipart = struct { // we can use the fact that we know the reader is FixedBufferStream // to extract the remaining body, then trim the boundary! // - // THIS ASSUMES ONLY ONE FILE IS IN THE WIRE. + // + // when we find a marker, we also need to know if its an ending marker, + // because the multipart files are prefixed with the boundary, not suffixed. + // + // bc of that we need to set the reader so that its directly on top of + // the next boundary marker for the next file to work const remaining_body = self.stream.buffer[self.stream.pos..self.stream.buffer.len]; - var end_boundary_buf: [512]u8 = undefined; - const boundary_end_marker = try std.fmt.bufPrint(&end_boundary_buf, "{s}--\r\n", .{self.boundary}); - const actual_body = std.mem.trim(u8, remaining_body, boundary_end_marker); + // read body until we find the boundary end marker (--{s} OR --{s}--) + var it = std.mem.split(remaining_body, self.boundary); + const body = it.next() orelse return error.MissingPartBody; - std.debug.warn("ctype out of this: '{s}'", .{content_type.?}); + const next_boundary_pos = self.stream.pos + body.len; + const next_boundary_body = self.stream.buffer[next_boundary_pos..self.stream.buffer.len]; + + // check out on the next 2 chars + const possible_end = it.next() orelse return error.MissingNextPrefixOrEndSuffix; + + if (std.mem.startsWith(u8, possible_end, "--")) { + // we just got the ending boundary marker. the reader should be disabled + // for future reads. + self.stream.pos = self.stream.buffer.len; + return Part{ + .allocator = allocator, + .disposition = content_disposition.?, + .content_type = content_type.?, + .body = body, + }; + } + + // there is a next file, the reader should be shifted forward to the + // boundary marker + self.stream.pos = self.stream.pos + body.len; return Part{ .allocator = allocator, .disposition = content_disposition.?, .content_type = content_type.?, - .body = actual_body, + .body = body, }; } }; @@ -317,18 +342,24 @@ fn fetchFile(response: *http.Response, request: http.Request, filename: []const pub const log_level: std.log.Level = .debug; test "multipart" { + const PART1_REAL_BODY = + "Hello!\n"; + + const PART2_REAL_BODY = + "{\"status\": \"OK\"}\n"; + const body = "--1234\r\n" ++ "Content-Type: text/plain\r\n" ++ "Content-Disposition: form-data; name=file1; filename=ab.txt\r\n" ++ "\r\n" ++ - "Hello!\n" ++ + PART1_REAL_BODY ++ "--1234\r\n" ++ "Content-Type: application/json\r\n" ++ // TODO: add 'content-type' support to content-disposition as well "Content-Disposition: form-data; name=file2; filename=data.json\r\n" ++ "\r\n" ++ - "{\"status\": \"OK\"}\n" ++ + PART2_REAL_BODY ++ "--1234--\r\n"; var buf: [512]u8 = undefined; @@ -350,7 +381,24 @@ test "multipart" { std.testing.expectEqualSlices(u8, "text/plain", part1.content_type); std.testing.expectEqualSlices(u8, "file1", part1.disposition.name); std.testing.expectEqualSlices(u8, "ab.txt", part1.disposition.filename); - std.testing.expectEqualSlices(u8, "Hello!", part1.body); + std.testing.expectEqualSlices(u8, PART1_REAL_BODY, part1.body); - // var part2 = (try multipart.next(&hzzp_buffer)).?; + var part2 = (try multipart.next(&hzzp_buffer, std.testing.allocator)).?; + defer part2.deinit(); + + std.debug.warn( + "\npart2={}\n", + .{part2}, + ); + + std.testing.expectEqualSlices(u8, "application/json", part2.content_type); + std.testing.expectEqualSlices(u8, "file2", part2.disposition.name); + std.testing.expectEqualSlices(u8, "data.json", part2.disposition.filename); + std.testing.expectEqualSlices(u8, PART2_REAL_BODY, part2.body); + + // stop the loop (if there were any) afterwards + std.testing.expectEqual( + @as(?Part, null), + try multipart.next(&hzzp_buffer, std.testing.allocator), + ); }