1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "gtest/gtest.h"
8 
9 #include "mozilla/DataStorage.h"
10 #include "nsAppDirectoryServiceDefs.h"
11 #include "nsDirectoryServiceUtils.h"
12 #include "nsNetUtil.h"
13 #include "nsPrintfCString.h"
14 #include "nsStreamUtils.h"
15 #include "prtime.h"
16 
17 using namespace mozilla;
18 
19 class psm_DataStorageTest : public ::testing::Test {
20  protected:
SetUp()21   void SetUp() override {
22     const ::testing::TestInfo* const testInfo =
23         ::testing::UnitTest::GetInstance()->current_test_info();
24     NS_ConvertUTF8toUTF16 testName(testInfo->name());
25     storage = DataStorage::GetFromRawFileName(testName);
26     storage->Init();
27   }
28 
29   RefPtr<DataStorage> storage;
30 };
31 
32 constexpr auto testKey = "test"_ns;
33 constexpr auto testValue = "value"_ns;
34 constexpr auto privateTestValue = "private"_ns;
35 
TEST_F(psm_DataStorageTest,GetPutRemove)36 TEST_F(psm_DataStorageTest, GetPutRemove) {
37   // Test Put/Get on Persistent data
38   EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
39   // Don't re-use testKey / testValue here, to make sure that this works as
40   // expected with objects that have the same semantic value but are not
41   // literally the same object.
42   nsCString result = storage->Get("test"_ns, DataStorage_Persistent);
43   EXPECT_STREQ("value", result.get());
44 
45   // Get on Temporary/Private data with the same key should give nothing
46   result = storage->Get(testKey, DataStorage_Temporary);
47   EXPECT_TRUE(result.IsEmpty());
48   result = storage->Get(testKey, DataStorage_Private);
49   EXPECT_TRUE(result.IsEmpty());
50 
51   // Put with Temporary/Private data shouldn't affect Persistent data
52   constexpr auto temporaryTestValue = "temporary"_ns;
53   EXPECT_EQ(NS_OK,
54             storage->Put(testKey, temporaryTestValue, DataStorage_Temporary));
55   EXPECT_EQ(NS_OK,
56             storage->Put(testKey, privateTestValue, DataStorage_Private));
57   result = storage->Get(testKey, DataStorage_Temporary);
58   EXPECT_STREQ("temporary", result.get());
59   result = storage->Get(testKey, DataStorage_Private);
60   EXPECT_STREQ("private", result.get());
61   result = storage->Get(testKey, DataStorage_Persistent);
62   EXPECT_STREQ("value", result.get());
63 
64   // Put of a previously-present key overwrites it (if of the same type)
65   constexpr auto newValue = "new"_ns;
66   EXPECT_EQ(NS_OK, storage->Put(testKey, newValue, DataStorage_Persistent));
67   result = storage->Get(testKey, DataStorage_Persistent);
68   EXPECT_STREQ("new", result.get());
69 
70   // Removal should work
71   storage->Remove(testKey, DataStorage_Temporary);
72   result = storage->Get(testKey, DataStorage_Temporary);
73   EXPECT_TRUE(result.IsEmpty());
74   // But removing one type shouldn't affect the others
75   result = storage->Get(testKey, DataStorage_Private);
76   EXPECT_STREQ("private", result.get());
77   result = storage->Get(testKey, DataStorage_Persistent);
78   EXPECT_STREQ("new", result.get());
79   // Test removing the other types as well
80   storage->Remove(testKey, DataStorage_Private);
81   result = storage->Get(testKey, DataStorage_Private);
82   EXPECT_TRUE(result.IsEmpty());
83   storage->Remove(testKey, DataStorage_Persistent);
84   result = storage->Get(testKey, DataStorage_Persistent);
85   EXPECT_TRUE(result.IsEmpty());
86 }
87 
TEST_F(psm_DataStorageTest,InputValidation)88 TEST_F(psm_DataStorageTest, InputValidation) {
89   // Keys may not have tabs or newlines
90   EXPECT_EQ(NS_ERROR_INVALID_ARG,
91             storage->Put("key\thas tab"_ns, testValue, DataStorage_Persistent));
92   nsCString result = storage->Get("key\thas tab"_ns, DataStorage_Persistent);
93   EXPECT_TRUE(result.IsEmpty());
94   EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put("key has\nnewline"_ns, testValue,
95                                                DataStorage_Persistent));
96   result = storage->Get("keyhas\nnewline"_ns, DataStorage_Persistent);
97   EXPECT_TRUE(result.IsEmpty());
98   // Values may not have newlines
99   EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put(testKey, "value\nhas newline"_ns,
100                                                DataStorage_Persistent));
101   result = storage->Get(testKey, DataStorage_Persistent);
102   // Values may have tabs
103   EXPECT_TRUE(result.IsEmpty());
104   EXPECT_EQ(NS_OK, storage->Put(testKey, "val\thas tab; this is ok"_ns,
105                                 DataStorage_Persistent));
106   result = storage->Get(testKey, DataStorage_Persistent);
107   EXPECT_STREQ("val\thas tab; this is ok", result.get());
108 
109   nsCString longKey("a");
110   for (int i = 0; i < 8; i++) {
111     longKey.Append(longKey);
112   }
113   // A key of length 256 will work
114   EXPECT_EQ(NS_OK, storage->Put(longKey, testValue, DataStorage_Persistent));
115   result = storage->Get(longKey, DataStorage_Persistent);
116   EXPECT_STREQ("value", result.get());
117   longKey.AppendLiteral("a");
118   // A key longer than that will not work
119   EXPECT_EQ(NS_ERROR_INVALID_ARG,
120             storage->Put(longKey, testValue, DataStorage_Persistent));
121   result = storage->Get(longKey, DataStorage_Persistent);
122   EXPECT_TRUE(result.IsEmpty());
123 
124   nsCString longValue("a");
125   for (int i = 0; i < 10; i++) {
126     longValue.Append(longValue);
127   }
128   // A value of length 1024 will work
129   EXPECT_EQ(NS_OK, storage->Put(testKey, longValue, DataStorage_Persistent));
130   result = storage->Get(testKey, DataStorage_Persistent);
131   EXPECT_STREQ(longValue.get(), result.get());
132   longValue.AppendLiteral("a");
133   // A value longer than that will not work
134   storage->Remove(testKey, DataStorage_Persistent);
135   EXPECT_EQ(NS_ERROR_INVALID_ARG,
136             storage->Put(testKey, longValue, DataStorage_Persistent));
137   result = storage->Get(testKey, DataStorage_Persistent);
138   EXPECT_TRUE(result.IsEmpty());
139 }
140 
TEST_F(psm_DataStorageTest,Eviction)141 TEST_F(psm_DataStorageTest, Eviction) {
142   // Eviction is on a per-table basis. Tables shouldn't affect each other.
143   EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
144   for (int i = 0; i < 1025; i++) {
145     EXPECT_EQ(NS_OK,
146               storage->Put(nsPrintfCString("%d", i), nsPrintfCString("%d", i),
147                            DataStorage_Temporary));
148     nsCString result =
149         storage->Get(nsPrintfCString("%d", i), DataStorage_Temporary);
150     EXPECT_STREQ(nsPrintfCString("%d", i).get(), result.get());
151   }
152   // We don't know which entry got evicted, but we can count them.
153   int entries = 0;
154   for (int i = 0; i < 1025; i++) {
155     nsCString result =
156         storage->Get(nsPrintfCString("%d", i), DataStorage_Temporary);
157     if (!result.IsEmpty()) {
158       entries++;
159     }
160   }
161   EXPECT_EQ(entries, 1024);
162   nsCString result = storage->Get(testKey, DataStorage_Persistent);
163   EXPECT_STREQ("value", result.get());
164 }
165 
TEST_F(psm_DataStorageTest,ClearPrivateData)166 TEST_F(psm_DataStorageTest, ClearPrivateData) {
167   EXPECT_EQ(NS_OK,
168             storage->Put(testKey, privateTestValue, DataStorage_Private));
169   nsCString result = storage->Get(testKey, DataStorage_Private);
170   EXPECT_STREQ("private", result.get());
171   storage->Observe(nullptr, "last-pb-context-exited", nullptr);
172   result = storage->Get(testKey, DataStorage_Private);
173   EXPECT_TRUE(result.IsEmpty());
174 }
175 
TEST_F(psm_DataStorageTest,Shutdown)176 TEST_F(psm_DataStorageTest, Shutdown) {
177   EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
178   nsCString result = storage->Get(testKey, DataStorage_Persistent);
179   EXPECT_STREQ("value", result.get());
180   // Get "now" (in days) close to when the data was last touched, so we won't
181   // get intermittent failures with the day not matching.
182   int64_t microsecondsPerDay = 24 * 60 * 60 * int64_t(PR_USEC_PER_SEC);
183   int32_t nowInDays = int32_t(PR_Now() / microsecondsPerDay);
184   // Simulate shutdown.
185   storage->Observe(nullptr, "profile-before-change", nullptr);
186   nsCOMPtr<nsIFile> backingFile;
187   EXPECT_EQ(NS_OK, NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
188                                           getter_AddRefs(backingFile)));
189   const ::testing::TestInfo* const testInfo =
190       ::testing::UnitTest::GetInstance()->current_test_info();
191   NS_ConvertUTF8toUTF16 testName(testInfo->name());
192   EXPECT_EQ(NS_OK, backingFile->Append(testName));
193   nsCOMPtr<nsIInputStream> fileInputStream;
194   EXPECT_EQ(NS_OK, NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
195                                               backingFile));
196   nsCString data;
197   EXPECT_EQ(NS_OK, NS_ConsumeStream(fileInputStream, UINT32_MAX, data));
198   // The data will be of the form 'test\t0\t<days since the epoch>\tvalue'
199   EXPECT_STREQ(nsPrintfCString("test\t0\t%d\tvalue\n", nowInDays).get(),
200                data.get());
201 }
202