/src/libjxl/lib/jxl/jpeg/dec_jpeg_data.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) the JPEG XL Project Authors. All rights reserved. |
2 | | // |
3 | | // Use of this source code is governed by a BSD-style |
4 | | // license that can be found in the LICENSE file. |
5 | | |
6 | | #include "lib/jxl/jpeg/dec_jpeg_data.h" |
7 | | |
8 | | #include <brotli/decode.h> |
9 | | |
10 | | #include <cstddef> |
11 | | #include <cstdint> |
12 | | #include <cstring> |
13 | | #include <vector> |
14 | | |
15 | | #include "lib/jxl/base/sanitizers.h" |
16 | | #include "lib/jxl/base/span.h" |
17 | | #include "lib/jxl/base/status.h" |
18 | | #include "lib/jxl/dec_bit_reader.h" |
19 | | #include "lib/jxl/fields.h" |
20 | | #include "lib/jxl/jpeg/jpeg_data.h" |
21 | | |
22 | | namespace jxl { |
23 | | namespace jpeg { |
24 | 0 | Status DecodeJPEGData(Span<const uint8_t> encoded, JPEGData* jpeg_data) { |
25 | 0 | Status ret = true; |
26 | 0 | const uint8_t* in = encoded.data(); |
27 | 0 | size_t available_in = encoded.size(); |
28 | 0 | { |
29 | 0 | BitReader br(encoded); |
30 | 0 | BitReaderScopedCloser br_closer(br, ret); |
31 | 0 | JXL_RETURN_IF_ERROR(Bundle::Read(&br, jpeg_data)); |
32 | 0 | JXL_RETURN_IF_ERROR(br.JumpToByteBoundary()); |
33 | 0 | in += br.TotalBitsConsumed() / 8; |
34 | 0 | available_in -= br.TotalBitsConsumed() / 8; |
35 | 0 | } |
36 | 0 | JXL_RETURN_IF_ERROR(ret); |
37 | | |
38 | 0 | BrotliDecoderState* brotli_dec = |
39 | 0 | BrotliDecoderCreateInstance(nullptr, nullptr, nullptr); |
40 | |
|
41 | 0 | struct BrotliDecDeleter { |
42 | 0 | BrotliDecoderState* brotli_dec; |
43 | 0 | ~BrotliDecDeleter() { BrotliDecoderDestroyInstance(brotli_dec); } |
44 | 0 | } brotli_dec_deleter{brotli_dec}; |
45 | |
|
46 | 0 | BrotliDecoderResult result = |
47 | 0 | BrotliDecoderResult::BROTLI_DECODER_RESULT_SUCCESS; |
48 | |
|
49 | 0 | auto br_read = [&](std::vector<uint8_t>& data) -> Status { |
50 | 0 | size_t available_out = data.size(); |
51 | 0 | uint8_t* out = data.data(); |
52 | 0 | while (available_out != 0) { |
53 | 0 | if (BrotliDecoderIsFinished(brotli_dec)) { |
54 | 0 | return JXL_FAILURE("Not enough decompressed output"); |
55 | 0 | } |
56 | 0 | uint8_t* next_out_before = out; |
57 | 0 | size_t avail_out_before = available_out; |
58 | 0 | msan::MemoryIsInitialized(in, available_in); |
59 | 0 | result = BrotliDecoderDecompressStream(brotli_dec, &available_in, &in, |
60 | 0 | &available_out, &out, nullptr); |
61 | 0 | if (result != |
62 | 0 | BrotliDecoderResult::BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT && |
63 | 0 | result != BrotliDecoderResult::BROTLI_DECODER_RESULT_SUCCESS) { |
64 | 0 | return JXL_FAILURE( |
65 | 0 | "Brotli decoding error: %s\n", |
66 | 0 | BrotliDecoderErrorString(BrotliDecoderGetErrorCode(brotli_dec))); |
67 | 0 | } |
68 | 0 | msan::UnpoisonMemory(next_out_before, avail_out_before - available_out); |
69 | 0 | } |
70 | 0 | return true; |
71 | 0 | }; |
72 | 0 | size_t num_icc = 0; |
73 | 0 | for (size_t i = 0; i < jpeg_data->app_data.size(); i++) { |
74 | 0 | auto& marker = jpeg_data->app_data[i]; |
75 | 0 | if (jpeg_data->app_marker_type[i] != AppMarkerType::kUnknown) { |
76 | | // Set the size of the marker. |
77 | 0 | size_t size_minus_1 = marker.size() - 1; |
78 | 0 | marker[1] = size_minus_1 >> 8; |
79 | 0 | marker[2] = size_minus_1 & 0xFF; |
80 | 0 | if (jpeg_data->app_marker_type[i] == AppMarkerType::kICC) { |
81 | 0 | if (marker.size() < 17) { |
82 | 0 | return JXL_FAILURE("ICC markers must be at least 17 bytes"); |
83 | 0 | } |
84 | 0 | marker[0] = 0xE2; |
85 | 0 | memcpy(&marker[3], kIccProfileTag, sizeof kIccProfileTag); |
86 | 0 | marker[15] = ++num_icc; |
87 | 0 | } |
88 | 0 | } else { |
89 | 0 | JXL_RETURN_IF_ERROR(br_read(marker)); |
90 | 0 | if (marker[1] * 256u + marker[2] + 1u != marker.size()) { |
91 | 0 | return JXL_FAILURE("Incorrect marker size"); |
92 | 0 | } |
93 | 0 | } |
94 | 0 | } |
95 | 0 | for (size_t i = 0; i < jpeg_data->app_data.size(); i++) { |
96 | 0 | auto& marker = jpeg_data->app_data[i]; |
97 | 0 | if (jpeg_data->app_marker_type[i] == AppMarkerType::kICC) { |
98 | 0 | marker[16] = num_icc; |
99 | 0 | } |
100 | 0 | if (jpeg_data->app_marker_type[i] == AppMarkerType::kExif) { |
101 | 0 | marker[0] = 0xE1; |
102 | 0 | if (marker.size() < 3 + sizeof kExifTag) { |
103 | 0 | return JXL_FAILURE("Incorrect Exif marker size"); |
104 | 0 | } |
105 | 0 | memcpy(&marker[3], kExifTag, sizeof kExifTag); |
106 | 0 | } |
107 | 0 | if (jpeg_data->app_marker_type[i] == AppMarkerType::kXMP) { |
108 | 0 | marker[0] = 0xE1; |
109 | 0 | if (marker.size() < 3 + sizeof kXMPTag) { |
110 | 0 | return JXL_FAILURE("Incorrect XMP marker size"); |
111 | 0 | } |
112 | 0 | memcpy(&marker[3], kXMPTag, sizeof kXMPTag); |
113 | 0 | } |
114 | 0 | } |
115 | | // TODO(eustas): actually inject ICC profile and check it fits perfectly. |
116 | 0 | for (auto& marker : jpeg_data->com_data) { |
117 | 0 | JXL_RETURN_IF_ERROR(br_read(marker)); |
118 | 0 | if (marker[1] * 256u + marker[2] + 1u != marker.size()) { |
119 | 0 | return JXL_FAILURE("Incorrect marker size"); |
120 | 0 | } |
121 | 0 | } |
122 | 0 | for (auto& data : jpeg_data->inter_marker_data) { |
123 | 0 | JXL_RETURN_IF_ERROR(br_read(data)); |
124 | 0 | } |
125 | 0 | JXL_RETURN_IF_ERROR(br_read(jpeg_data->tail_data)); |
126 | | |
127 | | // Check if there is more decompressed output. |
128 | 0 | size_t available_out = 1; |
129 | 0 | uint64_t sink; |
130 | 0 | uint8_t* next_out = reinterpret_cast<uint8_t*>(&sink); |
131 | 0 | result = BrotliDecoderDecompressStream(brotli_dec, &available_in, &in, |
132 | 0 | &available_out, &next_out, nullptr); |
133 | 0 | if (available_out == 0 || |
134 | 0 | result == BrotliDecoderResult::BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { |
135 | 0 | return JXL_FAILURE("Excess data in compressed stream"); |
136 | 0 | } |
137 | 0 | if (result == BrotliDecoderResult::BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) { |
138 | 0 | return JXL_FAILURE("Incomplete brotli-stream"); |
139 | 0 | } |
140 | 0 | if (!BrotliDecoderIsFinished(brotli_dec) || |
141 | 0 | result != BrotliDecoderResult::BROTLI_DECODER_RESULT_SUCCESS) { |
142 | 0 | return JXL_FAILURE("Corrupted brotli-stream"); |
143 | 0 | } |
144 | 0 | if (available_in != 0) { |
145 | 0 | return JXL_FAILURE("Unused data after brotli stream"); |
146 | 0 | } |
147 | | |
148 | 0 | return true; |
149 | 0 | } |
150 | | } // namespace jpeg |
151 | | } // namespace jxl |