Commit d31a7b76e7fa6fc6fa31d94394b59f990920d09c
1 parent
eaacf940
Improve message for stream decoding error
Tweak the message so that we inform the user that we are mitigating data loss.
Showing
16 changed files
with
59 additions
and
16 deletions
ChangeLog
| 1 | 1 | 2017-09-12 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | |
| 3 | + * Improve the error message that is issued when QPDFWriter | |
| 4 | + encounters a stream that can't be decoded. In particular, mention | |
| 5 | + that the stream will be copied without filtering to avoid data | |
| 6 | + loss. | |
| 7 | + | |
| 3 | 8 | * Add new methods to the C API to correspond to new additions to |
| 4 | 9 | QPDFWriter: |
| 5 | 10 | - qpdf_set_compress_streams | ... | ... |
TODO
| ... | ... | @@ -3,10 +3,6 @@ Before final 7.0.0 |
| 3 | 3 | |
| 4 | 4 | * Create release notes |
| 5 | 5 | |
| 6 | - * See if the error message that gets generated when retrying a stream | |
| 7 | - without filtering after error detection can be less scary. | |
| 8 | - Communicate that the original stream data is being preserved. | |
| 9 | - | |
| 10 | 6 | Soon |
| 11 | 7 | ==== |
| 12 | 8 | ... | ... |
include/qpdf/QPDF.hh
| ... | ... | @@ -554,11 +554,13 @@ class QPDF |
| 554 | 554 | static bool pipeStreamData(QPDF* qpdf, int objid, int generation, |
| 555 | 555 | qpdf_offset_t offset, size_t length, |
| 556 | 556 | QPDFObjectHandle dict, |
| 557 | - Pipeline* pipeline, bool suppress_warnings) | |
| 557 | + Pipeline* pipeline, | |
| 558 | + bool suppress_warnings, | |
| 559 | + bool will_retry) | |
| 558 | 560 | { |
| 559 | 561 | return qpdf->pipeStreamData( |
| 560 | 562 | objid, generation, offset, length, dict, pipeline, |
| 561 | - suppress_warnings); | |
| 563 | + suppress_warnings, will_retry); | |
| 562 | 564 | } |
| 563 | 565 | }; |
| 564 | 566 | friend class Pipe; |
| ... | ... | @@ -688,7 +690,8 @@ class QPDF |
| 688 | 690 | qpdf_offset_t offset, size_t length, |
| 689 | 691 | QPDFObjectHandle dict, |
| 690 | 692 | Pipeline* pipeline, |
| 691 | - bool suppress_warnings); | |
| 693 | + bool suppress_warnings, | |
| 694 | + bool will_retry); | |
| 692 | 695 | |
| 693 | 696 | // For QPDFWriter: |
| 694 | 697 | ... | ... |
include/qpdf/QPDFObjectHandle.hh
| ... | ... | @@ -420,12 +420,21 @@ class QPDFObjectHandle |
| 420 | 420 | // configured filters. QPDFWriter handles this by attempting to |
| 421 | 421 | // get the stream data without filtering, but callers should |
| 422 | 422 | // consider a false return value when decode_level is not |
| 423 | - // qpdf_dl_none to be a potential loss of data. | |
| 423 | + // qpdf_dl_none to be a potential loss of data. If you intend to | |
| 424 | + // retry in that case, pass true as the value of will_retry. This | |
| 425 | + // changes the warning issued by the library to indicate that the | |
| 426 | + // operation will be retried without filtering to avoid data loss. | |
| 424 | 427 | QPDF_DLL |
| 425 | 428 | bool pipeStreamData(Pipeline*, |
| 426 | 429 | unsigned long encode_flags, |
| 427 | 430 | qpdf_stream_decode_level_e decode_level, |
| 428 | 431 | bool suppress_warnings = false); |
| 432 | + QPDF_DLL | |
| 433 | + bool pipeStreamData(Pipeline*, | |
| 434 | + unsigned long encode_flags, | |
| 435 | + qpdf_stream_decode_level_e decode_level, | |
| 436 | + bool suppress_warnings, | |
| 437 | + bool will_retry); | |
| 429 | 438 | |
| 430 | 439 | // Legacy pipeStreamData. This maps to the the flags-based |
| 431 | 440 | // pipeStreamData as follows: | ... | ... |
libqpdf/QPDF.cc
| ... | ... | @@ -2382,7 +2382,8 @@ QPDF::pipeStreamData(int objid, int generation, |
| 2382 | 2382 | qpdf_offset_t offset, size_t length, |
| 2383 | 2383 | QPDFObjectHandle stream_dict, |
| 2384 | 2384 | Pipeline* pipeline, |
| 2385 | - bool suppress_warnings) | |
| 2385 | + bool suppress_warnings, | |
| 2386 | + bool will_retry) | |
| 2386 | 2387 | { |
| 2387 | 2388 | bool success = false; |
| 2388 | 2389 | std::vector<PointerHolder<Pipeline> > to_delete; |
| ... | ... | @@ -2430,6 +2431,13 @@ QPDF::pipeStreamData(int objid, int generation, |
| 2430 | 2431 | "error decoding stream data for object " + |
| 2431 | 2432 | QUtil::int_to_string(objid) + " " + |
| 2432 | 2433 | QUtil::int_to_string(generation) + ": " + e.what())); |
| 2434 | + if (will_retry) | |
| 2435 | + { | |
| 2436 | + warn(QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(), | |
| 2437 | + "", this->m->file->getLastOffset(), | |
| 2438 | + "stream will be re-processed without" | |
| 2439 | + " filtering to avoid data loss")); | |
| 2440 | + } | |
| 2433 | 2441 | } |
| 2434 | 2442 | } |
| 2435 | 2443 | if (! success) | ... | ... |
libqpdf/QPDFObjectHandle.cc
| ... | ... | @@ -501,9 +501,19 @@ QPDFObjectHandle::pipeStreamData(Pipeline* p, |
| 501 | 501 | qpdf_stream_decode_level_e decode_level, |
| 502 | 502 | bool suppress_warnings) |
| 503 | 503 | { |
| 504 | + return pipeStreamData( | |
| 505 | + p, encode_flags, decode_level, suppress_warnings, false); | |
| 506 | +} | |
| 507 | + | |
| 508 | +bool | |
| 509 | +QPDFObjectHandle::pipeStreamData(Pipeline* p, | |
| 510 | + unsigned long encode_flags, | |
| 511 | + qpdf_stream_decode_level_e decode_level, | |
| 512 | + bool suppress_warnings, bool will_retry) | |
| 513 | +{ | |
| 504 | 514 | assertStream(); |
| 505 | 515 | return dynamic_cast<QPDF_Stream*>(obj.getPointer())->pipeStreamData( |
| 506 | - p, encode_flags, decode_level, suppress_warnings); | |
| 516 | + p, encode_flags, decode_level, suppress_warnings, will_retry); | |
| 507 | 517 | } |
| 508 | 518 | |
| 509 | 519 | bool | ... | ... |
libqpdf/QPDFWriter.cc
| ... | ... | @@ -1623,7 +1623,7 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, |
| 1623 | 1623 | ((filter && compress) ? qpdf_ef_compress : 0)), |
| 1624 | 1624 | (filter |
| 1625 | 1625 | ? (uncompress ? qpdf_dl_all : this->m->stream_decode_level) |
| 1626 | - : qpdf_dl_none)); | |
| 1626 | + : qpdf_dl_none), false, (attempt == 1)); | |
| 1627 | 1627 | popPipelineStack(&stream_data); |
| 1628 | 1628 | if (filter && (! filtered)) |
| 1629 | 1629 | { | ... | ... |
libqpdf/QPDF_Stream.cc
| ... | ... | @@ -94,7 +94,7 @@ PointerHolder<Buffer> |
| 94 | 94 | QPDF_Stream::getStreamData(qpdf_stream_decode_level_e decode_level) |
| 95 | 95 | { |
| 96 | 96 | Pl_Buffer buf("stream data buffer"); |
| 97 | - if (! pipeStreamData(&buf, 0, decode_level, false)) | |
| 97 | + if (! pipeStreamData(&buf, 0, decode_level, false, false)) | |
| 98 | 98 | { |
| 99 | 99 | throw std::logic_error("getStreamData called on unfilterable stream"); |
| 100 | 100 | } |
| ... | ... | @@ -106,7 +106,7 @@ PointerHolder<Buffer> |
| 106 | 106 | QPDF_Stream::getRawStreamData() |
| 107 | 107 | { |
| 108 | 108 | Pl_Buffer buf("stream data buffer"); |
| 109 | - pipeStreamData(&buf, 0, qpdf_dl_none, false); | |
| 109 | + pipeStreamData(&buf, 0, qpdf_dl_none, false, false); | |
| 110 | 110 | QTC::TC("qpdf", "QPDF_Stream getRawStreamData"); |
| 111 | 111 | return buf.getBuffer(); |
| 112 | 112 | } |
| ... | ... | @@ -373,7 +373,7 @@ bool |
| 373 | 373 | QPDF_Stream::pipeStreamData(Pipeline* pipeline, |
| 374 | 374 | unsigned long encode_flags, |
| 375 | 375 | qpdf_stream_decode_level_e decode_level, |
| 376 | - bool suppress_warnings) | |
| 376 | + bool suppress_warnings, bool will_retry) | |
| 377 | 377 | { |
| 378 | 378 | std::vector<std::string> filters; |
| 379 | 379 | int predictor = 1; |
| ... | ... | @@ -540,7 +540,8 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, |
| 540 | 540 | if (! QPDF::Pipe::pipeStreamData(this->qpdf, this->objid, this->generation, |
| 541 | 541 | this->offset, this->length, |
| 542 | 542 | this->stream_dict, pipeline, |
| 543 | - suppress_warnings)) | |
| 543 | + suppress_warnings, | |
| 544 | + will_retry)) | |
| 544 | 545 | { |
| 545 | 546 | filter = false; |
| 546 | 547 | } | ... | ... |
libqpdf/qpdf/QPDF_Stream.hh
| ... | ... | @@ -25,7 +25,7 @@ class QPDF_Stream: public QPDFObject |
| 25 | 25 | bool pipeStreamData(Pipeline*, |
| 26 | 26 | unsigned long encode_flags, |
| 27 | 27 | qpdf_stream_decode_level_e decode_level, |
| 28 | - bool suppress_warnings); | |
| 28 | + bool suppress_warnings, bool will_retry); | |
| 29 | 29 | PointerHolder<Buffer> getStreamData(qpdf_stream_decode_level_e); |
| 30 | 30 | PointerHolder<Buffer> getRawStreamData(); |
| 31 | 31 | void replaceStreamData(PointerHolder<Buffer> data, | ... | ... |
qpdf/qtest/qpdf/bad-data.out
| 1 | 1 | WARNING: bad-data.pdf (file position 319): error decoding stream data for object 4 0: LZWDecoder: bad code received |
| 2 | +WARNING: bad-data.pdf (file position 319): stream will be re-processed without filtering to avoid data loss | |
| 2 | 3 | qpdf: operation succeeded with warnings; resulting file may have some problems | ... | ... |
qpdf/qtest/qpdf/bad-jpeg-check.out
| ... | ... | @@ -3,3 +3,4 @@ PDF Version: 1.3 |
| 3 | 3 | File is not encrypted |
| 4 | 4 | File is not linearized |
| 5 | 5 | WARNING: bad-jpeg.pdf (file position 735): error decoding stream data for object 6 0: Not a JPEG file: starts with 0x77 0x77 |
| 6 | +WARNING: bad-jpeg.pdf (file position 735): stream will be re-processed without filtering to avoid data loss | ... | ... |
qpdf/qtest/qpdf/bad-jpeg.out
| 1 | 1 | WARNING: bad-jpeg.pdf (file position 735): error decoding stream data for object 6 0: Not a JPEG file: starts with 0x77 0x77 |
| 2 | +WARNING: bad-jpeg.pdf (file position 735): stream will be re-processed without filtering to avoid data loss | |
| 2 | 3 | qpdf: operation succeeded with warnings; resulting file may have some problems | ... | ... |
qpdf/qtest/qpdf/damaged-stream-c-check.out
| ... | ... | @@ -3,3 +3,8 @@ warning: damaged-stream.pdf (file position 426): error decoding stream data for |
| 3 | 3 | file: damaged-stream.pdf |
| 4 | 4 | pos : 426 |
| 5 | 5 | text: error decoding stream data for object 5 0: LZWDecoder: bad code received |
| 6 | +warning: damaged-stream.pdf (file position 426): stream will be re-processed without filtering to avoid data loss | |
| 7 | + code: 5 | |
| 8 | + file: damaged-stream.pdf | |
| 9 | + pos : 426 | |
| 10 | + text: stream will be re-processed without filtering to avoid data loss | ... | ... |
qpdf/qtest/qpdf/damaged-stream.out
| ... | ... | @@ -3,3 +3,4 @@ PDF Version: 1.3 |
| 3 | 3 | File is not encrypted |
| 4 | 4 | File is not linearized |
| 5 | 5 | WARNING: damaged-stream.pdf (file position 426): error decoding stream data for object 5 0: LZWDecoder: bad code received |
| 6 | +WARNING: damaged-stream.pdf (file position 426): stream will be re-processed without filtering to avoid data loss | ... | ... |
qpdf/qtest/qpdf/issue-106.out
| ... | ... | @@ -2,4 +2,5 @@ WARNING: issue-106.pdf: file is damaged |
| 2 | 2 | WARNING: issue-106.pdf (file position 809): xref not found |
| 3 | 3 | WARNING: issue-106.pdf: Attempting to reconstruct cross-reference table |
| 4 | 4 | WARNING: issue-106.pdf (file position 965): error decoding stream data for object 8 0: stream inflate: inflate: data: incorrect data check |
| 5 | +WARNING: issue-106.pdf (file position 965): stream will be re-processed without filtering to avoid data loss | |
| 5 | 6 | qpdf: operation succeeded with warnings; resulting file may have some problems | ... | ... |
qpdf/qtest/qpdf/split-content-stream-errors.out
| ... | ... | @@ -3,6 +3,7 @@ PDF Version: 1.3 |
| 3 | 3 | File is not encrypted |
| 4 | 4 | File is not linearized |
| 5 | 5 | WARNING: split-content-stream-errors.pdf (file position 557): error decoding stream data for object 6 0: LZWDecoder: bad code received |
| 6 | +WARNING: split-content-stream-errors.pdf (file position 557): stream will be re-processed without filtering to avoid data loss | |
| 6 | 7 | WARNING: content stream: ignoring non-stream while parsing content streams |
| 7 | 8 | WARNING: split-content-stream-errors.pdf (file position 557): error decoding stream data for object 6 0: LZWDecoder: bad code received |
| 8 | 9 | WARNING: content stream (content stream object 6 0): errors while decoding content stream | ... | ... |