| // Copyright 2014 The PDFium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com |
| |
| #include "core/fxcrt/bytestring.h" |
| |
| #include <ctype.h> |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| |
| #include "core/fxcrt/check.h" |
| #include "core/fxcrt/check_op.h" |
| #include "core/fxcrt/fx_codepage.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_system.h" |
| #include "core/fxcrt/span.h" |
| #include "core/fxcrt/span_util.h" |
| #include "core/fxcrt/string_pool_template.h" |
| |
| // Instantiate. |
| template class fxcrt::StringViewTemplate<char>; |
| template class fxcrt::StringPoolTemplate<ByteString>; |
| template struct std::hash<ByteString>; |
| |
| namespace { |
| |
| constexpr char kTrimChars[] = "\x09\x0a\x0b\x0c\x0d\x20"; |
| |
| } // namespace |
| |
| namespace fxcrt { |
| |
| static_assert(sizeof(ByteString) <= sizeof(char*), |
| "Strings must not require more space than pointers"); |
| |
| // static |
| ByteString ByteString::FormatInteger(int i) { |
| char buf[32]; |
| FXSYS_snprintf(buf, sizeof(buf), "%d", i); |
| return ByteString(buf); |
| } |
| |
| // static |
| ByteString ByteString::FormatV(const char* pFormat, va_list argList) { |
| va_list argListCopy; |
| va_copy(argListCopy, argList); |
| int nMaxLen = vsnprintf(nullptr, 0, pFormat, argListCopy); |
| va_end(argListCopy); |
| |
| if (nMaxLen <= 0) |
| return ByteString(); |
| |
| ByteString ret; |
| { |
| // Span's lifetime must end before ReleaseBuffer() below. |
| pdfium::span<char> buf = ret.GetBuffer(nMaxLen); |
| |
| // SAFETY: In the following two calls, there's always space in the buffer |
| // for a terminating NUL that's not included in nMaxLen, and hence not |
| // included in the span. |
| UNSAFE_BUFFERS(FXSYS_memset(buf.data(), 0, nMaxLen + 1)); |
| va_copy(argListCopy, argList); |
| vsnprintf(buf.data(), nMaxLen + 1, pFormat, argListCopy); |
| va_end(argListCopy); |
| } |
| ret.ReleaseBuffer(ret.GetStringLength()); |
| return ret; |
| } |
| |
| // static |
| ByteString ByteString::Format(const char* pFormat, ...) { |
| va_list argList; |
| va_start(argList, pFormat); |
| ByteString ret = FormatV(pFormat, argList); |
| va_end(argList); |
| |
| return ret; |
| } |
| |
| ByteString::ByteString(const char* pStr, size_t nLen) { |
| if (nLen) { |
| // SAFETY: caller ensures `pStr` points to at least `nLen` chars. |
| m_pData = StringData::Create(UNSAFE_BUFFERS(pdfium::make_span(pStr, nLen))); |
| } |
| } |
| |
| ByteString::ByteString(const uint8_t* pStr, size_t nLen) |
| : ByteString(reinterpret_cast<const char*>(pStr), nLen) {} |
| |
| ByteString::ByteString(char ch) { |
| m_pData = StringData::Create(1); |
| m_pData->m_String[0] = ch; |
| } |
| |
| ByteString::ByteString(const char* ptr) |
| : ByteString(ptr, ptr ? strlen(ptr) : 0) {} |
| |
| ByteString::ByteString(ByteStringView bstrc) { |
| if (!bstrc.IsEmpty()) { |
| m_pData = StringData::Create(bstrc.span()); |
| } |
| } |
| |
| ByteString::ByteString(ByteStringView str1, ByteStringView str2) { |
| FX_SAFE_SIZE_T nSafeLen = str1.GetLength(); |
| nSafeLen += str2.GetLength(); |
| |
| size_t nNewLen = nSafeLen.ValueOrDie(); |
| if (nNewLen == 0) |
| return; |
| |
| m_pData = StringData::Create(nNewLen); |
| m_pData->CopyContents(str1.span()); |
| m_pData->CopyContentsAt(str1.GetLength(), str2.span()); |
| } |
| |
| ByteString::ByteString(const std::initializer_list<ByteStringView>& list) { |
| FX_SAFE_SIZE_T nSafeLen = 0; |
| for (const auto& item : list) |
| nSafeLen += item.GetLength(); |
| |
| size_t nNewLen = nSafeLen.ValueOrDie(); |
| if (nNewLen == 0) |
| return; |
| |
| m_pData = StringData::Create(nNewLen); |
| |
| size_t nOffset = 0; |
| for (const auto& item : list) { |
| m_pData->CopyContentsAt(nOffset, item.span()); |
| nOffset += item.GetLength(); |
| } |
| } |
| |
| ByteString::ByteString(const fxcrt::ostringstream& outStream) { |
| auto str = outStream.str(); |
| if (!str.empty()) { |
| m_pData = StringData::Create(pdfium::make_span(str)); |
| } |
| } |
| |
| ByteString& ByteString::operator=(const char* str) { |
| if (!str || !str[0]) |
| clear(); |
| else |
| AssignCopy(str, strlen(str)); |
| |
| return *this; |
| } |
| |
| ByteString& ByteString::operator=(ByteStringView str) { |
| if (str.IsEmpty()) |
| clear(); |
| else |
| AssignCopy(str.unterminated_c_str(), str.GetLength()); |
| |
| return *this; |
| } |
| |
| ByteString& ByteString::operator=(const ByteString& that) { |
| if (m_pData != that.m_pData) |
| m_pData = that.m_pData; |
| |
| return *this; |
| } |
| |
| ByteString& ByteString::operator=(ByteString&& that) noexcept { |
| if (m_pData != that.m_pData) |
| m_pData = std::move(that.m_pData); |
| |
| return *this; |
| } |
| |
| ByteString& ByteString::operator+=(const char* str) { |
| if (str) |
| Concat(str, strlen(str)); |
| |
| return *this; |
| } |
| |
| ByteString& ByteString::operator+=(char ch) { |
| Concat(&ch, 1); |
| return *this; |
| } |
| |
| ByteString& ByteString::operator+=(const ByteString& str) { |
| if (str.m_pData) |
| Concat(str.m_pData->m_String, str.m_pData->m_nDataLength); |
| |
| return *this; |
| } |
| |
| ByteString& ByteString::operator+=(ByteStringView str) { |
| if (!str.IsEmpty()) |
| Concat(str.unterminated_c_str(), str.GetLength()); |
| |
| return *this; |
| } |
| |
| bool ByteString::operator==(const char* ptr) const { |
| if (!m_pData) |
| return !ptr || !ptr[0]; |
| |
| if (!ptr) |
| return m_pData->m_nDataLength == 0; |
| |
| // SAFETY: `m_nDataLength` is within `m_String`, and the strlen() call |
| // ensures there are `m_nDataLength` bytes at `ptr` before the terminator. |
| return strlen(ptr) == m_pData->m_nDataLength && |
| UNSAFE_BUFFERS( |
| FXSYS_memcmp(ptr, m_pData->m_String, m_pData->m_nDataLength)) == 0; |
| } |
| |
| bool ByteString::operator==(ByteStringView str) const { |
| if (!m_pData) |
| return str.IsEmpty(); |
| |
| // SAFETY: `str` has `GetLength()` valid bytes in `unterminated_c_str()`, |
| // `m_nDataLength` is within `m_String`, and equality comparison. |
| return m_pData->m_nDataLength == str.GetLength() && |
| UNSAFE_BUFFERS(FXSYS_memcmp( |
| m_pData->m_String, str.unterminated_c_str(), str.GetLength())) == |
| 0; |
| } |
| |
| bool ByteString::operator==(const ByteString& other) const { |
| if (m_pData == other.m_pData) |
| return true; |
| |
| if (IsEmpty()) |
| return other.IsEmpty(); |
| |
| if (other.IsEmpty()) |
| return false; |
| |
| return other.m_pData->m_nDataLength == m_pData->m_nDataLength && |
| memcmp(other.m_pData->m_String, m_pData->m_String, |
| m_pData->m_nDataLength) == 0; |
| } |
| |
| bool ByteString::operator<(const char* ptr) const { |
| if (!m_pData && !ptr) |
| return false; |
| if (c_str() == ptr) |
| return false; |
| |
| size_t len = GetLength(); |
| size_t other_len = ptr ? strlen(ptr) : 0; |
| |
| // SAFETY: Comparison limited to minimum valid length of either argument. |
| int result = |
| UNSAFE_BUFFERS(FXSYS_memcmp(c_str(), ptr, std::min(len, other_len))); |
| return result < 0 || (result == 0 && len < other_len); |
| } |
| |
| bool ByteString::operator<(ByteStringView str) const { |
| return Compare(str) < 0; |
| } |
| |
| bool ByteString::operator<(const ByteString& other) const { |
| if (m_pData == other.m_pData) |
| return false; |
| |
| size_t len = GetLength(); |
| size_t other_len = other.GetLength(); |
| |
| // SAFETY: Comparison limited to minimum valid length of either argument. |
| int result = UNSAFE_BUFFERS( |
| FXSYS_memcmp(c_str(), other.c_str(), std::min(len, other_len))); |
| return result < 0 || (result == 0 && len < other_len); |
| } |
| |
| bool ByteString::EqualNoCase(ByteStringView str) const { |
| if (!m_pData) { |
| return str.IsEmpty(); |
| } |
| if (m_pData->m_nDataLength != str.GetLength()) { |
| return false; |
| } |
| pdfium::span<const uint8_t> this_span = pdfium::as_bytes(m_pData->span()); |
| pdfium::span<const uint8_t> that_span = str.unsigned_span(); |
| while (!this_span.empty()) { |
| uint8_t this_char = this_span.front(); |
| uint8_t that_char = that_span.front(); |
| if (this_char != that_char && tolower(this_char) != tolower(that_char)) { |
| return false; |
| } |
| this_span = this_span.subspan(1); |
| that_span = that_span.subspan(1); |
| } |
| return true; |
| } |
| |
| intptr_t ByteString::ReferenceCountForTesting() const { |
| return m_pData ? m_pData->m_nRefs : 0; |
| } |
| |
| ByteString ByteString::Substr(size_t offset) const { |
| // Unsigned underflow is well-defined and out-of-range is handled by Substr(). |
| return Substr(offset, GetLength() - offset); |
| } |
| |
| ByteString ByteString::Substr(size_t first, size_t count) const { |
| if (!m_pData) { |
| return ByteString(); |
| } |
| if (first == 0 && count == m_pData->m_nDataLength) { |
| return *this; |
| } |
| return ByteString(AsStringView().Substr(first, count)); |
| } |
| |
| ByteString ByteString::First(size_t count) const { |
| return Substr(0, count); |
| } |
| |
| ByteString ByteString::Last(size_t count) const { |
| // Unsigned underflow is well-defined and out-of-range is handled by Substr(). |
| return Substr(GetLength() - count, count); |
| } |
| |
| void ByteString::MakeLower() { |
| if (IsEmpty()) |
| return; |
| |
| ReallocBeforeWrite(m_pData->m_nDataLength); |
| FXSYS_strlwr(m_pData->m_String); |
| } |
| |
| void ByteString::MakeUpper() { |
| if (IsEmpty()) |
| return; |
| |
| ReallocBeforeWrite(m_pData->m_nDataLength); |
| FXSYS_strupr(m_pData->m_String); |
| } |
| |
| int ByteString::Compare(ByteStringView str) const { |
| if (!m_pData) |
| return str.IsEmpty() ? 0 : -1; |
| |
| size_t this_len = m_pData->m_nDataLength; |
| size_t that_len = str.GetLength(); |
| size_t min_len = std::min(this_len, that_len); |
| |
| // SAFETY: Comparison limited to minimum valid length of either argument. |
| int result = UNSAFE_BUFFERS( |
| FXSYS_memcmp(m_pData->m_String, str.unterminated_c_str(), min_len)); |
| if (result != 0) |
| return result; |
| if (this_len == that_len) |
| return 0; |
| return this_len < that_len ? -1 : 1; |
| } |
| |
| void ByteString::TrimWhitespace() { |
| TrimWhitespaceBack(); |
| TrimWhitespaceFront(); |
| } |
| |
| void ByteString::TrimWhitespaceFront() { |
| TrimFront(kTrimChars); |
| } |
| |
| void ByteString::TrimWhitespaceBack() { |
| TrimBack(kTrimChars); |
| } |
| |
| std::ostream& operator<<(std::ostream& os, const ByteString& str) { |
| return os.write(str.c_str(), str.GetLength()); |
| } |
| |
| std::ostream& operator<<(std::ostream& os, ByteStringView str) { |
| return os.write(str.unterminated_c_str(), str.GetLength()); |
| } |
| |
| } // namespace fxcrt |
| |
| uint32_t FX_HashCode_GetA(ByteStringView str) { |
| uint32_t dwHashCode = 0; |
| for (ByteStringView::UnsignedType c : str) |
| dwHashCode = 31 * dwHashCode + c; |
| return dwHashCode; |
| } |
| |
| uint32_t FX_HashCode_GetLoweredA(ByteStringView str) { |
| uint32_t dwHashCode = 0; |
| for (ByteStringView::UnsignedType c : str) |
| dwHashCode = 31 * dwHashCode + tolower(c); |
| return dwHashCode; |
| } |
| |
| uint32_t FX_HashCode_GetAsIfW(ByteStringView str) { |
| uint32_t dwHashCode = 0; |
| for (ByteStringView::UnsignedType c : str) |
| dwHashCode = 1313 * dwHashCode + c; |
| return dwHashCode; |
| } |
| |
| uint32_t FX_HashCode_GetLoweredAsIfW(ByteStringView str) { |
| uint32_t dwHashCode = 0; |
| for (ByteStringView::UnsignedType c : str) |
| dwHashCode = 1313 * dwHashCode + FXSYS_towlower(c); |
| return dwHashCode; |
| } |