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,11 +908,12 @@ class QPDF
908 908
909 // Methods to support optimization 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 void pushInheritedAttributesToPageInternal( 913 void pushInheritedAttributesToPageInternal(
913 QPDFObjectHandle, 914 QPDFObjectHandle,
914 std::map<std::string, std::vector<QPDFObjectHandle> >&, 915 std::map<std::string, std::vector<QPDFObjectHandle> >&,
915 - bool allow_changes); 916 + bool allow_changes, bool warn_skipped_keys);
916 void updateObjectMaps(ObjUser const& ou, QPDFObjectHandle oh); 917 void updateObjectMaps(ObjUser const& ou, QPDFObjectHandle oh);
917 void updateObjectMapsInternal(ObjUser const& ou, QPDFObjectHandle oh, 918 void updateObjectMapsInternal(ObjUser const& ou, QPDFObjectHandle oh,
918 std::set<ObjGen>& visited, bool top); 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,7 +167,7 @@ QPDF::optimize(std::map&lt;int, int&gt; const&amp; object_stream_data,
167 167
168 // Traverse pages tree pushing all inherited resources down to the 168 // Traverse pages tree pushing all inherited resources down to the
169 // page level. 169 // page level.
170 - pushInheritedAttributesToPage(allow_changes); 170 + pushInheritedAttributesToPage(allow_changes, false);
171 getAllPages(); 171 getAllPages();
172 172
173 // Traverse pages 173 // Traverse pages
@@ -224,11 +224,11 @@ void @@ -224,11 +224,11 @@ void
224 QPDF::pushInheritedAttributesToPage() 224 QPDF::pushInheritedAttributesToPage()
225 { 225 {
226 // Public API should not have access to allow_changes. 226 // Public API should not have access to allow_changes.
227 - pushInheritedAttributesToPage(true); 227 + pushInheritedAttributesToPage(true, false);
228 } 228 }
229 229
230 void 230 void
231 -QPDF::pushInheritedAttributesToPage(bool allow_changes) 231 +QPDF::pushInheritedAttributesToPage(bool allow_changes, bool warn_skipped_keys)
232 { 232 {
233 // Traverse pages tree pushing all inherited resources down to the 233 // Traverse pages tree pushing all inherited resources down to the
234 // page level. 234 // page level.
@@ -238,7 +238,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes) @@ -238,7 +238,7 @@ QPDF::pushInheritedAttributesToPage(bool allow_changes)
238 std::map<std::string, std::vector<QPDFObjectHandle> > key_ancestors; 238 std::map<std::string, std::vector<QPDFObjectHandle> > key_ancestors;
239 pushInheritedAttributesToPageInternal( 239 pushInheritedAttributesToPageInternal(
240 this->trailer.getKey("/Root").getKey("/Pages"), 240 this->trailer.getKey("/Root").getKey("/Pages"),
241 - key_ancestors, allow_changes); 241 + key_ancestors, allow_changes, warn_skipped_keys);
242 assert(key_ancestors.empty()); 242 assert(key_ancestors.empty());
243 } 243 }
244 244
@@ -246,7 +246,7 @@ void @@ -246,7 +246,7 @@ void
246 QPDF::pushInheritedAttributesToPageInternal( 246 QPDF::pushInheritedAttributesToPageInternal(
247 QPDFObjectHandle cur_pages, 247 QPDFObjectHandle cur_pages,
248 std::map<std::string, std::vector<QPDFObjectHandle> >& key_ancestors, 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 // Extract the underlying dictionary object 251 // Extract the underlying dictionary object
252 std::string type = cur_pages.getKey("/Type").getName(); 252 std::string type = cur_pages.getKey("/Type").getName();
@@ -264,8 +264,8 @@ QPDF::pushInheritedAttributesToPageInternal( @@ -264,8 +264,8 @@ QPDF::pushInheritedAttributesToPageInternal(
264 iter != keys.end(); ++iter) 264 iter != keys.end(); ++iter)
265 { 265 {
266 std::string const& key = *iter; 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 if (! allow_changes) 270 if (! allow_changes)
271 { 271 {
@@ -273,7 +273,7 @@ QPDF::pushInheritedAttributesToPageInternal( @@ -273,7 +273,7 @@ QPDF::pushInheritedAttributesToPageInternal(
273 this->last_object_description, 273 this->last_object_description,
274 this->file->getLastOffset(), 274 this->file->getLastOffset(),
275 "optimize detected an " 275 "optimize detected an "
276 - "inheritable resource when called " 276 + "inheritable attribute when called "
277 "in no-change mode"); 277 "in no-change mode");
278 } 278 }
279 279
@@ -309,6 +309,25 @@ QPDF::pushInheritedAttributesToPageInternal( @@ -309,6 +309,25 @@ QPDF::pushInheritedAttributesToPageInternal(
309 // reattached at the page level. 309 // reattached at the page level.
310 cur_pages.removeKey(key); 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 // Visit descendant nodes. 333 // Visit descendant nodes.
@@ -317,7 +336,8 @@ QPDF::pushInheritedAttributesToPageInternal( @@ -317,7 +336,8 @@ QPDF::pushInheritedAttributesToPageInternal(
317 for (int i = 0; i < n; ++i) 336 for (int i = 0; i < n; ++i)
318 { 337 {
319 pushInheritedAttributesToPageInternal( 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 // For each inheritable key, pop the stack. If the stack 343 // For each inheritable key, pop the stack. If the stack
libqpdf/QPDF_pages.cc
@@ -102,7 +102,7 @@ QPDF::flattenPagesTree() @@ -102,7 +102,7 @@ QPDF::flattenPagesTree()
102 } 102 }
103 103
104 // Push inherited objects down to the /Page level 104 // Push inherited objects down to the /Page level
105 - pushInheritedAttributesToPage(); 105 + pushInheritedAttributesToPage(true, true);
106 getAllPages(); 106 getAllPages();
107 107
108 QPDFObjectHandle pages = getRoot().getKey("/Pages"); 108 QPDFObjectHandle pages = getRoot().getKey("/Pages");
qpdf/qpdf.testcov
@@ -214,3 +214,4 @@ QPDFObjectHandle shallow copy array 0 @@ -214,3 +214,4 @@ QPDFObjectHandle shallow copy array 0
214 QPDFObjectHandle shallow copy dictionary 0 214 QPDFObjectHandle shallow copy dictionary 0
215 QPDFObjectHandle shallow copy scalar 0 215 QPDFObjectHandle shallow copy scalar 0
216 QPDFObjectHandle newStream with string 0 216 QPDFObjectHandle newStream with string 0
  217 +QPDF unknown key not inherited 0