Add one-arg form of string (and stringview) Substr().

Then use in CXFA_Document, CJS_Document, and CJX_Object where this
will avoid some manual length calculations.

-- Add comment about range checking.

Change-Id: Ica359122572d1e17597a209748026ebc9ac29166
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/86810
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
diff --git a/core/fxcrt/bytestring.cpp b/core/fxcrt/bytestring.cpp
index 1dd99cc..a547aad 100644
--- a/core/fxcrt/bytestring.cpp
+++ b/core/fxcrt/bytestring.cpp
@@ -469,6 +469,13 @@
   return m_pData ? m_pData->m_nRefs : 0;
 }
 
+ByteString ByteString::Substr(size_t offset) const {
+  if (offset >= GetLength())
+    return ByteString();
+
+  return Substr(offset, GetLength() - offset);
+}
+
 ByteString ByteString::Substr(size_t first, size_t count) const {
   if (!m_pData)
     return ByteString();
diff --git a/core/fxcrt/bytestring.h b/core/fxcrt/bytestring.h
index 76be3563..c2e45f2 100644
--- a/core/fxcrt/bytestring.h
+++ b/core/fxcrt/bytestring.h
@@ -167,6 +167,7 @@
   pdfium::span<char> GetBuffer(size_t nMinBufLength);
   void ReleaseBuffer(size_t nNewLength);
 
+  ByteString Substr(size_t offset) const;
   ByteString Substr(size_t first, size_t count) const;
   ByteString First(size_t count) const;
   ByteString Last(size_t count) const;
diff --git a/core/fxcrt/bytestring_unittest.cpp b/core/fxcrt/bytestring_unittest.cpp
index 0be68b9..7cbbd2e 100644
--- a/core/fxcrt/bytestring_unittest.cpp
+++ b/core/fxcrt/bytestring_unittest.cpp
@@ -644,7 +644,20 @@
   EXPECT_EQ("", empty);
 }
 
