diff options
Diffstat (limited to 'libqpdf/QPDF_Stream.cc')
-rw-r--r-- | libqpdf/QPDF_Stream.cc | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/libqpdf/QPDF_Stream.cc b/libqpdf/QPDF_Stream.cc new file mode 100644 index 00000000..9694f837 --- /dev/null +++ b/libqpdf/QPDF_Stream.cc @@ -0,0 +1,309 @@ + +#include <qpdf/QPDF_Stream.hh> + +#include <qpdf/QEXC.hh> +#include <qpdf/QUtil.hh> +#include <qpdf/Pipeline.hh> +#include <qpdf/Pl_Flate.hh> +#include <qpdf/Pl_PNGFilter.hh> +#include <qpdf/Pl_RC4.hh> +#include <qpdf/Pl_Buffer.hh> +#include <qpdf/Pl_ASCII85Decoder.hh> +#include <qpdf/Pl_ASCIIHexDecoder.hh> +#include <qpdf/Pl_LZWDecoder.hh> + +#include <qpdf/QTC.hh> +#include <qpdf/QPDF.hh> +#include <qpdf/QPDFExc.hh> +#include <qpdf/Pl_QPDFTokenizer.hh> + +QPDF_Stream::QPDF_Stream(QPDF* qpdf, int objid, int generation, + QPDFObjectHandle stream_dict, + off_t offset, int length) : + qpdf(qpdf), + objid(objid), + generation(generation), + stream_dict(stream_dict), + offset(offset), + length(length) +{ + if (! stream_dict.isDictionary()) + { + throw QEXC::Internal("stream object instantiated with non-dictionary " + "object for dictionary"); + } +} + +QPDF_Stream::~QPDF_Stream() +{ +} + +std::string +QPDF_Stream::unparse() +{ + // Unparse stream objects as indirect references + return QUtil::int_to_string(this->objid) + " " + + QUtil::int_to_string(this->generation) + " R"; +} + +QPDFObjectHandle +QPDF_Stream::getDict() const +{ + return this->stream_dict; +} + +PointerHolder<Buffer> +QPDF_Stream::getStreamData() +{ + Pl_Buffer buf("stream data buffer"); + if (! pipeStreamData(&buf, true, false, false)) + { + throw QPDFExc("getStreamData called on unfilterable stream"); + } + return buf.getBuffer(); +} + +bool +QPDF_Stream::filterable(std::vector<std::string>& filters, + int& predictor, int& columns, + bool& early_code_change) +{ + // Initialize values to their defaults as per the PDF spec + predictor = 1; + columns = 0; + early_code_change = true; + + bool filterable = true; + + // See if we can support any decode parameters that are specified. + + QPDFObjectHandle decode_obj = + this->stream_dict.getKey("/DecodeParms"); + if (decode_obj.isNull()) + { + // no problem + } + else if (decode_obj.isDictionary()) + { + std::set<std::string> keys = decode_obj.getKeys(); + for (std::set<std::string>::iterator iter = keys.begin(); + iter != keys.end(); ++iter) + { + std::string const& key = *iter; + if (key == "/Predictor") + { + QPDFObjectHandle predictor_obj = decode_obj.getKey(key); + if (predictor_obj.isInteger()) + { + predictor = predictor_obj.getIntValue(); + if (! ((predictor == 1) || (predictor == 12))) + { + filterable = false; + } + } + else + { + filterable = false; + } + } + else if (key == "/EarlyChange") + { + QPDFObjectHandle earlychange_obj = decode_obj.getKey(key); + if (earlychange_obj.isInteger()) + { + int earlychange = earlychange_obj.getIntValue(); + early_code_change = (earlychange == 1); + if (! ((earlychange == 0) || (earlychange == 1))) + { + filterable = false; + } + } + else + { + filterable = false; + } + } + else if (key == "/Columns") + { + QPDFObjectHandle columns_obj = decode_obj.getKey(key); + if (columns_obj.isInteger()) + { + columns = columns_obj.getIntValue(); + } + else + { + filterable = false; + } + } + else + { + filterable = false; + } + } + } + else + { + throw QPDFExc(qpdf->getFilename(), this->offset, + "invalid decode parameters object type for this stream"); + } + + if ((predictor > 1) && (columns == 0)) + { + // invalid + filterable = false; + } + + if (! filterable) + { + return false; + } + + // Check filters + + QPDFObjectHandle filter_obj = this->stream_dict.getKey("/Filter"); + bool filters_okay = true; + + if (filter_obj.isNull()) + { + // No filters + } + else if (filter_obj.isName()) + { + // One filter + filters.push_back(filter_obj.getName()); + } + else if (filter_obj.isArray()) + { + // Potentially multiple filters + int n = filter_obj.getArrayNItems(); + for (int i = 0; i < n; ++i) + { + QPDFObjectHandle item = filter_obj.getArrayItem(i); + if (item.isName()) + { + filters.push_back(item.getName()); + } + else + { + filters_okay = false; + } + } + } + else + { + filters_okay = false; + } + + if (! filters_okay) + { + QTC::TC("qpdf", "QPDF_Stream invalid filter"); + throw QPDFExc(qpdf->getFilename(), this->offset, + "invalid filter object type for this stream"); + } + + // `filters' now contains a list of filters to be applied in + // order. See which ones we can support. + + for (std::vector<std::string>::iterator iter = filters.begin(); + iter != filters.end(); ++iter) + { + std::string const& filter = *iter; + if (! ((filter == "/FlateDecode") || + (filter == "/LZWDecode") || + (filter == "/ASCII85Decode") || + (filter == "/ASCIIHexDecode"))) + { + filterable = false; + } + } + + return filterable; +} + +bool +QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter, + bool normalize, bool compress) +{ + std::vector<std::string> filters; + int predictor = 1; + int columns = 0; + bool early_code_change = true; + if (filter) + { + filter = filterable(filters, predictor, columns, early_code_change); + } + + if (pipeline == 0) + { + QTC::TC("qpdf", "QPDF_Stream pipeStreamData with null pipeline"); + return filter; + } + + // Construct the pipeline in reverse order. Force pipelines we + // create to be deleted when this function finishes. + std::vector<PointerHolder<Pipeline> > to_delete; + + if (filter) + { + if (compress) + { + pipeline = new Pl_Flate("compress object stream", pipeline, + Pl_Flate::a_deflate); + to_delete.push_back(pipeline); + } + + if (normalize) + { + pipeline = new Pl_QPDFTokenizer("normalizer", pipeline); + to_delete.push_back(pipeline); + } + + for (std::vector<std::string>::reverse_iterator iter = filters.rbegin(); + iter != filters.rend(); ++iter) + { + std::string const& filter = *iter; + if (filter == "/FlateDecode") + { + if (predictor == 12) + { + QTC::TC("qpdf", "QPDF_Stream PNG filter"); + pipeline = new Pl_PNGFilter( + "png decode", pipeline, Pl_PNGFilter::a_decode, + columns, 0 /* not used */); + to_delete.push_back(pipeline); + } + + pipeline = new Pl_Flate("stream inflate", + pipeline, Pl_Flate::a_inflate); + to_delete.push_back(pipeline); + } + else if (filter == "/ASCII85Decode") + { + pipeline = new Pl_ASCII85Decoder("ascii85 decode", pipeline); + to_delete.push_back(pipeline); + } + else if (filter == "/ASCIIHexDecode") + { + pipeline = new Pl_ASCIIHexDecoder("asciiHex decode", pipeline); + to_delete.push_back(pipeline); + } + else if (filter == "/LZWDecode") + { + pipeline = new Pl_LZWDecoder("lzw decode", pipeline, + early_code_change); + to_delete.push_back(pipeline); + } + else + { + throw QEXC::Internal("QPDFStream: unknown filter " + "encountered after check"); + } + } + } + + QPDF::Pipe::pipeStreamData(this->qpdf, this->objid, this->generation, + this->offset, this->length, + this->stream_dict, pipeline); + + return filter; +} |