1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <fcntl.h>
6#include <gmock/gmock.h>
7#include <ppapi/c/ppb_file_io.h>
8#include <ppapi/c/pp_errors.h>
9#include <ppapi/c/pp_instance.h>
10#include <sys/stat.h>
11#include <sys/types.h>
12
13#include "fake_ppapi/fake_pepper_interface_url_loader.h"
14
15#include "nacl_io/dir_node.h"
16#include "nacl_io/httpfs/http_fs.h"
17#include "nacl_io/kernel_handle.h"
18#include "nacl_io/kernel_intercept.h"
19#include "nacl_io/osdirent.h"
20#include "nacl_io/osunistd.h"
21
22using namespace nacl_io;
23
24namespace {
25
26class HttpFsForTesting : public HttpFs {
27 public:
28  HttpFsForTesting(StringMap_t map, PepperInterface* ppapi) {
29    FsInitArgs args(1);
30    args.string_map = map;
31    args.ppapi = ppapi;
32    EXPECT_EQ(0, Init(args));
33  }
34
35  using HttpFs::GetNodeCacheForTesting;
36  using HttpFs::ParseManifest;
37  using HttpFs::FindOrCreateDir;
38};
39
40enum {
41  kStringMapParamCacheNone = 0,
42  kStringMapParamCacheContent = 1,
43  kStringMapParamCacheStat = 2,
44  kStringMapParamCacheContentStat =
45      kStringMapParamCacheContent | kStringMapParamCacheStat,
46};
47typedef uint32_t StringMapParam;
48
49StringMap_t MakeStringMap(StringMapParam param) {
50  StringMap_t smap;
51  if (param & kStringMapParamCacheContent)
52    smap["cache_content"] = "true";
53  else
54    smap["cache_content"] = "false";
55
56  if (param & kStringMapParamCacheStat)
57    smap["cache_stat"] = "true";
58  else
59    smap["cache_stat"] = "false";
60  return smap;
61}
62
63class HttpFsTest : public ::testing::TestWithParam<StringMapParam> {
64 public:
65  HttpFsTest();
66
67 protected:
68  FakePepperInterfaceURLLoader ppapi_;
69  HttpFsForTesting fs_;
70};
71
72HttpFsTest::HttpFsTest() : fs_(MakeStringMap(GetParam()), &ppapi_) {}
73
74class HttpFsLargeFileTest : public HttpFsTest {
75 public:
76  HttpFsLargeFileTest() {}
77};
78
79}  // namespace
80
81TEST_P(HttpFsTest, OpenAndCloseServerError) {
82  EXPECT_TRUE(ppapi_.server_template()->AddError("file", 500));
83
84  ScopedNode node;
85  ASSERT_EQ(EIO, fs_.Open(Path("/file"), O_RDONLY, &node));
86}
87
88TEST_P(HttpFsTest, ReadPartial) {
89  const char contents[] = "0123456789abcdefg";
90  ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL));
91  ppapi_.server_template()->set_allow_partial(true);
92
93  int result_bytes = 0;
94
95  char buf[10];
96  memset(&buf[0], 0, sizeof(buf));
97
98  ScopedNode node;
99  ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node));
100  HandleAttr attr;
101  EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes));
102  EXPECT_EQ(sizeof(buf) - 1, result_bytes);
103  EXPECT_STREQ("012345678", &buf[0]);
104
105  // Read is clamped when reading past the end of the file.
106  attr.offs = 10;
107  ASSERT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes));
108  ASSERT_EQ(strlen("abcdefg"), result_bytes);
109  buf[result_bytes] = 0;
110  EXPECT_STREQ("abcdefg", &buf[0]);
111
112  // Read nothing when starting past the end of the file.
113  attr.offs = 100;
114  EXPECT_EQ(0, node->Read(attr, &buf[0], sizeof(buf), &result_bytes));
115  EXPECT_EQ(0, result_bytes);
116}
117
118TEST_P(HttpFsTest, ReadPartialNoServerSupport) {
119  const char contents[] = "0123456789abcdefg";
120  ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL));
121  ppapi_.server_template()->set_allow_partial(false);
122
123  int result_bytes = 0;
124
125  char buf[10];
126  memset(&buf[0], 0, sizeof(buf));
127
128  ScopedNode node;
129  ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node));
130  HandleAttr attr;
131  EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes));
132  EXPECT_EQ(sizeof(buf) - 1, result_bytes);
133  EXPECT_STREQ("012345678", &buf[0]);
134
135  // Read is clamped when reading past the end of the file.
136  attr.offs = 10;
137  ASSERT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes));
138  ASSERT_EQ(strlen("abcdefg"), result_bytes);
139  buf[result_bytes] = 0;
140  EXPECT_STREQ("abcdefg", &buf[0]);
141
142  // Read nothing when starting past the end of the file.
143  attr.offs = 100;
144  EXPECT_EQ(0, node->Read(attr, &buf[0], sizeof(buf), &result_bytes));
145  EXPECT_EQ(0, result_bytes);
146}
147
148TEST_P(HttpFsTest, Write) {
149  const char contents[] = "contents";
150  ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL));
151
152  ScopedNode node;
153  ASSERT_EQ(0, fs_.Open(Path("/file"), O_WRONLY, &node));
154
155  // Writing always fails.
156  HandleAttr attr;
157  attr.offs = 3;
158  int bytes_written = 1;  // Set to a non-zero value.
159  EXPECT_EQ(EACCES, node->Write(attr, "struct", 6, &bytes_written));
160  EXPECT_EQ(0, bytes_written);
161}
162
163TEST_P(HttpFsTest, GetStat) {
164  const char contents[] = "contents";
165  ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL));
166
167  ScopedNode node;
168  ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node));
169
170  struct stat statbuf;
171  EXPECT_EQ(0, node->GetStat(&statbuf));
172  EXPECT_EQ(S_IFREG | S_IRUSR | S_IRGRP | S_IROTH, statbuf.st_mode);
173  EXPECT_EQ(strlen(contents), statbuf.st_size);
174  // These are not currently set.
175  EXPECT_EQ(0, statbuf.st_atime);
176  EXPECT_EQ(0, statbuf.st_ctime);
177  EXPECT_EQ(0, statbuf.st_mtime);
178}
179
180TEST_P(HttpFsTest, FTruncate) {
181  const char contents[] = "contents";
182  ASSERT_TRUE(ppapi_.server_template()->AddEntity("file", contents, NULL));
183
184  ScopedNode node;
185  ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDWR, &node));
186  EXPECT_EQ(EACCES, node->FTruncate(4));
187}
188
189// Instantiate the above tests for all caching types.
190INSTANTIATE_TEST_CASE_P(
191    Default,
192    HttpFsTest,
193    ::testing::Values((uint32_t)kStringMapParamCacheNone,
194                      (uint32_t)kStringMapParamCacheContent,
195                      (uint32_t)kStringMapParamCacheStat,
196                      (uint32_t)kStringMapParamCacheContentStat));
197
198TEST_P(HttpFsLargeFileTest, ReadPartial) {
199  const char contents[] = "0123456789abcdefg";
200  off_t size = 0x110000000ll;
201  ASSERT_TRUE(
202      ppapi_.server_template()->AddEntity("file", contents, size, NULL));
203  ppapi_.server_template()->set_send_content_length(true);
204  ppapi_.server_template()->set_allow_partial(true);
205
206  int result_bytes = 0;
207
208  char buf[10];
209  memset(&buf[0], 0, sizeof(buf));
210
211  ScopedNode node;
212  ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node));
213  HandleAttr attr;
214  EXPECT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes));
215  EXPECT_EQ(sizeof(buf) - 1, result_bytes);
216  EXPECT_STREQ("012345678", &buf[0]);
217
218  // Read is clamped when reading past the end of the file.
219  attr.offs = size - 7;
220  ASSERT_EQ(0, node->Read(attr, buf, sizeof(buf) - 1, &result_bytes));
221  ASSERT_EQ(strlen("abcdefg"), result_bytes);
222  buf[result_bytes] = 0;
223  EXPECT_STREQ("abcdefg", &buf[0]);
224
225  // Read nothing when starting past the end of the file.
226  attr.offs = size + 100;
227  EXPECT_EQ(0, node->Read(attr, &buf[0], sizeof(buf), &result_bytes));
228  EXPECT_EQ(0, result_bytes);
229}
230
231TEST_P(HttpFsLargeFileTest, GetStat) {
232  const char contents[] = "contents";
233  off_t size = 0x110000000ll;
234  ASSERT_TRUE(
235      ppapi_.server_template()->AddEntity("file", contents, size, NULL));
236  // TODO(binji): If the server doesn't send the content length, this operation
237  // will be incredibly slow; it will attempt to read all of the data from the
238  // server to find the file length. Can we do anything smarter?
239  ppapi_.server_template()->set_send_content_length(true);
240
241  ScopedNode node;
242  ASSERT_EQ(0, fs_.Open(Path("/file"), O_RDONLY, &node));
243
244  struct stat statbuf;
245  EXPECT_EQ(0, node->GetStat(&statbuf));
246  EXPECT_EQ(S_IFREG | S_IRUSR | S_IRGRP | S_IROTH, statbuf.st_mode);
247  EXPECT_EQ(size, statbuf.st_size);
248  // These are not currently set.
249  EXPECT_EQ(0, statbuf.st_atime);
250  EXPECT_EQ(0, statbuf.st_ctime);
251  EXPECT_EQ(0, statbuf.st_mtime);
252}
253
254// Instantiate the large file tests, only when cache content is off.
255// TODO(binji): make cache content smarter, so it doesn't try to cache enormous
256// files. See http://crbug.com/369279.
257INSTANTIATE_TEST_CASE_P(Default,
258                        HttpFsLargeFileTest,
259                        ::testing::Values((uint32_t)kStringMapParamCacheNone,
260                                          (uint32_t)kStringMapParamCacheStat));
261
262TEST(HttpFsDirTest, Root) {
263  StringMap_t args;
264  HttpFsForTesting fs(args, NULL);
265
266  // Check root node is directory
267  ScopedNode node;
268  ASSERT_EQ(0, fs.Open(Path("/"), O_RDONLY, &node));
269  ASSERT_TRUE(node->IsaDir());
270
271  // We have to r+w access to the root node
272  struct stat buf;
273  ASSERT_EQ(0, node->GetStat(&buf));
274  ASSERT_EQ(S_IXUSR | S_IRUSR, buf.st_mode & S_IRWXU);
275}
276
277TEST(HttpFsDirTest, Mkdir) {
278  StringMap_t args;
279  HttpFsForTesting fs(args, NULL);
280  char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n";
281  ASSERT_EQ(0, fs.ParseManifest(manifest));
282  // mkdir of existing directories should give "File exists".
283  EXPECT_EQ(EEXIST, fs.Mkdir(Path("/"), 0));
284  EXPECT_EQ(EEXIST, fs.Mkdir(Path("/mydir"), 0));
285  // mkdir of non-existent directories should give "Permission denied".
286  EXPECT_EQ(EACCES, fs.Mkdir(Path("/non_existent"), 0));
287}
288
289TEST(HttpFsDirTest, Rmdir) {
290  StringMap_t args;
291  HttpFsForTesting fs(args, NULL);
292  char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n";
293  ASSERT_EQ(0, fs.ParseManifest(manifest));
294  // Rmdir on existing dirs should give "Permission Denied"
295  EXPECT_EQ(EACCES, fs.Rmdir(Path("/")));
296  EXPECT_EQ(EACCES, fs.Rmdir(Path("/mydir")));
297  // Rmdir on existing files should give "Not a direcotory"
298  EXPECT_EQ(ENOTDIR, fs.Rmdir(Path("/mydir/foo")));
299  // Rmdir on non-existent files should give "No such file or directory"
300  EXPECT_EQ(ENOENT, fs.Rmdir(Path("/non_existent")));
301}
302
303TEST(HttpFsDirTest, Unlink) {
304  StringMap_t args;
305  HttpFsForTesting fs(args, NULL);
306  char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n";
307  ASSERT_EQ(0, fs.ParseManifest(manifest));
308  // Unlink of existing files should give "Permission Denied"
309  EXPECT_EQ(EACCES, fs.Unlink(Path("/mydir/foo")));
310  // Unlink of existing directory should give "Is a directory"
311  EXPECT_EQ(EISDIR, fs.Unlink(Path("/mydir")));
312  // Unlink of non-existent files should give "No such file or directory"
313  EXPECT_EQ(ENOENT, fs.Unlink(Path("/non_existent")));
314}
315
316TEST(HttpFsDirTest, Remove) {
317  StringMap_t args;
318  HttpFsForTesting fs(args, NULL);
319  char manifest[] = "-r-- 123 /mydir/foo\n-rw- 234 /thatdir/bar\n";
320  ASSERT_EQ(0, fs.ParseManifest(manifest));
321  // Remove of existing files should give "Permission Denied"
322  EXPECT_EQ(EACCES, fs.Remove(Path("/mydir/foo")));
323  // Remove of existing directory should give "Permission Denied"
324  EXPECT_EQ(EACCES, fs.Remove(Path("/mydir")));
325  // Unlink of non-existent files should give "No such file or directory"
326  EXPECT_EQ(ENOENT, fs.Remove(Path("/non_existent")));
327}
328
329TEST(HttpFsDirTest, ParseManifest) {
330  StringMap_t args;
331  off_t result_size = 0;
332
333  HttpFsForTesting fs(args, NULL);
334
335  // Multiple consecutive newlines or spaces should be ignored.
336  char manifest[] = "-r-- 123 /mydir/foo\n\n-rw-   234  /thatdir/bar\n";
337  ASSERT_EQ(0, fs.ParseManifest(manifest));
338
339  ScopedNode root;
340  EXPECT_EQ(0, fs.FindOrCreateDir(Path("/"), &root));
341  ASSERT_NE((Node*)NULL, root.get());
342  EXPECT_EQ(2, root->ChildCount());
343
344  ScopedNode dir;
345  EXPECT_EQ(0, fs.FindOrCreateDir(Path("/mydir"), &dir));
346  ASSERT_NE((Node*)NULL, dir.get());
347  EXPECT_EQ(1, dir->ChildCount());
348
349  Node* node = (*fs.GetNodeCacheForTesting())["/mydir/foo"].get();
350  EXPECT_NE((Node*)NULL, node);
351  EXPECT_EQ(0, node->GetSize(&result_size));
352  EXPECT_EQ(123, result_size);
353
354  // Since these files are cached thanks to the manifest, we can open them
355  // without accessing the PPAPI URL API.
356  ScopedNode foo;
357  ASSERT_EQ(0, fs.Open(Path("/mydir/foo"), O_RDONLY, &foo));
358
359  ScopedNode bar;
360  ASSERT_EQ(0, fs.Open(Path("/thatdir/bar"), O_RDWR, &bar));
361
362  struct stat sfoo;
363  struct stat sbar;
364
365  EXPECT_FALSE(foo->GetStat(&sfoo));
366  EXPECT_FALSE(bar->GetStat(&sbar));
367
368  EXPECT_EQ(123, sfoo.st_size);
369  EXPECT_EQ(S_IFREG | S_IRALL, sfoo.st_mode);
370
371  EXPECT_EQ(234, sbar.st_size);
372  EXPECT_EQ(S_IFREG | S_IRALL | S_IWALL, sbar.st_mode);
373}
374
375TEST(HttpFsBlobUrlTest, Basic) {
376  const char* kUrl = "blob:http%3A//example.com/6b87a5a6-713e";
377  const char* kContent = "hello";
378  FakePepperInterfaceURLLoader ppapi;
379  ASSERT_TRUE(ppapi.server_template()->SetBlobEntity(kUrl, kContent, NULL));
380
381  StringMap_t args;
382  args["SOURCE"] = kUrl;
383
384  HttpFsForTesting fs(args, &ppapi);
385
386  // Any other path than / should fail.
387  ScopedNode node;
388  ASSERT_EQ(ENOENT, fs.Open(Path("/blah"), R_OK, &node));
389
390  // Check access to blob file
391  ASSERT_EQ(0, fs.Open(Path("/"), O_RDONLY, &node));
392  ASSERT_EQ(true, node->IsaFile());
393
394  // Verify file size and permissions
395  struct stat buf;
396  ASSERT_EQ(0, node->GetStat(&buf));
397  ASSERT_EQ(S_IRUSR, buf.st_mode & S_IRWXU);
398  ASSERT_EQ(strlen(kContent), buf.st_size);
399}
400