diff options
author | James M Snell <jasnell@gmail.com> | 2016-03-08 20:58:45 -0800 |
---|---|---|
committer | James M Snell <jasnell@gmail.com> | 2016-03-25 14:21:27 -0700 |
commit | 060e5f0c0064e578c2150f13e3f91ac15fdeed92 (patch) | |
tree | 50783508a123991a024a9a85a2994d5ef2a83c5d /src | |
parent | 4d4f3535a9fd7486d7a94d825ac8e92a65bf9121 (diff) | |
download | android-node-v8-060e5f0c0064e578c2150f13e3f91ac15fdeed92.tar.gz android-node-v8-060e5f0c0064e578c2150f13e3f91ac15fdeed92.tar.bz2 android-node-v8-060e5f0c0064e578c2150f13e3f91ac15fdeed92.zip |
fs: Buffer and encoding enhancements to fs API
This makes several changes:
1. Allow path/filename to be passed in as a Buffer on fs methods
2. Add `options.encoding` to fs.readdir, fs.readdirSync, fs.readlink,
fs.readlinkSync and fs.watch.
3. Documentation updates
For 1... it's now possible to do:
```js
fs.open(Buffer('/fs/foo/bar'), 'w+', (err, fd) => { });
```
For 2...
```js
fs.readdir('/fs/foo/bar', {encoding:'hex'}, (err,list) => { });
fs.readdir('/fs/foo/bar', {encoding:'buffer'}, (err, list) => { });
```
encoding can also be passed as a string
```js
fs.readdir('/fs/foo/bar', 'hex', (err,list) => { });
```
The default encoding is set to UTF8 so this addresses the
discrepency that existed previously between fs.readdir and
fs.watch handling filenames differently.
Fixes: https://github.com/nodejs/node/issues/2088
Refs: https://github.com/nodejs/node/issues/3519
PR-URL: https://github.com/nodejs/node/pull/5616
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/fs_event_wrap.cc | 27 | ||||
-rw-r--r-- | src/node_file.cc | 295 | ||||
-rw-r--r-- | src/string_bytes.cc | 31 | ||||
-rw-r--r-- | src/string_bytes.h | 4 | ||||
-rw-r--r-- | src/util.cc | 69 | ||||
-rw-r--r-- | src/util.h | 19 |
6 files changed, 310 insertions, 135 deletions
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_ |