/*
* Copyright (c) 2003-2025 Rony Shapiro <ronys@pwsafe.org>.
* All rights reserved. Use of the code is allowed under the
* Artistic License 2.0 terms, as specified in the LICENSE file
* distributed with this code, or available from
* http://www.opensource.org/licenses/artistic-license-2.0.php
*/
// FileV3Test.cpp: Unit test for V3 file format

#ifdef WIN32
#include "../ui/Windows/stdafx.h"
#endif

#include "core/PWSfileV3.h"
#include "os/file.h"
#include "core/PWScore.h"
#include <cstdio>

#include "gtest/gtest.h"

// A fixture for factoring common code across tests
class FileV3Test : public ::testing::Test
{
protected:
  FileV3Test(); // to init members
  CItemData smallItem, fullItem, item;
  void SetUp();
  void TearDown();

  const StringX passphrase;
  stringT fname;
  // members used to populate and test fullItem:
  const StringX title, password, user, notes, group;
  const StringX url, at, email, polname, symbols, runcmd;
  const time_t aTime, cTime, xTime, pmTime, rmTime;
  const int16 iDCA, iSDCA;
  const int32 kbs;
  time_t tVal;
  int16 iVal16;
  int32 iVal32;
};

FileV3Test::FileV3Test()
  : passphrase(_T("enchilada-sonol")), fname(_T("V3test.psafe3")),
    title(_T("a-title")), password(_T("b-password!?")),
    user(_T("C-UserR-ינור")), // non-English
    notes(_T("N is for notes\nwhich can span lines\r\nin several ways.")),
    group(_T("Groups.are.nested.by.dots")), url(_T("http://pwsafe.org/")),
    at(_T("\\u\\t\\t\\n\\p\\t\\n")), email(_T("joe@spammenot.com")),
    polname(_T("liberal")), symbols(_T("<-_+=@?>")), runcmd(_T("Run 4 your life")),
    aTime(1409901292), // time test was first added, from http://www.unixtimestamp.com/
    cTime(1409901293), xTime(1409901294), pmTime(1409901295), rmTime(1409901296),
    iDCA(3), iSDCA(8), kbs(0x12345678),
    tVal(0), iVal16(-1), iVal32(-1)
{}

void FileV3Test::SetUp()
{
  fullItem.CreateUUID();
  fullItem.SetTitle(title);
  fullItem.SetPassword(password);
  fullItem.SetUser(user);
  fullItem.SetNotes(notes);
  fullItem.SetGroup(group);
  fullItem.SetURL(url);
  fullItem.SetAutoType(at);
  fullItem.SetEmail(email);
  fullItem.SetPolicyName(polname);
  fullItem.SetSymbols(symbols);
  fullItem.SetRunCommand(runcmd);
  fullItem.SetATime(aTime);
  fullItem.SetCTime(cTime);
  fullItem.SetXTime(xTime);
  fullItem.SetPMTime(pmTime);
  fullItem.SetRMTime(rmTime);
  fullItem.SetDCA(iDCA);
  fullItem.SetShiftDCA(iSDCA);
  fullItem.SetKBShortcut(kbs);

  smallItem.CreateUUID();
  smallItem.SetTitle(_T("picollo"));
  smallItem.SetPassword(_T("tiny-passw"));
}

void FileV3Test::TearDown()
{
  ASSERT_TRUE(pws_os::DeleteAFile(fname));
  ASSERT_FALSE(pws_os::FileExists(fname));
}

// And now the tests...

TEST_F(FileV3Test, EmptyFile)
{
  PWSfileV3 fw(fname.c_str(), PWSfile::Write, PWSfile::V30);
  ASSERT_EQ(PWSfile::SUCCESS, fw.Open(passphrase));
  ASSERT_EQ(PWSfile::SUCCESS, fw.Close());
  ASSERT_TRUE(pws_os::FileExists(fname));

  PWSfileV3 fr(fname.c_str(), PWSfile::Read, PWSfile::V30);
  // Try opening with wrong passphrase, check failure
  EXPECT_EQ(PWSfile::WRONG_PASSWORD, fr.Open(_T("x")));

  // Now open with correct one, check emptiness
  ASSERT_EQ(PWSfile::SUCCESS, fr.Open(passphrase));
  EXPECT_EQ(PWSfile::END_OF_FILE, fr.ReadRecord(item));
  EXPECT_EQ(PWSfile::SUCCESS, fr.Close());
}

