• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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