|  | // 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 "fxjs/cfx_globaldata.h" | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "core/fdrm/fx_crypt.h" | 
|  | #include "core/fxcrt/fx_memcpy_wrappers.h" | 
|  | #include "core/fxcrt/numerics/safe_conversions.h" | 
|  | #include "core/fxcrt/stl_util.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | constexpr size_t kMinGlobalDataBytes = 12; | 
|  | constexpr size_t kMaxGlobalDataBytes = 4 * 1024 - 8; | 
|  | constexpr uint16_t kMagic = ('X' << 8) | 'F'; | 
|  | constexpr uint16_t kMaxVersion = 2; | 
|  |  | 
|  | const uint8_t kRC4KEY[] = { | 
|  | 0x19, 0xa8, 0xe8, 0x01, 0xf6, 0xa8, 0xb6, 0x4d, 0x82, 0x04, 0x45, 0x6d, | 
|  | 0xb4, 0xcf, 0xd7, 0x77, 0x67, 0xf9, 0x75, 0x9f, 0xf0, 0xe0, 0x1e, 0x51, | 
|  | 0xee, 0x46, 0xfd, 0x0b, 0xc9, 0x93, 0x25, 0x55, 0x4a, 0xee, 0xe0, 0x16, | 
|  | 0xd0, 0xdf, 0x8c, 0xfa, 0x2a, 0xa9, 0x49, 0xfd, 0x97, 0x1c, 0x0e, 0x22, | 
|  | 0x13, 0x28, 0x7c, 0xaf, 0xc4, 0xfc, 0x9c, 0x12, 0x65, 0x8c, 0x4e, 0x5b, | 
|  | 0x04, 0x75, 0x89, 0xc9, 0xb1, 0xed, 0x50, 0xca, 0x96, 0x6f, 0x1a, 0x7a, | 
|  | 0xfe, 0x58, 0x5d, 0xec, 0x19, 0x4a, 0xf6, 0x35, 0x6a, 0x97, 0x14, 0x00, | 
|  | 0x0e, 0xd0, 0x6b, 0xbb, 0xd5, 0x75, 0x55, 0x8b, 0x6e, 0x6b, 0x19, 0xa0, | 
|  | 0xf8, 0x77, 0xd5, 0xa3}; | 
|  |  | 
|  | CFX_GlobalData* g_pInstance = nullptr; | 
|  |  | 
|  | // Returns true if non-empty, setting sPropName | 
|  | bool TrimPropName(ByteString* sPropName) { | 
|  | sPropName->TrimWhitespace(); | 
|  | return sPropName->GetLength() != 0; | 
|  | } | 
|  |  | 
|  | void MakeNameTypeString(const ByteString& name, | 
|  | CFX_Value::DataType eType, | 
|  | BinaryBuffer* result) { | 
|  | uint32_t dwNameLen = pdfium::checked_cast<uint32_t>(name.GetLength()); | 
|  | result->AppendUint32(dwNameLen); | 
|  | result->AppendString(name); | 
|  | result->AppendUint16(static_cast<uint16_t>(eType)); | 
|  | } | 
|  |  | 
|  | bool MakeByteString(const ByteString& name, | 
|  | const CFX_KeyValue& pData, | 
|  | BinaryBuffer* result) { | 
|  | switch (pData.nType) { | 
|  | case CFX_Value::DataType::kNumber: { | 
|  | MakeNameTypeString(name, pData.nType, result); | 
|  | result->AppendDouble(pData.dData); | 
|  | return true; | 
|  | } | 
|  | case CFX_Value::DataType::kBoolean: { | 
|  | MakeNameTypeString(name, pData.nType, result); | 
|  | result->AppendUint16(static_cast<uint16_t>(pData.bData)); | 
|  | return true; | 
|  | } | 
|  | case CFX_Value::DataType::kString: { | 
|  | MakeNameTypeString(name, pData.nType, result); | 
|  | uint32_t dwDataLen = | 
|  | pdfium::checked_cast<uint32_t>(pData.sData.GetLength()); | 
|  | result->AppendUint32(dwDataLen); | 
|  | result->AppendString(pData.sData); | 
|  | return true; | 
|  | } | 
|  | case CFX_Value::DataType::kNull: { | 
|  | MakeNameTypeString(name, pData.nType, result); | 
|  | return true; | 
|  | } | 
|  | // Arrays don't get persisted per JS spec page 484. | 
|  | case CFX_Value::DataType::kObject: | 
|  | break; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // static | 
|  | CFX_GlobalData* CFX_GlobalData::GetRetainedInstance(Delegate* pDelegate) { | 
|  | if (!g_pInstance) { | 
|  | g_pInstance = new CFX_GlobalData(pDelegate); | 
|  | } | 
|  | ++g_pInstance->ref_count_; | 
|  | return g_pInstance; | 
|  | } | 
|  |  | 
|  | bool CFX_GlobalData::Release() { | 
|  | if (--ref_count_) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | delete g_pInstance; | 
|  | g_pInstance = nullptr; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | CFX_GlobalData::CFX_GlobalData(Delegate* pDelegate) : delegate_(pDelegate) { | 
|  | LoadGlobalPersistentVariables(); | 
|  | } | 
|  |  | 
|  | CFX_GlobalData::~CFX_GlobalData() { | 
|  | SaveGlobalPersisitentVariables(); | 
|  | } | 
|  |  | 
|  | CFX_GlobalData::iterator CFX_GlobalData::FindGlobalVariable( | 
|  | const ByteString& propname) { | 
|  | for (auto it = array_global_data_.begin(); it != array_global_data_.end(); | 
|  | ++it) { | 
|  | if ((*it)->data.sKey == propname) { | 
|  | return it; | 
|  | } | 
|  | } | 
|  | return array_global_data_.end(); | 
|  | } | 
|  |  | 
|  | CFX_GlobalData::Element* CFX_GlobalData::GetGlobalVariable( | 
|  | const ByteString& propname) { | 
|  | auto iter = FindGlobalVariable(propname); | 
|  | return iter != array_global_data_.end() ? iter->get() : nullptr; | 
|  | } | 
|  |  | 
|  | void CFX_GlobalData::SetGlobalVariableNumber(ByteString sPropName, | 
|  | double dData) { | 
|  | if (!TrimPropName(&sPropName)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | CFX_GlobalData::Element* pData = GetGlobalVariable(sPropName); | 
|  | if (pData) { | 
|  | pData->data.nType = CFX_Value::DataType::kNumber; | 
|  | pData->data.dData = dData; | 
|  | return; | 
|  | } | 
|  | auto pNewData = std::make_unique<CFX_GlobalData::Element>(); | 
|  | pNewData->data.sKey = std::move(sPropName); | 
|  | pNewData->data.nType = CFX_Value::DataType::kNumber; | 
|  | pNewData->data.dData = dData; | 
|  | array_global_data_.push_back(std::move(pNewData)); | 
|  | } | 
|  |  | 
|  | void CFX_GlobalData::SetGlobalVariableBoolean(ByteString sPropName, | 
|  | bool bData) { | 
|  | if (!TrimPropName(&sPropName)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | CFX_GlobalData::Element* pData = GetGlobalVariable(sPropName); | 
|  | if (pData) { | 
|  | pData->data.nType = CFX_Value::DataType::kBoolean; | 
|  | pData->data.bData = bData; | 
|  | return; | 
|  | } | 
|  | auto pNewData = std::make_unique<CFX_GlobalData::Element>(); | 
|  | pNewData->data.sKey = std::move(sPropName); | 
|  | pNewData->data.nType = CFX_Value::DataType::kBoolean; | 
|  | pNewData->data.bData = bData; | 
|  | array_global_data_.push_back(std::move(pNewData)); | 
|  | } | 
|  |  | 
|  | void CFX_GlobalData::SetGlobalVariableString(ByteString sPropName, | 
|  | const ByteString& sData) { | 
|  | if (!TrimPropName(&sPropName)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | CFX_GlobalData::Element* pData = GetGlobalVariable(sPropName); | 
|  | if (pData) { | 
|  | pData->data.nType = CFX_Value::DataType::kString; | 
|  | pData->data.sData = sData; | 
|  | return; | 
|  | } | 
|  | auto pNewData = std::make_unique<CFX_GlobalData::Element>(); | 
|  | pNewData->data.sKey = std::move(sPropName); | 
|  | pNewData->data.nType = CFX_Value::DataType::kString; | 
|  | pNewData->data.sData = sData; | 
|  | array_global_data_.push_back(std::move(pNewData)); | 
|  | } | 
|  |  | 
|  | void CFX_GlobalData::SetGlobalVariableObject( | 
|  | ByteString sPropName, | 
|  | std::vector<std::unique_ptr<CFX_KeyValue>> array) { | 
|  | if (!TrimPropName(&sPropName)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | CFX_GlobalData::Element* pData = GetGlobalVariable(sPropName); | 
|  | if (pData) { | 
|  | pData->data.nType = CFX_Value::DataType::kObject; | 
|  | pData->data.objData = std::move(array); | 
|  | return; | 
|  | } | 
|  | auto pNewData = std::make_unique<CFX_GlobalData::Element>(); | 
|  | pNewData->data.sKey = std::move(sPropName); | 
|  | pNewData->data.nType = CFX_Value::DataType::kObject; | 
|  | pNewData->data.objData = std::move(array); | 
|  | array_global_data_.push_back(std::move(pNewData)); | 
|  | } | 
|  |  | 
|  | void CFX_GlobalData::SetGlobalVariableNull(ByteString sPropName) { | 
|  | if (!TrimPropName(&sPropName)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | CFX_GlobalData::Element* pData = GetGlobalVariable(sPropName); | 
|  | if (pData) { | 
|  | pData->data.nType = CFX_Value::DataType::kNull; | 
|  | return; | 
|  | } | 
|  | auto pNewData = std::make_unique<CFX_GlobalData::Element>(); | 
|  | pNewData->data.sKey = std::move(sPropName); | 
|  | pNewData->data.nType = CFX_Value::DataType::kNull; | 
|  | array_global_data_.push_back(std::move(pNewData)); | 
|  | } | 
|  |  | 
|  | bool CFX_GlobalData::SetGlobalVariablePersistent(ByteString sPropName, | 
|  | bool bPersistent) { | 
|  | if (!TrimPropName(&sPropName)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | CFX_GlobalData::Element* pData = GetGlobalVariable(sPropName); | 
|  | if (!pData) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | pData->bPersistent = bPersistent; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool CFX_GlobalData::DeleteGlobalVariable(ByteString sPropName) { | 
|  | if (!TrimPropName(&sPropName)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | auto iter = FindGlobalVariable(sPropName); | 
|  | if (iter == array_global_data_.end()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | array_global_data_.erase(iter); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | int32_t CFX_GlobalData::GetSize() const { | 
|  | return fxcrt::CollectionSize<int32_t>(array_global_data_); | 
|  | } | 
|  |  | 
|  | CFX_GlobalData::Element* CFX_GlobalData::GetAt(int index) { | 
|  | if (index < 0 || index >= GetSize()) { | 
|  | return nullptr; | 
|  | } | 
|  | return array_global_data_[index].get(); | 
|  | } | 
|  |  | 
|  | bool CFX_GlobalData::LoadGlobalPersistentVariables() { | 
|  | if (!delegate_) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool ret; | 
|  | { | 
|  | // Span can't outlive call to BufferDone(). | 
|  | std::optional<pdfium::span<uint8_t>> buffer = delegate_->LoadBuffer(); | 
|  | if (!buffer.has_value() || buffer.value().empty()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | ret = LoadGlobalPersistentVariablesFromBuffer(buffer.value()); | 
|  | } | 
|  | delegate_->BufferDone(); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | bool CFX_GlobalData::LoadGlobalPersistentVariablesFromBuffer( | 
|  | pdfium::span<uint8_t> buffer) { | 
|  | if (buffer.size() < kMinGlobalDataBytes) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | CRYPT_ArcFourCryptBlock(buffer, kRC4KEY); | 
|  |  | 
|  | UNSAFE_TODO({ | 
|  | uint8_t* p = buffer.data(); | 
|  | uint16_t wType = *((uint16_t*)p); | 
|  | p += sizeof(uint16_t); | 
|  | if (wType != kMagic) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint16_t wVersion = *((uint16_t*)p); | 
|  | p += sizeof(uint16_t); | 
|  | if (wVersion > kMaxVersion) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint32_t dwCount = *((uint32_t*)p); | 
|  | p += sizeof(uint32_t); | 
|  |  | 
|  | uint32_t dwSize = *((uint32_t*)p); | 
|  | p += sizeof(uint32_t); | 
|  |  | 
|  | if (dwSize != buffer.size() - sizeof(uint16_t) * 2 - sizeof(uint32_t) * 2) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | for (int32_t i = 0, sz = dwCount; i < sz; i++) { | 
|  | if (p + sizeof(uint32_t) >= buffer.end()) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | uint32_t dwNameLen = 0; | 
|  | FXSYS_memcpy(&dwNameLen, p, sizeof(uint32_t)); | 
|  | p += sizeof(uint32_t); | 
|  | if (p + dwNameLen > buffer.end()) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | ByteString sEntry = ByteString(p, dwNameLen); | 
|  | p += sizeof(char) * dwNameLen; | 
|  |  | 
|  | uint16_t wDataType = 0; | 
|  | FXSYS_memcpy(&wDataType, p, sizeof(uint16_t)); | 
|  | p += sizeof(uint16_t); | 
|  |  | 
|  | CFX_Value::DataType eDataType = | 
|  | static_cast<CFX_Value::DataType>(wDataType); | 
|  |  | 
|  | switch (eDataType) { | 
|  | case CFX_Value::DataType::kNumber: { | 
|  | double dData = 0; | 
|  | switch (wVersion) { | 
|  | case 1: { | 
|  | uint32_t dwData = 0; | 
|  | FXSYS_memcpy(&dwData, p, sizeof(uint32_t)); | 
|  | p += sizeof(uint32_t); | 
|  | dData = dwData; | 
|  | } break; | 
|  | case 2: { | 
|  | dData = 0; | 
|  | FXSYS_memcpy(&dData, p, sizeof(double)); | 
|  | p += sizeof(double); | 
|  | } break; | 
|  | } | 
|  | SetGlobalVariableNumber(sEntry, dData); | 
|  | SetGlobalVariablePersistent(sEntry, true); | 
|  | } break; | 
|  | case CFX_Value::DataType::kBoolean: { | 
|  | uint16_t wData = 0; | 
|  | FXSYS_memcpy(&wData, p, sizeof(uint16_t)); | 
|  | p += sizeof(uint16_t); | 
|  | SetGlobalVariableBoolean(sEntry, (bool)(wData == 1)); | 
|  | SetGlobalVariablePersistent(sEntry, true); | 
|  | } break; | 
|  | case CFX_Value::DataType::kString: { | 
|  | uint32_t dwLength = 0; | 
|  | FXSYS_memcpy(&dwLength, p, sizeof(uint32_t)); | 
|  | p += sizeof(uint32_t); | 
|  | if (p + dwLength > buffer.end()) { | 
|  | break; | 
|  | } | 
|  | SetGlobalVariableString(sEntry, ByteString(p, dwLength)); | 
|  | SetGlobalVariablePersistent(sEntry, true); | 
|  | p += sizeof(char) * dwLength; | 
|  | } break; | 
|  | case CFX_Value::DataType::kNull: { | 
|  | SetGlobalVariableNull(sEntry); | 
|  | SetGlobalVariablePersistent(sEntry, true); | 
|  | } break; | 
|  | case CFX_Value::DataType::kObject: | 
|  | // Arrays aren't allowed in these buffers, nor are unrecognized tags. | 
|  | return false; | 
|  | } | 
|  | } | 
|  | }); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool CFX_GlobalData::SaveGlobalPersisitentVariables() { | 
|  | if (!delegate_) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint32_t nCount = 0; | 
|  | BinaryBuffer sData; | 
|  | for (const auto& pElement : array_global_data_) { | 
|  | if (!pElement->bPersistent) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | BinaryBuffer sElement; | 
|  | if (!MakeByteString(pElement->data.sKey, pElement->data, &sElement)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (sData.GetSize() + sElement.GetSize() > kMaxGlobalDataBytes) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | sData.AppendSpan(sElement.GetSpan()); | 
|  | nCount++; | 
|  | } | 
|  |  | 
|  | BinaryBuffer sFile; | 
|  | sFile.AppendUint16(kMagic); | 
|  | sFile.AppendUint16(kMaxVersion); | 
|  | sFile.AppendUint32(nCount); | 
|  |  | 
|  | uint32_t dwSize = pdfium::checked_cast<uint32_t>(sData.GetSize()); | 
|  | sFile.AppendUint32(dwSize); | 
|  | sFile.AppendSpan(sData.GetSpan()); | 
|  |  | 
|  | CRYPT_ArcFourCryptBlock(sFile.GetMutableSpan(), kRC4KEY); | 
|  | return delegate_->StoreBuffer(sFile.GetSpan()); | 
|  | } | 
|  |  | 
|  | CFX_GlobalData::Element::Element() = default; | 
|  |  | 
|  | CFX_GlobalData::Element::~Element() = default; |