Add unit test for CFXA_Document::RecongnizeXFAVersionNumber() and fix.

Split into static method so it can be tested without initializing all
of cppgc. Then fix parsing of minor version number where length was
off-by-one.

Change-Id: Ie50d40e3f8bc3544c60663ebff389f7982e69d32
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/86790
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/xfa/fxfa/parser/BUILD.gn b/xfa/fxfa/parser/BUILD.gn
index 9091e1b..58a30b2 100644
--- a/xfa/fxfa/parser/BUILD.gn
+++ b/xfa/fxfa/parser/BUILD.gn
@@ -698,6 +698,7 @@
 pdfium_unittest_source_set("unittests") {
   sources = [
     "cxfa_document_builder_unittest.cpp",
+    "cxfa_document_unittest.cpp",
     "cxfa_localevalue_unittest.cpp",
     "cxfa_measurement_unittest.cpp",
     "cxfa_node_unittest.cpp",
diff --git a/xfa/fxfa/parser/cxfa_document.cpp b/xfa/fxfa/parser/cxfa_document.cpp
index 634daec..b9e69f2 100644
--- a/xfa/fxfa/parser/cxfa_document.cpp
+++ b/xfa/fxfa/parser/cxfa_document.cpp
@@ -1467,6 +1467,15 @@
 
 XFA_VERSION CXFA_Document::RecognizeXFAVersionNumber(
     const WideString& wsTemplateNS) {
+  XFA_VERSION eVersion = ParseXFAVersion(wsTemplateNS);
+  if (eVersion != XFA_VERSION_UNKNOWN)
+    m_eCurVersionMode = eVersion;
+
+  return eVersion;
+}
+
+// static
+XFA_VERSION CXFA_Document::ParseXFAVersion(const WideString& wsTemplateNS) {
   WideStringView wsTemplateURIPrefix(kTemplateNS);
   if (wsTemplateNS.GetLength() <= wsTemplateURIPrefix.GetLength())
     return XFA_VERSION_UNKNOWN;
@@ -1485,14 +1494,13 @@
   int8_t iMinor =
       FXSYS_wtoi(wsTemplateNS
                      .Substr(nDotPos.value() + 1,
-                             wsTemplateNS.GetLength() - nDotPos.value() - 2)
+                             wsTemplateNS.GetLength() - nDotPos.value() - 1)
                      .c_str());
   XFA_VERSION eVersion =
       static_cast<XFA_VERSION>(static_cast<int32_t>(iMajor) * 100 + iMinor);
   if (eVersion < XFA_VERSION_MIN || eVersion > XFA_VERSION_MAX)
     return XFA_VERSION_UNKNOWN;
 
-  m_eCurVersionMode = eVersion;
   return eVersion;
 }
 
diff --git a/xfa/fxfa/parser/cxfa_document.h b/xfa/fxfa/parser/cxfa_document.h
index 7065772..f7e2a537 100644
--- a/xfa/fxfa/parser/cxfa_document.h
+++ b/xfa/fxfa/parser/cxfa_document.h
@@ -14,6 +14,7 @@
 #include <memory>
 #include <vector>
 
+#include "core/fxcrt/fx_string.h"
 #include "core/fxcrt/unowned_ptr.h"
 #include "fxjs/gc/heap.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -145,6 +146,10 @@
   void SetPendingNodesUnusedAndUnbound();
 
  private:
+  friend class CXFA_DocumentTest_ParseXFAVersion_Test;
+
+  static XFA_VERSION ParseXFAVersion(const WideString& wsTemplateNS);
+
   CXFA_Document(CXFA_FFNotify* notify,
                 cppgc::Heap* heap,
                 LayoutProcessorIface* pLayout);
diff --git a/xfa/fxfa/parser/cxfa_document_unittest.cpp b/xfa/fxfa/parser/cxfa_document_unittest.cpp
new file mode 100644
index 0000000..43872d1
--- /dev/null
+++ b/xfa/fxfa/parser/cxfa_document_unittest.cpp
@@ -0,0 +1,83 @@
+// Copyright 2021 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "xfa/fxfa/parser/cxfa_document.h"
+
+#include "core/fxcrt/fx_string.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(CXFA_DocumentTest, ParseXFAVersion) {
+  // Malformed
+  EXPECT_EQ(XFA_VERSION_UNKNOWN, CXFA_Document::ParseXFAVersion(L""));
+  EXPECT_EQ(XFA_VERSION_UNKNOWN,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template"));
+  EXPECT_EQ(XFA_VERSION_UNKNOWN,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-templatX/"));
+  EXPECT_EQ(XFA_VERSION_UNKNOWN,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/"));
+  EXPECT_EQ(XFA_VERSION_UNKNOWN,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/2"));
+
+  // Out-of-range
+  EXPECT_EQ(XFA_VERSION_UNKNOWN,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/-1.0"));
+  EXPECT_EQ(XFA_VERSION_UNKNOWN,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/1.9"));
+  EXPECT_EQ(XFA_VERSION_UNKNOWN,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/4.1"));
+
+  // Missing digits
+  EXPECT_EQ(XFA_VERSION_UNKNOWN,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/."));
+  EXPECT_EQ(XFA_VERSION_UNKNOWN,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/.3"));
+  EXPECT_EQ(XFA_VERSION_300, CXFA_Document::ParseXFAVersion(
+                                 L"http://www.xfa.org/schema/xfa-template/3."));
+  EXPECT_EQ(XFA_VERSION_UNKNOWN,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/clams.6"));
+  EXPECT_EQ(XFA_VERSION_300,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/3.clams"));
+
+  // Min / max values
+  EXPECT_EQ(XFA_VERSION_200,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/2.0"));
+  EXPECT_EQ(400, CXFA_Document::ParseXFAVersion(
+                     L"http://www.xfa.org/schema/xfa-template/4.0"));
+
+  // Number and decimal point parsing.
+  EXPECT_EQ(XFA_VERSION_306,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/3.6"));
+
+  // TODO(tsepez): maybe fail on these dubious values?
+  EXPECT_EQ(XFA_VERSION_306,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/0003.00006"));
+  EXPECT_EQ(XFA_VERSION_306,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/0003.00006.0000"));
+  EXPECT_EQ(XFA_VERSION_206,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/2.6clams"));
+  EXPECT_EQ(XFA_VERSION_206,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/1.106"));
+  EXPECT_EQ(XFA_VERSION_306,
+            CXFA_Document::ParseXFAVersion(
+                L"http://www.xfa.org/schema/xfa-template/4.-94"));
+  EXPECT_EQ(317, CXFA_Document::ParseXFAVersion(
+                     L"http://www.xfa.org/schema/xfa-template/3.17"));
+}