TEST_F(FileV3Test, HeaderTest)
{
  // header is written when file's opened for write.
  PWSfileHeader hdr1, hdr2;
  hdr1.m_prefString = _T("aPrefString");
  hdr1.m_whenlastsaved = 1413129351; // overwritten in Open()
  hdr1.m_whenpwdlastchanged = 1529684734;
  hdr1.m_lastsavedby = _T("aUser");
  hdr1.m_lastsavedon = _T("aMachine");
  hdr1.m_whatlastsaved = _T("PasswordSafe test framework");
  hdr1.m_DB_Name = fname.c_str();
  hdr1.m_DB_Description = _T("Test the header's persistency");

  PWSfileV3 fw(fname.c_str(), PWSfile::Write, PWSfile::V30);
  fw.SetHeader(hdr1);
  ASSERT_EQ(PWSfile::SUCCESS, fw.Open(passphrase));

  hdr1 = fw.GetHeader(); // Some fields set by Open()
  ASSERT_EQ(PWSfile::SUCCESS, fw.Close());
  ASSERT_TRUE(pws_os::FileExists(fname));

  PWSfileV3 fr(fname.c_str(), PWSfile::Read, PWSfile::V30);
  ASSERT_EQ(PWSfile::SUCCESS, fr.Open(passphrase));

  hdr2 = fr.GetHeader();
  // We need the following to read past the termination block!
  EXPECT_EQ(PWSfile::END_OF_FILE, fr.ReadRecord(item));
  EXPECT_EQ(PWSfile::SUCCESS, fr.Close());
  ASSERT_EQ(hdr1, hdr2);
}

TEST_F(FileV3Test, ItemTest)
{
  PWSfileV3 fw(fname.c_str(), PWSfile::Write, PWSfile::V30);
  ASSERT_EQ(PWSfile::SUCCESS, fw.Open(passphrase));
  EXPECT_EQ(PWSfile::SUCCESS, fw.WriteRecord(smallItem));
  EXPECT_EQ(PWSfile::SUCCESS, fw.WriteRecord(fullItem));
  ASSERT_EQ(PWSfile::SUCCESS, fw.Close());
  ASSERT_TRUE(pws_os::FileExists(fname));

  PWSfileV3 fr(fname.c_str(), PWSfile::Read, PWSfile::V30);
  ASSERT_EQ(PWSfile::SUCCESS, fr.Open(passphrase));
  EXPECT_EQ(PWSfile::SUCCESS, fr.ReadRecord(item));
  EXPECT_EQ(smallItem, item);
  EXPECT_EQ(PWSfile::SUCCESS, fr.ReadRecord(item));
  EXPECT_EQ(fullItem, item);
  EXPECT_EQ(PWSfile::END_OF_FILE, fr.ReadRecord(item));
  EXPECT_EQ(PWSfile::SUCCESS, fr.Close());
}

TEST_F(FileV3Test, UnknownPersistencyTest)
{
  CItemData d1;
  d1.CreateUUID();
  d1.SetTitle(_T("future"));
  d1.SetPassword(_T("possible"));
  unsigned char uv[] = {55, 42, 78, 30, 16, 93};
  d1.SetUnknownField(CItemData::UNKNOWN_TESTING, sizeof(uv), uv);

  PWSfileV3 fw(fname.c_str(), PWSfile::Write, PWSfile::V30);
  ASSERT_EQ(PWSfile::SUCCESS, fw.Open(passphrase));
  EXPECT_EQ(PWSfile::SUCCESS, fw.WriteRecord(d1));
  ASSERT_EQ(PWSfile::SUCCESS, fw.Close());
  ASSERT_TRUE(pws_os::FileExists(fname));

  PWSfileV3 fr(fname.c_str(), PWSfile::Read, PWSfile::V30);
  ASSERT_EQ(PWSfile::SUCCESS, fr.Open(passphrase));
  EXPECT_EQ(PWSfile::SUCCESS, fr.ReadRecord(item));
  EXPECT_EQ(d1, item);
  EXPECT_EQ(PWSfile::END_OF_FILE, fr.ReadRecord(item));
  EXPECT_EQ(PWSfile::SUCCESS, fr.Close());
}

