Commit abb53ac36913fe56c404bf1748a230300c9a5c4a
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.
Showing
4 changed files
with
34 additions
and
12 deletions
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<int, int> const& object_stream_data, | @@ -167,7 +167,7 @@ QPDF::optimize(std::map<int, int> const& 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 |