aboutsummaryrefslogtreecommitdiffstats
path: root/libqpdf/QPDF_Stream.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libqpdf/QPDF_Stream.cc')
-rw-r--r--libqpdf/QPDF_Stream.cc309
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;
+}