/* -*- 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 "util/CompleteFile.h" #include // std::strcmp #include // FILE, fileno, fopen, getc, getc_unlocked, _getc_nolock #include // stat, fstat #ifdef __wasi__ # include "js/Vector.h" #endif // __wasi__ #include "js/CharacterEncoding.h" // EncodeUtf8ToWide, EncodeUtf8ToNarrow #include "js/ErrorReport.h" // JS_ReportErrorNumberUTF8 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_CANT_OPEN bool js::ReadCompleteFile(JSContext* cx, FILE* fp, FileContents& buffer) { /* Get the complete length of the file, if possible. */ struct stat st; int ok = fstat(fileno(fp), &st); if (ok != 0) { // Use the Latin1 variant here (and below), because the encoding of // strerror() is platform-dependent. JS_ReportErrorLatin1(cx, "error reading file: %s", strerror(errno)); errno = 0; return false; } if ((st.st_mode & S_IFDIR) != 0) { JS_ReportErrorLatin1(cx, "error reading file: %s", strerror(EISDIR)); return false; } if (st.st_size > 0) { if (!buffer.reserve(st.st_size)) { return false; } } /* Use the fastest available getc. */ auto fast_getc = #if defined(HAVE_GETC_UNLOCKED) getc_unlocked #elif defined(HAVE__GETC_NOLOCK) _getc_nolock #else getc #endif ; // Read in the whole file. Note that we can't assume the data's length // is actually st.st_size, because 1) some files lie about their size // (/dev/zero and /dev/random), and 2) reading files in text mode on // Windows collapses "\r\n" pairs to single \n characters. for (;;) { int c = fast_getc(fp); if (c == EOF) { break; } if (!buffer.append(c)) { return false; } } if (ferror(fp)) { // getc failed JS_ReportErrorLatin1(cx, "error reading file: %s", strerror(errno)); errno = 0; return false; } return true; } #ifdef __wasi__ static bool NormalizeWASIPath(const char* filename, js::Vector* normalized, JSContext* cx) { // On WASI, we need to collapse ".." path components for the capabilities // that we pass to our unit tests to be reasonable; otherwise we need to // grant "tests/script1.js/../lib.js" and "tests/script2.js/../lib.js" // separately (because the check appears to be a prefix only). for (const char* cur = filename; *cur; ++cur) { if (std::strncmp(cur, "/../", 4) == 0) { do { if (normalized->empty()) { JS_ReportErrorASCII(cx, "Path processing error"); return false; } } while (normalized->popCopy() != '/'); cur += 2; continue; } if (!normalized->append(*cur)) { return false; } } if (!normalized->append('\0')) { return false; } return true; } #endif static FILE* OpenFile(JSContext* cx, const char* filename) { #ifdef XP_WIN JS::UniqueWideChars wideFilename = JS::EncodeUtf8ToWide(cx, filename); if (!wideFilename) { return nullptr; } return _wfopen(wideFilename.get(), L"r"); #else JS::UniqueChars narrowFilename = JS::EncodeUtf8ToNarrow(cx, filename); if (!narrowFilename) { return nullptr; } return fopen(narrowFilename.get(), "r"); #endif } /* * Open a source file for reading. Supports "-" and nullptr to mean stdin. The * return value must be fclosed unless it is stdin. */ bool js::AutoFile::open(JSContext* cx, const char* filename) { if (!filename || std::strcmp(filename, "-") == 0) { fp_ = stdin; } else { #ifdef __wasi__ js::Vector normalized(cx); if (!NormalizeWASIPath(filename, &normalized, cx)) { return false; } fp_ = OpenFile(cx, normalized.begin()); #else fp_ = OpenFile(cx, filename); #endif if (!fp_) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_CANT_OPEN, filename, "No such file or directory"); return false; } } return true; }