Commit bf615b8c9c9100152f7dd9861fc3f19716f09405

Authored by m-holger
Committed by GitHub
2 parents bdceb083 9a3f2969

Merge pull request #1535 from m-holger/afdh

Refactor `QPDFAcroFormDocumentHelper`: simplify `traverseField`, remo…
include/qpdf/QPDFAcroFormDocumentHelper.hh
@@ -226,8 +226,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper @@ -226,8 +226,7 @@ class QPDFAcroFormDocumentHelper: public QPDFDocumentHelper
226 226
227 private: 227 private:
228 void analyze(); 228 void analyze();
229 - void traverseField(  
230 - QPDFObjectHandle field, QPDFObjectHandle parent, int depth, QPDFObjGen::set& visited); 229 + void traverseField(QPDFObjectHandle const& field, QPDFObjectHandle const& parent, int depth);
231 QPDFObjectHandle getOrCreateAcroForm(); 230 QPDFObjectHandle getOrCreateAcroForm();
232 void adjustInheritedFields( 231 void adjustInheritedFields(
233 QPDFObjectHandle obj, 232 QPDFObjectHandle obj,
libqpdf/QPDFAcroFormDocumentHelper.cc
@@ -89,8 +89,7 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) @@ -89,8 +89,7 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff)
89 fields = acroform.replaceKeyAndGetNew("/Fields", QPDFObjectHandle::newArray()); 89 fields = acroform.replaceKeyAndGetNew("/Fields", QPDFObjectHandle::newArray());
90 } 90 }
91 fields.appendItem(ff.getObjectHandle()); 91 fields.appendItem(ff.getObjectHandle());
92 - QPDFObjGen::set visited;  
93 - traverseField(ff.getObjectHandle(), QPDFObjectHandle::newNull(), 0, visited); 92 + traverseField(ff.getObjectHandle(), {}, 0);
94 } 93 }
95 94
96 void 95 void
@@ -188,9 +187,7 @@ void @@ -188,9 +187,7 @@ void
188 QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name) 187 QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name)
189 { 188 {
190 ff.setFieldAttribute("/T", name); 189 ff.setFieldAttribute("/T", name);
191 - QPDFObjGen::set visited;  
192 - auto ff_oh = ff.getObjectHandle();  
193 - traverseField(ff_oh, ff_oh.getKey("/Parent"), 0, visited); 190 + traverseField(ff, ff["/Parent"], 0);
194 } 191 }
195 192
196 std::vector<QPDFFormFieldObjectHelper> 193 std::vector<QPDFFormFieldObjectHelper>
@@ -199,7 +196,7 @@ QPDFAcroFormDocumentHelper::getFormFields() @@ -199,7 +196,7 @@ QPDFAcroFormDocumentHelper::getFormFields()
199 analyze(); 196 analyze();
200 std::vector<QPDFFormFieldObjectHelper> result; 197 std::vector<QPDFFormFieldObjectHelper> result;
201 for (auto const& [og, data]: m->field_to) { 198 for (auto const& [og, data]: m->field_to) {
202 - if (!(data.annotations.empty())) { 199 + if (!data.annotations.empty()) {
203 result.emplace_back(qpdf.getObject(og)); 200 result.emplace_back(qpdf.getObject(og));
204 } 201 }
205 } 202 }
@@ -279,18 +276,14 @@ QPDFAcroFormDocumentHelper::analyze() @@ -279,18 +276,14 @@ QPDFAcroFormDocumentHelper::analyze()
279 return; 276 return;
280 } 277 }
281 QPDFObjectHandle fields = acroform.getKey("/Fields"); 278 QPDFObjectHandle fields = acroform.getKey("/Fields");
282 - if (auto fa = fields.as_array(strict)) { 279 + if (Array fa = fields) {
283 // Traverse /AcroForm to find annotations and map them bidirectionally to fields. 280 // Traverse /AcroForm to find annotations and map them bidirectionally to fields.
284 -  
285 - QPDFObjGen::set visited;  
286 - QPDFObjectHandle null(QPDFObjectHandle::newNull());  
287 for (auto const& field: fa) { 281 for (auto const& field: fa) {
288 - traverseField(field, null, 0, visited); 282 + traverseField(field, {}, 0);
289 } 283 }
290 } else { 284 } else {
291 - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper fields not array");  
292 acroform.warn("/Fields key of /AcroForm dictionary is not an array; ignoring"); 285 acroform.warn("/Fields key of /AcroForm dictionary is not an array; ignoring");
293 - fields = QPDFObjectHandle::newArray(); 286 + fields = Array::empty();
294 } 287 }
295 288
296 // All Widget annotations should have been encountered by traversing /AcroForm, but in case any 289 // All Widget annotations should have been encountered by traversing /AcroForm, but in case any
@@ -305,7 +298,6 @@ QPDFAcroFormDocumentHelper::analyze() @@ -305,7 +298,6 @@ QPDFAcroFormDocumentHelper::analyze()
305 QPDFObjectHandle annot(iter.getObjectHandle()); 298 QPDFObjectHandle annot(iter.getObjectHandle());
306 QPDFObjGen og(annot.getObjGen()); 299 QPDFObjGen og(annot.getObjGen());
307 if (!m->annotation_to_field.contains(og)) { 300 if (!m->annotation_to_field.contains(og)) {
308 - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper orphaned widget");  
309 // This is not supposed to happen, but it's easy enough for us to handle this case. 301 // This is not supposed to happen, but it's easy enough for us to handle this case.
310 // Treat the annotation as its own field. This could allow qpdf to sensibly handle a 302 // Treat the annotation as its own field. This could allow qpdf to sensibly handle a
311 // case such as a PDF creator adding a self-contained annotation (merged with the 303 // case such as a PDF creator adding a self-contained annotation (merged with the
@@ -323,7 +315,7 @@ QPDFAcroFormDocumentHelper::analyze() @@ -323,7 +315,7 @@ QPDFAcroFormDocumentHelper::analyze()
323 315
324 void 316 void
325 QPDFAcroFormDocumentHelper::traverseField( 317 QPDFAcroFormDocumentHelper::traverseField(
326 - QPDFObjectHandle field, QPDFObjectHandle parent, int depth, QPDFObjGen::set& visited) 318 + QPDFObjectHandle const& field, QPDFObjectHandle const& parent, int depth)
327 { 319 {
328 if (depth > 100) { 320 if (depth > 100) {
329 // Arbitrarily cut off recursion at a fixed depth to avoid specially crafted files that 321 // Arbitrarily cut off recursion at a fixed depth to avoid specially crafted files that
@@ -331,22 +323,19 @@ QPDFAcroFormDocumentHelper::traverseField( @@ -331,22 +323,19 @@ QPDFAcroFormDocumentHelper::traverseField(
331 return; 323 return;
332 } 324 }
333 if (!field.indirect()) { 325 if (!field.indirect()) {
334 - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper direct field");  
335 field.warn( 326 field.warn(
336 - "encountered a direct object as a field or annotation while "  
337 - "traversing /AcroForm; ignoring field or annotation"); 327 + "encountered a direct object as a field or annotation while traversing /AcroForm; "
  328 + "ignoring field or annotation");
338 return; 329 return;
339 } 330 }
340 if (!field.isDictionary()) { 331 if (!field.isDictionary()) {
341 - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper non-dictionary field");  
342 field.warn( 332 field.warn(
343 - "encountered a non-dictionary as a field or annotation while"  
344 - " traversing /AcroForm; ignoring field or annotation"); 333 + "encountered a non-dictionary as a field or annotation while traversing /AcroForm; "
  334 + "ignoring field or annotation");
345 return; 335 return;
346 } 336 }
347 QPDFObjGen og(field.getObjGen()); 337 QPDFObjGen og(field.getObjGen());
348 - if (!visited.add(og)) {  
349 - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper loop"); 338 + if (m->field_to.contains(og) || m->annotation_to_field.contains(og)) {
350 field.warn("loop detected while traversing /AcroForm"); 339 field.warn("loop detected while traversing /AcroForm");
351 return; 340 return;
352 } 341 }
@@ -357,21 +346,10 @@ QPDFAcroFormDocumentHelper::traverseField( @@ -357,21 +346,10 @@ QPDFAcroFormDocumentHelper::traverseField(
357 // fields can be merged with terminal field dictionaries. Otherwise, the annotation fields might 346 // fields can be merged with terminal field dictionaries. Otherwise, the annotation fields might
358 // be there to be inherited by annotations below it. 347 // be there to be inherited by annotations below it.
359 348
360 - bool is_annotation = false;  
361 - bool is_field = (0 == depth);  
362 - if (auto a = field.getKey("/Kids").as_array(strict)) {  
363 - is_field = true;  
364 - for (auto const& item: a) {  
365 - traverseField(item, field, 1 + depth, visited);  
366 - }  
367 - } else {  
368 - if (field.hasKey("/Parent")) {  
369 - is_field = true;  
370 - }  
371 - if (field.hasKey("/Subtype") || field.hasKey("/Rect") || field.hasKey("/AP")) {  
372 - is_annotation = true;  
373 - }  
374 - } 349 + Array Kids = field["/Kids"];
  350 + const bool is_field = depth == 0 || Kids || field.hasKey("/Parent");
  351 + const bool is_annotation =
  352 + !Kids && (field.hasKey("/Subtype") || field.hasKey("/Rect") || field.hasKey("/AP"));
375 353
376 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field found", (depth == 0) ? 0 : 1); 354 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field found", (depth == 0) ? 0 : 1);
377 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper annotation found", (is_field ? 0 : 1)); 355 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper annotation found", (is_field ? 0 : 1));
@@ -384,15 +362,18 @@ QPDFAcroFormDocumentHelper::traverseField( @@ -384,15 +362,18 @@ QPDFAcroFormDocumentHelper::traverseField(
384 362
385 if (is_field && (field.hasKey("/T"))) { 363 if (is_field && (field.hasKey("/T"))) {
386 QPDFFormFieldObjectHelper foh(field); 364 QPDFFormFieldObjectHelper foh(field);
387 - auto f_og = field.getObjGen();  
388 std::string name = foh.getFullyQualifiedName(); 365 std::string name = foh.getFullyQualifiedName();
389 - auto old = m->field_to.find(f_og); 366 + auto old = m->field_to.find(og);
390 if (old != m->field_to.end() && !old->second.name.empty()) { 367 if (old != m->field_to.end() && !old->second.name.empty()) {
391 // We might be updating after a name change, so remove any old information 368 // We might be updating after a name change, so remove any old information
392 - m->name_to_fields[old->second.name].erase(f_og); 369 + m->name_to_fields[old->second.name].erase(og);
393 } 370 }
394 - m->field_to[f_og].name = name;  
395 - m->name_to_fields[name].insert(f_og); 371 + m->field_to[og].name = name;
  372 + m->name_to_fields[name].insert(og);
  373 + }
  374 +
  375 + for (auto const& kid: Kids) {
  376 + traverseField(kid, field, 1 + depth);
396 } 377 }
397 } 378 }
398 379
qpdf/qpdf.testcov
@@ -204,11 +204,6 @@ QPDFAnnotationObjectHelper AP stream 0 @@ -204,11 +204,6 @@ QPDFAnnotationObjectHelper AP stream 0
204 QPDFAnnotationObjectHelper AP dictionary 0 204 QPDFAnnotationObjectHelper AP dictionary 0
205 QPDFAnnotationObjectHelper AP sub stream 0 205 QPDFAnnotationObjectHelper AP sub stream 0
206 QPDFAnnotationObjectHelper AP null 0 206 QPDFAnnotationObjectHelper AP null 0
207 -QPDFAcroFormDocumentHelper fields not array 0  
208 -QPDFAcroFormDocumentHelper orphaned widget 0  
209 -QPDFAcroFormDocumentHelper direct field 0  
210 -QPDFAcroFormDocumentHelper non-dictionary field 0  
211 -QPDFAcroFormDocumentHelper loop 0  
212 QPDFAcroFormDocumentHelper field found 1 207 QPDFAcroFormDocumentHelper field found 1
213 QPDFAcroFormDocumentHelper annotation found 1 208 QPDFAcroFormDocumentHelper annotation found 1
214 QPDFJob automatically set keep files open 1 209 QPDFJob automatically set keep files open 1