Commit 8aab68386a860433fb681f8cf69b5511696fcf15

Authored by m-holger
1 parent cf0e3422

Fix appearance stream handling in `QPDFFormFieldObjectHelper::generateTextAppear…

…ance`: copy stream data if shared references exceed threshold, ensuring safe updates.
libqpdf/QPDFFormFieldObjectHelper.cc
... ... @@ -915,19 +915,26 @@ FormNode::generateTextAppearance(QPDFAnnotationObjectHelper& aoh)
915 915 }
916 916  
917 917 if (AS.obj_sp().use_count() > 3) {
918   - // The following check ensures that we only update the appearance stream if it is not
919   - // shared. The threshold of 3 is based on the current implementation details:
  918 + // Ensures that the appearance stream is not shared by copying it if the threshold of 3 is
  919 + // exceeded. The threshold is based on the current implementation details:
920 920 // - One reference from the local variable AS
921 921 // - One reference from the appearance dictionary (/AP)
922 922 // - One reference from the object table
923 923 // If use_count() is greater than 3, it means the appearance stream is shared elsewhere,
924 924 // and updating it could have unintended side effects. This threshold may need to be updated
925 925 // if the internal reference counting changes in the future.
926   - // The long-term solution will we to replace appearance streams at the point of flattening
927   - // annotations rather than attaching token filters that modify the streams at time of
928   - // writing.
929   - aoh.warn("unable to generate text appearance from shared appearance stream for update");
930   - return;
  926 + //
  927 + // There is currently no explicit CI test for this code> I has been manually tested bu
  928 + // running it through CI with a threshold of 0, unconditionally copying streams.
  929 + auto data = AS.getStreamData(qpdf_dl_all);
  930 + AS = AS.copy();
  931 + AS.replaceStreamData(std::move(data), Null::temp(), Null::temp());
  932 + if (Dictionary AP = aoh.getAppearanceDictionary()) {
  933 + AP.replace("/N", AS);
  934 + } else {
  935 + aoh.replace("/AP", Dictionary({{"/N", AS}}));
  936 + // aoh is a dictionary, so insertion will succeed. No need to check by retrieving it.
  937 + }
931 938 }
932 939 QPDFObjectHandle bbox_obj = AS.getDict()["/BBox"];
933 940 if (!bbox_obj.isRectangle()) {
... ...
manual/release-notes.rst
... ... @@ -115,10 +115,6 @@ more detail.
115 115 - There has been significant internal refactoring affecting most parts of
116 116 qpdf's code base.
117 117  
118   - - When flattening widget annotations further checks have been added to detect
119   - when qpdf cannot reliably generate the necessary appearance streams. As in
120   - other such cases a warning is issued and the annotation remains unflattened.
121   -
122 118 - By default, streams with more than 25 filters are now treated as unfilterable.
123 119 A large number of filters typically occur in damaged or specially constructed
124 120 files and can cause excessive use of resources and/or stack overflows. The
... ...