Implement PDF version handling from catalog dictionary

Add support for reading PDF version from the Version entry in the
catalog dictionary. The catalog version takes precedence over the
file header version.


Bug: 409579362
Change-Id: I581e01a2ce79fd68126696a6ccbe45046f767bb4
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/130850
Auto-Submit: Helmut Januschka <helmut@januschka.com>
Commit-Queue: Nicolás Peña <npm@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Nicolás Peña <npm@chromium.org>
diff --git a/fpdfsdk/fpdf_view.cpp b/fpdfsdk/fpdf_view.cpp
index fb72ec0..b93dcae 100644
--- a/fpdfsdk/fpdf_view.cpp
+++ b/fpdfsdk/fpdf_view.cpp
@@ -35,9 +35,11 @@
 #include "core/fxcrt/cfx_timer.h"
 #include "core/fxcrt/check_op.h"
 #include "core/fxcrt/compiler_specific.h"
+#include "core/fxcrt/fx_extension.h"
 #include "core/fxcrt/fx_memcpy_wrappers.h"
 #include "core/fxcrt/fx_safe_types.h"
 #include "core/fxcrt/fx_stream.h"
+#include "core/fxcrt/fx_string.h"
 #include "core/fxcrt/fx_system.h"
 #include "core/fxcrt/numerics/safe_conversions.h"
 #include "core/fxcrt/ptr_util.h"
@@ -405,6 +407,24 @@
   }
 
   *fileVersion = pParser->GetFileVersion();
+
+  const CPDF_Dictionary* root_dict = pDoc->GetRoot();
+  if (root_dict) {
+    ByteString version = root_dict->GetNameFor("Version");
+    if (!version.IsEmpty()) {
+      // Check for valid PDF version format "X.Y"
+      const bool has_valid_length = version.GetLength() == 3;
+      const bool has_valid_format =
+          has_valid_length && FXSYS_IsDecimalDigit(version[0]) &&
+          version[1] == '.' && FXSYS_IsDecimalDigit(version[2]);
+      if (has_valid_format) {
+        const int major = FXSYS_DecimalCharToInt(version[0]);
+        const int minor = FXSYS_DecimalCharToInt(version[2]);
+        *fileVersion = major * 10 + minor;
+      }
+    }
+  }
+
   return true;
 }
 
diff --git a/fpdfsdk/fpdf_view_embeddertest.cpp b/fpdfsdk/fpdf_view_embeddertest.cpp
index bf5c8ed..7fb3479 100644
--- a/fpdfsdk/fpdf_view_embeddertest.cpp
+++ b/fpdfsdk/fpdf_view_embeddertest.cpp
@@ -2236,3 +2236,10 @@
     ASSERT_FALSE(bitmap);
   }
 }
+
+TEST_F(FPDFViewEmbedderTest, DocumentVersionInCatalog) {
+  ASSERT_TRUE(OpenDocument("version_in_catalog.pdf"));
+  int version;
+  EXPECT_TRUE(FPDF_GetFileVersion(document(), &version));
+  EXPECT_EQ(16, version);
+}
diff --git a/testing/resources/version_in_catalog.in b/testing/resources/version_in_catalog.in
new file mode 100644
index 0000000..061e921
--- /dev/null
+++ b/testing/resources/version_in_catalog.in
@@ -0,0 +1,24 @@
+{{header}}
+%PDF-1.5
+{{object 1 0}} <<
+  /Type /Catalog
+  /Version /1.6
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+>>
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
\ No newline at end of file
diff --git a/testing/resources/version_in_catalog.pdf b/testing/resources/version_in_catalog.pdf
new file mode 100644
index 0000000..4835c0b
--- /dev/null
+++ b/testing/resources/version_in_catalog.pdf
@@ -0,0 +1,34 @@
+%PDF-1.7
+% ò¤ô
+%PDF-1.5
+1 0 obj <<
+  /Type /Catalog
+  /Version /1.6
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+>>
+endobj
+xref
+0 4
+0000000000 65535 f 
+0000000024 00000 n 
+0000000093 00000 n 
+0000000156 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 4
+>>
+startxref
+233
+%%EOF