Commit abb53ac36913fe56c404bf1748a230300c9a5c4a

Authored by Tobias Hoffmann
Committed by Jay Berkenbilt
1 parent 7770a1b0

Limited inheritance to the attributes explicitly listed in the PDF spec

Previous versions of qpdf incorrectly passed arbitrary objects from
/Pages objects down to individual pages in direct contradition with
the PDF specification.  These are now left in /Pages.  When
intermediate /Pages nodes are being discarded as when the /Pages tree
is being flattened, a warning is issued when unknown keys are
encountered.
include/qpdf/QPDF.hh
... ... @@ -908,11 +908,12 @@ class QPDF
908 908  
909 909 // Methods to support optimization
910 910  
911   - void pushInheritedAttributesToPage(bool allow_changes);
  911 + void pushInheritedAttributesToPage(bool allow_changes,
  912 + bool warn_skipped_keys);
912 913 void pushInheritedAttributesToPageInternal(
913 914 QPDFObjectHandle,
914 915 std::map<std::string, std::vector<QPDFObjectHandle> >&,
915   - bool allow_changes);
  916 + bool allow_changes, bool warn_skipped_keys);
916 917 void updateObjectMaps(ObjUser const& ou, QPDFObjectHandle oh);
917 918 void updateObjectMapsInternal(ObjUser const& ou, QPDFObjectHandle oh,
918 919 std::set<ObjGen>& visited, bool top);
... ...
libqpdf/QPDF_optimization.cc
... ... @@ -167,7 +167,7 @@ QPDF::optimize(std::map&lt;int, int&gt; const&amp; object_stream_data,
167 167  
168 168 // Traverse pages tree pushing all inherited resources down to the
169 169 // page level.
170   - pushInheritedAttributesToPage(allow_changes);
  170 + pushInheritedAttributesToPage(allow_changes, false);
171 171 getAllPages();
172 172  
173 173 // Traverse pages
... ... @@ -224,11 +224,11 @@ void
224 224 QPDF::pushInheritedAttributesToPage()
225 225 {
226 226 // Public API should not have access to allow_changes.
227   - pushInheritedAttributesToPage(true);
  227 + pushInheritedAttributesToPage(true, false);
228 228 }
229 229  
230 230 void
231   -QPDF::pushInheritedAttributesToPage(bool allow_changes)
  231 +QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
232 232 {
233 233 // Traverse pages tree pushing all inherited resources down to the
234 234 // page level.
... ... @@ -238,7 +238,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes)
238 238 std::map<std::string, std::vector<QPDFObjectHandle> > key_ancestors;
239 239 pushInheritedAttributesToPageInternal(
240 240 this->trailer.getKey("/Root").getKey("/Pages"),
241   - key_ancestors, allow_changes);
  241 + key_ancestors, allow_changes, warn_skipped_keys);
242 242 assert(key_ancestors.empty());
243 243 }
244 244  
... ... @@ -246,7 +246,7 @@ void
246 246 QPDF::pushInheritedAttributesToPageInternal(
247 247 QPDFObjectHandle cur_pages,
248 248 std::map<std::string, std::vector<QPDFObjectHandle> >& key_ancestors,
249   - bool allow_changes)
  249 + bool allow_changes, bool warn_skipped_keys)
250 250 {
251 251 // Extract the underlying dictionary object
252 252 std::string type = cur_pages.getKey("/Type").getName();
... ... @@ -264,8 +264,8 @@ QPDF::pushInheritedAttributesToPageInternal(
264 264 iter != keys.end(); ++iter)
265 265 {
266 266 std::string const& key = *iter;
267   - if (! ((key == "/Type") || (key == "/Parent") ||
268   - (key == "/Kids") || (key == "/Count")))
  267 + if ( (key == "/MediaBox") || (key == "/CropBox") ||
  268 + (key == "/Resources") || (key == "/Rotate") )
269 269 {
270 270 if (! allow_changes)
271 271 {
... ... @@ -273,7 +273,7 @@ QPDF::pushInheritedAttributesToPageInternal(
273 273 this->last_object_description,
274 274 this->file->getLastOffset(),
275 275 "optimize detected an "
276   - "inheritable resource when called "
  276 + "inheritable attribute when called "
277 277 "in no-change mode");
278 278 }
279 279  
... ... @@ -309,6 +309,25 @@ QPDF::pushInheritedAttributesToPageInternal(
309 309 // reattached at the page level.
310 310 cur_pages.removeKey(key);
311 311 }
  312 + else if (! ((key == "/Type") || (key == "/Parent") ||
  313 + (key == "/Kids") || (key == "/Count")))
  314 + {
  315 + // Warn when flattening, but not if the key is at the top
  316 + // level (i.e. "/Parent" not set), as we don't change these;
  317 + // but flattening removes intermediate /Pages nodes.
  318 + if ( (warn_skipped_keys) && (cur_pages.hasKey("/Parent")) )
  319 + {
  320 + QTC::TC("qpdf", "QPDF unknown key not inherited");
  321 + setLastObjectDescription("Pages object",
  322 + cur_pages.getObjectID(),
  323 + cur_pages.getGeneration());
  324 + warn(QPDFExc(qpdf_e_pages, this->file->getName(),
  325 + this->last_object_description, 0,
  326 + "Unknown key " + key + " in /Pages object"
  327 + " is being discarded as a result of"
  328 + " flattening the /Pages tree"));
  329 + }
  330 + }
312 331 }
313 332  
314 333 // Visit descendant nodes.
... ... @@ -317,7 +336,8 @@ QPDF::pushInheritedAttributesToPageInternal(
317 336 for (int i = 0; i < n; ++i)
318 337 {
319 338 pushInheritedAttributesToPageInternal(
320   - kids.getArrayItem(i), key_ancestors, allow_changes);
  339 + kids.getArrayItem(i), key_ancestors,
  340 + allow_changes, warn_skipped_keys);
321 341 }
322 342  
323 343 // For each inheritable key, pop the stack. If the stack
... ...
libqpdf/QPDF_pages.cc
... ... @@ -102,7 +102,7 @@ QPDF::flattenPagesTree()
102 102 }
103 103  
104 104 // Push inherited objects down to the /Page level
105   - pushInheritedAttributesToPage();
  105 + pushInheritedAttributesToPage(true, true);
106 106 getAllPages();
107 107  
108 108 QPDFObjectHandle pages = getRoot().getKey("/Pages");
... ...
qpdf/qpdf.testcov
... ... @@ -214,3 +214,4 @@ QPDFObjectHandle shallow copy array 0
214 214 QPDFObjectHandle shallow copy dictionary 0
215 215 QPDFObjectHandle shallow copy scalar 0
216 216 QPDFObjectHandle newStream with string 0
  217 +QPDF unknown key not inherited 0
... ...