master
1//! Parser for transfer-encoding: chunked.
2
3const ChunkParser = @This();
4const std = @import("std");
5
6state: State,
7chunk_len: u64,
8
9pub const init: ChunkParser = .{
10 .state = .head_size,
11 .chunk_len = 0,
12};
13
14pub const State = enum {
15 head_size,
16 head_ext,
17 head_r,
18 data,
19 data_suffix,
20 data_suffix_r,
21 invalid,
22};
23
24/// Returns the number of bytes consumed by the chunk size. This is always
25/// less than or equal to `bytes.len`.
26///
27/// After this function returns, `chunk_len` will contain the parsed chunk size
28/// in bytes when `state` is `data`. Alternately, `state` may become `invalid`,
29/// indicating a syntax error in the input stream.
30///
31/// If the amount returned is less than `bytes.len`, the parser is in the
32/// `chunk_data` state and the first byte of the chunk is at `bytes[result]`.
33///
34/// Asserts `state` is neither `data` nor `invalid`.
35pub fn feed(p: *ChunkParser, bytes: []const u8) usize {
36 for (bytes, 0..) |c, i| switch (p.state) {
37 .data_suffix => switch (c) {
38 '\r' => p.state = .data_suffix_r,
39 '\n' => p.state = .head_size,
40 else => {
41 p.state = .invalid;
42 return i;
43 },
44 },
45 .data_suffix_r => switch (c) {
46 '\n' => p.state = .head_size,
47 else => {
48 p.state = .invalid;
49 return i;
50 },
51 },
52 .head_size => {
53 const digit = switch (c) {
54 '0'...'9' => |b| b - '0',
55 'A'...'Z' => |b| b - 'A' + 10,
56 'a'...'z' => |b| b - 'a' + 10,
57 '\r' => {
58 p.state = .head_r;
59 continue;
60 },
61 '\n' => {
62 p.state = .data;
63 return i + 1;
64 },
65 else => {
66 p.state = .head_ext;
67 continue;
68 },
69 };
70
71 const new_len = p.chunk_len *% 16 +% digit;
72 if (new_len <= p.chunk_len and p.chunk_len != 0) {
73 p.state = .invalid;
74 return i;
75 }
76
77 p.chunk_len = new_len;
78 },
79 .head_ext => switch (c) {
80 '\r' => p.state = .head_r,
81 '\n' => {
82 p.state = .data;
83 return i + 1;
84 },
85 else => continue,
86 },
87 .head_r => switch (c) {
88 '\n' => {
89 p.state = .data;
90 return i + 1;
91 },
92 else => {
93 p.state = .invalid;
94 return i;
95 },
96 },
97 .data => unreachable,
98 .invalid => unreachable,
99 };
100 return bytes.len;
101}
102
103test feed {
104 const testing = std.testing;
105
106 const data = "Ff\r\nf0f000 ; ext\n0\r\nffffffffffffffffffffffffffffffffffffffff\r\n";
107
108 var p = init;
109 const first = p.feed(data[0..]);
110 try testing.expectEqual(@as(u32, 4), first);
111 try testing.expectEqual(@as(u64, 0xff), p.chunk_len);
112 try testing.expectEqual(.data, p.state);
113
114 p = init;
115 const second = p.feed(data[first..]);
116 try testing.expectEqual(@as(u32, 13), second);
117 try testing.expectEqual(@as(u64, 0xf0f000), p.chunk_len);
118 try testing.expectEqual(.data, p.state);
119
120 p = init;
121 const third = p.feed(data[first + second ..]);
122 try testing.expectEqual(@as(u32, 3), third);
123 try testing.expectEqual(@as(u64, 0), p.chunk_len);
124 try testing.expectEqual(.data, p.state);
125
126 p = init;
127 const fourth = p.feed(data[first + second + third ..]);
128 try testing.expectEqual(@as(u32, 16), fourth);
129 try testing.expectEqual(@as(u64, 0xffffffffffffffff), p.chunk_len);
130 try testing.expectEqual(.invalid, p.state);
131}