1 // Copyright 2014 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7 #include "core/fxcodec/jpeg/jpegmodule.h"
8
9 #include <setjmp.h>
10
11 #include <memory>
12 #include <optional>
13 #include <type_traits>
14 #include <utility>
15
16 #include "build/build_config.h"
17 #include "core/fxcodec/cfx_codec_memory.h"
18 #include "core/fxcodec/jpeg/jpeg_common.h"
19 #include "core/fxcodec/scanlinedecoder.h"
20 #include "core/fxcrt/check.h"
21 #include "core/fxcrt/check_op.h"
22 #include "core/fxcrt/compiler_specific.h"
23 #include "core/fxcrt/data_vector.h"
24 #include "core/fxcrt/fx_safe_types.h"
25 #include "core/fxcrt/raw_span.h"
26 #include "core/fxge/dib/cfx_dibbase.h"
27 #include "core/fxge/dib/fx_dib.h"
28
JpegScanSOI(pdfium::span<const uint8_t> src_span)29 static pdfium::span<const uint8_t> JpegScanSOI(
30 pdfium::span<const uint8_t> src_span) {
31 DCHECK(!src_span.empty());
32
33 for (size_t offset = 0; offset + 1 < src_span.size(); ++offset) {
34 if (src_span[offset] == 0xff && src_span[offset + 1] == 0xd8)
35 return src_span.subspan(offset);
36 }
37 return src_span;
38 }
39
40 extern "C" {
41
error_fatal(j_common_ptr cinfo)42 static void error_fatal(j_common_ptr cinfo) {
43 longjmp(*(jmp_buf*)cinfo->client_data, -1);
44 }
45
src_skip_data(jpeg_decompress_struct * cinfo,long num)46 static void src_skip_data(jpeg_decompress_struct* cinfo, long num) {
47 if (num > (long)cinfo->src->bytes_in_buffer) {
48 error_fatal((j_common_ptr)cinfo);
49 }
50 // SAFETY: required from library API as checked above.
51 UNSAFE_BUFFERS(cinfo->src->next_input_byte += num);
52 cinfo->src->bytes_in_buffer -= num;
53 }
54
55 #if BUILDFLAG(IS_WIN)
dest_do_nothing(j_compress_ptr cinfo)56 static void dest_do_nothing(j_compress_ptr cinfo) {}
57
dest_empty(j_compress_ptr cinfo)58 static boolean dest_empty(j_compress_ptr cinfo) {
59 return false;
60 }
61 #endif // BUILDFLAG(IS_WIN)
62
63 } // extern "C"
64
JpegLoadInfo(pdfium::span<const uint8_t> src_span,JpegModule::ImageInfo * pInfo)65 static bool JpegLoadInfo(pdfium::span<const uint8_t> src_span,
66 JpegModule::ImageInfo* pInfo) {
67 src_span = JpegScanSOI(src_span);
68 jpeg_decompress_struct cinfo;
69 jpeg_error_mgr jerr;
70 jerr.error_exit = error_fatal;
71 jerr.emit_message = error_do_nothing_int;
72 jerr.output_message = error_do_nothing;
73 jerr.format_message = error_do_nothing_char;
74 jerr.reset_error_mgr = error_do_nothing;
75 jerr.trace_level = 0;
76 cinfo.err = &jerr;
77 jmp_buf mark;
78 cinfo.client_data = &mark;
79 if (setjmp(mark) == -1)
80 return false;
81
82 jpeg_create_decompress(&cinfo);
83 jpeg_source_mgr src;
84 src.init_source = src_do_nothing;
85 src.term_source = src_do_nothing;
86 src.skip_input_data = src_skip_data;
87 src.fill_input_buffer = src_fill_buffer;
88 src.resync_to_restart = src_resync;
89 src.bytes_in_buffer = src_span.size();
90 src.next_input_byte = src_span.data();
91 cinfo.src = &src;
92 if (setjmp(mark) == -1) {
93 jpeg_destroy_decompress(&cinfo);
94 return false;
95 }
96 int ret = jpeg_read_header(&cinfo, TRUE);
97 if (ret != JPEG_HEADER_OK) {
98 jpeg_destroy_decompress(&cinfo);
99 return false;
100 }
101 pInfo->width = cinfo.image_width;
102 pInfo->height = cinfo.image_height;
103 pInfo->num_components = cinfo.num_components;
104 pInfo->color_transform =
105 cinfo.jpeg_color_space == JCS_YCbCr || cinfo.jpeg_color_space == JCS_YCCK;
106 pInfo->bits_per_components = cinfo.data_precision;
107 jpeg_destroy_decompress(&cinfo);
108 return true;
109 }
110
111 namespace fxcodec {
112
113 namespace {
114
115 constexpr size_t kKnownBadHeaderWithInvalidHeightByteOffsetStarts[] = {94, 163};
116
117 class JpegDecoder final : public ScanlineDecoder {
118 public:
119 JpegDecoder();
120 ~JpegDecoder() override;
121
122 bool Create(pdfium::span<const uint8_t> src_span,
123 uint32_t width,
124 uint32_t height,
125 int nComps,
126 bool ColorTransform);
127
128 // ScanlineDecoder:
129 bool Rewind() override;
130 pdfium::span<uint8_t> GetNextLine() override;
131 uint32_t GetSrcOffset() override;
132
133 bool InitDecode(bool bAcceptKnownBadHeader);
134
135 private:
136 void CalcPitch();
137 void InitDecompressSrc();
138
139 // Can only be called inside a jpeg_read_header() setjmp handler.
140 bool HasKnownBadHeaderWithInvalidHeight(size_t dimension_offset) const;
141
142 // Is a JPEG SOFn marker, which is defined as 0xff, 0xc[0-9a-f].
143 bool IsSofSegment(size_t marker_offset) const;
144
145 // Patch up the in-memory JPEG header for known bad JPEGs.
146 void PatchUpKnownBadHeaderWithInvalidHeight(size_t dimension_offset);
147
148 // Patch up the JPEG trailer, even if it is correct.
149 void PatchUpTrailer();
150
151 pdfium::span<uint8_t> GetWritableSrcData();
152
153 // For a given invalid height byte offset in
154 // |kKnownBadHeaderWithInvalidHeightByteOffsetStarts|, the SOFn marker should
155 // be this many bytes before that.
156 static constexpr size_t kSofMarkerByteOffset = 5;
157
158 jmp_buf m_JmpBuf;
159 jpeg_decompress_struct m_Cinfo = {};
160 jpeg_error_mgr m_Jerr = {};
161 jpeg_source_mgr m_Src = {};
162 pdfium::raw_span<const uint8_t> m_SrcSpan;
163 DataVector<uint8_t> m_ScanlineBuf;
164 bool m_bInited = false;
165 bool m_bStarted = false;
166 bool m_bJpegTransform = false;
167 uint32_t m_nDefaultScaleDenom = 1;
168
169 static_assert(std::is_aggregate_v<decltype(m_Cinfo)>);
170 static_assert(std::is_aggregate_v<decltype(m_Jerr)>);
171 static_assert(std::is_aggregate_v<decltype(m_Src)>);
172 };
173
174 JpegDecoder::JpegDecoder() = default;
175
~JpegDecoder()176 JpegDecoder::~JpegDecoder() {
177 if (m_bInited)
178 jpeg_destroy_decompress(&m_Cinfo);
179
180 // Span in superclass can't outlive our buffer.
181 m_pLastScanline = pdfium::span<uint8_t>();
182 }
183
InitDecode(bool bAcceptKnownBadHeader)184 bool JpegDecoder::InitDecode(bool bAcceptKnownBadHeader) {
185 m_Cinfo.err = &m_Jerr;
186 m_Cinfo.client_data = &m_JmpBuf;
187 if (setjmp(m_JmpBuf) == -1)
188 return false;
189
190 jpeg_create_decompress(&m_Cinfo);
191 InitDecompressSrc();
192 m_bInited = true;
193
194 if (setjmp(m_JmpBuf) == -1) {
195 std::optional<size_t> known_bad_header_offset;
196 if (bAcceptKnownBadHeader) {
197 for (size_t offset : kKnownBadHeaderWithInvalidHeightByteOffsetStarts) {
198 if (HasKnownBadHeaderWithInvalidHeight(offset)) {
199 known_bad_header_offset = offset;
200 break;
201 }
202 }
203 }
204 jpeg_destroy_decompress(&m_Cinfo);
205 if (!known_bad_header_offset.has_value()) {
206 m_bInited = false;
207 return false;
208 }
209
210 PatchUpKnownBadHeaderWithInvalidHeight(known_bad_header_offset.value());
211
212 jpeg_create_decompress(&m_Cinfo);
213 InitDecompressSrc();
214 }
215 m_Cinfo.image_width = m_OrigWidth;
216 m_Cinfo.image_height = m_OrigHeight;
217 int ret = jpeg_read_header(&m_Cinfo, TRUE);
218 if (ret != JPEG_HEADER_OK)
219 return false;
220
221 if (m_Cinfo.saw_Adobe_marker)
222 m_bJpegTransform = true;
223
224 if (m_Cinfo.num_components == 3 && !m_bJpegTransform)
225 m_Cinfo.out_color_space = m_Cinfo.jpeg_color_space;
226
227 m_OrigWidth = m_Cinfo.image_width;
228 m_OrigHeight = m_Cinfo.image_height;
229 m_OutputWidth = m_OrigWidth;
230 m_OutputHeight = m_OrigHeight;
231 m_nDefaultScaleDenom = m_Cinfo.scale_denom;
232 return true;
233 }
234
Create(pdfium::span<const uint8_t> src_span,uint32_t width,uint32_t height,int nComps,bool ColorTransform)235 bool JpegDecoder::Create(pdfium::span<const uint8_t> src_span,
236 uint32_t width,
237 uint32_t height,
238 int nComps,
239 bool ColorTransform) {
240 m_SrcSpan = JpegScanSOI(src_span);
241 if (m_SrcSpan.size() < 2)
242 return false;
243
244 PatchUpTrailer();
245
246 m_Jerr.error_exit = error_fatal;
247 m_Jerr.emit_message = error_do_nothing_int;
248 m_Jerr.output_message = error_do_nothing;
249 m_Jerr.format_message = error_do_nothing_char;
250 m_Jerr.reset_error_mgr = error_do_nothing;
251 m_Src.init_source = src_do_nothing;
252 m_Src.term_source = src_do_nothing;
253 m_Src.skip_input_data = src_skip_data;
254 m_Src.fill_input_buffer = src_fill_buffer;
255 m_Src.resync_to_restart = src_resync;
256 m_bJpegTransform = ColorTransform;
257 m_OutputWidth = m_OrigWidth = width;
258 m_OutputHeight = m_OrigHeight = height;
259 if (!InitDecode(/*bAcceptKnownBadHeader=*/true))
260 return false;
261
262 if (m_Cinfo.num_components < nComps)
263 return false;
264
265 if (m_Cinfo.image_width < width)
266 return false;
267
268 CalcPitch();
269 m_ScanlineBuf = DataVector<uint8_t>(m_Pitch);
270 m_nComps = m_Cinfo.num_components;
271 m_bpc = 8;
272 m_bStarted = false;
273 return true;
274 }
275
Rewind()276 bool JpegDecoder::Rewind() {
277 if (m_bStarted) {
278 jpeg_destroy_decompress(&m_Cinfo);
279 if (!InitDecode(/*bAcceptKnownBadHeader=*/false)) {
280 return false;
281 }
282 }
283 if (setjmp(m_JmpBuf) == -1) {
284 return false;
285 }
286 m_Cinfo.scale_denom = m_nDefaultScaleDenom;
287 m_OutputWidth = m_OrigWidth;
288 m_OutputHeight = m_OrigHeight;
289 if (!jpeg_start_decompress(&m_Cinfo)) {
290 jpeg_destroy_decompress(&m_Cinfo);
291 return false;
292 }
293 CHECK_LE(static_cast<int>(m_Cinfo.output_width), m_OrigWidth);
294 m_bStarted = true;
295 return true;
296 }
297
GetNextLine()298 pdfium::span<uint8_t> JpegDecoder::GetNextLine() {
299 if (setjmp(m_JmpBuf) == -1)
300 return pdfium::span<uint8_t>();
301
302 uint8_t* row_array[] = {m_ScanlineBuf.data()};
303 int nlines = jpeg_read_scanlines(&m_Cinfo, row_array, 1);
304 if (nlines <= 0)
305 return pdfium::span<uint8_t>();
306
307 return m_ScanlineBuf;
308 }
309
GetSrcOffset()310 uint32_t JpegDecoder::GetSrcOffset() {
311 return static_cast<uint32_t>(m_SrcSpan.size() - m_Src.bytes_in_buffer);
312 }
313
CalcPitch()314 void JpegDecoder::CalcPitch() {
315 m_Pitch = static_cast<uint32_t>(m_Cinfo.image_width) * m_Cinfo.num_components;
316 m_Pitch += 3;
317 m_Pitch /= 4;
318 m_Pitch *= 4;
319 }
320
InitDecompressSrc()321 void JpegDecoder::InitDecompressSrc() {
322 m_Cinfo.src = &m_Src;
323 m_Src.bytes_in_buffer = m_SrcSpan.size();
324 m_Src.next_input_byte = m_SrcSpan.data();
325 }
326
HasKnownBadHeaderWithInvalidHeight(size_t dimension_offset) const327 bool JpegDecoder::HasKnownBadHeaderWithInvalidHeight(
328 size_t dimension_offset) const {
329 // Perform lots of possibly redundant checks to make sure this has no false
330 // positives.
331 bool bDimensionChecks = m_Cinfo.err->msg_code == JERR_IMAGE_TOO_BIG &&
332 m_Cinfo.image_width < JPEG_MAX_DIMENSION &&
333 m_Cinfo.image_height == 0xffff && m_OrigWidth > 0 &&
334 m_OrigWidth <= JPEG_MAX_DIMENSION &&
335 m_OrigHeight > 0 &&
336 m_OrigHeight <= JPEG_MAX_DIMENSION;
337 if (!bDimensionChecks)
338 return false;
339
340 if (m_SrcSpan.size() <= dimension_offset + 3u)
341 return false;
342
343 if (!IsSofSegment(dimension_offset - kSofMarkerByteOffset))
344 return false;
345
346 const auto pHeaderDimensions = m_SrcSpan.subspan(dimension_offset);
347 uint8_t nExpectedWidthByte1 = (m_OrigWidth >> 8) & 0xff;
348 uint8_t nExpectedWidthByte2 = m_OrigWidth & 0xff;
349 // Height high byte, height low byte, width high byte, width low byte.
350 return pHeaderDimensions[0] == 0xff && pHeaderDimensions[1] == 0xff &&
351 pHeaderDimensions[2] == nExpectedWidthByte1 &&
352 pHeaderDimensions[3] == nExpectedWidthByte2;
353 }
354
IsSofSegment(size_t marker_offset) const355 bool JpegDecoder::IsSofSegment(size_t marker_offset) const {
356 const auto pHeaderMarker = m_SrcSpan.subspan(marker_offset);
357 return pHeaderMarker[0] == 0xff && pHeaderMarker[1] >= 0xc0 &&
358 pHeaderMarker[1] <= 0xcf;
359 }
360
PatchUpKnownBadHeaderWithInvalidHeight(size_t dimension_offset)361 void JpegDecoder::PatchUpKnownBadHeaderWithInvalidHeight(
362 size_t dimension_offset) {
363 DCHECK(m_SrcSpan.size() > dimension_offset + 1u);
364 auto pData = GetWritableSrcData().subspan(dimension_offset);
365 pData[0] = (m_OrigHeight >> 8) & 0xff;
366 pData[1] = m_OrigHeight & 0xff;
367 }
368
PatchUpTrailer()369 void JpegDecoder::PatchUpTrailer() {
370 auto pData = GetWritableSrcData();
371 pData[m_SrcSpan.size() - 2] = 0xff;
372 pData[m_SrcSpan.size() - 1] = 0xd9;
373 }
374
GetWritableSrcData()375 pdfium::span<uint8_t> JpegDecoder::GetWritableSrcData() {
376 // SAFETY: const_cast<> doesn't change size.
377 return UNSAFE_BUFFERS(pdfium::make_span(
378 const_cast<uint8_t*>(m_SrcSpan.data()), m_SrcSpan.size()));
379 }
380
381 } // namespace
382
383 // static
CreateDecoder(pdfium::span<const uint8_t> src_span,uint32_t width,uint32_t height,int nComps,bool ColorTransform)384 std::unique_ptr<ScanlineDecoder> JpegModule::CreateDecoder(
385 pdfium::span<const uint8_t> src_span,
386 uint32_t width,
387 uint32_t height,
388 int nComps,
389 bool ColorTransform) {
390 DCHECK(!src_span.empty());
391
392 auto pDecoder = std::make_unique<JpegDecoder>();
393 if (!pDecoder->Create(src_span, width, height, nComps, ColorTransform))
394 return nullptr;
395
396 return pDecoder;
397 }
398
399 // static
LoadInfo(pdfium::span<const uint8_t> src_span)400 std::optional<JpegModule::ImageInfo> JpegModule::LoadInfo(
401 pdfium::span<const uint8_t> src_span) {
402 ImageInfo info;
403 if (!JpegLoadInfo(src_span, &info))
404 return std::nullopt;
405
406 return info;
407 }
408
409 #if BUILDFLAG(IS_WIN)
JpegEncode(const RetainPtr<const CFX_DIBBase> & pSource,uint8_t ** dest_buf,size_t * dest_size)410 bool JpegModule::JpegEncode(const RetainPtr<const CFX_DIBBase>& pSource,
411 uint8_t** dest_buf,
412 size_t* dest_size) {
413 jpeg_error_mgr jerr;
414 jerr.error_exit = error_do_nothing;
415 jerr.emit_message = error_do_nothing_int;
416 jerr.output_message = error_do_nothing;
417 jerr.format_message = error_do_nothing_char;
418 jerr.reset_error_mgr = error_do_nothing;
419
420 jpeg_compress_struct cinfo = {}; // Aggregate initialization.
421 static_assert(std::is_aggregate_v<decltype(cinfo)>);
422 cinfo.err = &jerr;
423 jpeg_create_compress(&cinfo);
424 const int bytes_per_pixel = pSource->GetBPP() / 8;
425 uint32_t nComponents = bytes_per_pixel >= 3 ? 3 : 1;
426 uint32_t pitch = pSource->GetPitch();
427 uint32_t width = pdfium::checked_cast<uint32_t>(pSource->GetWidth());
428 uint32_t height = pdfium::checked_cast<uint32_t>(pSource->GetHeight());
429 FX_SAFE_UINT32 safe_buf_len = width;
430 safe_buf_len *= height;
431 safe_buf_len *= nComponents;
432 safe_buf_len += 1024;
433 if (!safe_buf_len.IsValid())
434 return false;
435
436 uint32_t dest_buf_length = safe_buf_len.ValueOrDie();
437 *dest_buf = FX_TryAlloc(uint8_t, dest_buf_length);
438 const int MIN_TRY_BUF_LEN = 1024;
439 while (!(*dest_buf) && dest_buf_length > MIN_TRY_BUF_LEN) {
440 dest_buf_length >>= 1;
441 *dest_buf = FX_TryAlloc(uint8_t, dest_buf_length);
442 }
443 if (!(*dest_buf))
444 return false;
445
446 jpeg_destination_mgr dest;
447 dest.init_destination = dest_do_nothing;
448 dest.term_destination = dest_do_nothing;
449 dest.empty_output_buffer = dest_empty;
450 dest.next_output_byte = *dest_buf;
451 dest.free_in_buffer = dest_buf_length;
452 cinfo.dest = &dest;
453 cinfo.image_width = width;
454 cinfo.image_height = height;
455 cinfo.input_components = nComponents;
456 if (nComponents == 1) {
457 cinfo.in_color_space = JCS_GRAYSCALE;
458 } else if (nComponents == 3) {
459 cinfo.in_color_space = JCS_RGB;
460 } else {
461 cinfo.in_color_space = JCS_CMYK;
462 }
463 uint8_t* line_buf = nullptr;
464 if (nComponents > 1)
465 line_buf = FX_Alloc2D(uint8_t, width, nComponents);
466
467 jpeg_set_defaults(&cinfo);
468 jpeg_start_compress(&cinfo, TRUE);
469 JSAMPROW row_pointer[1];
470 JDIMENSION row;
471 while (cinfo.next_scanline < cinfo.image_height) {
472 pdfium::span<const uint8_t> src_scan =
473 pSource->GetScanline(cinfo.next_scanline);
474 if (nComponents > 1) {
475 uint8_t* dest_scan = line_buf;
476 if (nComponents == 3) {
477 UNSAFE_TODO({
478 for (uint32_t i = 0; i < width; i++) {
479 ReverseCopy3Bytes(dest_scan, src_scan.data());
480 dest_scan += 3;
481 src_scan = src_scan.subspan(bytes_per_pixel);
482 }
483 });
484 } else {
485 UNSAFE_TODO({
486 for (uint32_t i = 0; i < pitch; i++) {
487 *dest_scan++ = ~src_scan.front();
488 src_scan = src_scan.subspan(1);
489 }
490 });
491 }
492 row_pointer[0] = line_buf;
493 } else {
494 row_pointer[0] = const_cast<uint8_t*>(src_scan.data());
495 }
496 row = cinfo.next_scanline;
497 jpeg_write_scanlines(&cinfo, row_pointer, 1);
498 UNSAFE_TODO({
499 if (cinfo.next_scanline == row) {
500 constexpr size_t kJpegBlockSize = 1048576;
501 *dest_buf =
502 FX_Realloc(uint8_t, *dest_buf, dest_buf_length + kJpegBlockSize);
503 dest.next_output_byte =
504 *dest_buf + dest_buf_length - dest.free_in_buffer;
505 dest_buf_length += kJpegBlockSize;
506 dest.free_in_buffer += kJpegBlockSize;
507 }
508 });
509 }
510 jpeg_finish_compress(&cinfo);
511 jpeg_destroy_compress(&cinfo);
512 FX_Free(line_buf);
513 *dest_size = dest_buf_length - static_cast<size_t>(dest.free_in_buffer);
514
515 return true;
516 }
517 #endif // BUILDFLAG(IS_WIN)
518
519 } // namespace fxcodec
520