lib/deflate/src/frame.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const decode = @import("decode.zig");
  3 const model = @import("model.zig");
  4 
  5 fn readLittle16(input: []const u8) u16 {
  6     return @as(u16, input[0]) | (@as(u16, input[1]) << 8);
  7 }
  8 
  9 fn readLittle32(input: []const u8) u32 {
 10     return @as(u32, input[0]) |
 11         (@as(u32, input[1]) << 8) |
 12         (@as(u32, input[2]) << 16) |
 13         (@as(u32, input[3]) << 24);
 14 }
 15 
 16 fn readBig32(input: []const u8) u32 {
 17     return (@as(u32, input[0]) << 24) |
 18         (@as(u32, input[1]) << 16) |
 19         (@as(u32, input[2]) << 8) |
 20         @as(u32, input[3]);
 21 }
 22 
 23 fn zlib(input: []const u8, output: []u8) model.Error!model.Result {
 24     if (input.len < 6) return error.TruncatedInput;
 25     const header = (@as(u16, input[0]) << 8) | input[1];
 26     if ((input[0] & 0x0f) != 8 or (input[0] >> 4) > 7 or header % 31 != 0) {
 27         return error.BadHeader;
 28     }
 29     if ((input[1] & 0x20) != 0) return error.PresetDictionaryUnsupported;
 30     const payload = try decode.raw(input[2..], output);
 31     const footer_start = 2 + payload.consumed;
 32     if (footer_start > input.len or input.len - footer_start < 4) {
 33         return error.TruncatedInput;
 34     }
 35     const expected = readBig32(input[footer_start..][0..4]);
 36     if (std.hash.Adler32.hash(output[0..payload.written]) != expected) {
 37         return error.BadChecksum;
 38     }
 39     return .{
 40         .consumed = footer_start + 4,
 41         .written = payload.written,
 42     };
 43 }
 44 
 45 fn zeroTerminated(input: []const u8, start: usize) model.Error!usize {
 46     const relative = std.mem.indexOfScalar(u8, input[start..], 0) orelse {
 47         return error.TruncatedInput;
 48     };
 49     return start + relative + 1;
 50 }
 51 
 52 fn gzip(input: []const u8, output: []u8) model.Error!model.Result {
 53     if (input.len < 18) return error.TruncatedInput;
 54     if (input[0] != 0x1f or input[1] != 0x8b or input[2] != 8) {
 55         return error.BadHeader;
 56     }
 57     const flags = input[3];
 58     if ((flags & 0xe0) != 0) return error.BadHeader;
 59     var index: usize = 10;
 60     if ((flags & 0x04) != 0) {
 61         if (input.len - index < 2) return error.TruncatedInput;
 62         const extra_length = readLittle16(input[index..][0..2]);
 63         index += 2;
 64         if (extra_length > input.len - index) return error.TruncatedInput;
 65         index += extra_length;
 66     }
 67     if ((flags & 0x08) != 0) index = try zeroTerminated(input, index);
 68     if ((flags & 0x10) != 0) index = try zeroTerminated(input, index);
 69     if ((flags & 0x02) != 0) {
 70         if (input.len - index < 2) return error.TruncatedInput;
 71         const expected = readLittle16(input[index..][0..2]);
 72         const actual: u16 = @truncate(std.hash.Crc32.hash(input[0..index]));
 73         if (actual != expected) return error.BadChecksum;
 74         index += 2;
 75     }
 76     const payload = try decode.raw(input[index..], output);
 77     const footer_start = index + payload.consumed;
 78     if (footer_start > input.len or input.len - footer_start < 8) {
 79         return error.TruncatedInput;
 80     }
 81     const expected_crc = readLittle32(input[footer_start..][0..4]);
 82     const expected_size = readLittle32(input[footer_start + 4 ..][0..4]);
 83     if (std.hash.Crc32.hash(output[0..payload.written]) != expected_crc) {
 84         return error.BadChecksum;
 85     }
 86     if (@as(u32, @truncate(payload.written)) != expected_size) {
 87         return error.BadSize;
 88     }
 89     return .{
 90         .consumed = footer_start + 8,
 91         .written = payload.written,
 92     };
 93 }
 94 
 95 /// Expands one compressed stream with whatever header and trailer its format adds (a frame of
 96 /// `input`), in the container that `format` names, into the front of `output`, so callers can
 97 /// expand a compressed buffer, such as a PDF content stream, into memory they already own. The call
 98 /// allocates nothing. The call returns the number of input bytes the frame used and the number of
 99 /// bytes written to `output`. After a success, only the first `result.written` bytes of `output`
100 /// hold expanded data. After a failure, `output` may hold part of the expanded data. The zlib and
101 /// gzip checksums, and the gzip length, are checked before the call returns. The exact-input and
102 /// exact-output checks run after the frame and its checksum pass. With exact input on, which is the
103 /// default, bytes left after the frame fail with `error.TrailingInput`. With exact output on,
104 /// expanded data shorter than `output` fails with `error.BadSize`. A stream that needs more room
105 /// than `output` has fails with `error.OutputTooSmall`. Every other failure is one tag of `Error`
106 /// that describes the input. The PDF reader calls `decompress` with the zlib container and the
107 /// default options, and treats `error.OutputTooSmall` as its own capacity error.
108 pub fn decompress(
109     input: []const u8,
110     output: []u8,
111     format: model.Format,
112     options: model.Options,
113 ) model.Error!model.Result {
114     const result = switch (format) {
115         .raw => try decode.raw(input, output),
116         .zlib => try zlib(input, output),
117         .gzip => try gzip(input, output),
118     };
119     if (options.exact_input and result.consumed != input.len) {
120         return error.TrailingInput;
121     }
122     if (options.exact_output and result.written != output.len) {
123         return error.BadSize;
124     }
125     return result;
126 }