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 226  
227 227 private:
228 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 230 QPDFObjectHandle getOrCreateAcroForm();
232 231 void adjustInheritedFields(
233 232 QPDFObjectHandle obj,
... ...
libqpdf/QPDFAcroFormDocumentHelper.cc
... ... @@ -89,8 +89,7 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff)
89 89 fields = acroform.replaceKeyAndGetNew("/Fields", QPDFObjectHandle::newArray());
90 90 }
91 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 95 void
... ... @@ -188,9 +187,7 @@ void
188 187 QPDFAcroFormDocumentHelper::setFormFieldName(QPDFFormFieldObjectHelper ff, std::string const& name)
189 188 {
190 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 193 std::vector<QPDFFormFieldObjectHelper>
... ... @@ -199,7 +196,7 @@ QPDFAcroFormDocumentHelper::getFormFields()
199 196 analyze();
200 197 std::vector<QPDFFormFieldObjectHelper> result;
201 198 for (auto const& [og, data]: m->field_to) {
202   - if (!(data.annotations.empty())) {
  199 + if (!data.annotations.empty()) {
203 200 result.emplace_back(qpdf.getObject(og));
204 201 }
205 202 }
... ... @@ -279,18 +276,14 @@ QPDFAcroFormDocumentHelper::analyze()
279 276 return;
280 277 }
281 278 QPDFObjectHandle fields = acroform.getKey("/Fields");
282   - if (auto fa = fields.as_array(strict)) {
  279 + if (Array fa = fields) {
283 280 // Traverse /AcroForm to find annotations and map them bidirectionally to fields.
284   -
285   - QPDFObjGen::set visited;
286   - QPDFObjectHandle null(QPDFObjectHandle::newNull());
287 281 for (auto const& field: fa) {
288   - traverseField(field, null, 0, visited);
  282 + traverseField(field, {}, 0);
289 283 }
290 284 } else {
291   - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper fields not array");
292 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 289 // All Widget annotations should have been encountered by traversing /AcroForm, but in case any
... ... @@ -305,7 +298,6 @@ QPDFAcroFormDocumentHelper::analyze()
305 298 QPDFObjectHandle annot(iter.getObjectHandle());
306 299 QPDFObjGen og(annot.getObjGen());
307 300 if (!m->annotation_to_field.contains(og)) {
308   - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper orphaned widget");
309 301 // This is not supposed to happen, but it's easy enough for us to handle this case.
310 302 // Treat the annotation as its own field. This could allow qpdf to sensibly handle a
311 303 // case such as a PDF creator adding a self-contained annotation (merged with the
... ... @@ -323,7 +315,7 @@ QPDFAcroFormDocumentHelper::analyze()
323 315  
324 316 void
325 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 320 if (depth > 100) {
329 321 // Arbitrarily cut off recursion at a fixed depth to avoid specially crafted files that
... ... @@ -331,22 +323,19 @@ QPDFAcroFormDocumentHelper::traverseField(
331 323 return;
332 324 }
333 325 if (!field.indirect()) {
334   - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper direct field");
335 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 329 return;
339 330 }
340 331 if (!field.isDictionary()) {
341   - QTC::TC("qpdf", "QPDFAcroFormDocumentHelper non-dictionary field");
342 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 335 return;
346 336 }
347 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 339 field.warn("loop detected while traversing /AcroForm");
351 340 return;
352 341 }
... ... @@ -357,21 +346,10 @@ QPDFAcroFormDocumentHelper::traverseField(
357 346 // fields can be merged with terminal field dictionaries. Otherwise, the annotation fields might
358 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 354 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper field found", (depth == 0) ? 0 : 1);
377 355 QTC::TC("qpdf", "QPDFAcroFormDocumentHelper annotation found", (is_field ? 0 : 1));
... ... @@ -384,15 +362,18 @@ QPDFAcroFormDocumentHelper::traverseField(
384 362  
385 363 if (is_field && (field.hasKey("/T"))) {
386 364 QPDFFormFieldObjectHelper foh(field);
387   - auto f_og = field.getObjGen();
388 365 std::string name = foh.getFullyQualifiedName();
389   - auto old = m->field_to.find(f_og);
  366 + auto old = m->field_to.find(og);
390 367 if (old != m->field_to.end() && !old->second.name.empty()) {
391 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 204 QPDFAnnotationObjectHelper AP dictionary 0
205 205 QPDFAnnotationObjectHelper AP sub stream 0
206 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 207 QPDFAcroFormDocumentHelper field found 1
213 208 QPDFAcroFormDocumentHelper annotation found 1
214 209 QPDFJob automatically set keep files open 1
... ...