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 "util/CompleteFile.h"
8 
9 #include <cstring>     // std::strcmp
10 #include <stdio.h>     // FILE, fileno, fopen, getc, getc_unlocked, _getc_nolock
11 #include <sys/stat.h>  // stat, fstat
12 
13 #ifdef __wasi__
14 #  include "js/Vector.h"
15 #endif  // __wasi__
16 
17 #include "jsapi.h"  // JS_ReportErrorNumberLatin1
18 
19 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_CANT_OPEN
20 
ReadCompleteFile(JSContext * cx,FILE * fp,FileContents & buffer)21 bool js::ReadCompleteFile(JSContext* cx, FILE* fp, FileContents& buffer) {
22   /* Get the complete length of the file, if possible. */
23   struct stat st;
24   int ok = fstat(fileno(fp), &st);
25   if (ok != 0) {
26     // Use the Latin1 variant here (and below), because the encoding of
27     // strerror() is platform-dependent.
28     JS_ReportErrorLatin1(cx, "error reading file: %s", strerror(errno));
29     errno = 0;
30     return false;
31   }
32   if ((st.st_mode & S_IFDIR) != 0) {
33     JS_ReportErrorLatin1(cx, "error reading file: %s", strerror(EISDIR));
34     return false;
35   }
36 
37   if (st.st_size > 0) {
38     if (!buffer.reserve(st.st_size)) {
39       return false;
40     }
41   }
42 
43   /* Use the fastest available getc. */
44   auto fast_getc =
45 #if defined(HAVE_GETC_UNLOCKED)
46       getc_unlocked
47 #elif defined(HAVE__GETC_NOLOCK)
48       _getc_nolock
49 #else
50       getc
51 #endif
52       ;
53 
54   // Read in the whole file. Note that we can't assume the data's length
55   // is actually st.st_size, because 1) some files lie about their size
56   // (/dev/zero and /dev/random), and 2) reading files in text mode on
57   // Windows collapses "\r\n" pairs to single \n characters.
58   for (;;) {
59     int c = fast_getc(fp);
60     if (c == EOF) {
61       break;
62     }
63     if (!buffer.append(c)) {
64       return false;
65     }
66   }
67 
68   if (ferror(fp)) {
69     // getc failed
70     JS_ReportErrorLatin1(cx, "error reading file: %s", strerror(errno));
71     errno = 0;
72     return false;
73   }
74 
75   return true;
76 }
77 
78 #ifdef __wasi__
NormalizeWASIPath(const char * filename,js::Vector<char> * normalized,JSContext * cx)79 static bool NormalizeWASIPath(const char* filename,
80                               js::Vector<char>* normalized, JSContext* cx) {
81   // On WASI, we need to collapse ".." path components for the capabilities
82   // that we pass to our unit tests to be reasonable; otherwise we need to
83   // grant "tests/script1.js/../lib.js" and "tests/script2.js/../lib.js"
84   // separately (because the check appears to be a prefix only).
85   for (const char* cur = filename; *cur; ++cur) {
86     if (std::strncmp(cur, "/../", 4) == 0) {
87       do {
88         if (normalized->empty()) {
89           JS_ReportErrorASCII(cx, "Path processing error");
90           return false;
91         }
92       } while (normalized->popCopy() != '/');
93       cur += 2;
94       continue;
95     }
96     if (!normalized->append(*cur)) {
97       return false;
98     }
99   }
100   if (!normalized->append('\0')) {
101     return false;
102   }
103   return true;
104 }
105 #endif
106 
107 /*
108  * Open a source file for reading. Supports "-" and nullptr to mean stdin. The
109  * return value must be fclosed unless it is stdin.
110  */
open(JSContext * cx,const char * filename)111 bool js::AutoFile::open(JSContext* cx, const char* filename) {
112   if (!filename || std::strcmp(filename, "-") == 0) {
113     fp_ = stdin;
114   } else {
115 #ifdef __wasi__
116     js::Vector<char> normalized(cx);
117     if (!NormalizeWASIPath(filename, &normalized, cx)) {
118       return false;
119     }
120     fp_ = fopen(normalized.begin(), "r");
121 #else
122     fp_ = fopen(filename, "r");
123 #endif
124     if (!fp_) {
125       /*
126        * Use Latin1 variant here because the encoding of filename is
127        * platform dependent.
128        */
129       JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_CANT_OPEN,
130                                  filename, "No such file or directory");
131       return false;
132     }
133   }
134   return true;
135 }
136