summaryrefslogtreecommitdiffstats
path: root/js/src/util/CompleteFile.cpp
blob: dae397f52b8f487ccb70f8c326cfc4d21761885b (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
/* -*- 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 <cstring>     // std::strcmp
#include <stdio.h>     // FILE, fileno, fopen, getc, getc_unlocked, _getc_nolock
#include <sys/stat.h>  // 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<char>* 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<char> 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;
}