summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/fs.markdown189
-rw-r--r--lib/fs.js106
-rw-r--r--src/fs_event_wrap.cc27
-rw-r--r--src/node_file.cc295
-rw-r--r--src/string_bytes.cc31
-rw-r--r--src/string_bytes.h4
-rw-r--r--src/util.cc69
-rw-r--r--src/util.h19
-rw-r--r--test/common.js10
-rw-r--r--test/parallel/test-fs-access.js2
-rw-r--r--test/parallel/test-fs-buffer.js41
-rw-r--r--test/parallel/test-fs-link.js4
-rw-r--r--test/parallel/test-fs-readdir-ucs2.js31
-rw-r--r--test/parallel/test-fs-watch-encoding.js52
-rw-r--r--test/sequential/test-module-loading.js2
15 files changed, 654 insertions, 228 deletions
diff --git a/doc/api/fs.markdown b/doc/api/fs.markdown
index 11aeb9d5b9..e44db918aa 100644
--- a/doc/api/fs.markdown
+++ b/doc/api/fs.markdown
@@ -101,11 +101,24 @@ Objects returned from `fs.watch()` are of this type.
### Event: 'change'
* `event` {String} The type of fs change
-* `filename` {String} The filename that changed (if relevant/available)
+* `filename` {String | Buffer} The filename that changed (if relevant/available)
Emitted when something changes in a watched directory or file.
See more details in [`fs.watch()`][].
+The `filename` argument may not be provided depending on operating system
+support. If `filename` is provided, it will be provided as a `Buffer` if
+`fs.watch()` is called with it's `encoding` option set to `'buffer'`, otherwise
+`filename` will be a string.
+
+```js
+fs.watch('./tmp', {encoding: 'buffer'}, (event, filename) => {
+ if (filename)
+ console.log(filename);
+ // Prints: <Buffer ...>
+});
+```
+
### Event: 'error'
* `error` {Error}
@@ -128,7 +141,10 @@ Emitted when the ReadStream's file is opened.
### readStream.path
-The path to the file the stream is reading from.
+The path to the file the stream is reading from as specified in the first
+argument to `fs.createReadStream()`. If `path` is passed as a string, then
+`readStream.path` will be a string. If `path` is passed as a `Buffer`, then
+`readStream.path` will be a `Buffer`.
## Class: fs.Stats
@@ -217,11 +233,14 @@ for writing.
### writeStream.path
-The path to the file the stream is writing to.
+The path to the file the stream is writing to as specified in the first
+argument to `fs.createWriteStream()`. If `path` is passed as a string, then
+`writeStream.path` will be a string. If `path` is passed as a `Buffer`, then
+`writeStream.path` will be a `Buffer`.
## fs.access(path[, mode], callback)
-* `path` {String}
+* `path` {String | Buffer}
* `mode` {Integer}
* `callback` {Function}
@@ -251,7 +270,7 @@ fs.access('/etc/passwd', fs.R_OK | fs.W_OK, (err) => {
## fs.accessSync(path[, mode])
-* `path` {String}
+* `path` {String | Buffer}
* `mode` {Integer}
Synchronous version of [`fs.access()`][]. This throws if any accessibility checks
@@ -259,7 +278,7 @@ fail, and does nothing otherwise.
## fs.appendFile(file, data[, options], callback)
-* `file` {String | Number} filename or file descriptor
+* `file` {String | Buffer | Number} filename or file descriptor
* `data` {String | Buffer}
* `options` {Object | String}
* `encoding` {String | Null} default = `'utf8'`
@@ -291,11 +310,18 @@ _Note: Specified file descriptors will not be closed automatically._
## fs.appendFileSync(file, data[, options])
+* `file` {String | Buffer}
+* `data` {String | Buffer}
+* `options` {Object | String}
+ * `encoding` {String | Null} default = `'utf8'`
+ * `mode` {Integer} default = `0o666`
+ * `flag` {String} default = `'a'`
+
The synchronous version of [`fs.appendFile()`][]. Returns `undefined`.
## fs.chmod(path, mode, callback)
-* `path` {String}
+* `path` {String | Buffer}
* `mode` {Integer}
* `callback` {Function}
@@ -304,14 +330,14 @@ to the completion callback.
## fs.chmodSync(path, mode)
-* `path` {String}
+* `path` {String | Buffer}
* `mode` {Integer}
Synchronous chmod(2). Returns `undefined`.
## fs.chown(path, uid, gid, callback)
-* `path` {String}
+* `path` {String | Buffer}
* `uid` {Integer}
* `gid` {Integer}
* `callback` {Function}
@@ -321,7 +347,7 @@ to the completion callback.
## fs.chownSync(path, uid, gid)
-* `path` {String}
+* `path` {String | Buffer}
* `uid` {Integer}
* `gid` {Integer}
@@ -343,7 +369,7 @@ Synchronous close(2). Returns `undefined`.
## fs.createReadStream(path[, options])
-* `path` {String}
+* `path` {String | Buffer}
* `options` {String | Object}
* `flags` {String}
* `encoding` {String}
@@ -399,7 +425,7 @@ If `options` is a string, then it specifies the encoding.
## fs.createWriteStream(path[, options])
-* `path` {String}
+* `path` {String | Buffer}
* `options` {String | Object}
* `flags` {String}
* `defaultEncoding` {String}
@@ -444,7 +470,7 @@ If `options` is a string, then it specifies the encoding.
Stability: 0 - Deprecated: Use [`fs.stat()`][] or [`fs.access()`][] instead.
-* `path` {String}
+* `path` {String | Buffer}
* `callback` {Function}
Test whether or not the given path exists by checking with the file system.
@@ -466,7 +492,7 @@ non-existent.
Stability: 0 - Deprecated: Use [`fs.statSync()`][] or [`fs.accessSync()`][] instead.
-* `path` {String}
+* `path` {String | Buffer}
Synchronous version of [`fs.exists()`][].
Returns `true` if the file exists, `false` otherwise.
@@ -584,7 +610,7 @@ Synchronous version of [`fs.futimes()`][]. Returns `undefined`.
## fs.lchmod(path, mode, callback)
-* `path` {String}
+* `path` {String | Buffer}
* `mode` {Integer}
* `callback` {Function}
@@ -595,14 +621,14 @@ Only available on Mac OS X.
## fs.lchmodSync(path, mode)
-* `path` {String}
+* `path` {String | Buffer}
* `mode` {Integer}
Synchronous lchmod(2). Returns `undefined`.
## fs.lchown(path, uid, gid, callback)
-* `path` {String}
+* `path` {String | Buffer}
* `uid` {Integer}
* `gid` {Integer}
* `callback` {Function}
@@ -612,7 +638,7 @@ to the completion callback.
## fs.lchownSync(path, uid, gid)
-* `path` {String}
+* `path` {String | Buffer}
* `uid` {Integer}
* `gid` {Integer}
@@ -620,8 +646,8 @@ Synchronous lchown(2). Returns `undefined`.
## fs.link(srcpath, dstpath, callback)
-* `srcpath` {String}
-* `dstpath` {String}
+* `srcpath` {String | Buffer}
+* `dstpath` {String | Buffer}
* `callback` {Function}
Asynchronous link(2). No arguments other than a possible exception are given to
@@ -629,14 +655,14 @@ the completion callback.
## fs.linkSync(srcpath, dstpath)
-* `srcpath` {String}
-* `dstpath` {String}
+* `srcpath` {String | Buffer}
+* `dstpath` {String | Buffer}
Synchronous link(2). Returns `undefined`.
## fs.lstat(path, callback)
-* `path` {String}
+* `path` {String | Buffer}
* `callback` {Function}
Asynchronous lstat(2). The callback gets two arguments `(err, stats)` where
@@ -646,13 +672,13 @@ refers to.
## fs.lstatSync(path)
-* `path` {String}
+* `path` {String | Buffer}
Synchronous lstat(2). Returns an instance of `fs.Stats`.
## fs.mkdir(path[, mode], callback)
-* `path` {String}
+* `path` {String | Buffer}
* `mode` {Integer}
* `callback` {Function}
@@ -661,7 +687,7 @@ to the completion callback. `mode` defaults to `0o777`.
## fs.mkdirSync(path[, mode])
-* `path` {String}
+* `path` {String | Buffer}
* `mode` {Integer}
Synchronous mkdir(2). Returns `undefined`.
@@ -692,7 +718,7 @@ folder path.
## fs.open(path, flags[, mode], callback)
-* `path` {String}
+* `path` {String | Buffer}
* `flags` {String | Number}
* `mode` {Integer}
* `callback` {Function}
@@ -759,7 +785,7 @@ the end of the file.
## fs.openSync(path, flags[, mode])
-* `path` {String}
+* `path` {String | Buffer}
* `flags` {String | Number}
* `mode` {Integer}
@@ -788,7 +814,12 @@ If `position` is `null`, data will be read from the current file position.
The callback is given the three arguments, `(err, bytesRead, buffer)`.
-## fs.readdir(path, callback)
+## fs.readdir(path[, options], callback)
+
+* `path` {String | Buffer}
+* `options` {String | Object}
+ * `encoding` {String} default = `'utf8'`
+* `callback` {Function}
* `path` {String}
* `callback` {Function}
@@ -797,16 +828,30 @@ Asynchronous readdir(3). Reads the contents of a directory.
The callback gets two arguments `(err, files)` where `files` is an array of
the names of the files in the directory excluding `'.'` and `'..'`.
-## fs.readdirSync(path)
+The optional `options` argument can be a string specifying an encoding, or an
+object with an `encoding` property specifying the character encoding to use for
+the filenames passed to the callback. If the `encoding` is set to `'buffer'`,
+the filenames returned will be passed as `Buffer` objects.
+
+## fs.readdirSync(path[, options])
+
+* `path` {String | Buffer}
+* `options` {String | Object}
+ * `encoding` {String} default = `'utf8'`
* `path` {String}
Synchronous readdir(3). Returns an array of filenames excluding `'.'` and
`'..'`.
+The optional `options` argument can be a string specifying an encoding, or an
+object with an `encoding` property specifying the character encoding to use for
+the filenames passed to the callback. If the `encoding` is set to `'buffer'`,
+the filenames returned will be passed as `Buffer` objects.
+
## fs.readFile(file[, options], callback)
-* `file` {String | Integer} filename or file descriptor
+* `file` {String | Buffer | Integer} filename or file descriptor
* `options` {Object | String}
* `encoding` {String | Null} default = `null`
* `flag` {String} default = `'r'`
@@ -838,7 +883,7 @@ _Note: Specified file descriptors will not be closed automatically._
## fs.readFileSync(file[, options])
-* `file` {String | Integer} filename or file descriptor
+* `file` {String | Buffer | Integer} filename or file descriptor
* `options` {Object | String}
* `encoding` {String | Null} default = `null`
* `flag` {String} default = `'r'`
@@ -848,7 +893,12 @@ Synchronous version of [`fs.readFile`][]. Returns the contents of the `file`.
If the `encoding` option is specified then this function returns a
string. Otherwise it returns a buffer.
-## fs.readlink(path, callback)
+## fs.readlink(path[, options], callback)
+
+* `path` {String | Buffer}
+* `options` {String | Object}
+ * `encoding` {String} default = `'utf8'`
+* `callback` {Function}
* `path` {String}
* `callback` {Function}
@@ -856,15 +906,29 @@ string. Otherwise it returns a buffer.
Asynchronous readlink(2). The callback gets two arguments `(err,
linkString)`.
-## fs.readlinkSync(path)
+The optional `options` argument can be a string specifying an encoding, or an
+object with an `encoding` property specifying the character encoding to use for
+the link path passed to the callback. If the `encoding` is set to `'buffer'`,
+the link path returned will be passed as a `Buffer` object.
+
+## fs.readlinkSync(path[, options])
+
+* `path` {String | Buffer}
+* `options` {String | Object}
+ * `encoding` {String} default = `'utf8'`
* `path` {String}
Synchronous readlink(2). Returns the symbolic link's string value.
+The optional `options` argument can be a string specifying an encoding, or an
+object with an `encoding` property specifying the character encoding to use for
+the link path passed to the callback. If the `encoding` is set to `'buffer'`,
+the link path returned will be passed as a `Buffer` object.
+
## fs.realpath(path[, cache], callback)
-* `path` {String}
+* `path` {String | Buffer}
* `cache` {Object}
* `callback` {Function}
@@ -895,7 +959,7 @@ Synchronous version of [`fs.read()`][]. Returns the number of `bytesRead`.
## fs.realpathSync(path[, cache])
-* `path` {String}
+* `path` {String | Buffer};
* `cache` {Object}
Synchronous realpath(2). Returns the resolved path. `cache` is an
@@ -904,8 +968,8 @@ resolution or avoid additional `fs.stat` calls for known real paths.
## fs.rename(oldPath, newPath, callback)
-* `oldPath` {String}
-* `newPath` {String}
+* `oldPath` {String | Buffer}
+* `newPath` {String | Buffer}
* `callback` {Function}
Asynchronous rename(2). No arguments other than a possible exception are given
@@ -913,14 +977,14 @@ to the completion callback.
## fs.renameSync(oldPath, newPath)
-* `oldPath` {String}
-* `newPath` {String}
+* `oldPath` {String | Buffer}
+* `newPath` {String | Buffer}
Synchronous rename(2). Returns `undefined`.
## fs.rmdir(path, callback)
-* `path` {String}
+* `path` {String | Buffer}
* `callback` {Function}
Asynchronous rmdir(2). No arguments other than a possible exception are given
@@ -928,13 +992,13 @@ to the completion callback.
## fs.rmdirSync(path)
-* `path` {String}
+* `path` {String | Buffer}
Synchronous rmdir(2). Returns `undefined`.
## fs.stat(path, callback)
-* `path` {String}
+* `path` {String | Buffer}
* `callback` {Function}
Asynchronous stat(2). The callback gets two arguments `(err, stats)` where
@@ -943,14 +1007,14 @@ information.
## fs.statSync(path)
-* `path` {String}
+* `path` {String | Buffer}
Synchronous stat(2). Returns an instance of [`fs.Stats`][].
## fs.symlink(target, path[, type], callback)
-* `target` {String}
-* `path` {String}
+* `target` {String | Buffer}
+* `path` {String | Buffer}
* `type` {String}
* `callback` {Function}
@@ -971,15 +1035,15 @@ It creates a symbolic link named "new-port" that points to "foo".
## fs.symlinkSync(target, path[, type])
-* `target` {String}
-* `path` {String}
+* `target` {String | Buffer}
+* `path` {String | Buffer}
* `type` {String}
Synchronous symlink(2). Returns `undefined`.
## fs.truncate(path, len, callback)
-* `path` {String}
+* `path` {String | Buffer}
* `len` {Integer}
* `callback` {Function}
@@ -989,14 +1053,14 @@ first argument. In this case, `fs.ftruncate()` is called.
## fs.truncateSync(path, len)
-* `path` {String}
+* `path` {String | Buffer}
* `len` {Integer}
Synchronous truncate(2). Returns `undefined`.
## fs.unlink(path, callback)
-* `path` {String}
+* `path` {String | Buffer}
* `callback` {Function}
Asynchronous unlink(2). No arguments other than a possible exception are given
@@ -1004,13 +1068,13 @@ to the completion callback.
## fs.unlinkSync(path)
-* `path` {String}
+* `path` {String | Buffer}
Synchronous unlink(2). Returns `undefined`.
## fs.unwatchFile(filename[, listener])
-* `filename` {String}
+* `filename` {String | Buffer}
* `listener` {Function}
Stop watching for changes on `filename`. If `listener` is specified, only that
@@ -1026,7 +1090,7 @@ when possible._
## fs.utimes(path, atime, mtime, callback)
-* `path` {String}
+* `path` {String | Buffer}
* `atime` {Integer}
* `mtime` {Integer}
* `callback` {Function}
@@ -1043,7 +1107,7 @@ follow the below rules:
## fs.utimesSync(path, atime, mtime)
-* `path` {String}
+* `path` {String | Buffer}
* `atime` {Integer}
* `mtime` {Integer}
@@ -1051,19 +1115,24 @@ Synchronous version of [`fs.utimes()`][]. Returns `undefined`.
## fs.watch(filename[, options][, listener])
-* `filename` {String}
-* `options` {Object}
+* `filename` {String | Buffer}
+* `options` {String | Object}
* `persistent` {Boolean} Indicates whether the process should continue to run
as long as files are being watched. default = `true`
* `recursive` {Boolean} Indicates whether all subdirectories should be
watched, or only the current directory. The applies when a directory is
specified, and only on supported platforms (See [Caveats][]). default =
`false`
+ * `encoding` {String} Specifies the character encoding to be used for the
+ filename passed to the listener. default = `'utf8'`
* `listener` {Function}
Watch for changes on `filename`, where `filename` is either a file or a
directory. The returned object is a [`fs.FSWatcher`][].
+The second argument is optional. If `options` is provided as a string, it
+specifies the `encoding`. Otherwise `options` should be passed as an object.
+
The listener callback gets two arguments `(event, filename)`. `event` is either
`'rename'` or `'change'`, and `filename` is the name of the file which triggered
the event.
@@ -1120,7 +1189,7 @@ fs.watch('somedir', (event, filename) => {
## fs.watchFile(filename[, options], listener)
-* `filename` {String}
+* `filename` {String | Buffer}
* `options` {Object}
* `persistent` {Boolean}
* `interval` {Integer}
@@ -1224,7 +1293,7 @@ the end of the file.
## fs.writeFile(file, data[, options], callback)
-* `file` {String | Integer} filename or file descriptor
+* `file` {String | Buffer | Integer} filename or file descriptor
* `data` {String | Buffer}
* `options` {Object | String}
* `encoding` {String | Null} default = `'utf8'`
@@ -1263,7 +1332,7 @@ _Note: Specified file descriptors will not be closed automatically._
## fs.writeFileSync(file, data[, options])
-* `file` {String | Integer} filename or file descriptor
+* `file` {String | Buffer | Integer} filename or file descriptor
* `data` {String | Buffer}
* `options` {Object | String}
* `encoding` {String | Null} default = `'utf8'`
diff --git a/lib/fs.js b/lib/fs.js
index de7af70e4b..f4a31a6f4d 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -903,17 +903,32 @@ fs.mkdirSync = function(path, mode) {
modeNum(mode, 0o777));
};
-fs.readdir = function(path, callback) {
+fs.readdir = function(path, options, callback) {
+ options = options || {};
+ if (typeof options === 'function') {
+ callback = options;
+ options = {};
+ } else if (typeof options === 'string') {
+ options = {encoding: options};
+ }
+ if (typeof options !== 'object')
+ throw new TypeError('"options" must be a string or an object');
+
callback = makeCallback(callback);
if (!nullCheck(path, callback)) return;
var req = new FSReqWrap();
req.oncomplete = callback;
- binding.readdir(pathModule._makeLong(path), req);
+ binding.readdir(pathModule._makeLong(path), options.encoding, req);
};
-fs.readdirSync = function(path) {
+fs.readdirSync = function(path, options) {
+ options = options || {};
+ if (typeof options === 'string')
+ options = {encoding: options};
+ if (typeof options !== 'object')
+ throw new TypeError('"options" must be a string or an object');
nullCheck(path);
- return binding.readdir(pathModule._makeLong(path));
+ return binding.readdir(pathModule._makeLong(path), options.encoding);
};
fs.fstat = function(fd, callback) {
@@ -952,17 +967,31 @@ fs.statSync = function(path) {
return binding.stat(pathModule._makeLong(path));
};
-fs.readlink = function(path, callback) {
+fs.readlink = function(path, options, callback) {
+ options = options || {};
+ if (typeof options === 'function') {
+ callback = options;
+ options = {};
+ } else if (typeof options === 'string') {
+ options = {encoding: options};
+ }
+ if (typeof options !== 'object')
+ throw new TypeError('"options" must be a string or an object');
callback = makeCallback(callback);
if (!nullCheck(path, callback)) return;
var req = new FSReqWrap();
req.oncomplete = callback;
- binding.readlink(pathModule._makeLong(path), req);
+ binding.readlink(pathModule._makeLong(path), options.encoding, req);
};
-fs.readlinkSync = function(path) {
+fs.readlinkSync = function(path, options) {
+ options = options || {};
+ if (typeof options === 'string')
+ options = {encoding: options};
+ if (typeof options !== 'object')
+ throw new TypeError('"options" must be a string or an object');
nullCheck(path);
- return binding.readlink(pathModule._makeLong(path));
+ return binding.readlink(pathModule._makeLong(path), options.encoding);
};
function preprocessSymlinkDestination(path, type, linkPath) {
@@ -1363,11 +1392,15 @@ function FSWatcher() {
}
util.inherits(FSWatcher, EventEmitter);
-FSWatcher.prototype.start = function(filename, persistent, recursive) {
+FSWatcher.prototype.start = function(filename,
+ persistent,
+ recursive,
+ encoding) {
nullCheck(filename);
var err = this._handle.start(pathModule._makeLong(filename),
persistent,
- recursive);
+ recursive,
+ encoding);
if (err) {
this._handle.close();
const error = errnoException(err, `watch ${filename}`);
@@ -1380,25 +1413,27 @@ FSWatcher.prototype.close = function() {
this._handle.close();
};
-fs.watch = function(filename) {
+fs.watch = function(filename, options, listener) {
nullCheck(filename);
- var watcher;
- var options;
- var listener;
- if (arguments[1] !== null && typeof arguments[1] === 'object') {
- options = arguments[1];
- listener = arguments[2];
- } else {
+ options = options || {};
+ if (typeof options === 'function') {
+ listener = options;
options = {};
- listener = arguments[1];
+ } else if (typeof options === 'string') {
+ options = {encoding: options};
}
+ if (typeof options !== 'object')
+ throw new TypeError('"options" must be a string or an object');
if (options.persistent === undefined) options.persistent = true;
if (options.recursive === undefined) options.recursive = false;
- watcher = new FSWatcher();
- watcher.start(filename, options.persistent, options.recursive);
+ const watcher = new FSWatcher();
+ watcher.start(filename,
+ options.persistent,
+ options.recursive,
+ options.encoding);
if (listener) {
watcher.addListener('change', listener);
@@ -2139,10 +2174,19 @@ SyncWriteStream.prototype.destroy = function() {
SyncWriteStream.prototype.destroySoon = SyncWriteStream.prototype.destroy;
-fs.mkdtemp = function(prefix, callback) {
- if (typeof callback !== 'function') {
- throw new TypeError('"callback" argument must be a function');
+fs.mkdtemp = function(prefix, options, callback) {
+ if (!prefix || typeof prefix !== 'string')
+ throw new TypeError('filename prefix is required');
+
+ options = options || {};
+ if (typeof options === 'function') {
+ callback = options;
+ options = {};
+ } else if (typeof options === 'string') {
+ options = {encoding: options};
}
+ if (typeof options !== 'object')
+ throw new TypeError('"options" must be a string or an object');
if (!nullCheck(prefix, callback)) {
return;
@@ -2151,11 +2195,19 @@ fs.mkdtemp = function(prefix, callback) {
var req = new FSReqWrap();
req.oncomplete = callback;
- binding.mkdtemp(prefix + 'XXXXXX', req);
+ binding.mkdtemp(prefix + 'XXXXXX', options.encoding, req);
};
-fs.mkdtempSync = function(prefix) {
+fs.mkdtempSync = function(prefix, options) {
+ if (!prefix || typeof prefix !== 'string')
+ throw new TypeError('filename prefix is required');
+
+ options = options || {};
+ if (typeof options === 'string')
+ options = {encoding: options};
+ if (typeof options !== 'object')
+ throw new TypeError('"options" must be a string or an object');
nullCheck(prefix);
- return binding.mkdtemp(prefix + 'XXXXXX');
+ return binding.mkdtemp(prefix + 'XXXXXX', options.encoding);
};
diff --git a/src/fs_event_wrap.cc b/src/fs_event_wrap.cc
index 7768f94459..58f2716a6c 100644
--- a/src/fs_event_wrap.cc
+++ b/src/fs_event_wrap.cc
@@ -6,6 +6,7 @@
#include "util-inl.h"
#include "node.h"
#include "handle_wrap.h"
+#include "string_bytes.h"
#include <stdlib.h>
@@ -41,6 +42,7 @@ class FSEventWrap: public HandleWrap {
uv_fs_event_t handle_;
bool initialized_;
+ enum encoding encoding_;
};
@@ -86,16 +88,20 @@ void FSEventWrap::Start(const FunctionCallbackInfo<Value>& args) {
FSEventWrap* wrap = Unwrap<FSEventWrap>(args.Holder());
- if (args.Length() < 1 || !args[0]->IsString()) {
- return env->ThrowTypeError("filename must be a valid string");
- }
+ static const char kErrMsg[] = "filename must be a string or Buffer";
+ if (args.Length() < 1)
+ return env->ThrowTypeError(kErrMsg);
- node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ if (*path == nullptr)
+ return env->ThrowTypeError(kErrMsg);
unsigned int flags = 0;
if (args[2]->IsTrue())
flags |= UV_FS_EVENT_RECURSIVE;
+ wrap->encoding_ = ParseEncoding(env->isolate(), args[3], UTF8);
+
int err = uv_fs_event_init(wrap->env()->event_loop(), &wrap->handle_);
if (err == 0) {
wrap->initialized_ = true;
@@ -156,7 +162,18 @@ void FSEventWrap::OnEvent(uv_fs_event_t* handle, const char* filename,
};
if (filename != nullptr) {
- argv[2] = OneByteString(env->isolate(), filename);
+ Local<Value> fn = StringBytes::Encode(env->isolate(),
+ filename,
+ wrap->encoding_);
+ if (fn.IsEmpty()) {
+ argv[0] = Integer::New(env->isolate(), UV_EINVAL);
+ argv[2] = StringBytes::Encode(env->isolate(),
+ filename,
+ strlen(filename),
+ BUFFER);
+ } else {
+ argv[2] = fn;
+ }
}
wrap->MakeCallback(env->onchange_string(), ARRAY_SIZE(argv), argv);
diff --git a/src/node_file.cc b/src/node_file.cc
index 9c2bea4897..a669c0855f 100644
--- a/src/node_file.cc
+++ b/src/node_file.cc
@@ -34,6 +34,7 @@ using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Integer;
+using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
@@ -56,6 +57,7 @@ class FSReqWrap: public ReqWrap<uv_fs_t> {
Local<Object> req,
const char* syscall,
const char* data = nullptr,
+ enum encoding encoding = UTF8,
Ownership ownership = COPY);
inline void Dispose();
@@ -69,6 +71,7 @@ class FSReqWrap: public ReqWrap<uv_fs_t> {
const char* syscall() const { return syscall_; }
const char* data() const { return data_; }
+ const enum encoding encoding_;
size_t self_size() const override { return sizeof(*this); }
@@ -76,8 +79,10 @@ class FSReqWrap: public ReqWrap<uv_fs_t> {
FSReqWrap(Environment* env,
Local<Object> req,
const char* syscall,
- const char* data)
+ const char* data,
+ enum encoding encoding)
: ReqWrap(env, req, AsyncWrap::PROVIDER_FSREQWRAP),
+ encoding_(encoding),
syscall_(syscall),
data_(data) {
Wrap(object(), this);
@@ -95,17 +100,21 @@ class FSReqWrap: public ReqWrap<uv_fs_t> {
DISALLOW_COPY_AND_ASSIGN(FSReqWrap);
};
+#define ASSERT_PATH(path) \
+ if (*path == nullptr) \
+ return TYPE_ERROR( #path " must be a string or Buffer");
FSReqWrap* FSReqWrap::New(Environment* env,
Local<Object> req,
const char* syscall,
const char* data,
+ enum encoding encoding,
Ownership ownership) {
const bool copy = (data != nullptr && ownership == COPY);
const size_t size = copy ? 1 + strlen(data) : 0;
FSReqWrap* that;
char* const storage = new char[sizeof(*that) + size];
- that = new(storage) FSReqWrap(env, req, syscall, data);
+ that = new(storage) FSReqWrap(env, req, syscall, data, encoding);
if (copy)
that->data_ = static_cast<char*>(memcpy(that->inline_data(), data, size));
return that;
@@ -127,7 +136,6 @@ static inline bool IsInt64(double x) {
return x == static_cast<double>(static_cast<int64_t>(x));
}
-
static void After(uv_fs_t *req) {
FSReqWrap* req_wrap = static_cast<FSReqWrap*>(req->data);
CHECK_EQ(&req_wrap->req_, req);
@@ -143,6 +151,7 @@ static void After(uv_fs_t *req) {
// Allocate space for two args. We may only use one depending on the case.
// (Feel free to increase this if you need more)
Local<Value> argv[2];
+ Local<Value> link;
if (req->result < 0) {
// An error happened.
@@ -201,13 +210,35 @@ static void After(uv_fs_t *req) {
break;
case UV_FS_MKDTEMP:
- argv[1] = String::NewFromUtf8(env->isolate(),
- static_cast<const char*>(req->path));
+ link = StringBytes::Encode(env->isolate(),
+ static_cast<const char*>(req->path),
+ req_wrap->encoding_);
+ if (link.IsEmpty()) {
+ argv[0] = UVException(env->isolate(),
+ UV_EINVAL,
+ req_wrap->syscall(),
+ "Invalid character encoding for filename",
+ req->path,
+ req_wrap->data());
+ } else {
+ argv[1] = link;
+ }
break;
case UV_FS_READLINK:
- argv[1] = String::NewFromUtf8(env->isolate(),
- static_cast<const char*>(req->ptr));
+ link = StringBytes::Encode(env->isolate(),
+ static_cast<const char*>(req->ptr),
+ req_wrap->encoding_);
+ if (link.IsEmpty()) {
+ argv[0] = UVException(env->isolate(),
+ UV_EINVAL,
+ req_wrap->syscall(),
+ "Invalid character encoding for link",
+ req->path,
+ req_wrap->data());
+ } else {
+ argv[1] = link;
+ }
break;
case UV_FS_READ:
@@ -237,8 +268,19 @@ static void After(uv_fs_t *req) {
break;
}
- name_argv[name_idx++] =
- String::NewFromUtf8(env->isolate(), ent.name);
+ Local<Value> filename = StringBytes::Encode(env->isolate(),
+ ent.name,
+ req_wrap->encoding_);
+ if (filename.IsEmpty()) {
+ argv[0] = UVException(env->isolate(),
+ UV_EINVAL,
+ req_wrap->syscall(),
+ "Invalid character encoding for filename",
+ req->path,
+ req_wrap->data());
+ break;
+ }
+ name_argv[name_idx++] = filename;
if (name_idx >= ARRAY_SIZE(name_argv)) {
fn->Call(env->context(), names, name_idx, name_argv)
@@ -277,10 +319,11 @@ struct fs_req_wrap {
};
-#define ASYNC_DEST_CALL(func, req, dest, ...) \
+#define ASYNC_DEST_CALL(func, req, dest, encoding, ...) \
Environment* env = Environment::GetCurrent(args); \
CHECK(req->IsObject()); \
- FSReqWrap* req_wrap = FSReqWrap::New(env, req.As<Object>(), #func, dest); \
+ FSReqWrap* req_wrap = FSReqWrap::New(env, req.As<Object>(), \
+ #func, dest, encoding); \
int err = uv_fs_ ## func(env->event_loop(), \
&req_wrap->req_, \
__VA_ARGS__, \
@@ -296,8 +339,8 @@ struct fs_req_wrap {
args.GetReturnValue().Set(req_wrap->persistent()); \
}
-#define ASYNC_CALL(func, req, ...) \
- ASYNC_DEST_CALL(func, req, nullptr, __VA_ARGS__) \
+#define ASYNC_CALL(func, req, encoding, ...) \
+ ASYNC_DEST_CALL(func, req, nullptr, encoding, __VA_ARGS__) \
#define SYNC_DEST_CALL(func, path, dest, ...) \
fs_req_wrap req_wrap; \
@@ -317,23 +360,22 @@ struct fs_req_wrap {
#define SYNC_RESULT err
-
static void Access(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args.GetIsolate());
HandleScope scope(env->isolate());
if (args.Length() < 2)
return TYPE_ERROR("path and mode are required");
- if (!args[0]->IsString())
- return TYPE_ERROR("path must be a string");
if (!args[1]->IsInt32())
return TYPE_ERROR("mode must be an integer");
- node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ ASSERT_PATH(path)
+
int mode = static_cast<int>(args[1]->Int32Value());
if (args[2]->IsObject()) {
- ASYNC_CALL(access, args[2], *path, mode);
+ ASYNC_CALL(access, args[2], UTF8, *path, mode);
} else {
SYNC_CALL(access, *path, *path, mode);
}
@@ -351,7 +393,7 @@ static void Close(const FunctionCallbackInfo<Value>& args) {
int fd = args[0]->Int32Value();
if (args[1]->IsObject()) {
- ASYNC_CALL(close, args[1], fd)
+ ASYNC_CALL(close, args[1], UTF8, fd)
} else {
SYNC_CALL(close, 0, fd)
}
@@ -544,13 +586,12 @@ static void Stat(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 1)
return TYPE_ERROR("path required");
- if (!args[0]->IsString())
- return TYPE_ERROR("path must be a string");
- node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ ASSERT_PATH(path)
if (args[1]->IsObject()) {
- ASYNC_CALL(stat, args[1], *path)
+ ASYNC_CALL(stat, args[1], UTF8, *path)
} else {
SYNC_CALL(stat, *path, *path)
args.GetReturnValue().Set(
@@ -563,13 +604,12 @@ static void LStat(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 1)
return TYPE_ERROR("path required");
- if (!args[0]->IsString())
- return TYPE_ERROR("path must be a string");
- node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ ASSERT_PATH(path)
if (args[1]->IsObject()) {
- ASYNC_CALL(lstat, args[1], *path)
+ ASYNC_CALL(lstat, args[1], UTF8, *path)
} else {
SYNC_CALL(lstat, *path, *path)
args.GetReturnValue().Set(
@@ -588,7 +628,7 @@ static void FStat(const FunctionCallbackInfo<Value>& args) {
int fd = args[0]->Int32Value();
if (args[1]->IsObject()) {
- ASYNC_CALL(fstat, args[1], fd)
+ ASYNC_CALL(fstat, args[1], UTF8, fd)
} else {
SYNC_CALL(fstat, 0, fd)
args.GetReturnValue().Set(
@@ -604,13 +644,12 @@ static void Symlink(const FunctionCallbackInfo<Value>& args) {
return TYPE_ERROR("target path required");
if (len < 2)
return TYPE_ERROR("src path required");
- if (!args[0]->IsString())
- return TYPE_ERROR("target path must be a string");
- if (!args[1]->IsString())
- return TYPE_ERROR("src path must be a string");
- node::Utf8Value target(env->isolate(), args[0]);
- node::Utf8Value path(env->isolate(), args[1]);
+ BufferValue target(env->isolate(), args[0]);
+ ASSERT_PATH(target)
+ BufferValue path(env->isolate(), args[1]);
+ ASSERT_PATH(path)
+
int flags = 0;
if (args[2]->IsString()) {
@@ -625,7 +664,7 @@ static void Symlink(const FunctionCallbackInfo<Value>& args) {
}
if (args[3]->IsObject()) {
- ASYNC_DEST_CALL(symlink, args[3], *path, *target, *path, flags)
+ ASYNC_DEST_CALL(symlink, args[3], *path, UTF8, *target, *path, flags)
} else {
SYNC_DEST_CALL(symlink, *target, *path, *target, *path, flags)
}
@@ -639,37 +678,51 @@ static void Link(const FunctionCallbackInfo<Value>& args) {
return TYPE_ERROR("src path required");
if (len < 2)
return TYPE_ERROR("dest path required");
- if (!args[0]->IsString())
- return TYPE_ERROR("src path must be a string");
- if (!args[1]->IsString())
- return TYPE_ERROR("dest path must be a string");
- node::Utf8Value orig_path(env->isolate(), args[0]);
- node::Utf8Value new_path(env->isolate(), args[1]);
+ BufferValue src(env->isolate(), args[0]);
+ ASSERT_PATH(src)
+
+ BufferValue dest(env->isolate(), args[1]);
+ ASSERT_PATH(dest)
if (args[2]->IsObject()) {
- ASYNC_DEST_CALL(link, args[2], *new_path, *orig_path, *new_path)
+ ASYNC_DEST_CALL(link, args[2], *dest, UTF8, *src, *dest)
} else {
- SYNC_DEST_CALL(link, *orig_path, *new_path, *orig_path, *new_path)
+ SYNC_DEST_CALL(link, *src, *dest, *src, *dest)
}
}
static void ReadLink(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
- if (args.Length() < 1)
+ const int argc = args.Length();
+
+ if (argc < 1)
return TYPE_ERROR("path required");
- if (!args[0]->IsString())
- return TYPE_ERROR("path must be a string");
- node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ ASSERT_PATH(path)
- if (args[1]->IsObject()) {
- ASYNC_CALL(readlink, args[1], *path)
+ const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);
+
+ Local<Value> callback = Null(env->isolate());
+ if (argc == 3)
+ callback = args[2];
+
+ if (callback->IsObject()) {
+ ASYNC_CALL(readlink, callback, encoding, *path)
} else {
SYNC_CALL(readlink, *path, *path)
const char* link_path = static_cast<const char*>(SYNC_REQ.ptr);
- Local<String> rc = String::NewFromUtf8(env->isolate(), link_path);
+ Local<Value> rc = StringBytes::Encode(env->isolate(),
+ link_path,
+ encoding);
+ if (rc.IsEmpty()) {
+ return env->ThrowUVException(UV_EINVAL,
+ "readlink",
+ "Invalid character encoding for link",
+ *path);
+ }
args.GetReturnValue().Set(rc);
}
}
@@ -682,16 +735,14 @@ static void Rename(const FunctionCallbackInfo<Value>& args) {
return TYPE_ERROR("old path required");
if (len < 2)
return TYPE_ERROR("new path required");
- if (!args[0]->IsString())
- return TYPE_ERROR("old path must be a string");
- if (!args[1]->IsString())
- return TYPE_ERROR("new path must be a string");
- node::Utf8Value old_path(env->isolate(), args[0]);
- node::Utf8Value new_path(env->isolate(), args[1]);
+ BufferValue old_path(env->isolate(), args[0]);
+ ASSERT_PATH(old_path)
+ BufferValue new_path(env->isolate(), args[1]);
+ ASSERT_PATH(new_path)
if (args[2]->IsObject()) {
- ASYNC_DEST_CALL(rename, args[2], *new_path, *old_path, *new_path)
+ ASYNC_DEST_CALL(rename, args[2], *new_path, UTF8, *old_path, *new_path)
} else {
SYNC_DEST_CALL(rename, *old_path, *new_path, *old_path, *new_path)
}
@@ -720,7 +771,7 @@ static void FTruncate(const FunctionCallbackInfo<Value>& args) {
const int64_t len = len_v->IntegerValue();
if (args[2]->IsObject()) {
- ASYNC_CALL(ftruncate, args[2], fd, len)
+ ASYNC_CALL(ftruncate, args[2], UTF8, fd, len)
} else {
SYNC_CALL(ftruncate, 0, fd, len)
}
@@ -737,7 +788,7 @@ static void Fdatasync(const FunctionCallbackInfo<Value>& args) {
int fd = args[0]->Int32Value();
if (args[1]->IsObject()) {
- ASYNC_CALL(fdatasync, args[1], fd)
+ ASYNC_CALL(fdatasync, args[1], UTF8, fd)
} else {
SYNC_CALL(fdatasync, 0, fd)
}
@@ -754,7 +805,7 @@ static void Fsync(const FunctionCallbackInfo<Value>& args) {
int fd = args[0]->Int32Value();
if (args[1]->IsObject()) {
- ASYNC_CALL(fsync, args[1], fd)
+ ASYNC_CALL(fsync, args[1], UTF8, fd)
} else {
SYNC_CALL(fsync, 0, fd)
}
@@ -765,13 +816,12 @@ static void Unlink(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 1)
return TYPE_ERROR("path required");
- if (!args[0]->IsString())
- return TYPE_ERROR("path must be a string");
- node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ ASSERT_PATH(path)
if (args[1]->IsObject()) {
- ASYNC_CALL(unlink, args[1], *path)
+ ASYNC_CALL(unlink, args[1], UTF8, *path)
} else {
SYNC_CALL(unlink, *path, *path)
}
@@ -782,13 +832,12 @@ static void RMDir(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 1)
return TYPE_ERROR("path required");
- if (!args[0]->IsString())
- return TYPE_ERROR("path must be a string");
- node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ ASSERT_PATH(path)
if (args[1]->IsObject()) {
- ASYNC_CALL(rmdir, args[1], *path)
+ ASYNC_CALL(rmdir, args[1], UTF8, *path)
} else {
SYNC_CALL(rmdir, *path, *path)
}
@@ -799,16 +848,16 @@ static void MKDir(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 2)
return TYPE_ERROR("path and mode are required");
- if (!args[0]->IsString())
- return TYPE_ERROR("path must be a string");
if (!args[1]->IsInt32())
return TYPE_ERROR("mode must be an integer");
- node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ ASSERT_PATH(path)
+
int mode = static_cast<int>(args[1]->Int32Value());
if (args[2]->IsObject()) {
- ASYNC_CALL(mkdir, args[2], *path, mode)
+ ASYNC_CALL(mkdir, args[2], UTF8, *path, mode)
} else {
SYNC_CALL(mkdir, *path, *path, mode)
}
@@ -817,15 +866,22 @@ static void MKDir(const FunctionCallbackInfo<Value>& args) {
static void ReadDir(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
- if (args.Length() < 1)
+ const int argc = args.Length();
+
+ if (argc < 1)
return TYPE_ERROR("path required");
- if (!args[0]->IsString())
- return TYPE_ERROR("path must be a string");
- node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ ASSERT_PATH(path)
- if (args[1]->IsObject()) {
- ASYNC_CALL(scandir, args[1], *path, 0 /*flags*/)
+ const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);
+
+ Local<Value> callback = Null(env->isolate());
+ if (argc == 3)
+ callback = args[2];
+
+ if (callback->IsObject()) {
+ ASYNC_CALL(scandir, callback, encoding, *path, 0 /*flags*/)
} else {
SYNC_CALL(scandir, *path, *path, 0 /*flags*/)
@@ -845,8 +901,17 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
if (r != 0)
return env->ThrowUVException(r, "readdir", "", *path);
+ Local<Value> filename = StringBytes::Encode(env->isolate(),
+ ent.name,
+ encoding);
+ if (filename.IsEmpty()) {
+ return env->ThrowUVException(UV_EINVAL,
+ "readdir",
+ "Invalid character encoding for filename",
+ *path);
+ }
- name_v[name_idx++] = String::NewFromUtf8(env->isolate(), ent.name);
+ name_v[name_idx++] = filename;
if (name_idx >= ARRAY_SIZE(name_v)) {
fn->Call(env->context(), names, name_idx, name_v)
@@ -873,19 +938,19 @@ static void Open(const FunctionCallbackInfo<Value>& args) {
return TYPE_ERROR("flags required");
if (len < 3)
return TYPE_ERROR("mode required");
- if (!args[0]->IsString())
- return TYPE_ERROR("path must be a string");
if (!args[1]->IsInt32())
return TYPE_ERROR("flags must be an int");
if (!args[2]->IsInt32())
return TYPE_ERROR("mode must be an int");
- node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ ASSERT_PATH(path)
+
int flags = args[1]->Int32Value();
int mode = static_cast<int>(args[2]->Int32Value());
if (args[3]->IsObject()) {
- ASYNC_CALL(open, args[3], *path, flags, mode)
+ ASYNC_CALL(open, args[3], UTF8, *path, flags, mode)
} else {
SYNC_CALL(open, *path, *path, flags, mode)
args.GetReturnValue().Set(SYNC_RESULT);
@@ -933,7 +998,7 @@ static void WriteBuffer(const FunctionCallbackInfo<Value>& args) {
uv_buf_t uvbuf = uv_buf_init(const_cast<char*>(buf), len);
if (req->IsObject()) {
- ASYNC_CALL(write, req, fd, &uvbuf, 1, pos)
+ ASYNC_CALL(write, req, UTF8, fd, &uvbuf, 1, pos)
return;
}
@@ -983,7 +1048,7 @@ static void WriteBuffers(const FunctionCallbackInfo<Value>& args) {
}
if (req->IsObject()) {
- ASYNC_CALL(write, req, fd, iovs, chunkCount, pos)
+ ASYNC_CALL(write, req, UTF8, fd, iovs, chunkCount, pos)
if (iovs != s_iovs)
delete[] iovs;
return;
@@ -1049,7 +1114,7 @@ static void WriteString(const FunctionCallbackInfo<Value>& args) {
}
FSReqWrap* req_wrap =
- FSReqWrap::New(env, req.As<Object>(), "write", buf, ownership);
+ FSReqWrap::New(env, req.As<Object>(), "write", buf, UTF8, ownership);
int err = uv_fs_write(env->event_loop(),
&req_wrap->req_,
fd,
@@ -1123,7 +1188,7 @@ static void Read(const FunctionCallbackInfo<Value>& args) {
req = args[5];
if (req->IsObject()) {
- ASYNC_CALL(read, req, fd, &uvbuf, 1, pos);
+ ASYNC_CALL(read, req, UTF8, fd, &uvbuf, 1, pos);
} else {
SYNC_CALL(read, 0, fd, &uvbuf, 1, pos)
args.GetReturnValue().Set(SYNC_RESULT);
@@ -1139,16 +1204,16 @@ static void Chmod(const FunctionCallbackInfo<Value>& args) {
if (args.Length() < 2)
return TYPE_ERROR("path and mode are required");
- if (!args[0]->IsString())
- return TYPE_ERROR("path must be a string");
if (!args[1]->IsInt32())
return TYPE_ERROR("mode must be an integer");
- node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ ASSERT_PATH(path)
+
int mode = static_cast<int>(args[1]->Int32Value());
if (args[2]->IsObject()) {
- ASYNC_CALL(chmod, args[2], *path, mode);
+ ASYNC_CALL(chmod, args[2], UTF8, *path, mode);
} else {
SYNC_CALL(chmod, *path, *path, mode);
}
@@ -1172,7 +1237,7 @@ static void FChmod(const FunctionCallbackInfo<Value>& args) {
int mode = static_cast<int>(args[1]->Int32Value());
if (args[2]->IsObject()) {
- ASYNC_CALL(fchmod, args[2], fd, mode);
+ ASYNC_CALL(fchmod, args[2], UTF8, fd, mode);
} else {
SYNC_CALL(fchmod, 0, fd, mode);
}
@@ -1192,19 +1257,19 @@ static void Chown(const FunctionCallbackInfo<Value>& args) {
return TYPE_ERROR("uid required");
if (len < 3)
return TYPE_ERROR("gid required");
- if (!args[0]->IsString())
- return TYPE_ERROR("path must be a string");
if (!args[1]->IsUint32())
return TYPE_ERROR("uid must be an unsigned int");
if (!args[2]->IsUint32())
return TYPE_ERROR("gid must be an unsigned int");
- node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ ASSERT_PATH(path)
+
uv_uid_t uid = static_cast<uv_uid_t>(args[1]->Uint32Value());
uv_gid_t gid = static_cast<uv_gid_t>(args[2]->Uint32Value());
if (args[3]->IsObject()) {
- ASYNC_CALL(chown, args[3], *path, uid, gid);
+ ASYNC_CALL(chown, args[3], UTF8, *path, uid, gid);
} else {
SYNC_CALL(chown, *path, *path, uid, gid);
}
@@ -1236,7 +1301,7 @@ static void FChown(const FunctionCallbackInfo<Value>& args) {
uv_gid_t gid = static_cast<uv_gid_t>(args[2]->Uint32Value());
if (args[3]->IsObject()) {
- ASYNC_CALL(fchown, args[3], fd, uid, gid);
+ ASYNC_CALL(fchown, args[3], UTF8, fd, uid, gid);
} else {
SYNC_CALL(fchown, 0, fd, uid, gid);
}
@@ -1253,19 +1318,19 @@ static void UTimes(const FunctionCallbackInfo<Value>& args) {
return TYPE_ERROR("atime required");
if (len < 3)
return TYPE_ERROR("mtime required");
- if (!args[0]->IsString())
- return TYPE_ERROR("path must be a string");
if (!args[1]->IsNumber())
return TYPE_ERROR("atime must be a number");
if (!args[2]->IsNumber())
return TYPE_ERROR("mtime must be a number");
- const node::Utf8Value path(env->isolate(), args[0]);
+ BufferValue path(env->isolate(), args[0]);
+ ASSERT_PATH(path)
+
const double atime = static_cast<double>(args[1]->NumberValue());
const double mtime = static_cast<double>(args[2]->NumberValue());
if (args[3]->IsObject()) {
- ASYNC_CALL(utime, args[3], *path, atime, mtime);
+ ASYNC_CALL(utime, args[3], UTF8, *path, atime, mtime);
} else {
SYNC_CALL(utime, *path, *path, atime, mtime);
}
@@ -1293,7 +1358,7 @@ static void FUTimes(const FunctionCallbackInfo<Value>& args) {
const double mtime = static_cast<double>(args[2]->NumberValue());
if (args[3]->IsObject()) {
- ASYNC_CALL(futime, args[3], fd, atime, mtime);
+ ASYNC_CALL(futime, args[3], UTF8, fd, atime, mtime);
} else {
SYNC_CALL(futime, 0, fd, atime, mtime);
}
@@ -1302,19 +1367,27 @@ static void FUTimes(const FunctionCallbackInfo<Value>& args) {
static void Mkdtemp(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
- if (args.Length() < 1)
- return TYPE_ERROR("template is required");
- if (!args[0]->IsString())
- return TYPE_ERROR("template must be a string");
+ CHECK_GE(args.Length(), 2);
- node::Utf8Value tmpl(env->isolate(), args[0]);
+ BufferValue tmpl(env->isolate(), args[0]);
+ if (*tmpl == nullptr)
+ return TYPE_ERROR("template must be a string or Buffer");
- if (args[1]->IsObject()) {
- ASYNC_CALL(mkdtemp, args[1], *tmpl);
+ const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);
+
+ if (args[2]->IsObject()) {
+ ASYNC_CALL(mkdtemp, args[2], encoding, *tmpl);
} else {
SYNC_CALL(mkdtemp, *tmpl, *tmpl);
- args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(),
- SYNC_REQ.path));
+ const char* path = static_cast<const char*>(SYNC_REQ.path);
+ Local<Value> rc = StringBytes::Encode(env->isolate(), path, encoding);
+ if (rc.IsEmpty()) {
+ return env->ThrowUVException(UV_EINVAL,
+ "mkdtemp",
+ "Invalid character encoding for filename",
+ *tmpl);
+ }
+ args.GetReturnValue().Set(rc);
}
}
diff --git a/src/string_bytes.cc b/src/string_bytes.cc
index a2e4fe388a..b8d7d3b42f 100644
--- a/src/string_bytes.cc
+++ b/src/string_bytes.cc
@@ -24,7 +24,6 @@ using v8::String;
using v8::Value;
using v8::MaybeLocal;
-
template <typename ResourceType, typename TypeName>
class ExternString: public ResourceType {
public:
@@ -895,4 +894,34 @@ Local<Value> StringBytes::Encode(Isolate* isolate,
return val;
}
+Local<Value> StringBytes::Encode(Isolate* isolate,
+ const char* buf,
+ enum encoding encoding) {
+ const size_t len = strlen(buf);
+ Local<Value> ret;
+ if (encoding == UCS2) {
+ // In Node, UCS2 means utf16le. The data must be in little-endian
+ // order and must be aligned on 2-bytes. This returns an empty
+ // value if it's not aligned and ensures the appropriate byte order
+ // on big endian architectures.
+ const bool be = IsBigEndian();
+ if (len % 2 != 0)
+ return ret;
+ std::vector<uint16_t> vec(len / 2);
+ for (size_t i = 0, k = 0; i < len; i += 2, k += 1) {
+ const uint8_t hi = static_cast<uint8_t>(buf[i + 0]);
+ const uint8_t lo = static_cast<uint8_t>(buf[i + 1]);
+ vec[k] = be ?
+ static_cast<uint16_t>(hi) << 8 | lo
+ : static_cast<uint16_t>(lo) << 8 | hi;
+ }
+ ret = vec.empty() ?
+ static_cast< Local<Value> >(String::Empty(isolate))
+ : StringBytes::Encode(isolate, &vec[0], vec.size());
+ } else {
+ ret = StringBytes::Encode(isolate, buf, len, encoding);
+ }
+ return ret;
+}
+
} // namespace node
diff --git a/src/string_bytes.h b/src/string_bytes.h
index 79520d2470..7108862139 100644
--- a/src/string_bytes.h
+++ b/src/string_bytes.h
@@ -106,6 +106,10 @@ class StringBytes {
const uint16_t* buf,
size_t buflen);
+ static v8::Local<v8::Value> Encode(v8::Isolate* isolate,
+ const char* buf,
+ enum encoding encoding);
+
// Deprecated legacy interface
NODE_DEPRECATED("Use IsValidString(isolate, ...)",
diff --git a/src/util.cc b/src/util.cc
index 095e5582db..3b0278ceda 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -1,37 +1,48 @@
#include "util.h"
#include "string_bytes.h"
+#include "node_buffer.h"
+#include <stdio.h>
namespace node {
-Utf8Value::Utf8Value(v8::Isolate* isolate, v8::Local<v8::Value> value)
- : length_(0), str_(str_st_) {
- if (value.IsEmpty())
- return;
+using v8::Isolate;
+using v8::String;
+using v8::Local;
+using v8::Value;
- v8::Local<v8::String> string = value->ToString(isolate);
+static int MakeUtf8String(Isolate* isolate,
+ Local<Value> value,
+ char** dst,
+ const size_t size) {
+ Local<String> string = value->ToString(isolate);
if (string.IsEmpty())
- return;
-
- // Allocate enough space to include the null terminator
+ return 0;
size_t len = StringBytes::StorageSize(isolate, string, UTF8) + 1;
- if (len > sizeof(str_st_)) {
- str_ = static_cast<char*>(malloc(len));
- CHECK_NE(str_, nullptr);
+ if (len > size) {
+ *dst = static_cast<char*>(malloc(len));
+ CHECK_NE(*dst, nullptr);
}
-
const int flags =
- v8::String::NO_NULL_TERMINATION | v8::String::REPLACE_INVALID_UTF8;
- length_ = string->WriteUtf8(str_, len, 0, flags);
- str_[length_] = '\0';
+ String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8;
+ const int length = string->WriteUtf8(*dst, len, 0, flags);
+ (*dst)[length] = '\0';
+ return length;
+}
+
+Utf8Value::Utf8Value(Isolate* isolate, Local<Value> value)
+ : length_(0), str_(str_st_) {
+ if (value.IsEmpty())
+ return;
+ length_ = MakeUtf8String(isolate, value, &str_, sizeof(str_st_));
}
-TwoByteValue::TwoByteValue(v8::Isolate* isolate, v8::Local<v8::Value> value)
+TwoByteValue::TwoByteValue(Isolate* isolate, Local<Value> value)
: length_(0), str_(str_st_) {
if (value.IsEmpty())
return;
- v8::Local<v8::String> string = value->ToString(isolate);
+ Local<String> string = value->ToString(isolate);
if (string.IsEmpty())
return;
@@ -43,9 +54,31 @@ TwoByteValue::TwoByteValue(v8::Isolate* isolate, v8::Local<v8::Value> value)
}
const int flags =
- v8::String::NO_NULL_TERMINATION | v8::String::REPLACE_INVALID_UTF8;
+ String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8;
length_ = string->Write(str_, 0, len, flags);
str_[length_] = '\0';
}
+BufferValue::BufferValue(Isolate* isolate, Local<Value> value)
+ : str_(str_st_), fail_(true) {
+ // Slightly different take on Utf8Value. If value is a String,
+ // it will return a Utf8 encoded string. If value is a Buffer,
+ // it will copy the data out of the Buffer as is.
+ if (value.IsEmpty())
+ return;
+ if (value->IsString()) {
+ MakeUtf8String(isolate, value, &str_, sizeof(str_st_));
+ fail_ = false;
+ } else if (Buffer::HasInstance(value)) {
+ size_t len = Buffer::Length(value) + 1;
+ if (len > sizeof(str_st_)) {
+ str_ = static_cast<char*>(malloc(len));
+ CHECK_NE(str_, nullptr);
+ }
+ memcpy(str_, Buffer::Data(value), len);
+ str_[len - 1] = '\0';
+ fail_ = false;
+ }
+}
+
} // namespace node
diff --git a/src/util.h b/src/util.h
index 84d0b5a170..2c4d8c6286 100644
--- a/src/util.h
+++ b/src/util.h
@@ -232,6 +232,25 @@ class TwoByteValue {
uint16_t str_st_[1024];
};
+class BufferValue {
+ public:
+ explicit BufferValue(v8::Isolate* isolate, v8::Local<v8::Value> value);
+
+ ~BufferValue() {
+ if (str_ != str_st_)
+ free(str_);
+ }
+
+ const char* operator*() const {
+ return fail_ ? nullptr : str_;
+ };
+
+ private:
+ char* str_;
+ char str_st_[1024];
+ bool fail_;
+};
+
} // namespace node
#endif // SRC_UTIL_H_
diff --git a/test/common.js b/test/common.js
index 28e7972e22..6fb357ba25 100644
--- a/test/common.js
+++ b/test/common.js
@@ -57,8 +57,14 @@ function rmdirSync(p, originalEr) {
if (e.code === 'ENOTDIR')
throw originalEr;
if (e.code === 'ENOTEMPTY' || e.code === 'EEXIST' || e.code === 'EPERM') {
- fs.readdirSync(p).forEach(function(f) {
- rimrafSync(path.join(p, f));
+ const enc = process.platform === 'linux' ? 'buffer' : 'utf8';
+ fs.readdirSync(p, enc).forEach((f) => {
+ if (f instanceof Buffer) {
+ const buf = Buffer.concat([Buffer.from(p), Buffer.from(path.sep), f]);
+ rimrafSync(buf);
+ } else {
+ rimrafSync(path.join(p, f));
+ }
});
fs.rmdirSync(p);
}
diff --git a/test/parallel/test-fs-access.js b/test/parallel/test-fs-access.js
index 9a1b6b8a5d..6b82b4bbf3 100644
--- a/test/parallel/test-fs-access.js
+++ b/test/parallel/test-fs-access.js
@@ -92,7 +92,7 @@ fs.access(readOnlyFile, fs.W_OK, function(err) {
assert.throws(function() {
fs.access(100, fs.F_OK, function(err) {});
-}, /path must be a string/);
+}, /path must be a string or Buffer/);
assert.throws(function() {
fs.access(__filename, fs.F_OK);
diff --git a/test/parallel/test-fs-buffer.js b/test/parallel/test-fs-buffer.js
new file mode 100644
index 0000000000..6f142310f5
--- /dev/null
+++ b/test/parallel/test-fs-buffer.js
@@ -0,0 +1,41 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+
+common.refreshTmpDir();
+
+assert.doesNotThrow(() => {
+ fs.access(Buffer.from(common.tmpDir), common.mustCall((err) => {
+ if (err) throw err;
+ }));
+});
+
+assert.doesNotThrow(() => {
+ const buf = Buffer.from(path.join(common.tmpDir, 'a.txt'));
+ fs.open(buf, 'w+', common.mustCall((err, fd) => {
+ if (err) throw err;
+ assert(fd);
+ fs.close(fd, common.mustCall(() => {
+ fs.unlinkSync(buf);
+ }));
+ }));
+});
+
+assert.throws(() => {
+ fs.accessSync(true);
+}, /path must be a string or Buffer/);
+
+const dir = Buffer.from(common.fixturesDir);
+fs.readdir(dir, 'hex', common.mustCall((err, list) => {
+ if (err) throw err;
+ list = list.map((i) => {
+ return Buffer.from(i, 'hex').toString();
+ });
+ fs.readdir(dir, common.mustCall((err, list2) => {
+ if (err) throw err;
+ assert.deepStrictEqual(list, list2);
+ }));
+}));
diff --git a/test/parallel/test-fs-link.js b/test/parallel/test-fs-link.js
index 292d48fb53..2cba47bfec 100644
--- a/test/parallel/test-fs-link.js
+++ b/test/parallel/test-fs-link.js
@@ -25,12 +25,12 @@ assert.throws(
function() {
fs.link();
},
- /src path/
+ /src must be a string or Buffer/
);
assert.throws(
function() {
fs.link('abc');
},
- /dest path/
+ /dest must be a string or Buffer/
);
diff --git a/test/parallel/test-fs-readdir-ucs2.js b/test/parallel/test-fs-readdir-ucs2.js
new file mode 100644
index 0000000000..578723117a
--- /dev/null
+++ b/test/parallel/test-fs-readdir-ucs2.js
@@ -0,0 +1,31 @@
+'use strict';
+
+const common = require('../common');
+const path = require('path');
+const fs = require('fs');
+const assert = require('assert');
+
+if (process.platform !== 'linux') {
+ console.log('1..0 # Skipped: Test is linux specific.');
+ return;
+}
+
+common.refreshTmpDir();
+const filename = '\uD83D\uDC04';
+const root = Buffer.from(`${common.tmpDir}${path.sep}`);
+const filebuff = Buffer.from(filename, 'ucs2');
+const fullpath = Buffer.concat([root, filebuff]);
+
+fs.closeSync(fs.openSync(fullpath, 'w+'));
+
+fs.readdir(common.tmpDir, 'ucs2', (err, list) => {
+ if (err) throw err;
+ assert.equal(1, list.length);
+ const fn = list[0];
+ assert.deepStrictEqual(filebuff, Buffer.from(fn, 'ucs2'));
+ assert.strictEqual(fn, filename);
+});
+
+process.on('exit', () => {
+ fs.unlinkSync(fullpath);
+});
diff --git a/test/parallel/test-fs-watch-encoding.js b/test/parallel/test-fs-watch-encoding.js
new file mode 100644
index 0000000000..cb7af00f66
--- /dev/null
+++ b/test/parallel/test-fs-watch-encoding.js
@@ -0,0 +1,52 @@
+'use strict';
+
+const common = require('../common');
+const fs = require('fs');
+const path = require('path');
+const assert = require('assert');
+
+if (common.isFreeBSD) {
+ console.log('1..0 # Skipped: Test currently not working on FreeBSD');
+ return;
+}
+
+const fn = '新建文夹件.txt';
+const a = path.join(common.tmpDir, fn);
+
+const watcher1 = fs.watch(
+ common.tmpDir,
+ {encoding: 'hex'},
+ (event, filename) => {
+ if (filename)
+ assert.equal(filename, 'e696b0e5bbbae69687e5a4b9e4bbb62e747874');
+ watcher1.close();
+ }
+);
+
+const watcher2 = fs.watch(
+ common.tmpDir,
+ (event, filename) => {
+ if (filename)
+ assert.equal(filename, fn);
+ watcher2.close();
+ }
+);
+
+const watcher3 = fs.watch(
+ common.tmpDir,
+ {encoding: 'buffer'},
+ (event, filename) => {
+ if (filename) {
+ assert(filename instanceof Buffer);
+ assert.equal(filename.toString('utf8'), fn);
+ }
+ watcher3.close();
+ }
+);
+
+const fd = fs.openSync(a, 'w+');
+fs.closeSync(fd);
+
+process.on('exit', () => {
+ fs.unlink(a);
+});
diff --git a/test/sequential/test-module-loading.js b/test/sequential/test-module-loading.js
index a6f9488f5f..7370f8290d 100644
--- a/test/sequential/test-module-loading.js
+++ b/test/sequential/test-module-loading.js
@@ -247,7 +247,7 @@ assert.deepEqual(children, {
assert.throws(function() {
console.error('require non-string');
require({ foo: 'bar' });
-}, 'path must be a string');
+}, 'path must be a string or Buffer');
assert.throws(function() {
console.error('require empty string');