1 // Copyright 2017 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 #include "core/fpdfapi/parser/cpdf_read_validator.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "core/fpdfapi/parser/cpdf_stream.h"
11 #include "core/fxcrt/fx_safe_types.h"
12 #include "third_party/base/notreached.h"
13
14 namespace {
15
16 constexpr FX_FILESIZE kAlignBlockValue = CPDF_Stream::kFileBufSize;
17
AlignDown(FX_FILESIZE offset)18 FX_FILESIZE AlignDown(FX_FILESIZE offset) {
19 return offset > 0 ? (offset - offset % kAlignBlockValue) : 0;
20 }
21
AlignUp(FX_FILESIZE offset)22 FX_FILESIZE AlignUp(FX_FILESIZE offset) {
23 FX_SAFE_FILESIZE safe_result = AlignDown(offset);
24 safe_result += kAlignBlockValue;
25 return safe_result.ValueOrDefault(offset);
26 }
27
28 } // namespace
29
ScopedSession(RetainPtr<CPDF_ReadValidator> validator)30 CPDF_ReadValidator::ScopedSession::ScopedSession(
31 RetainPtr<CPDF_ReadValidator> validator)
32 : validator_(std::move(validator)),
33 saved_read_error_(validator_->read_error_),
34 saved_has_unavailable_data_(validator_->has_unavailable_data_) {
35 validator_->ResetErrors();
36 }
37
~ScopedSession()38 CPDF_ReadValidator::ScopedSession::~ScopedSession() {
39 validator_->read_error_ |= saved_read_error_;
40 validator_->has_unavailable_data_ |= saved_has_unavailable_data_;
41 }
42
CPDF_ReadValidator(RetainPtr<IFX_SeekableReadStream> file_read,CPDF_DataAvail::FileAvail * file_avail)43 CPDF_ReadValidator::CPDF_ReadValidator(
44 RetainPtr<IFX_SeekableReadStream> file_read,
45 CPDF_DataAvail::FileAvail* file_avail)
46 : file_read_(std::move(file_read)),
47 file_avail_(file_avail),
48 file_size_(file_read_->GetSize()) {}
49
50 CPDF_ReadValidator::~CPDF_ReadValidator() = default;
51
ResetErrors()52 void CPDF_ReadValidator::ResetErrors() {
53 read_error_ = false;
54 has_unavailable_data_ = false;
55 }
56
ReadBlockAtOffset(pdfium::span<uint8_t> buffer,FX_FILESIZE offset)57 bool CPDF_ReadValidator::ReadBlockAtOffset(pdfium::span<uint8_t> buffer,
58 FX_FILESIZE offset) {
59 if (offset < 0) {
60 NOTREACHED();
61 return false;
62 }
63
64 FX_SAFE_FILESIZE end_offset = offset;
65 end_offset += buffer.size();
66 if (!end_offset.IsValid() || end_offset.ValueOrDie() > file_size_)
67 return false;
68
69 if (!IsDataRangeAvailable(offset, buffer.size())) {
70 ScheduleDownload(offset, buffer.size());
71 return false;
72 }
73
74 if (file_read_->ReadBlockAtOffset(buffer, offset))
75 return true;
76
77 read_error_ = true;
78 ScheduleDownload(offset, buffer.size());
79 return false;
80 }
81
GetSize()82 FX_FILESIZE CPDF_ReadValidator::GetSize() {
83 return file_size_;
84 }
85
ScheduleDownload(FX_FILESIZE offset,size_t size)86 void CPDF_ReadValidator::ScheduleDownload(FX_FILESIZE offset, size_t size) {
87 has_unavailable_data_ = true;
88 if (!hints_ || size == 0)
89 return;
90
91 const FX_FILESIZE start_segment_offset = AlignDown(offset);
92 FX_SAFE_FILESIZE end_segment_offset = offset;
93 end_segment_offset += size;
94 if (!end_segment_offset.IsValid()) {
95 NOTREACHED();
96 return;
97 }
98 end_segment_offset =
99 std::min(file_size_, AlignUp(end_segment_offset.ValueOrDie()));
100
101 FX_SAFE_SIZE_T segment_size = end_segment_offset;
102 segment_size -= start_segment_offset;
103 if (!segment_size.IsValid()) {
104 NOTREACHED();
105 return;
106 }
107 hints_->AddSegment(start_segment_offset, segment_size.ValueOrDie());
108 }
109
IsDataRangeAvailable(FX_FILESIZE offset,size_t size) const110 bool CPDF_ReadValidator::IsDataRangeAvailable(FX_FILESIZE offset,
111 size_t size) const {
112 return whole_file_already_available_ || !file_avail_ ||
113 file_avail_->IsDataAvail(offset, size);
114 }
115
IsWholeFileAvailable()116 bool CPDF_ReadValidator::IsWholeFileAvailable() {
117 const FX_SAFE_SIZE_T safe_size = file_size_;
118 whole_file_already_available_ =
119 whole_file_already_available_ ||
120 (safe_size.IsValid() && IsDataRangeAvailable(0, safe_size.ValueOrDie()));
121
122 return whole_file_already_available_;
123 }
124
CheckDataRangeAndRequestIfUnavailable(FX_FILESIZE offset,size_t size)125 bool CPDF_ReadValidator::CheckDataRangeAndRequestIfUnavailable(
126 FX_FILESIZE offset,
127 size_t size) {
128 if (offset > file_size_)
129 return true;
130
131 FX_SAFE_FILESIZE end_segment_offset = offset;
132 end_segment_offset += size;
133 // Increase checked range to allow CPDF_SyntaxParser read whole buffer.
134 end_segment_offset += CPDF_Stream::kFileBufSize;
135 if (!end_segment_offset.IsValid()) {
136 NOTREACHED();
137 return false;
138 }
139 end_segment_offset = std::min(
140 file_size_, static_cast<FX_FILESIZE>(end_segment_offset.ValueOrDie()));
141 FX_SAFE_SIZE_T segment_size = end_segment_offset;
142 segment_size -= offset;
143 if (!segment_size.IsValid()) {
144 NOTREACHED();
145 return false;
146 }
147
148 if (IsDataRangeAvailable(offset, segment_size.ValueOrDie()))
149 return true;
150
151 ScheduleDownload(offset, segment_size.ValueOrDie());
152 return false;
153 }
154
CheckWholeFileAndRequestIfUnavailable()155 bool CPDF_ReadValidator::CheckWholeFileAndRequestIfUnavailable() {
156 if (IsWholeFileAvailable())
157 return true;
158
159 const FX_SAFE_SIZE_T safe_size = file_size_;
160 if (safe_size.IsValid())
161 ScheduleDownload(0, safe_size.ValueOrDie());
162
163 return false;
164 }
165