summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/tests/gtest/DataStorageTest.cpp
blob: c288aeb3dba8031a810e3ab16bc39a2440ec3b85 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "gtest/gtest.h"

#include "mozilla/DataStorage.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsStreamUtils.h"
#include "prtime.h"

using namespace mozilla;

class psm_DataStorageTest : public ::testing::Test {
 protected:
  void SetUp() override {
    const ::testing::TestInfo* const testInfo =
        ::testing::UnitTest::GetInstance()->current_test_info();
    NS_ConvertUTF8toUTF16 testName(testInfo->name());
    storage = DataStorage::GetFromRawFileName(testName);
    storage->Init();
  }

  RefPtr<DataStorage> storage;
};

constexpr auto testKey = "test"_ns;
constexpr auto testValue = "value"_ns;
constexpr auto privateTestValue = "private"_ns;

TEST_F(psm_DataStorageTest, GetPutRemove) {
  // Test Put/Get on Persistent data
  EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
  // Don't re-use testKey / testValue here, to make sure that this works as
  // expected with objects that have the same semantic value but are not
  // literally the same object.
  nsCString result = storage->Get("test"_ns, DataStorage_Persistent);
  EXPECT_STREQ("value", result.get());

  // Get on Temporary/Private data with the same key should give nothing
  result = storage->Get(testKey, DataStorage_Temporary);
  EXPECT_TRUE(result.IsEmpty());
  result = storage->Get(testKey, DataStorage_Private);
  EXPECT_TRUE(result.IsEmpty());

  // Put with Temporary/Private data shouldn't affect Persistent data
  constexpr auto temporaryTestValue = "temporary"_ns;
  EXPECT_EQ(NS_OK,
            storage->Put(testKey, temporaryTestValue, DataStorage_Temporary));
  EXPECT_EQ(NS_OK,
            storage->Put(testKey, privateTestValue, DataStorage_Private));
  result = storage->Get(testKey, DataStorage_Temporary);
  EXPECT_STREQ("temporary", result.get());
  result = storage->Get(testKey, DataStorage_Private);
  EXPECT_STREQ("private", result.get());
  result = storage->Get(testKey, DataStorage_Persistent);
  EXPECT_STREQ("value", result.get());

  // Put of a previously-present key overwrites it (if of the same type)
  constexpr auto newValue = "new"_ns;
  EXPECT_EQ(NS_OK, storage->Put(testKey, newValue, DataStorage_Persistent));
  result = storage->Get(testKey, DataStorage_Persistent);
  EXPECT_STREQ("new", result.get());

  // Removal should work
  storage->Remove(testKey, DataStorage_Temporary);
  result = storage->Get(testKey, DataStorage_Temporary);
  EXPECT_TRUE(result.IsEmpty());
  // But removing one type shouldn't affect the others
  result = storage->Get(testKey, DataStorage_Private);
  EXPECT_STREQ("private", result.get());
  result = storage->Get(testKey, DataStorage_Persistent);
  EXPECT_STREQ("new", result.get());
  // Test removing the other types as well
  storage->Remove(testKey, DataStorage_Private);
  result = storage->Get(testKey, DataStorage_Private);
  EXPECT_TRUE(result.IsEmpty());
  storage->Remove(testKey, DataStorage_Persistent);
  result = storage->Get(testKey, DataStorage_Persistent);
  EXPECT_TRUE(result.IsEmpty());
}

