| // Copyright 2016 The PDFium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com | |
| #include "core/fpdfapi/page/cpdf_streamparser.h" | |
| #include <algorithm> | |
| #include <memory> | |
| #include <utility> | |
| #include "constants/stream_dict_common.h" | |
| #include "core/fpdfapi/page/cpdf_docpagedata.h" | |
| #include "core/fpdfapi/parser/cpdf_array.h" | |
| #include "core/fpdfapi/parser/cpdf_boolean.h" | |
| #include "core/fpdfapi/parser/cpdf_dictionary.h" | |
| #include "core/fpdfapi/parser/cpdf_name.h" | |
| #include "core/fpdfapi/parser/cpdf_null.h" | |
| #include "core/fpdfapi/parser/cpdf_number.h" | |
| #include "core/fpdfapi/parser/cpdf_stream.h" | |
| #include "core/fpdfapi/parser/cpdf_string.h" | |
| #include "core/fpdfapi/parser/fpdf_parser_decode.h" | |
| #include "core/fpdfapi/parser/fpdf_parser_utility.h" | |
| #include "core/fxcodec/data_and_bytes_consumed.h" | |
| #include "core/fxcodec/jpeg/jpegmodule.h" | |
| #include "core/fxcodec/scanlinedecoder.h" | |
| #include "core/fxcrt/autorestorer.h" | |
| #include "core/fxcrt/check.h" | |
| #include "core/fxcrt/data_vector.h" | |
| #include "core/fxcrt/fx_extension.h" | |
| #include "core/fxcrt/fx_safe_types.h" | |
| #include "core/fxcrt/span_util.h" | |
| #include "core/fxge/calculate_pitch.h" | |
| #if defined(PDF_ENABLE_BROTLI) | |
| #include "core/fxcodec/brotli/brotli_decoder.h" | |
| #endif | |
| namespace { | |
| const uint32_t kMaxNestedParsingLevel = 512; | |
| const size_t kMaxStringLength = 32767; | |
| const char kTrue[] = "true"; | |
| const char kFalse[] = "false"; | |
| const char kNull[] = "null"; | |
| uint32_t DecodeAllScanlines(std::unique_ptr<ScanlineDecoder> pDecoder) { | |
| if (!pDecoder) { | |
| return FX_INVALID_OFFSET; | |
| } | |
| int ncomps = pDecoder->CountComps(); | |
| int bpc = pDecoder->GetBPC(); | |
| int width = pDecoder->GetWidth(); | |
| int height = pDecoder->GetHeight(); | |
| if (width <= 0 || height <= 0) { | |
| return FX_INVALID_OFFSET; | |
| } | |
| std::optional<uint32_t> maybe_size = | |
| fxge::CalculatePitch8(bpc, ncomps, width); | |
| if (!maybe_size.has_value()) { | |
| return FX_INVALID_OFFSET; | |
| } | |
| FX_SAFE_UINT32 size = maybe_size.value(); | |
| size *= height; | |
| if (size.ValueOrDefault(0) == 0) { | |
| return FX_INVALID_OFFSET; | |
| } | |
| for (int row = 0; row < height; ++row) { | |
| if (pDecoder->GetScanline(row).empty()) { | |
| break; | |
| } | |
| } | |
| return pDecoder->GetSrcOffset(); | |
| } | |
| uint32_t DecodeInlineStream(pdfium::span<const uint8_t> src_span, | |
| int width, | |
| int height, | |
| const ByteString& decoder, | |
| RetainPtr<const CPDF_Dictionary> pParam, | |
| uint32_t orig_size) { | |
| // |decoder| should not be an abbreviation. | |
| DCHECK(decoder != "A85"); | |
| DCHECK(decoder != "AHx"); | |
| DCHECK(decoder != "CCF"); | |
| DCHECK(decoder != "DCT"); | |
| DCHECK(decoder != "Fl"); | |
| DCHECK(decoder != "LZW"); | |
| DCHECK(decoder != "RL"); | |
| if (decoder == "FlateDecode") { | |
| return FlateOrLZWDecode(/*use_lzw=*/false, src_span, pParam.Get(), | |
| /*estimated_size=*/orig_size) | |
| .bytes_consumed; | |
| } | |
| if (decoder == "LZWDecode") { | |
| return FlateOrLZWDecode( | |
| /*use_lzw=*/true, src_span, pParam.Get(), | |
| /*estimated_size=*/0) | |
| .bytes_consumed; | |
| } | |
| if (decoder == "DCTDecode") { | |
| std::unique_ptr<ScanlineDecoder> pDecoder = JpegModule::CreateDecoder( | |
| src_span, width, height, 0, | |
| !pParam || pParam->GetIntegerFor("ColorTransform", 1)); | |
| return DecodeAllScanlines(std::move(pDecoder)); | |
| } | |
| if (decoder == "CCITTFaxDecode") { | |
| std::unique_ptr<ScanlineDecoder> pDecoder = | |
| CreateFaxDecoder(src_span, width, height, pParam.Get()); | |
| return DecodeAllScanlines(std::move(pDecoder)); | |
| } | |
| if (decoder == "ASCII85Decode") { | |
| return A85Decode(src_span).bytes_consumed; | |
| } | |
| if (decoder == "ASCIIHexDecode") { | |
| return HexDecode(src_span).bytes_consumed; | |
| } | |
| if (decoder == "RunLengthDecode") { | |
| return RunLengthDecode(src_span).bytes_consumed; | |
| } | |
| #if defined(PDF_ENABLE_BROTLI) | |
| if (decoder == "BrotliDecode" && BrotliDecoder::GetBrotliEnabled()) { | |
| return BrotliDecoder::Decode(src_span, orig_size).bytes_consumed; | |
| } | |
| #endif | |
| return FX_INVALID_OFFSET; | |
| } | |
| } // namespace | |
| CPDF_StreamParser::CPDF_StreamParser(pdfium::span<const uint8_t> span) | |
| : buf_(span) {} | |
| CPDF_StreamParser::CPDF_StreamParser(pdfium::span<const uint8_t> span, | |
| const WeakPtr<ByteStringPool>& pPool) | |
| : pool_(pPool), buf_(span) {} | |
| CPDF_StreamParser::~CPDF_StreamParser() = default; | |
| RetainPtr<CPDF_Stream> CPDF_StreamParser::ReadInlineStream( | |
| CPDF_Document* doc, | |
| RetainPtr<CPDF_Dictionary> dict, | |
| const CPDF_Object* pCSObj) { | |
| auto stream_span = buf_.subspan(pos_); | |
| if (stream_span.empty()) { | |
| return nullptr; | |
| } | |
| if (PDFCharIsWhitespace(stream_span.front())) { | |
| pos_++; | |
| stream_span = stream_span.subspan<1>(); | |
| if (stream_span.empty()) { | |
| return nullptr; | |
| } | |
| } | |
| ByteString decoder; | |
| RetainPtr<const CPDF_Dictionary> param_dict; | |
| RetainPtr<const CPDF_Object> filter = dict->GetDirectObjectFor("Filter"); | |
| if (filter) { | |
| const CPDF_Array* array = filter->AsArray(); | |
| if (array) { | |
| decoder = array->GetByteStringAt(0); | |
| RetainPtr<const CPDF_Array> params = | |
| dict->GetArrayFor(pdfium::stream::kDecodeParms); | |
| if (params) { | |
| param_dict = params->GetDictAt(0); | |
| } | |
| } else { | |
| decoder = filter->GetString(); | |
| param_dict = dict->GetDictFor(pdfium::stream::kDecodeParms); | |
| } | |
| } | |
| uint32_t width = dict->GetIntegerFor("Width"); | |
| uint32_t height = dict->GetIntegerFor("Height"); | |
| uint32_t bpc = 1; | |
| uint32_t nComponents = 1; | |
| if (pCSObj) { | |
| RetainPtr<CPDF_ColorSpace> pCS = | |
| CPDF_DocPageData::FromDocument(doc)->GetColorSpace(pCSObj, nullptr); | |
| nComponents = pCS ? pCS->ComponentCount() : 3; | |
| bpc = dict->GetIntegerFor("BitsPerComponent"); | |
| } | |
| std::optional<uint32_t> maybe_size = | |
| fxge::CalculatePitch8(bpc, nComponents, width); | |
| if (!maybe_size.has_value()) { | |
| return nullptr; | |
| } | |
| FX_SAFE_UINT32 size = maybe_size.value(); | |
| size *= height; | |
| if (!size.IsValid()) { | |
| return nullptr; | |
| } | |
| uint32_t original_size = size.ValueOrDie(); | |
| DataVector<uint8_t> data; | |
| uint32_t actual_stream_size; | |
| if (decoder.IsEmpty()) { | |
| original_size = std::min<uint32_t>(original_size, stream_span.size()); | |
| auto src_span = stream_span.first(original_size); | |
| data = DataVector<uint8_t>(src_span.begin(), src_span.end()); | |
| actual_stream_size = original_size; | |
| pos_ += original_size; | |
| } else { | |
| actual_stream_size = | |
| DecodeInlineStream(stream_span, width, height, decoder, | |
| std::move(param_dict), original_size); | |
| if (!pdfium::IsValueInRangeForNumericType<int>(actual_stream_size)) { | |
| return nullptr; | |
| } | |
| { | |
| AutoRestorer<uint32_t> saved_position(&pos_); | |
| pos_ += actual_stream_size; | |
| while (true) { | |
| uint32_t saved_iteration_position = pos_; | |
| ElementType type = ParseNextElement(); | |
| if (type == ElementType::kEndOfData) { | |
| return nullptr; | |
| } | |
| if (type == ElementType::kKeyword && GetWord() == "EI") { | |
| break; | |
| } | |
| actual_stream_size += pos_ - saved_iteration_position; | |
| } | |
| } | |
| auto src_span = stream_span.first(actual_stream_size); | |
| data = DataVector<uint8_t>(src_span.begin(), src_span.end()); | |
| pos_ += actual_stream_size; | |
| } | |
| dict->SetNewFor<CPDF_Number>("Length", static_cast<int>(actual_stream_size)); | |
| return pdfium::MakeRetain<CPDF_Stream>(std::move(data), std::move(dict)); | |
| } | |
| CPDF_StreamParser::ElementType CPDF_StreamParser::ParseNextElement() { | |
| last_obj_.Reset(); | |
| word_size_ = 0; | |
| if (!PositionIsInBounds()) { | |
| return ElementType::kEndOfData; | |
| } | |
| uint8_t ch = buf_[pos_++]; | |
| while (true) { | |
| while (PDFCharIsWhitespace(ch)) { | |
| if (!PositionIsInBounds()) { | |
| return ElementType::kEndOfData; | |
| } | |
| ch = buf_[pos_++]; | |
| } | |
| if (ch != '%') { | |
| break; | |
| } | |
| while (true) { | |
| if (!PositionIsInBounds()) { | |
| return ElementType::kEndOfData; | |
| } | |
| ch = buf_[pos_++]; | |
| if (PDFCharIsLineEnding(ch)) { | |
| break; | |
| } | |
| } | |
| } | |
| if (PDFCharIsDelimiter(ch) && ch != '/') { | |
| pos_--; | |
| last_obj_ = ReadNextObject(false, false, 0); | |
| return ElementType::kOther; | |
| } | |
| bool bIsNumber = true; | |
| while (true) { | |
| if (word_size_ < kMaxWordLength) { | |
| word_buffer_[word_size_++] = ch; | |
| } | |
| if (!PDFCharIsNumeric(ch)) { | |
| bIsNumber = false; | |
| } | |
| if (!PositionIsInBounds()) { | |
| break; | |
| } | |
| ch = buf_[pos_++]; | |
| if (PDFCharIsDelimiter(ch) || PDFCharIsWhitespace(ch)) { | |
| pos_--; | |
| break; | |
| } | |
| } | |
| word_buffer_[word_size_] = 0; | |
| if (bIsNumber) { | |
| return ElementType::kNumber; | |
| } | |
| if (word_buffer_[0] == '/') { | |
| return ElementType::kName; | |
| } | |
| if (word_size_ == 4) { | |
| if (GetWord() == kTrue) { | |
| last_obj_ = pdfium::MakeRetain<CPDF_Boolean>(true); | |
| return ElementType::kOther; | |
| } | |
| if (GetWord() == kNull) { | |
| last_obj_ = pdfium::MakeRetain<CPDF_Null>(); | |
| return ElementType::kOther; | |
| } | |
| } else if (word_size_ == 5) { | |
| if (GetWord() == kFalse) { | |
| last_obj_ = pdfium::MakeRetain<CPDF_Boolean>(false); | |
| return ElementType::kOther; | |
| } | |
| } | |
| return ElementType::kKeyword; | |
| } | |
| RetainPtr<CPDF_Object> CPDF_StreamParser::ReadNextObject( | |
| bool bAllowNestedArray, | |
| bool bInArray, | |
| uint32_t dwRecursionLevel) { | |
| bool bIsNumber; | |
| // Must get the next word before returning to avoid infinite loops. | |
| GetNextWord(bIsNumber); | |
| if (!word_size_ || dwRecursionLevel > kMaxNestedParsingLevel) { | |
| return nullptr; | |
| } | |
| if (bIsNumber) { | |
| word_buffer_[word_size_] = 0; | |
| return pdfium::MakeRetain<CPDF_Number>(GetWord()); | |
| } | |
| int first_char = word_buffer_[0]; | |
| if (first_char == '/') { | |
| ByteString name = PDF_NameDecode(GetWord().Substr(1)); | |
| return pdfium::MakeRetain<CPDF_Name>(pool_, name); | |
| } | |
| if (first_char == '(') { | |
| return pdfium::MakeRetain<CPDF_String>(pool_, ReadString()); | |
| } | |
| if (first_char == '<') { | |
| if (word_size_ == 1) { | |
| return pdfium::MakeRetain<CPDF_String>(pool_, ReadHexString(), | |
| CPDF_String::DataType::kIsHex); | |
| } | |
| auto dict = pdfium::MakeRetain<CPDF_Dictionary>(pool_); | |
| while (true) { | |
| GetNextWord(bIsNumber); | |
| if (word_size_ == 2 && word_buffer_[0] == '>') { | |
| break; | |
| } | |
| if (!word_size_ || word_buffer_[0] != '/') { | |
| return nullptr; | |
| } | |
| ByteString key = PDF_NameDecode(GetWord().Substr(1)); | |
| RetainPtr<CPDF_Object> pObj = | |
| ReadNextObject(true, bInArray, dwRecursionLevel + 1); | |
| if (!pObj) { | |
| return nullptr; | |
| } | |
| dict->SetFor(key, std::move(pObj)); | |
| } | |
| return dict; | |
| } | |
| if (first_char == '[') { | |
| if ((!bAllowNestedArray && bInArray)) { | |
| return nullptr; | |
| } | |
| auto pArray = pdfium::MakeRetain<CPDF_Array>(); | |
| while (true) { | |
| RetainPtr<CPDF_Object> pObj = | |
| ReadNextObject(bAllowNestedArray, true, dwRecursionLevel + 1); | |
| if (pObj) { | |
| pArray->Append(std::move(pObj)); | |
| continue; | |
| } | |
| if (!word_size_ || word_buffer_[0] == ']') { | |
| break; | |
| } | |
| } | |
| return pArray; | |
| } | |
| if (GetWord() == kFalse) { | |
| return pdfium::MakeRetain<CPDF_Boolean>(false); | |
| } | |
| if (GetWord() == kTrue) { | |
| return pdfium::MakeRetain<CPDF_Boolean>(true); | |
| } | |
| if (GetWord() == kNull) { | |
| return pdfium::MakeRetain<CPDF_Null>(); | |
| } | |
| return nullptr; | |
| } | |
| // TODO(npm): the following methods are almost identical in cpdf_syntaxparser | |
| void CPDF_StreamParser::GetNextWord(bool& bIsNumber) { | |
| word_size_ = 0; | |
| bIsNumber = true; | |
| if (!PositionIsInBounds()) { | |
| return; | |
| } | |
| uint8_t ch = buf_[pos_++]; | |
| while (true) { | |
| while (PDFCharIsWhitespace(ch)) { | |
| if (!PositionIsInBounds()) { | |
| return; | |
| } | |
| ch = buf_[pos_++]; | |
| } | |
| if (ch != '%') { | |
| break; | |
| } | |
| while (true) { | |
| if (!PositionIsInBounds()) { | |
| return; | |
| } | |
| ch = buf_[pos_++]; | |
| if (PDFCharIsLineEnding(ch)) { | |
| break; | |
| } | |
| } | |
| } | |
| if (PDFCharIsDelimiter(ch)) { | |
| bIsNumber = false; | |
| word_buffer_[word_size_++] = ch; | |
| if (ch == '/') { | |
| while (true) { | |
| if (!PositionIsInBounds()) { | |
| return; | |
| } | |
| ch = buf_[pos_++]; | |
| if (!PDFCharIsOther(ch) && !PDFCharIsNumeric(ch)) { | |
| pos_--; | |
| return; | |
| } | |
| if (word_size_ < kMaxWordLength) { | |
| word_buffer_[word_size_++] = ch; | |
| } | |
| } | |
| } else if (ch == '<') { | |
| if (!PositionIsInBounds()) { | |
| return; | |
| } | |
| ch = buf_[pos_++]; | |
| if (ch == '<') { | |
| word_buffer_[word_size_++] = ch; | |
| } else { | |
| pos_--; | |
| } | |
| } else if (ch == '>') { | |
| if (!PositionIsInBounds()) { | |
| return; | |
| } | |
| ch = buf_[pos_++]; | |
| if (ch == '>') { | |
| word_buffer_[word_size_++] = ch; | |
| } else { | |
| pos_--; | |
| } | |
| } | |
| return; | |
| } | |
| while (true) { | |
| if (word_size_ < kMaxWordLength) { | |
| word_buffer_[word_size_++] = ch; | |
| } | |
| if (!PDFCharIsNumeric(ch)) { | |
| bIsNumber = false; | |
| } | |
| if (!PositionIsInBounds()) { | |
| return; | |
| } | |
| ch = buf_[pos_++]; | |
| if (PDFCharIsDelimiter(ch) || PDFCharIsWhitespace(ch)) { | |
| pos_--; | |
| break; | |
| } | |
| } | |
| } | |
| ByteString CPDF_StreamParser::ReadString() { | |
| if (!PositionIsInBounds()) { | |
| return ByteString(); | |
| } | |
| ByteString buf; | |
| int parlevel = 0; | |
| int status = 0; | |
| int iEscCode = 0; | |
| uint8_t ch = buf_[pos_++]; | |
| while (true) { | |
| switch (status) { | |
| case 0: | |
| if (ch == ')') { | |
| if (parlevel == 0) { | |
| return buf.First(std::min(buf.GetLength(), kMaxStringLength)); | |
| } | |
| parlevel--; | |
| buf += ')'; | |
| } else if (ch == '(') { | |
| parlevel++; | |
| buf += '('; | |
| } else if (ch == '\\') { | |
| status = 1; | |
| } else { | |
| buf += static_cast<char>(ch); | |
| } | |
| break; | |
| case 1: | |
| if (FXSYS_IsOctalDigit(ch)) { | |
| iEscCode = FXSYS_DecimalCharToInt(static_cast<char>(ch)); | |
| status = 2; | |
| break; | |
| } | |
| if (ch == '\r') { | |
| status = 4; | |
| break; | |
| } | |
| if (ch == '\n') { | |
| // Do nothing. | |
| } else if (ch == 'n') { | |
| buf += '\n'; | |
| } else if (ch == 'r') { | |
| buf += '\r'; | |
| } else if (ch == 't') { | |
| buf += '\t'; | |
| } else if (ch == 'b') { | |
| buf += '\b'; | |
| } else if (ch == 'f') { | |
| buf += '\f'; | |
| } else { | |
| buf += static_cast<char>(ch); | |
| } | |
| status = 0; | |
| break; | |
| case 2: | |
| if (FXSYS_IsOctalDigit(ch)) { | |
| iEscCode = | |
| iEscCode * 8 + FXSYS_DecimalCharToInt(static_cast<char>(ch)); | |
| status = 3; | |
| } else { | |
| buf += static_cast<char>(iEscCode); | |
| status = 0; | |
| continue; | |
| } | |
| break; | |
| case 3: | |
| if (FXSYS_IsOctalDigit(ch)) { | |
| iEscCode = | |
| iEscCode * 8 + FXSYS_DecimalCharToInt(static_cast<char>(ch)); | |
| buf += static_cast<char>(iEscCode); | |
| status = 0; | |
| } else { | |
| buf += static_cast<char>(iEscCode); | |
| status = 0; | |
| continue; | |
| } | |
| break; | |
| case 4: | |
| status = 0; | |
| if (ch != '\n') { | |
| continue; | |
| } | |
| break; | |
| } | |
| if (!PositionIsInBounds()) { | |
| return buf.First(std::min(buf.GetLength(), kMaxStringLength)); | |
| } | |
| ch = buf_[pos_++]; | |
| } | |
| } | |
| DataVector<uint8_t> CPDF_StreamParser::ReadHexString() { | |
| if (!PositionIsInBounds()) { | |
| return DataVector<uint8_t>(); | |
| } | |
| // TODO(thestig): Deduplicate CPDF_SyntaxParser::ReadHexString()? | |
| DataVector<uint8_t> buf; | |
| bool bFirst = true; | |
| uint8_t code = 0; | |
| while (PositionIsInBounds()) { | |
| uint8_t ch = buf_[pos_++]; | |
| if (ch == '>') { | |
| break; | |
| } | |
| if (!FXSYS_IsHexDigit(ch)) { | |
| continue; | |
| } | |
| int val = FXSYS_HexCharToInt(ch); | |
| if (bFirst) { | |
| code = val * 16; | |
| } else { | |
| code += val; | |
| buf.push_back(code); | |
| } | |
| bFirst = !bFirst; | |
| } | |
| if (!bFirst) { | |
| buf.push_back(code); | |
| } | |
| if (buf.size() > kMaxStringLength) { | |
| buf.resize(kMaxStringLength); | |
| } | |
| return buf; | |
| } | |
| bool CPDF_StreamParser::PositionIsInBounds() const { | |
| return pos_ < buf_.size(); | |
| } |