TEST_F(FileV3Test, AttachmentTest)
{
  int attSizes[] = {0, 1, 8, 15, 16, 17, 32, 48 };
  std::vector<CItemData> cItems;

  for (auto attSize : attSizes)
  {
    CItemData ci;
    ci.CreateUUID();
    auto t = _T("future") + std::to_wstring(attSize);
    ci.SetTitle(t.c_str());
    ci.SetPassword(_T("possible"));
    ci.SetAttMediaType(_T("image/png"));
    std::vector<unsigned char> buf(attSize, static_cast<unsigned char>(attSize));
    ci.SetAttContent(buf.data(), buf.size());
    cItems.push_back(ci);
  }

  PWSfileV3 fw(fname.c_str(), PWSfile::Write, PWSfile::V30);
  ASSERT_EQ(PWSfile::SUCCESS, fw.Open(passphrase));

  for (auto ci : cItems)
  {
    EXPECT_EQ(PWSfile::SUCCESS, fw.WriteRecord(ci));
  }

  ASSERT_EQ(PWSfile::SUCCESS, fw.Close());
  ASSERT_TRUE(pws_os::FileExists(fname));


  PWSfileV3 fr(fname.c_str(), PWSfile::Read, PWSfile::V30);
  ASSERT_EQ(PWSfile::SUCCESS, fr.Open(passphrase));

  for (auto ci : cItems)
  {
    EXPECT_EQ(PWSfile::SUCCESS, fr.ReadRecord(item));
    EXPECT_EQ(ci, item);
  }

  EXPECT_EQ(PWSfile::END_OF_FILE, fr.ReadRecord(item));
  EXPECT_EQ(PWSfile::SUCCESS, fr.Close());
}

TEST_F(FileV3Test, V4Extension)
{
  PWScore core;
  stringT fname4(_T("V3test.psafe4"));

  PWSfileV3 fw(fname.c_str(), PWSfile::Write, PWSfile::V30);
  ASSERT_EQ(PWSfile::SUCCESS, fw.Open(passphrase));
  ASSERT_EQ(PWSfile::SUCCESS, fw.Close());
  ASSERT_TRUE(pws_os::FileExists(fname));

  // Change extension to psafe4
  ASSERT_EQ(0, std::rename("V3test.psafe3", "V3test.psafe4"));

  EXPECT_EQ(PWSfile::WRONG_PASSWORD, core.ReadFile(fname4.c_str(), L"WrongPassword", true));
  EXPECT_EQ(PWSfile::SUCCESS, core.ReadFile(fname4.c_str(), passphrase, true));

  ASSERT_EQ(0, std::rename("V3test.psafe4", "V3test.psafe3"));
}

TEST_F(FileV3Test, PasskeyTest)
{
  CItemData ci;
  ci.CreateUUID();
  ci.SetTitle(_T("passkey"));
  ci.SetPassword(_T("possible"));
  ci.SetPasskeyCredentialID(VectorX<unsigned char>(64,1));
  ci.SetPasskeyRelyingPartyID(_T("relying party"));
  ci.SetPasskeyUserHandle(VectorX<unsigned char>(32,2));
  ci.SetPasskeyAlgorithmID(123);
  ci.SetPasskeySignCount(456);
  ci.SetPasskeyPrivateKey(VectorX<unsigned char>(512,3));

  PWSfileV3 fw(fname.c_str(), PWSfile::Write, PWSfile::V30);
  ASSERT_EQ(PWSfile::SUCCESS, fw.Open(passphrase));
  EXPECT_EQ(PWSfile::SUCCESS, fw.WriteRecord(ci));
  ASSERT_EQ(PWSfile::SUCCESS, fw.Close());
  ASSERT_TRUE(pws_os::FileExists(fname));


  PWSfileV3 fr(fname.c_str(), PWSfile::Read, PWSfile::V30);
  ASSERT_EQ(PWSfile::SUCCESS, fr.Open(passphrase));
  EXPECT_EQ(PWSfile::SUCCESS, fr.ReadRecord(item));
  EXPECT_EQ(ci, item);
  EXPECT_EQ(PWSfile::END_OF_FILE, fr.ReadRecord(item));
  EXPECT_EQ(PWSfile::SUCCESS, fr.Close());
}