TEST_F(psm_DataStorageTest, InputValidation) {
  // Keys may not have tabs or newlines
  EXPECT_EQ(NS_ERROR_INVALID_ARG,
            storage->Put("key\thas tab"_ns, testValue, DataStorage_Persistent));
  nsCString result = storage->Get("key\thas tab"_ns, DataStorage_Persistent);
  EXPECT_TRUE(result.IsEmpty());
  EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put("key has\nnewline"_ns, testValue,
                                               DataStorage_Persistent));
  result = storage->Get("keyhas\nnewline"_ns, DataStorage_Persistent);
  EXPECT_TRUE(result.IsEmpty());
  // Values may not have newlines
  EXPECT_EQ(NS_ERROR_INVALID_ARG, storage->Put(testKey, "value\nhas newline"_ns,
                                               DataStorage_Persistent));
  result = storage->Get(testKey, DataStorage_Persistent);
  // Values may have tabs
  EXPECT_TRUE(result.IsEmpty());
  EXPECT_EQ(NS_OK, storage->Put(testKey, "val\thas tab; this is ok"_ns,
                                DataStorage_Persistent));
  result = storage->Get(testKey, DataStorage_Persistent);
  EXPECT_STREQ("val\thas tab; this is ok", result.get());

  nsCString longKey("a");
  for (int i = 0; i < 8; i++) {
    longKey.Append(longKey);
  }
  // A key of length 256 will work
  EXPECT_EQ(NS_OK, storage->Put(longKey, testValue, DataStorage_Persistent));
  result = storage->Get(longKey, DataStorage_Persistent);
  EXPECT_STREQ("value", result.get());
  longKey.AppendLiteral("a");
  // A key longer than that will not work
  EXPECT_EQ(NS_ERROR_INVALID_ARG,
            storage->Put(longKey, testValue, DataStorage_Persistent));
  result = storage->Get(longKey, DataStorage_Persistent);
  EXPECT_TRUE(result.IsEmpty());

  nsCString longValue("a");
  for (int i = 0; i < 10; i++) {
    longValue.Append(longValue);
  }
  // A value of length 1024 will work
  EXPECT_EQ(NS_OK, storage->Put(testKey, longValue, DataStorage_Persistent));
  result = storage->Get(testKey, DataStorage_Persistent);
  EXPECT_STREQ(longValue.get(), result.get());
  longValue.AppendLiteral("a");
  // A value longer than that will not work
  storage->Remove(testKey, DataStorage_Persistent);
  EXPECT_EQ(NS_ERROR_INVALID_ARG,
            storage->Put(testKey, longValue, DataStorage_Persistent));
  result = storage->Get(testKey, DataStorage_Persistent);
  EXPECT_TRUE(result.IsEmpty());
}

TEST_F(psm_DataStorageTest, Eviction) {
  // Eviction is on a per-table basis. Tables shouldn't affect each other.
  EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
  for (int i = 0; i < 1025; i++) {
    EXPECT_EQ(NS_OK,
              storage->Put(nsPrintfCString("%d", i), nsPrintfCString("%d", i),
                           DataStorage_Temporary));
    nsCString result =
        storage->Get(nsPrintfCString("%d", i), DataStorage_Temporary);
    EXPECT_STREQ(nsPrintfCString("%d", i).get(), result.get());
  }
  // We don't know which entry got evicted, but we can count them.
  int entries = 0;
  for (int i = 0; i < 1025; i++) {
    nsCString result =
        storage->Get(nsPrintfCString("%d", i), DataStorage_Temporary);
    if (!result.IsEmpty()) {
      entries++;
    }
  }
  EXPECT_EQ(entries, 1024);
  nsCString result = storage->Get(testKey, DataStorage_Persistent);
  EXPECT_STREQ("value", result.get());
}

TEST_F(psm_DataStorageTest, ClearPrivateData) {
  EXPECT_EQ(NS_OK,
            storage->Put(testKey, privateTestValue, DataStorage_Private));
  nsCString result = storage->Get(testKey, DataStorage_Private);
  EXPECT_STREQ("private", result.get());
  storage->Observe(nullptr, "last-pb-context-exited", nullptr);
  result = storage->Get(testKey, DataStorage_Private);
  EXPECT_TRUE(result.IsEmpty());
}

TEST_F(psm_DataStorageTest, Shutdown) {
  EXPECT_EQ(NS_OK, storage->Put(testKey, testValue, DataStorage_Persistent));
  nsCString result = storage->Get(testKey, DataStorage_Persistent);
  EXPECT_STREQ("value", result.get());
  // Get "now" (in days) close to when the data was last touched, so we won't
  // get intermittent failures with the day not matching.
  int64_t microsecondsPerDay = 24 * 60 * 60 * int64_t(PR_USEC_PER_SEC);
  int32_t nowInDays = int32_t(PR_Now() / microsecondsPerDay);
  // Simulate shutdown.
  storage->Observe(nullptr, "profile-before-change", nullptr);
  nsCOMPtr<nsIFile> backingFile;
  EXPECT_EQ(NS_OK, NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                          getter_AddRefs(backingFile)));
  const ::testing::TestInfo* const testInfo =
      ::testing::UnitTest::GetInstance()->current_test_info();
  NS_ConvertUTF8toUTF16 testName(testInfo->name());
  EXPECT_EQ(NS_OK, backingFile->Append(testName));
  nsCOMPtr<nsIInputStream> fileInputStream;
  EXPECT_EQ(NS_OK, NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
                                              backingFile));
  nsCString data;
  EXPECT_EQ(NS_OK, NS_ConsumeStream(fileInputStream, UINT32_MAX, data));
  // The data will be of the form 'test\t0\t<days since the epoch>\tvalue'
  EXPECT_STREQ(nsPrintfCString("test\t0\t%d\tvalue\n", nowInDays).get(),
               data.get());
}