-TEST(ByteString, Substr) {
+TEST(ByteString, OneArgSubstr) {
+  ByteString fred("FRED");
+  EXPECT_EQ("FRED", fred.Substr(0));
+  EXPECT_EQ("RED", fred.Substr(1));
+  EXPECT_EQ("ED", fred.Substr(2));
+  EXPECT_EQ("D", fred.Substr(3));
+  EXPECT_EQ("", fred.Substr(4));
+
+  ByteString empty;
+  EXPECT_EQ("", empty.Substr(0));
+  EXPECT_EQ("", empty.Substr(1));
+}
+
+TEST(ByteString, TwoArgSubstr) {
   ByteString fred("FRED");
   EXPECT_EQ("", fred.Substr(0, 0));
   EXPECT_EQ("", fred.Substr(3, 0));
@@ -1298,7 +1311,28 @@
   EXPECT_EQ(2u, result.value());
 }
 
-TEST(ByteStringView, Substr) {
+TEST(ByteStringView, OneArgSubstr) {
+  ByteStringView null_string;
+  EXPECT_EQ(null_string, null_string.Substr(0));
+  EXPECT_EQ(null_string, null_string.Substr(1));
+
+  ByteStringView empty_string("");
+  EXPECT_EQ("", empty_string.Substr(0));
+  EXPECT_EQ("", empty_string.Substr(1));
+
+  ByteStringView single_character("a");
+  EXPECT_EQ(single_character, single_character.Substr(0));
+  EXPECT_EQ("", single_character.Substr(1));
+
+  ByteStringView longer_string("abcdef");
+  EXPECT_EQ(longer_string, longer_string.Substr(0));
+  EXPECT_EQ("", longer_string.Substr(187));
+
+  ByteStringView trailing_substring("ef");
+  EXPECT_EQ(trailing_substring, longer_string.Substr(4));
+}
+
+TEST(ByteStringView, TwoArgSubstr) {
   ByteStringView null_string;
   EXPECT_EQ(null_string, null_string.Substr(0, 1));
   EXPECT_EQ(null_string, null_string.Substr(1, 1));
diff --git a/core/fxcrt/string_view_template.h b/core/fxcrt/string_view_template.h
index 16e61d6..77f761b 100644
--- a/core/fxcrt/string_view_template.h
+++ b/core/fxcrt/string_view_template.h
@@ -26,6 +26,11 @@
 //
 // String view arguments should be passed by value, since they are small,
 // rather than const-ref, even if they are not modified.
+//
+// Front() and Back() tolerate empty strings and must return NUL in those
+// cases. Substr(), First(), and Last() tolerate out-of-range indices and
+// must return an empty string view in those cases. The aim here is allowing
+// callers to avoid range-checking first.
 template <typename T>
 class StringViewTemplate {
  public:
@@ -199,6 +204,13 @@
 
   bool Contains(CharType ch) const { return Find(ch).has_value(); }
 
+  StringViewTemplate Substr(size_t offset) const {
+    if (offset >= GetLength())
+      return StringViewTemplate();
+
+    return Substr(offset, GetLength() - offset);
+  }
+
   StringViewTemplate Substr(size_t first, size_t count) const {
     if (!m_Span.data())
       return StringViewTemplate();
diff --git a/core/fxcrt/widestring.cpp b/core/fxcrt/widestring.cpp
index 21d1422..e3d7369 100644
--- a/core/fxcrt/widestring.cpp
+++ b/core/fxcrt/widestring.cpp
@@ -704,6 +704,13 @@
   return ret;
 }
 
+WideString WideString::Substr(size_t offset) const {
+  if (offset >= GetLength())
+    return WideString();
+
+  return Substr(offset, GetLength() - offset);
+}
+
 WideString WideString::Substr(size_t first, size_t count) const {
   if (!m_pData)
     return WideString();
diff --git a/core/fxcrt/widestring.h b/core/fxcrt/widestring.h
index 59013fc..2b77321 100644
--- a/core/fxcrt/widestring.h
+++ b/core/fxcrt/widestring.h
@@ -155,6 +155,7 @@
   int Compare(const WideString& str) const;
   int CompareNoCase(const wchar_t* str) const;
 
+  WideString Substr(size_t offset) const;
   WideString Substr(size_t first, size_t count) const;
   WideString First(size_t count) const;
   WideString Last(size_t count) const;
diff --git a/core/fxcrt/widestring_unittest.cpp b/core/fxcrt/widestring_unittest.cpp
index 8b7f2ec..b05f26b 100644
--- a/core/fxcrt/widestring_unittest.cpp
+++ b/core/fxcrt/widestring_unittest.cpp
@@ -651,7 +651,20 @@
   EXPECT_EQ(L"", empty);
 }
 
-TEST(WideString, Substr) {
+TEST(WideString, OneArgSubstr) {
+  WideString fred(L"FRED");
+  EXPECT_EQ(L"FRED", fred.Substr(0));
+  EXPECT_EQ(L"RED", fred.Substr(1));
+  EXPECT_EQ(L"ED", fred.Substr(2));
+  EXPECT_EQ(L"D", fred.Substr(3));
+  EXPECT_EQ(L"", fred.Substr(4));
+
+  WideString empty;
+  EXPECT_EQ(L"", empty.Substr(0));
+  EXPECT_EQ(L"", empty.Substr(1));
+}
+
+TEST(WideString, TwoArgSubstr) {
   WideString fred(L"FRED");
   EXPECT_EQ(L"", fred.Substr(0, 0));
   EXPECT_EQ(L"", fred.Substr(3, 0));
diff --git a/fxjs/cjs_document.cpp b/fxjs/cjs_document.cpp
index 3f598cd..fb072e6 100644
--- a/fxjs/cjs_document.cpp
+++ b/fxjs/cjs_document.cpp
@@ -947,9 +947,10 @@
     if (wsFilePath[i - 1] == L'\\' || wsFilePath[i - 1] == L'/')
       break;
   }
-  if (i > 0 && i < wsFilePath.GetLength())
-    return CJS_Result::Success(pRuntime->NewString(wsFilePath.c_str() + i));
-
+  if (i > 0 && i < wsFilePath.GetLength()) {
+    return CJS_Result::Success(
+        pRuntime->NewString(wsFilePath.AsStringView().Substr(i)));
+  }
   return CJS_Result::Success(pRuntime->NewString(""));
 }
 
diff --git a/fxjs/xfa/cjx_object.cpp b/fxjs/xfa/cjx_object.cpp
index cf487c5..b38b7dc 100644
--- a/fxjs/xfa/cjx_object.cpp
+++ b/fxjs/xfa/cjx_object.cpp
@@ -1010,7 +1010,7 @@
   WideString wsSOM;
   if (!wsValue.IsEmpty()) {
     if (wsValue[0] == '#')
-      wsID = wsValue.Substr(1, wsValue.GetLength() - 1);
+      wsID = wsValue.Substr(1);
     else
       wsSOM = std::move(wsValue);
   }
diff --git a/xfa/fxfa/parser/cxfa_document.cpp b/xfa/fxfa/parser/cxfa_document.cpp
index 0ea2395..572e78c 100644
--- a/xfa/fxfa/parser/cxfa_document.cpp
+++ b/xfa/fxfa/parser/cxfa_document.cpp
@@ -1491,11 +1491,7 @@
   int8_t iMajor = FXSYS_wtoi(
       wsTemplateNS.Substr(prefixLength, nDotPos.value() - prefixLength)
           .c_str());
-  int8_t iMinor =
-      FXSYS_wtoi(wsTemplateNS
-                     .Substr(nDotPos.value() + 1,
-                             wsTemplateNS.GetLength() - nDotPos.value() - 1)
-                     .c_str());
+  int8_t iMinor = FXSYS_wtoi(wsTemplateNS.Substr(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)
@@ -1604,16 +1600,14 @@
     return;
   }
   wsURI = wsUseVal.AsStringView().First(uSharpPos.value());
-  size_t uLen = wsUseVal.GetLength();
-  if (uLen >= uSharpPos.value() + 5 &&
-      wsUseVal.AsStringView().Substr(uSharpPos.value(), 5) == L"#som(" &&
-      wsUseVal[uLen - 1] == ')') {
-    wsSOM = wsUseVal.AsStringView().Substr(uSharpPos.value() + 5,
-                                           uLen - 1 - uSharpPos.value() - 5);
+  if (wsUseVal.AsStringView().Substr(uSharpPos.value(), 5) == L"#som(" &&
+      wsUseVal.Back() == ')') {
+    wsSOM = wsUseVal.AsStringView().Substr(
+        uSharpPos.value() + 5,
+        wsUseVal.GetLength() - 1 - uSharpPos.value() - 5);
     return;
   }
-  wsID = wsUseVal.AsStringView().Substr(uSharpPos.value() + 1,
-                                        uLen - uSharpPos.value() - 1);
+  wsID = wsUseVal.AsStringView().Substr(uSharpPos.value() + 1);
 }
 
 // static
@@ -1624,7 +1618,7 @@
     return;
 
   if (wsUseVal[0] == '#') {
-    wsID = wsUseVal.AsStringView().Substr(1, wsUseVal.GetLength() - 1);
+    wsID = wsUseVal.AsStringView().Substr(1);
     return;
   }
   wsSOM = wsUseVal.AsStringView();