Commit aabd3f6f9b09e844958fd4ee07bde5e8df546fc4
1 parent
ef866d68
Add private method QPDF::validateStreamLineEnd
Showing
2 changed files
with
46 additions
and
39 deletions
include/qpdf/QPDF.hh
| ... | ... | @@ -1009,6 +1009,7 @@ class QPDF |
| 1009 | 1009 | QPDFObjectHandle readTrailer(); |
| 1010 | 1010 | QPDFObjectHandle readObject(std::string const& description, QPDFObjGen og); |
| 1011 | 1011 | void readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset); |
| 1012 | + void validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset); | |
| 1012 | 1013 | QPDFObjectHandle readObjectInStream(std::shared_ptr<InputSource>, QPDFObjGen og); |
| 1013 | 1014 | size_t recoverStreamLength( |
| 1014 | 1015 | std::shared_ptr<InputSource> input, QPDFObjGen const& og, qpdf_offset_t stream_offset); | ... | ... |
libqpdf/QPDF.cc
| ... | ... | @@ -1324,6 +1324,47 @@ QPDF::readObject(std::string const& description, QPDFObjGen og) |
| 1324 | 1324 | void |
| 1325 | 1325 | QPDF::readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset) |
| 1326 | 1326 | { |
| 1327 | + validateStreamLineEnd(object, og, offset); | |
| 1328 | + | |
| 1329 | + // Must get offset before accessing any additional objects since resolving a previously | |
| 1330 | + // unresolved indirect object will change file position. | |
| 1331 | + qpdf_offset_t stream_offset = m->file->tell(); | |
| 1332 | + size_t length = 0; | |
| 1333 | + | |
| 1334 | + try { | |
| 1335 | + auto length_obj = object.getKey("/Length"); | |
| 1336 | + | |
| 1337 | + if (!length_obj.isInteger()) { | |
| 1338 | + if (length_obj.isNull()) { | |
| 1339 | + QTC::TC("qpdf", "QPDF stream without length"); | |
| 1340 | + throw damagedPDF(offset, "stream dictionary lacks /Length key"); | |
| 1341 | + } | |
| 1342 | + QTC::TC("qpdf", "QPDF stream length not integer"); | |
| 1343 | + throw damagedPDF(offset, "/Length key in stream dictionary is not an integer"); | |
| 1344 | + } | |
| 1345 | + | |
| 1346 | + length = toS(length_obj.getUIntValue()); | |
| 1347 | + // Seek in two steps to avoid potential integer overflow | |
| 1348 | + m->file->seek(stream_offset, SEEK_SET); | |
| 1349 | + m->file->seek(toO(length), SEEK_CUR); | |
| 1350 | + if (!readToken(m->file).isWord("endstream")) { | |
| 1351 | + QTC::TC("qpdf", "QPDF missing endstream"); | |
| 1352 | + throw damagedPDF("expected endstream"); | |
| 1353 | + } | |
| 1354 | + } catch (QPDFExc& e) { | |
| 1355 | + if (m->attempt_recovery) { | |
| 1356 | + warn(e); | |
| 1357 | + length = recoverStreamLength(m->file, og, stream_offset); | |
| 1358 | + } else { | |
| 1359 | + throw; | |
| 1360 | + } | |
| 1361 | + } | |
| 1362 | + object = newIndirect(og, QPDF_Stream::create(this, og, object, stream_offset, length)); | |
| 1363 | +} | |
| 1364 | + | |
| 1365 | +void | |
| 1366 | +QPDF::validateStreamLineEnd(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset) | |
| 1367 | +{ | |
| 1327 | 1368 | // The PDF specification states that the word "stream" should be followed by either a carriage |
| 1328 | 1369 | // return and a newline or by a newline alone. It specifically disallowed following it by a |
| 1329 | 1370 | // carriage return alone since, in that case, there would be no way to tell whether the NL in a |
| ... | ... | @@ -1336,12 +1377,12 @@ QPDF::readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset) |
| 1336 | 1377 | if (m->file->read(&ch, 1) == 0) { |
| 1337 | 1378 | // A premature EOF here will result in some other problem that will get reported at |
| 1338 | 1379 | // another time. |
| 1339 | - break; | |
| 1380 | + return; | |
| 1340 | 1381 | } |
| 1341 | 1382 | if (ch == '\n') { |
| 1342 | 1383 | // ready to read stream data |
| 1343 | 1384 | QTC::TC("qpdf", "QPDF stream with NL only"); |
| 1344 | - break; | |
| 1385 | + return; | |
| 1345 | 1386 | } |
| 1346 | 1387 | if (ch == '\r') { |
| 1347 | 1388 | // Read another character |
| ... | ... | @@ -1358,52 +1399,17 @@ QPDF::readStream(QPDFObjectHandle& object, QPDFObjGen og, qpdf_offset_t offset) |
| 1358 | 1399 | m->file->tell(), "stream keyword followed by carriage return only")); |
| 1359 | 1400 | } |
| 1360 | 1401 | } |
| 1361 | - break; | |
| 1402 | + return; | |
| 1362 | 1403 | } |
| 1363 | 1404 | if (!QUtil::is_space(ch)) { |
| 1364 | 1405 | QTC::TC("qpdf", "QPDF stream without newline"); |
| 1365 | 1406 | m->file->unreadCh(ch); |
| 1366 | 1407 | warn(damagedPDF( |
| 1367 | 1408 | m->file->tell(), "stream keyword not followed by proper line terminator")); |
| 1368 | - break; | |
| 1409 | + return; | |
| 1369 | 1410 | } |
| 1370 | 1411 | warn(damagedPDF(m->file->tell(), "stream keyword followed by extraneous whitespace")); |
| 1371 | 1412 | } |
| 1372 | - | |
| 1373 | - // Must get offset before accessing any additional objects since resolving a previously | |
| 1374 | - // unresolved indirect object will change file position. | |
| 1375 | - qpdf_offset_t stream_offset = m->file->tell(); | |
| 1376 | - size_t length = 0; | |
| 1377 | - | |
| 1378 | - try { | |
| 1379 | - auto length_obj = object.getKey("/Length"); | |
| 1380 | - | |
| 1381 | - if (!length_obj.isInteger()) { | |
| 1382 | - if (length_obj.isNull()) { | |
| 1383 | - QTC::TC("qpdf", "QPDF stream without length"); | |
| 1384 | - throw damagedPDF(offset, "stream dictionary lacks /Length key"); | |
| 1385 | - } | |
| 1386 | - QTC::TC("qpdf", "QPDF stream length not integer"); | |
| 1387 | - throw damagedPDF(offset, "/Length key in stream dictionary is not an integer"); | |
| 1388 | - } | |
| 1389 | - | |
| 1390 | - length = toS(length_obj.getUIntValue()); | |
| 1391 | - // Seek in two steps to avoid potential integer overflow | |
| 1392 | - m->file->seek(stream_offset, SEEK_SET); | |
| 1393 | - m->file->seek(toO(length), SEEK_CUR); | |
| 1394 | - if (!readToken(m->file).isWord("endstream")) { | |
| 1395 | - QTC::TC("qpdf", "QPDF missing endstream"); | |
| 1396 | - throw damagedPDF("expected endstream"); | |
| 1397 | - } | |
| 1398 | - } catch (QPDFExc& e) { | |
| 1399 | - if (m->attempt_recovery) { | |
| 1400 | - warn(e); | |
| 1401 | - length = recoverStreamLength(m->file, og, stream_offset); | |
| 1402 | - } else { | |
| 1403 | - throw; | |
| 1404 | - } | |
| 1405 | - } | |
| 1406 | - object = newIndirect(og, QPDF_Stream::create(this, og, object, stream_offset, length)); | |
| 1407 | 1413 | } |
| 1408 | 1414 | |
| 1409 | 1415 | QPDFObjectHandle | ... | ... |