diff --git a/README.md b/README.md index 09c90397618220..cf3f3dd98cfedd 100644 --- a/README.md +++ b/README.md @@ -114,11 +114,11 @@ documentation of the latest stable version. ### Verifying Binaries -Current, LTS and Nightly download directories all contain a _SHASUM256.txt_ +Current, LTS and Nightly download directories all contain a _SHASUMS256.txt_ file that lists the SHA checksums for each file available for download. -The _SHASUM256.txt_ can be downloaded using curl. +The _SHASUMS256.txt_ can be downloaded using curl. ```console $ curl -O https://nodejs.org/dist/vx.y.z/SHASUMS256.txt @@ -135,10 +135,10 @@ _(Where "node-vx.y.z.tar.gz" is the name of the file you have downloaded)_ Additionally, Current and LTS releases (not Nightlies) have GPG signed -copies of SHASUM256.txt files available as SHASUM256.txt.asc. You can use +copies of SHASUMS256.txt files available as SHASUMS256.txt.asc. You can use `gpg` to verify that the file has not been tampered with. -To verify a SHASUM256.txt.asc, you will first need to import all of +To verify a SHASUMS256.txt.asc, you will first need to import all of the GPG keys of individuals authorized to create releases. They are listed at the bottom of this README under [Release Team](#release-team). Use a command such as this to import the keys: diff --git a/deps/openssl/config/opensslconf.h b/deps/openssl/config/opensslconf.h index 9b20fb6485aa84..1c89babcf6c864 100644 --- a/deps/openssl/config/opensslconf.h +++ b/deps/openssl/config/opensslconf.h @@ -37,6 +37,8 @@ | solaris | x64 | solaris64-x86_64-gcc | o | | freebsd | ia32 | BSD-x86 | o | | freebsd | x64 | BSD-x86_64 | o | + | netbsd | ia32 | BSD-x86 | o | + | netbsd | x64 | BSD-x86_64 | o | | openbsd | ia32 | BSD-x86 | - | | openbsd | x64 | BSD-x86_64 | - | | others | others | linux-elf | - | @@ -51,6 +53,7 @@ | mac | __APPLE__ && __MACH__ | | solaris | __sun | | freebsd | __FreeBSD__ | + | netbsd | __NetBSD__ | | openbsd | __OpenBSD__ | | linux (not andorid)| __linux__ && !__ANDROID__ | | android | __ANDROID__ | @@ -94,6 +97,11 @@ # define OPENSSL_LINUX 1 #endif +#undef OPENSSL_BSD +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) +# define OPENSSL_BSD 1 +#endif + #if defined(OPENSSL_LINUX) && defined(__i386__) # include "./archs/linux-elf/opensslconf.h" #elif defined(OPENSSL_LINUX) && defined(__ILP32__) @@ -112,9 +120,9 @@ # include "./archs/VC-WIN32/opensslconf.h" #elif defined(_WIN32) && defined(_M_X64) # include "./archs/VC-WIN64A/opensslconf.h" -#elif (defined(__FreeBSD__) || defined(__OpenBSD__)) && defined(__i386__) +#elif defined(OPENSSL_BSD) && defined(__i386__) # include "./archs/BSD-x86/opensslconf.h" -#elif (defined(__FreeBSD__) || defined(__OpenBSD__)) && defined(__x86_64__) +#elif defined(OPENSSL_BSD) && defined(__x86_64__) # include "./archs/BSD-x86_64/opensslconf.h" #elif defined(__sun) && defined(__i386__) # include "./archs/solaris-x86-gcc/opensslconf.h" diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 79c7a3c733e90a..e0f3689bc21025 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -896,6 +896,10 @@ console.log(sign.sign(privateKey).toString('hex')); ### sign.sign(private_key[, output_format]) Calculates the signature on all the data passed through using either @@ -903,10 +907,21 @@ Calculates the signature on all the data passed through using either The `private_key` argument can be an object or a string. If `private_key` is a string, it is treated as a raw key with no passphrase. If `private_key` is an -object, it is interpreted as a hash containing two properties: +object, it must contain one or more of the following properties: -* `key`: {string} - PEM encoded private key +* `key`: {string} - PEM encoded private key (required) * `passphrase`: {string} - passphrase for the private key +* `padding`: {integer} - Optional padding value for RSA, one of the following: + * `crypto.constants.RSA_PKCS1_PADDING` (default) + * `crypto.constants.RSA_PKCS1_PSS_PADDING` + + Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function + used to sign the message as specified in section 3.1 of [RFC 4055][]. +* `saltLength`: {integer} - salt length for when padding is + `RSA_PKCS1_PSS_PADDING`. The special value + `crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest + size, `crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN` (default) sets it to the + maximum permissible value. The `output_format` can specify one of `'latin1'`, `'hex'` or `'base64'`. If `output_format` is provided a string is returned; otherwise a [`Buffer`][] is @@ -989,11 +1004,33 @@ This can be called many times with new data as it is streamed. ### verifier.verify(object, signature[, signature_format]) +- `object` {string | Object} +- `signature` {string | Buffer | Uint8Array} +- `signature_format` {string} Verifies the provided data using the given `object` and `signature`. -The `object` argument is a string containing a PEM encoded object, which can be -one an RSA public key, a DSA public key, or an X.509 certificate. +The `object` argument can be either a string containing a PEM encoded object, +which can be an RSA public key, a DSA public key, or an X.509 certificate, +or an object with one or more of the following properties: + +* `key`: {string} - PEM encoded public key (required) +* `padding`: {integer} - Optional padding value for RSA, one of the following: + * `crypto.constants.RSA_PKCS1_PADDING` (default) + * `crypto.constants.RSA_PKCS1_PSS_PADDING` + + Note that `RSA_PKCS1_PSS_PADDING` will use MGF1 with the same hash function + used to verify the message as specified in section 3.1 of [RFC 4055][]. +* `saltLength`: {integer} - salt length for when padding is + `RSA_PKCS1_PSS_PADDING`. The special value + `crypto.constants.RSA_PSS_SALTLEN_DIGEST` sets the salt length to the digest + size, `crypto.constants.RSA_PSS_SALTLEN_AUTO` (default) causes it to be + determined automatically. + The `signature` argument is the previously calculated signature for the data, in the `signature_format` which can be `'latin1'`, `'hex'` or `'base64'`. If a `signature_format` is specified, the `signature` is expected to be a @@ -1900,6 +1937,21 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL. RSA_PKCS1_PSS_PADDING + + RSA_PSS_SALTLEN_DIGEST + Sets the salt length for `RSA_PKCS1_PSS_PADDING` to the digest size + when signing or verifying. + + + RSA_PSS_SALTLEN_MAX_SIGN + Sets the salt length for `RSA_PKCS1_PSS_PADDING` to the maximum + permissible value when signing data. + + + RSA_PSS_SALTLEN_AUTO + Causes the salt length for `RSA_PKCS1_PSS_PADDING` to be determined + automatically when verifying a signature. + POINT_CONVERSION_COMPRESSED @@ -1975,6 +2027,7 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL. [publicly trusted list of CAs]: https://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt [RFC 2412]: https://www.rfc-editor.org/rfc/rfc2412.txt [RFC 3526]: https://www.rfc-editor.org/rfc/rfc3526.txt +[RFC 4055]: https://www.rfc-editor.org/rfc/rfc4055.txt [stream]: stream.html [stream-writable-write]: stream.html#stream_writable_write_chunk_encoding_callback [Crypto Constants]: #crypto_crypto_constants_1 diff --git a/lib/crypto.js b/lib/crypto.js index f2dede115487b0..f4eeb7baa3aad5 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -283,7 +283,28 @@ Sign.prototype.sign = function sign(options, encoding) { var key = options.key || options; var passphrase = options.passphrase || null; - var ret = this._handle.sign(toBuf(key), null, passphrase); + + // Options specific to RSA + var rsaPadding = constants.RSA_PKCS1_PADDING; + if (options.hasOwnProperty('padding')) { + if (options.padding === options.padding >> 0) { + rsaPadding = options.padding; + } else { + throw new TypeError('padding must be an integer'); + } + } + + var pssSaltLength = constants.RSA_PSS_SALTLEN_AUTO; + if (options.hasOwnProperty('saltLength')) { + if (options.saltLength === options.saltLength >> 0) { + pssSaltLength = options.saltLength; + } else { + throw new TypeError('saltLength must be an integer'); + } + } + + var ret = this._handle.sign(toBuf(key), null, passphrase, rsaPadding, + pssSaltLength); encoding = encoding || exports.DEFAULT_ENCODING; if (encoding && encoding !== 'buffer') @@ -309,9 +330,31 @@ util.inherits(Verify, stream.Writable); Verify.prototype._write = Sign.prototype._write; Verify.prototype.update = Sign.prototype.update; -Verify.prototype.verify = function verify(object, signature, sigEncoding) { +Verify.prototype.verify = function verify(options, signature, sigEncoding) { + var key = options.key || options; sigEncoding = sigEncoding || exports.DEFAULT_ENCODING; - return this._handle.verify(toBuf(object), toBuf(signature, sigEncoding)); + + // Options specific to RSA + var rsaPadding = constants.RSA_PKCS1_PADDING; + if (options.hasOwnProperty('padding')) { + if (options.padding === options.padding >> 0) { + rsaPadding = options.padding; + } else { + throw new TypeError('padding must be an integer'); + } + } + + var pssSaltLength = constants.RSA_PSS_SALTLEN_AUTO; + if (options.hasOwnProperty('saltLength')) { + if (options.saltLength === options.saltLength >> 0) { + pssSaltLength = options.saltLength; + } else { + throw new TypeError('saltLength must be an integer'); + } + } + + return this._handle.verify(toBuf(key), toBuf(signature, sigEncoding), null, + rsaPadding, pssSaltLength); }; function rsaPublic(method, defaultPadding) { diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js index de215dc2dce62b..310dc9dd029375 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -317,20 +317,14 @@ } function tryGetCwd(path) { - var threw = true; - var cwd; try { - cwd = process.cwd(); - threw = false; - } finally { - if (threw) { - // getcwd(3) can fail if the current working directory has been deleted. - // Fall back to the directory name of the (absolute) executable path. - // It's not really correct but what are the alternatives? - return path.dirname(process.execPath); - } + return process.cwd(); + } catch (ex) { + // getcwd(3) can fail if the current working directory has been deleted. + // Fall back to the directory name of the (absolute) executable path. + // It's not really correct but what are the alternatives? + return path.dirname(process.execPath); } - return cwd; } function evalScript(name) { diff --git a/lib/path.js b/lib/path.js index dc8a33f4996e5f..2d09eb595a2e2a 100644 --- a/lib/path.js +++ b/lib/path.js @@ -14,6 +14,7 @@ function normalizeStringWin32(path, allowAboveRoot) { var lastSlash = -1; var dots = 0; var code; + var isAboveRoot = false; for (var i = 0; i <= path.length; ++i) { if (i < path.length) code = path.charCodeAt(i); @@ -25,7 +26,7 @@ function normalizeStringWin32(path, allowAboveRoot) { if (lastSlash === i - 1 || dots === 1) { // NOOP } else if (lastSlash !== i - 1 && dots === 2) { - if (res.length < 2 || + if (res.length < 2 || !isAboveRoot || res.charCodeAt(res.length - 1) !== 46/*.*/ || res.charCodeAt(res.length - 2) !== 46/*.*/) { if (res.length > 2) { @@ -42,12 +43,14 @@ function normalizeStringWin32(path, allowAboveRoot) { res = res.slice(0, j); lastSlash = i; dots = 0; + isAboveRoot = false; continue; } } else if (res.length === 2 || res.length === 1) { res = ''; lastSlash = i; dots = 0; + isAboveRoot = false; continue; } } @@ -56,12 +59,14 @@ function normalizeStringWin32(path, allowAboveRoot) { res += '\\..'; else res = '..'; + isAboveRoot = true; } } else { if (res.length > 0) res += '\\' + path.slice(lastSlash + 1, i); else res = path.slice(lastSlash + 1, i); + isAboveRoot = false; } lastSlash = i; dots = 0; @@ -80,6 +85,7 @@ function normalizeStringPosix(path, allowAboveRoot) { var lastSlash = -1; var dots = 0; var code; + var isAboveRoot = false; for (var i = 0; i <= path.length; ++i) { if (i < path.length) code = path.charCodeAt(i); @@ -91,7 +97,7 @@ function normalizeStringPosix(path, allowAboveRoot) { if (lastSlash === i - 1 || dots === 1) { // NOOP } else if (lastSlash !== i - 1 && dots === 2) { - if (res.length < 2 || + if (res.length < 2 || !isAboveRoot || res.charCodeAt(res.length - 1) !== 46/*.*/ || res.charCodeAt(res.length - 2) !== 46/*.*/) { if (res.length > 2) { @@ -108,12 +114,14 @@ function normalizeStringPosix(path, allowAboveRoot) { res = res.slice(0, j); lastSlash = i; dots = 0; + isAboveRoot = false; continue; } } else if (res.length === 2 || res.length === 1) { res = ''; lastSlash = i; dots = 0; + isAboveRoot = false; continue; } } @@ -122,12 +130,14 @@ function normalizeStringPosix(path, allowAboveRoot) { res += '/..'; else res = '..'; + isAboveRoot = true; } } else { if (res.length > 0) res += '/' + path.slice(lastSlash + 1, i); else res = path.slice(lastSlash + 1, i); + isAboveRoot = false; } lastSlash = i; dots = 0; diff --git a/src/node_constants.cc b/src/node_constants.cc index 750df9c669bad3..be6e034a923a92 100644 --- a/src/node_constants.cc +++ b/src/node_constants.cc @@ -974,6 +974,18 @@ void DefineOpenSSLConstants(Local target) { NODE_DEFINE_CONSTANT(target, RSA_PKCS1_PSS_PADDING); #endif +#ifdef RSA_PSS_SALTLEN_DIGEST + NODE_DEFINE_CONSTANT(target, RSA_PSS_SALTLEN_DIGEST); +#endif + +#ifdef RSA_PSS_SALTLEN_MAX_SIGN + NODE_DEFINE_CONSTANT(target, RSA_PSS_SALTLEN_MAX_SIGN); +#endif + +#ifdef RSA_PSS_SALTLEN_AUTO + NODE_DEFINE_CONSTANT(target, RSA_PSS_SALTLEN_AUTO); +#endif + #if HAVE_OPENSSL // NOTE: These are not defines NODE_DEFINE_CONSTANT(target, POINT_CONVERSION_COMPRESSED); diff --git a/src/node_constants.h b/src/node_constants.h index 7ba6ec3bd1b015..db365f3d87a7fc 100644 --- a/src/node_constants.h +++ b/src/node_constants.h @@ -7,6 +7,19 @@ #include "v8.h" #if HAVE_OPENSSL + +#ifndef RSA_PSS_SALTLEN_DIGEST +#define RSA_PSS_SALTLEN_DIGEST -1 +#endif + +#ifndef RSA_PSS_SALTLEN_MAX_SIGN +#define RSA_PSS_SALTLEN_MAX_SIGN -2 +#endif + +#ifndef RSA_PSS_SALTLEN_AUTO +#define RSA_PSS_SALTLEN_AUTO -2 +#endif + #define DEFAULT_CIPHER_LIST_CORE "ECDHE-RSA-AES128-GCM-SHA256:" \ "ECDHE-ECDSA-AES128-GCM-SHA256:" \ "ECDHE-RSA-AES256-GCM-SHA384:" \ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index aa2dafebc5cf81..335972bab2a466 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -1,5 +1,6 @@ #include "node.h" #include "node_buffer.h" +#include "node_constants.h" #include "node_crypto.h" #include "node_crypto_bio.h" #include "node_crypto_groups.h" @@ -80,6 +81,7 @@ using v8::HandleScope; using v8::Integer; using v8::Isolate; using v8::Local; +using v8::Maybe; using v8::Null; using v8::Object; using v8::Persistent; @@ -3990,6 +3992,19 @@ void SignBase::CheckThrow(SignBase::Error error) { } } +static bool ApplyRSAOptions(EVP_PKEY* pkey, EVP_PKEY_CTX* pkctx, int padding, + int salt_len) { + if (pkey->type == EVP_PKEY_RSA || pkey->type == EVP_PKEY_RSA2) { + if (EVP_PKEY_CTX_set_rsa_padding(pkctx, padding) <= 0) + return false; + if (padding == RSA_PKCS1_PSS_PADDING) { + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkctx, salt_len) <= 0) + return false; + } + } + + return true; +} @@ -4019,7 +4034,7 @@ SignBase::Error Sign::SignInit(const char* sign_type) { return kSignUnknownDigest; EVP_MD_CTX_init(&mdctx_); - if (!EVP_SignInit_ex(&mdctx_, md, nullptr)) + if (!EVP_DigestInit_ex(&mdctx_, md, nullptr)) return kSignInit; initialised_ = true; @@ -4046,7 +4061,7 @@ void Sign::SignInit(const FunctionCallbackInfo& args) { SignBase::Error Sign::SignUpdate(const char* data, int len) { if (!initialised_) return kSignNotInitialised; - if (!EVP_SignUpdate(&mdctx_, data, len)) + if (!EVP_DigestUpdate(&mdctx_, data, len)) return kSignUpdate; return kSignOk; } @@ -4076,12 +4091,54 @@ void Sign::SignUpdate(const FunctionCallbackInfo& args) { sign->CheckThrow(err); } +static int Node_SignFinal(EVP_MD_CTX* mdctx, unsigned char* md, + unsigned int* sig_len, EVP_PKEY* pkey, int padding, + int pss_salt_len) { + unsigned char m[EVP_MAX_MD_SIZE]; + unsigned int m_len; + int rv = 0; + EVP_PKEY_CTX* pkctx = nullptr; + + *sig_len = 0; + if (!EVP_DigestFinal_ex(mdctx, m, &m_len)) + return rv; + + if (mdctx->digest->flags & EVP_MD_FLAG_PKEY_METHOD_SIGNATURE) { + size_t sltmp = static_cast(EVP_PKEY_size(pkey)); + pkctx = EVP_PKEY_CTX_new(pkey, nullptr); + if (pkctx == nullptr) + goto err; + if (EVP_PKEY_sign_init(pkctx) <= 0) + goto err; + if (!ApplyRSAOptions(pkey, pkctx, padding, pss_salt_len)) + goto err; + if (EVP_PKEY_CTX_set_signature_md(pkctx, mdctx->digest) <= 0) + goto err; + if (EVP_PKEY_sign(pkctx, md, &sltmp, m, m_len) <= 0) + goto err; + *sig_len = sltmp; + rv = 1; + err: + EVP_PKEY_CTX_free(pkctx); + return rv; + } + + if (mdctx->digest->sign == nullptr) { + EVPerr(EVP_F_EVP_SIGNFINAL, EVP_R_NO_SIGN_FUNCTION_CONFIGURED); + return 0; + } + + return mdctx->digest->sign(mdctx->digest->type, m, m_len, md, sig_len, + pkey->pkey.ptr); +} SignBase::Error Sign::SignFinal(const char* key_pem, int key_pem_len, const char* passphrase, unsigned char** sig, - unsigned int *sig_len) { + unsigned int* sig_len, + int padding, + int salt_len) { if (!initialised_) return kSignNotInitialised; @@ -4127,7 +4184,7 @@ SignBase::Error Sign::SignFinal(const char* key_pem, } #endif // NODE_FIPS_MODE - if (EVP_SignFinal(&mdctx_, *sig, sig_len, pkey)) + if (Node_SignFinal(&mdctx_, *sig, sig_len, pkey, padding, salt_len)) fatal = false; initialised_ = false; @@ -4168,6 +4225,16 @@ void Sign::SignFinal(const FunctionCallbackInfo& args) { size_t buf_len = Buffer::Length(args[0]); char* buf = Buffer::Data(args[0]); + CHECK(args[3]->IsInt32()); + Maybe maybe_padding = args[3]->Int32Value(env->context()); + CHECK(maybe_padding.IsJust()); + int padding = maybe_padding.FromJust(); + + CHECK(args[4]->IsInt32()); + Maybe maybe_salt_len = args[4]->Int32Value(env->context()); + CHECK(maybe_salt_len.IsJust()); + int salt_len = maybe_salt_len.FromJust(); + md_len = 8192; // Maximum key size is 8192 bits md_value = new unsigned char[md_len]; @@ -4179,7 +4246,9 @@ void Sign::SignFinal(const FunctionCallbackInfo& args) { buf_len, len >= 3 && !args[2]->IsNull() ? *passphrase : nullptr, &md_value, - &md_len); + &md_len, + padding, + salt_len); if (err != kSignOk) { delete[] md_value; md_value = nullptr; @@ -4223,7 +4292,7 @@ SignBase::Error Verify::VerifyInit(const char* verify_type) { return kSignUnknownDigest; EVP_MD_CTX_init(&mdctx_); - if (!EVP_VerifyInit_ex(&mdctx_, md, nullptr)) + if (!EVP_DigestInit_ex(&mdctx_, md, nullptr)) return kSignInit; initialised_ = true; @@ -4251,7 +4320,7 @@ SignBase::Error Verify::VerifyUpdate(const char* data, int len) { if (!initialised_) return kSignNotInitialised; - if (!EVP_VerifyUpdate(&mdctx_, data, len)) + if (!EVP_DigestUpdate(&mdctx_, data, len)) return kSignUpdate; return kSignOk; @@ -4287,6 +4356,8 @@ SignBase::Error Verify::VerifyFinal(const char* key_pem, int key_pem_len, const char* sig, int siglen, + int padding, + int saltlen, bool* verify_result) { if (!initialised_) return kSignNotInitialised; @@ -4298,7 +4369,10 @@ SignBase::Error Verify::VerifyFinal(const char* key_pem, BIO* bp = nullptr; X509* x509 = nullptr; bool fatal = true; + unsigned char m[EVP_MAX_MD_SIZE]; + unsigned int m_len; int r = 0; + EVP_PKEY_CTX* pkctx = nullptr; bp = BIO_new_mem_buf(const_cast(key_pem), key_pem_len); if (bp == nullptr) @@ -4333,11 +4407,29 @@ SignBase::Error Verify::VerifyFinal(const char* key_pem, goto exit; } + if (!EVP_DigestFinal_ex(&mdctx_, m, &m_len)) { + goto exit; + } + fatal = false; - r = EVP_VerifyFinal(&mdctx_, + + pkctx = EVP_PKEY_CTX_new(pkey, nullptr); + if (pkctx == nullptr) + goto err; + if (EVP_PKEY_verify_init(pkctx) <= 0) + goto err; + if (!ApplyRSAOptions(pkey, pkctx, padding, saltlen)) + goto err; + if (EVP_PKEY_CTX_set_signature_md(pkctx, mdctx_.digest) <= 0) + goto err; + r = EVP_PKEY_verify(pkctx, reinterpret_cast(sig), siglen, - pkey); + m, + m_len); + + err: + EVP_PKEY_CTX_free(pkctx); exit: if (pkey != nullptr) @@ -4391,8 +4483,19 @@ void Verify::VerifyFinal(const FunctionCallbackInfo& args) { hbuf = Buffer::Data(args[1]); } + CHECK(args[3]->IsInt32()); + Maybe maybe_padding = args[3]->Int32Value(env->context()); + CHECK(maybe_padding.IsJust()); + int padding = maybe_padding.FromJust(); + + CHECK(args[4]->IsInt32()); + Maybe maybe_salt_len = args[4]->Int32Value(env->context()); + CHECK(maybe_salt_len.IsJust()); + int salt_len = maybe_salt_len.FromJust(); + bool verify_result; - Error err = verify->VerifyFinal(kbuf, klen, hbuf, hlen, &verify_result); + Error err = verify->VerifyFinal(kbuf, klen, hbuf, hlen, padding, salt_len, + &verify_result); if (args[1]->IsString()) delete[] hbuf; if (err != kSignOk) diff --git a/src/node_crypto.h b/src/node_crypto.h index 746c954b26fb43..373d859530bcf5 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -569,7 +569,9 @@ class Sign : public SignBase { int key_pem_len, const char* passphrase, unsigned char** sig, - unsigned int *sig_len); + unsigned int *sig_len, + int padding, + int saltlen); protected: static void New(const v8::FunctionCallbackInfo& args); @@ -592,6 +594,8 @@ class Verify : public SignBase { int key_pem_len, const char* sig, int siglen, + int padding, + int saltlen, bool* verify_result); protected: diff --git a/test/fixtures/pss-vectors.json b/test/fixtures/pss-vectors.json new file mode 100644 index 00000000000000..b540d13a540646 --- /dev/null +++ b/test/fixtures/pss-vectors.json @@ -0,0 +1,89 @@ +{ + "example01": { + "publicKey": [ + "-----BEGIN PUBLIC KEY-----", + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClbkoOcBAXWJpRh9x+qEHRVvLs", + "DjatUqRN/rHmH3rZkdjFEFb/7bFitMDyg6EqiKOU3/Umq3KRy7MHzqv84LHf1c2V", + "CAltWyuLbfXWce9jd8CSHLI8Jwpw4lmOb/idGfEFrMLT8Ms18pKA4Thrb2TE7yLh", + "4fINDOjP+yJJvZohNwIDAQAB", + "-----END PUBLIC KEY-----" + ], + "tests": [ + { + "message": "cdc87da223d786df3b45e0bbbc721326d1ee2af806cc315475cc6f0d9c66e1b62371d45ce2392e1ac92844c310102f156a0d8d52c1f4c40ba3aa65095786cb769757a6563ba958fed0bcc984e8b517a3d5f515b23b8a41e74aa867693f90dfb061a6e86dfaaee64472c00e5f20945729cbebe77f06ce78e08f4098fba41f9d6193c0317e8b60d4b6084acb42d29e3808a3bc372d85e331170fcbf7cc72d0b71c296648b3a4d10f416295d0807aa625cab2744fd9ea8fd223c42537029828bd16be02546f130fd2e33b936d2676e08aed1b73318b750a0167d0", + "salt": "dee959c7e06411361420ff80185ed57f3e6776af", + "signature": "9074308fb598e9701b2294388e52f971faac2b60a5145af185df5287b5ed2887e57ce7fd44dc8634e407c8e0e4360bc226f3ec227f9d9e54638e8d31f5051215df6ebb9c2f9579aa77598a38f914b5b9c1bd83c4e2f9f382a0d0aa3542ffee65984a601bc69eb28deb27dca12c82c2d4c3f66cd500f1ff2b994d8a4e30cbb33c" + }, + { + "message": "851384cdfe819c22ed6c4ccb30daeb5cf059bc8e1166b7e3530c4c233e2b5f8f71a1cca582d43ecc72b1bca16dfc7013226b9e", + "salt": "ef2869fa40c346cb183dab3d7bffc98fd56df42d", + "signature": "3ef7f46e831bf92b32274142a585ffcefbdca7b32ae90d10fb0f0c729984f04ef29a9df0780775ce43739b97838390db0a5505e63de927028d9d29b219ca2c4517832558a55d694a6d25b9dab66003c4cccd907802193be5170d26147d37b93590241be51c25055f47ef62752cfbe21418fafe98c22c4d4d47724fdb5669e843" + }, + { + "message": "a4b159941761c40c6a82f2b80d1b94f5aa2654fd17e12d588864679b54cd04ef8bd03012be8dc37f4b83af7963faff0dfa225477437c48017ff2be8191cf3955fc07356eab3f322f7f620e21d254e5db4324279fe067e0910e2e81ca2cab31c745e67a54058eb50d993cdb9ed0b4d029c06d21a94ca661c3ce27fae1d6cb20f4564d66ce4767583d0e5f060215b59017be85ea848939127bd8c9c4d47b51056c031cf336f17c9980f3b8f5b9b6878e8b797aa43b882684333e17893fe9caa6aa299f7ed1a18ee2c54864b7b2b99b72618fb02574d139ef50f019c9eef416971338e7d470", + "salt": "710b9c4747d800d4de87f12afdce6df18107cc77", + "signature": "666026fba71bd3e7cf13157cc2c51a8e4aa684af9778f91849f34335d141c00154c4197621f9624a675b5abc22ee7d5baaffaae1c9baca2cc373b3f33e78e6143c395a91aa7faca664eb733afd14d8827259d99a7550faca501ef2b04e33c23aa51f4b9e8282efdb728cc0ab09405a91607c6369961bc8270d2d4f39fce612b1" + }, + { + "message": "bc656747fa9eafb3f0", + "salt": "056f00985de14d8ef5cea9e82f8c27bef720335e", + "signature": "4609793b23e9d09362dc21bb47da0b4f3a7622649a47d464019b9aeafe53359c178c91cd58ba6bcb78be0346a7bc637f4b873d4bab38ee661f199634c547a1ad8442e03da015b136e543f7ab07c0c13e4225b8de8cce25d4f6eb8400f81f7e1833b7ee6e334d370964ca79fdb872b4d75223b5eeb08101591fb532d155a6de87" + }, + { + "message": "b45581547e5427770c768e8b82b75564e0ea4e9c32594d6bff706544de0a8776c7a80b4576550eee1b2acabc7e8b7d3ef7bb5b03e462c11047eadd00629ae575480ac1470fe046f13a2bf5af17921dc4b0aa8b02bee6334911651d7f8525d10f32b51d33be520d3ddf5a709955a3dfe78283b9e0ab54046d150c177f037fdccc5be4ea5f68b5e5a38c9d7edcccc4975f455a6909b4", + "salt": "80e70ff86a08de3ec60972b39b4fbfdcea67ae8e", + "signature": "1d2aad221ca4d31ddf13509239019398e3d14b32dc34dc5af4aeaea3c095af73479cf0a45e5629635a53a018377615b16cb9b13b3e09d671eb71e387b8545c5960da5a64776e768e82b2c93583bf104c3fdb23512b7b4e89f633dd0063a530db4524b01c3f384c09310e315a79dcd3d684022a7f31c865a664e316978b759fad" + }, + { + "message": "10aae9a0ab0b595d0841207b700d48d75faedde3b775cd6b4cc88ae06e4694ec74ba18f8520d4f5ea69cbbe7cc2beba43efdc10215ac4eb32dc302a1f53dc6c4352267e7936cfebf7c8d67035784a3909fa859c7b7b59b8e39c5c2349f1886b705a30267d402f7486ab4f58cad5d69adb17ab8cd0ce1caf5025af4ae24b1fb8794c6070cc09a51e2f9911311e3877d0044c71c57a993395008806b723ac38373d395481818528c1e7053739282053529510e935cd0fa77b8fa53cc2d474bd4fb3cc5c672d6ffdc90a00f9848712c4bcfe46c60573659b11e6457e861f0f604b6138d144f8ce4e2da73", + "salt": "a8ab69dd801f0074c2a1fc60649836c616d99681", + "signature": "2a34f6125e1f6b0bf971e84fbd41c632be8f2c2ace7de8b6926e31ff93e9af987fbc06e51e9be14f5198f91f3f953bd67da60a9df59764c3dc0fe08e1cbef0b75f868d10ad3fba749fef59fb6dac46a0d6e504369331586f58e4628f39aa278982543bc0eeb537dc61958019b394fb273f215858a0a01ac4d650b955c67f4c58" + } + ] + }, + "example10": { + "publicKey": [ + "-----BEGIN PUBLIC KEY-----", + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApd2GesTLAvkLlFfUjBSn", + "cO+ZHFbDnA7GX9Ea+ok3zqV7m+esc7RcABdhW4LWIuMYdTtgJ8D9FXvhL4CQ/uKn", + "rc0O73WfiLpJl8ekLVjJqhLLma4AH+UhwTu1QxRFqNWuT15MfpSKwifTYEBx8g5X", + "fpBfvrFd+vBtHeWuYlPWOmohILMaXaXavJVQYA4g8n03OeJieSX+o8xQnyHf8E5u", + "6kVJxUDWgJ/5MH7t6R//WHM9g4WiN9bTcFoz45GQCZIHDfet8TV89+NwDONmfeg/", + "F7jfF3jbOB3OCctK0FilEQAac4GY7ifPVaE7dUU5kGWC7IsXS9WNXR89dnxhNyGu", + "BQIDAQAB", + "-----END PUBLIC KEY-----" + ], + "tests": [ + { + "message": "883177e5126b9be2d9a9680327d5370c6f26861f5820c43da67a3ad609", + "salt": "04e215ee6ff934b9da70d7730c8734abfcecde89", + "signature": "82c2b160093b8aa3c0f7522b19f87354066c77847abf2a9fce542d0e84e920c5afb49ffdfdace16560ee94a1369601148ebad7a0e151cf16331791a5727d05f21e74e7eb811440206935d744765a15e79f015cb66c532c87a6a05961c8bfad741a9a6657022894393e7223739796c02a77455d0f555b0ec01ddf259b6207fd0fd57614cef1a5573baaff4ec00069951659b85f24300a25160ca8522dc6e6727e57d019d7e63629b8fe5e89e25cc15beb3a647577559299280b9b28f79b0409000be25bbd96408ba3b43cc486184dd1c8e62553fa1af4040f60663de7f5e49c04388e257f1ce89c95dab48a315d9b66b1b7628233876ff2385230d070d07e1666" + }, + { + "message": "dd670a01465868adc93f26131957a50c52fb777cdbaa30892c9e12361164ec13979d43048118e4445db87bee58dd987b3425d02071d8dbae80708b039dbb64dbd1de5657d9fed0c118a54143742e0ff3c87f74e45857647af3f79eb0a14c9d75ea9a1a04b7cf478a897a708fd988f48e801edb0b7039df8c23bb3c56f4e821ac", + "salt": "8b2bdd4b40faf545c778ddf9bc1a49cb57f9b71b", + "signature": "14ae35d9dd06ba92f7f3b897978aed7cd4bf5ff0b585a40bd46ce1b42cd2703053bb9044d64e813d8f96db2dd7007d10118f6f8f8496097ad75e1ff692341b2892ad55a633a1c55e7f0a0ad59a0e203a5b8278aec54dd8622e2831d87174f8caff43ee6c46445345d84a59659bfb92ecd4c818668695f34706f66828a89959637f2bf3e3251c24bdba4d4b7649da0022218b119c84e79a6527ec5b8a5f861c159952e23ec05e1e717346faefe8b1686825bd2b262fb2531066c0de09acde2e4231690728b5d85e115a2f6b92b79c25abc9bd9399ff8bcf825a52ea1f56ea76dd26f43baafa18bfa92a504cbd35699e26d1dcc5a2887385f3c63232f06f3244c3" + }, + { + "message": "48b2b6a57a63c84cea859d65c668284b08d96bdcaabe252db0e4a96cb1bac6019341db6fbefb8d106b0e90eda6bcc6c6262f37e7ea9c7e5d226bd7df85ec5e71efff2f54c5db577ff729ff91b842491de2741d0c631607df586b905b23b91af13da12304bf83eca8a73e871ff9db", + "salt": "4e96fc1b398f92b44671010c0dc3efd6e20c2d73", + "signature": "6e3e4d7b6b15d2fb46013b8900aa5bbb3939cf2c095717987042026ee62c74c54cffd5d7d57efbbf950a0f5c574fa09d3fc1c9f513b05b4ff50dd8df7edfa20102854c35e592180119a70ce5b085182aa02d9ea2aa90d1df03f2daae885ba2f5d05afdac97476f06b93b5bc94a1a80aa9116c4d615f333b098892b25fface266f5db5a5a3bcc10a824ed55aad35b727834fb8c07da28fcf416a5d9b2224f1f8b442b36f91e456fdea2d7cfe3367268de0307a4c74e924159ed33393d5e0655531c77327b89821bdedf880161c78cd4196b5419f7acc3f13e5ebf161b6e7c6724716ca33b85c2e25640192ac2859651d50bde7eb976e51cec828b98b6563b86bb" + }, + { + "message": "0b8777c7f839baf0a64bbbdbc5ce79755c57a205b845c174e2d2e90546a089c4e6ec8adffa23a7ea97bae6b65d782b82db5d2b5a56d22a29a05e7c4433e2b82a621abba90add05ce393fc48a840542451a", + "salt": "c7cd698d84b65128d8835e3a8b1eb0e01cb541ec", + "signature": "34047ff96c4dc0dc90b2d4ff59a1a361a4754b255d2ee0af7d8bf87c9bc9e7ddeede33934c63ca1c0e3d262cb145ef932a1f2c0a997aa6a34f8eaee7477d82ccf09095a6b8acad38d4eec9fb7eab7ad02da1d11d8e54c1825e55bf58c2a23234b902be124f9e9038a8f68fa45dab72f66e0945bf1d8bacc9044c6f07098c9fcec58a3aab100c805178155f030a124c450e5acbda47d0e4f10b80a23f803e774d023b0015c20b9f9bbe7c91296338d5ecb471cafb032007b67a60be5f69504a9f01abb3cb467b260e2bce860be8d95bf92c0c8e1496ed1e528593a4abb6df462dde8a0968dffe4683116857a232f5ebf6c85be238745ad0f38f767a5fdbf486fb" + }, + { + "message": "f1036e008e71e964dadc9219ed30e17f06b4b68a955c16b312b1eddf028b74976bed6b3f6a63d4e77859243c9cccdc98016523abb02483b35591c33aad81213bb7c7bb1a470aabc10d44256c4d4559d916", + "salt": "efa8bff96212b2f4a3f371a10d574152655f5dfb", + "signature": "7e0935ea18f4d6c1d17ce82eb2b3836c55b384589ce19dfe743363ac9948d1f346b7bfddfe92efd78adb21faefc89ade42b10f374003fe122e67429a1cb8cbd1f8d9014564c44d120116f4990f1a6e38774c194bd1b8213286b077b0499d2e7b3f434ab12289c556684deed78131934bb3dd6537236f7c6f3dcb09d476be07721e37e1ceed9b2f7b406887bd53157305e1c8b4f84d733bc1e186fe06cc59b6edb8f4bd7ffefdf4f7ba9cfb9d570689b5a1a4109a746a690893db3799255a0cb9215d2d1cd490590e952e8c8786aa0011265252470c041dfbc3eec7c3cbf71c24869d115c0cb4a956f56d530b80ab589acfefc690751ddf36e8d383f83cedd2cc" + }, + { + "message": "25f10895a87716c137450bb9519dfaa1f207faa942ea88abf71e9c17980085b555aebab76264ae2a3ab93c2d12981191ddac6fb5949eb36aee3c5da940f00752c916d94608fa7d97ba6a2915b688f20323d4e9d96801d89a72ab5892dc2117c07434fcf972e058cf8c41ca4b4ff554f7d5068ad3155fced0f3125bc04f9193378a8f5c4c3b8cb4dd6d1cc69d30ecca6eaa51e36a05730e9e342e855baf099defb8afd7", + "salt": "ad8b1523703646224b660b550885917ca2d1df28", + "signature": "6d3b5b87f67ea657af21f75441977d2180f91b2c5f692de82955696a686730d9b9778d970758ccb26071c2209ffbd6125be2e96ea81b67cb9b9308239fda17f7b2b64ecda096b6b935640a5a1cb42a9155b1c9ef7a633a02c59f0d6ee59b852c43b35029e73c940ff0410e8f114eed46bbd0fae165e42be2528a401c3b28fd818ef3232dca9f4d2a0f5166ec59c42396d6c11dbc1215a56fa17169db9575343ef34f9de32a49cdc3174922f229c23e18e45df9353119ec4319cedce7a17c64088c1f6f52be29634100b3919d38f3d1ed94e6891e66a73b8fb849f5874df59459e298c7bbce2eee782a195aa66fe2d0732b25e595f57d3e061b1fc3e4063bf98f" + } + ] + } +} \ No newline at end of file diff --git a/test/parallel/test-crypto-sign-verify.js b/test/parallel/test-crypto-sign-verify.js index a9a2cac78828ab..fa410de4a62b4f 100644 --- a/test/parallel/test-crypto-sign-verify.js +++ b/test/parallel/test-crypto-sign-verify.js @@ -4,12 +4,20 @@ if (!common.hasCrypto) common.skip('missing crypto'); const assert = require('assert'); +const exec = require('child_process').exec; const fs = require('fs'); +const path = require('path'); + +if (!common.hasCrypto) { + common.skip('missing crypto'); + return; +} const crypto = require('crypto'); // Test certificates const certPem = fs.readFileSync(`${common.fixturesDir}/test_cert.pem`, 'ascii'); const keyPem = fs.readFileSync(`${common.fixturesDir}/test_key.pem`, 'ascii'); +const modSize = 1024; // Test signing and verifying { @@ -69,9 +77,203 @@ const keyPem = fs.readFileSync(`${common.fixturesDir}/test_key.pem`, 'ascii'); assert.strictEqual(verified, true, 'sign and verify (stream)'); } +// Special tests for RSA_PKCS1_PSS_PADDING +{ + function testPSS(algo, hLen) { + // Maximum permissible salt length + const max = modSize / 8 - hLen - 2; + + function getEffectiveSaltLength(saltLength) { + switch (saltLength) { + case crypto.constants.RSA_PSS_SALTLEN_DIGEST: + return hLen; + case crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN: + return max; + default: + return saltLength; + } + } + + const signSaltLengths = [ + crypto.constants.RSA_PSS_SALTLEN_DIGEST, + getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_DIGEST), + crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN, + getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN), + 0, 16, 32, 64, 128 + ]; + + const verifySaltLengths = [ + crypto.constants.RSA_PSS_SALTLEN_DIGEST, + getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_DIGEST), + getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN), + 0, 16, 32, 64, 128 + ]; + + signSaltLengths.forEach((signSaltLength) => { + if (signSaltLength > max) { + // If the salt length is too big, an Error should be thrown + assert.throws(() => { + crypto.createSign(algo) + .update('Test123') + .sign({ + key: keyPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: signSaltLength + }); + }, /^Error:.*data too large for key size$/); + } else { + // Otherwise, a valid signature should be generated + const s4 = crypto.createSign(algo) + .update('Test123') + .sign({ + key: keyPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: signSaltLength + }); + + let verified; + verifySaltLengths.forEach((verifySaltLength) => { + // Verification should succeed if and only if the salt length is + // correct + verified = crypto.createVerify(algo) + .update('Test123') + .verify({ + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: verifySaltLength + }, s4); + const saltLengthCorrect = getEffectiveSaltLength(signSaltLength) == + getEffectiveSaltLength(verifySaltLength); + assert.strictEqual(verified, saltLengthCorrect, 'verify (PSS)'); + }); + + // Verification using RSA_PSS_SALTLEN_AUTO should always work + verified = crypto.createVerify(algo) + .update('Test123') + .verify({ + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO + }, s4); + assert.strictEqual(verified, true, 'verify (PSS with SALTLEN_AUTO)'); + + // Verifying an incorrect message should never work + verified = crypto.createVerify(algo) + .update('Test1234') + .verify({ + key: certPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO + }, s4); + assert.strictEqual(verified, false, 'verify (PSS, incorrect)'); + } + }); + } + + testPSS('RSA-SHA1', 20); + testPSS('RSA-SHA256', 32); +} + +// Test vectors for RSA_PKCS1_PSS_PADDING provided by the RSA Laboratories: +// https://www.emc.com/emc-plus/rsa-labs/standards-initiatives/pkcs-rsa-cryptography-standard.htm +{ + // We only test verification as we cannot specify explicit salts when signing + function testVerify(cert, vector) { + const verified = crypto.createVerify('RSA-SHA1') + .update(Buffer.from(vector.message, 'hex')) + .verify({ + key: cert, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: vector.salt.length / 2 + }, vector.signature, 'hex'); + assert.strictEqual(verified, true, 'verify (PSS)'); + } + + const vectorfile = path.join(common.fixturesDir, 'pss-vectors.json'); + const examples = JSON.parse(fs.readFileSync(vectorfile, { + encoding: 'utf8' + })); + + for (const key in examples) { + const example = examples[key]; + const publicKey = example.publicKey.join('\n'); + example.tests.forEach((test) => testVerify(publicKey, test)); + } +} + +// Test exceptions for invalid `padding` and `saltLength` values +{ + [null, undefined, NaN, 'boom', {}, [], true, false] + .forEach((invalidValue) => { + assert.throws(() => { + crypto.createSign('RSA-SHA256') + .update('Test123') + .sign({ + key: keyPem, + padding: invalidValue + }); + }, /^TypeError: padding must be an integer$/); + + assert.throws(() => { + crypto.createSign('RSA-SHA256') + .update('Test123') + .sign({ + key: keyPem, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING, + saltLength: invalidValue + }); + }, /^TypeError: saltLength must be an integer$/); + }); + + assert.throws(() => { + crypto.createSign('RSA-SHA1') + .update('Test123') + .sign({ + key: keyPem, + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING + }); + }, /^Error:.*illegal or unsupported padding mode$/); +} + // Test throws exception when key options is null { assert.throws(() => { crypto.createSign('RSA-SHA1').update('Test123').sign(null, 'base64'); }, /^Error: No key provided to sign$/); } + +// RSA-PSS Sign test by verifying with 'openssl dgst -verify' +{ + if (!common.opensslCli) { + common.skip('node compiled without OpenSSL CLI.'); + return; + } + + const pubfile = path.join(common.fixturesDir, 'keys/rsa_public_2048.pem'); + const privfile = path.join(common.fixturesDir, 'keys/rsa_private_2048.pem'); + const privkey = fs.readFileSync(privfile); + + const msg = 'Test123'; + const s5 = crypto.createSign('RSA-SHA256') + .update(msg) + .sign({ + key: privkey, + padding: crypto.constants.RSA_PKCS1_PSS_PADDING + }); + + common.refreshTmpDir(); + + const sigfile = path.join(common.tmpDir, 's5.sig'); + fs.writeFileSync(sigfile, s5); + const msgfile = path.join(common.tmpDir, 's5.msg'); + fs.writeFileSync(msgfile, msg); + + const cmd = '"' + common.opensslCli + '" dgst -sha256 -verify "' + pubfile + + '" -signature "' + sigfile + + '" -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-2 "' + + msgfile + '"'; + + exec(cmd, common.mustCall((err, stdout, stderr) => { + assert(stdout.includes('Verified OK')); + })); +} diff --git a/test/parallel/test-path-basename.js b/test/parallel/test-path-basename.js new file mode 100644 index 00000000000000..6b64e40b7eca7a --- /dev/null +++ b/test/parallel/test-path-basename.js @@ -0,0 +1,70 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); + +assert.strictEqual(path.basename(__filename), 'test-path-basename.js'); +assert.strictEqual(path.basename(__filename, '.js'), 'test-path-basename'); +assert.strictEqual(path.basename('.js', '.js'), ''); +assert.strictEqual(path.basename(''), ''); +assert.strictEqual(path.basename('/dir/basename.ext'), 'basename.ext'); +assert.strictEqual(path.basename('/basename.ext'), 'basename.ext'); +assert.strictEqual(path.basename('basename.ext'), 'basename.ext'); +assert.strictEqual(path.basename('basename.ext/'), 'basename.ext'); +assert.strictEqual(path.basename('basename.ext//'), 'basename.ext'); +assert.strictEqual(path.basename('aaa/bbb', '/bbb'), 'bbb'); +assert.strictEqual(path.basename('aaa/bbb', 'a/bbb'), 'bbb'); +assert.strictEqual(path.basename('aaa/bbb', 'bbb'), 'bbb'); +assert.strictEqual(path.basename('aaa/bbb//', 'bbb'), 'bbb'); +assert.strictEqual(path.basename('aaa/bbb', 'bb'), 'b'); +assert.strictEqual(path.basename('aaa/bbb', 'b'), 'bb'); +assert.strictEqual(path.basename('/aaa/bbb', '/bbb'), 'bbb'); +assert.strictEqual(path.basename('/aaa/bbb', 'a/bbb'), 'bbb'); +assert.strictEqual(path.basename('/aaa/bbb', 'bbb'), 'bbb'); +assert.strictEqual(path.basename('/aaa/bbb//', 'bbb'), 'bbb'); +assert.strictEqual(path.basename('/aaa/bbb', 'bb'), 'b'); +assert.strictEqual(path.basename('/aaa/bbb', 'b'), 'bb'); +assert.strictEqual(path.basename('/aaa/bbb'), 'bbb'); +assert.strictEqual(path.basename('/aaa/'), 'aaa'); +assert.strictEqual(path.basename('/aaa/b'), 'b'); +assert.strictEqual(path.basename('/a/b'), 'b'); +assert.strictEqual(path.basename('//a'), 'a'); + +// On Windows a backslash acts as a path separator. +assert.strictEqual(path.win32.basename('\\dir\\basename.ext'), 'basename.ext'); +assert.strictEqual(path.win32.basename('\\basename.ext'), 'basename.ext'); +assert.strictEqual(path.win32.basename('basename.ext'), 'basename.ext'); +assert.strictEqual(path.win32.basename('basename.ext\\'), 'basename.ext'); +assert.strictEqual(path.win32.basename('basename.ext\\\\'), 'basename.ext'); +assert.strictEqual(path.win32.basename('foo'), 'foo'); +assert.strictEqual(path.win32.basename('aaa\\bbb', '\\bbb'), 'bbb'); +assert.strictEqual(path.win32.basename('aaa\\bbb', 'a\\bbb'), 'bbb'); +assert.strictEqual(path.win32.basename('aaa\\bbb', 'bbb'), 'bbb'); +assert.strictEqual(path.win32.basename('aaa\\bbb\\\\\\\\', 'bbb'), 'bbb'); +assert.strictEqual(path.win32.basename('aaa\\bbb', 'bb'), 'b'); +assert.strictEqual(path.win32.basename('aaa\\bbb', 'b'), 'bb'); +assert.strictEqual(path.win32.basename('C:'), ''); +assert.strictEqual(path.win32.basename('C:.'), '.'); +assert.strictEqual(path.win32.basename('C:\\'), ''); +assert.strictEqual(path.win32.basename('C:\\dir\\base.ext'), 'base.ext'); +assert.strictEqual(path.win32.basename('C:\\basename.ext'), 'basename.ext'); +assert.strictEqual(path.win32.basename('C:basename.ext'), 'basename.ext'); +assert.strictEqual(path.win32.basename('C:basename.ext\\'), 'basename.ext'); +assert.strictEqual(path.win32.basename('C:basename.ext\\\\'), 'basename.ext'); +assert.strictEqual(path.win32.basename('C:foo'), 'foo'); +assert.strictEqual(path.win32.basename('file:stream'), 'file:stream'); + +// On unix a backslash is just treated as any other character. +assert.strictEqual(path.posix.basename('\\dir\\basename.ext'), + '\\dir\\basename.ext'); +assert.strictEqual(path.posix.basename('\\basename.ext'), '\\basename.ext'); +assert.strictEqual(path.posix.basename('basename.ext'), 'basename.ext'); +assert.strictEqual(path.posix.basename('basename.ext\\'), 'basename.ext\\'); +assert.strictEqual(path.posix.basename('basename.ext\\\\'), 'basename.ext\\\\'); +assert.strictEqual(path.posix.basename('foo'), 'foo'); + +// POSIX filenames may include control characters +// c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html +const controlCharFilename = `Icon${String.fromCharCode(13)}`; +assert.strictEqual(path.posix.basename(`/a/b/${controlCharFilename}`), + controlCharFilename); diff --git a/test/parallel/test-path-dirname.js b/test/parallel/test-path-dirname.js new file mode 100644 index 00000000000000..6ecb5c93b4db74 --- /dev/null +++ b/test/parallel/test-path-dirname.js @@ -0,0 +1,56 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const path = require('path'); + +assert.strictEqual(path.dirname(__filename).substr(-13), + common.isWindows ? 'test\\parallel' : 'test/parallel'); + +assert.strictEqual(path.posix.dirname('/a/b/'), '/a'); +assert.strictEqual(path.posix.dirname('/a/b'), '/a'); +assert.strictEqual(path.posix.dirname('/a'), '/'); +assert.strictEqual(path.posix.dirname(''), '.'); +assert.strictEqual(path.posix.dirname('/'), '/'); +assert.strictEqual(path.posix.dirname('////'), '/'); +assert.strictEqual(path.posix.dirname('//a'), '//'); +assert.strictEqual(path.posix.dirname('foo'), '.'); + +assert.strictEqual(path.win32.dirname('c:\\'), 'c:\\'); +assert.strictEqual(path.win32.dirname('c:\\foo'), 'c:\\'); +assert.strictEqual(path.win32.dirname('c:\\foo\\'), 'c:\\'); +assert.strictEqual(path.win32.dirname('c:\\foo\\bar'), 'c:\\foo'); +assert.strictEqual(path.win32.dirname('c:\\foo\\bar\\'), 'c:\\foo'); +assert.strictEqual(path.win32.dirname('c:\\foo\\bar\\baz'), 'c:\\foo\\bar'); +assert.strictEqual(path.win32.dirname('\\'), '\\'); +assert.strictEqual(path.win32.dirname('\\foo'), '\\'); +assert.strictEqual(path.win32.dirname('\\foo\\'), '\\'); +assert.strictEqual(path.win32.dirname('\\foo\\bar'), '\\foo'); +assert.strictEqual(path.win32.dirname('\\foo\\bar\\'), '\\foo'); +assert.strictEqual(path.win32.dirname('\\foo\\bar\\baz'), '\\foo\\bar'); +assert.strictEqual(path.win32.dirname('c:'), 'c:'); +assert.strictEqual(path.win32.dirname('c:foo'), 'c:'); +assert.strictEqual(path.win32.dirname('c:foo\\'), 'c:'); +assert.strictEqual(path.win32.dirname('c:foo\\bar'), 'c:foo'); +assert.strictEqual(path.win32.dirname('c:foo\\bar\\'), 'c:foo'); +assert.strictEqual(path.win32.dirname('c:foo\\bar\\baz'), 'c:foo\\bar'); +assert.strictEqual(path.win32.dirname('file:stream'), '.'); +assert.strictEqual(path.win32.dirname('dir\\file:stream'), 'dir'); +assert.strictEqual(path.win32.dirname('\\\\unc\\share'), + '\\\\unc\\share'); +assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo'), + '\\\\unc\\share\\'); +assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\'), + '\\\\unc\\share\\'); +assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\bar'), + '\\\\unc\\share\\foo'); +assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\bar\\'), + '\\\\unc\\share\\foo'); +assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\bar\\baz'), + '\\\\unc\\share\\foo\\bar'); +assert.strictEqual(path.win32.dirname('/a/b/'), '/a'); +assert.strictEqual(path.win32.dirname('/a/b'), '/a'); +assert.strictEqual(path.win32.dirname('/a'), '/'); +assert.strictEqual(path.win32.dirname(''), '.'); +assert.strictEqual(path.win32.dirname('/'), '/'); +assert.strictEqual(path.win32.dirname('////'), '/'); +assert.strictEqual(path.win32.dirname('foo'), '.'); diff --git a/test/parallel/test-path-extname.js b/test/parallel/test-path-extname.js new file mode 100644 index 00000000000000..47b327d370d78e --- /dev/null +++ b/test/parallel/test-path-extname.js @@ -0,0 +1,99 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); + +const failures = []; +const slashRE = /\//g; + +[ + [__filename, '.js'], + ['', ''], + ['/path/to/file', ''], + ['/path/to/file.ext', '.ext'], + ['/path.to/file.ext', '.ext'], + ['/path.to/file', ''], + ['/path.to/.file', ''], + ['/path.to/.file.ext', '.ext'], + ['/path/to/f.ext', '.ext'], + ['/path/to/..ext', '.ext'], + ['/path/to/..', ''], + ['file', ''], + ['file.ext', '.ext'], + ['.file', ''], + ['.file.ext', '.ext'], + ['/file', ''], + ['/file.ext', '.ext'], + ['/.file', ''], + ['/.file.ext', '.ext'], + ['.path/file.ext', '.ext'], + ['file.ext.ext', '.ext'], + ['file.', '.'], + ['.', ''], + ['./', ''], + ['.file.ext', '.ext'], + ['.file', ''], + ['.file.', '.'], + ['.file..', '.'], + ['..', ''], + ['../', ''], + ['..file.ext', '.ext'], + ['..file', '.file'], + ['..file.', '.'], + ['..file..', '.'], + ['...', '.'], + ['...ext', '.ext'], + ['....', '.'], + ['file.ext/', '.ext'], + ['file.ext//', '.ext'], + ['file/', ''], + ['file//', ''], + ['file./', '.'], + ['file.//', '.'], +].forEach((test) => { + const expected = test[1]; + [path.posix.extname, path.win32.extname].forEach((extname) => { + let input = test[0]; + let os; + if (extname === path.win32.extname) { + input = input.replace(slashRE, '\\'); + os = 'win32'; + } else { + os = 'posix'; + } + const actual = extname(input); + const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected) + failures.push(`\n${message}`); + }); + { + const input = `C:${test[0].replace(slashRE, '\\')}`; + const actual = path.win32.extname(input); + const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected) + failures.push(`\n${message}`); + } +}); +assert.strictEqual(failures.length, 0, failures.join('')); + +// On Windows, backslash is a path separator. +assert.strictEqual(path.win32.extname('.\\'), ''); +assert.strictEqual(path.win32.extname('..\\'), ''); +assert.strictEqual(path.win32.extname('file.ext\\'), '.ext'); +assert.strictEqual(path.win32.extname('file.ext\\\\'), '.ext'); +assert.strictEqual(path.win32.extname('file\\'), ''); +assert.strictEqual(path.win32.extname('file\\\\'), ''); +assert.strictEqual(path.win32.extname('file.\\'), '.'); +assert.strictEqual(path.win32.extname('file.\\\\'), '.'); + +// On *nix, backslash is a valid name component like any other character. +assert.strictEqual(path.posix.extname('.\\'), ''); +assert.strictEqual(path.posix.extname('..\\'), '.\\'); +assert.strictEqual(path.posix.extname('file.ext\\'), '.ext\\'); +assert.strictEqual(path.posix.extname('file.ext\\\\'), '.ext\\\\'); +assert.strictEqual(path.posix.extname('file\\'), ''); +assert.strictEqual(path.posix.extname('file\\\\'), ''); +assert.strictEqual(path.posix.extname('file.\\'), '.\\'); +assert.strictEqual(path.posix.extname('file.\\\\'), '.\\\\'); diff --git a/test/parallel/test-path-isabsolute.js b/test/parallel/test-path-isabsolute.js new file mode 100644 index 00000000000000..66b4f1ee51103a --- /dev/null +++ b/test/parallel/test-path-isabsolute.js @@ -0,0 +1,28 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); + +assert.strictEqual(path.win32.isAbsolute('/'), true); +assert.strictEqual(path.win32.isAbsolute('//'), true); +assert.strictEqual(path.win32.isAbsolute('//server'), true); +assert.strictEqual(path.win32.isAbsolute('//server/file'), true); +assert.strictEqual(path.win32.isAbsolute('\\\\server\\file'), true); +assert.strictEqual(path.win32.isAbsolute('\\\\server'), true); +assert.strictEqual(path.win32.isAbsolute('\\\\'), true); +assert.strictEqual(path.win32.isAbsolute('c'), false); +assert.strictEqual(path.win32.isAbsolute('c:'), false); +assert.strictEqual(path.win32.isAbsolute('c:\\'), true); +assert.strictEqual(path.win32.isAbsolute('c:/'), true); +assert.strictEqual(path.win32.isAbsolute('c://'), true); +assert.strictEqual(path.win32.isAbsolute('C:/Users/'), true); +assert.strictEqual(path.win32.isAbsolute('C:\\Users\\'), true); +assert.strictEqual(path.win32.isAbsolute('C:cwd/another'), false); +assert.strictEqual(path.win32.isAbsolute('C:cwd\\another'), false); +assert.strictEqual(path.win32.isAbsolute('directory/directory'), false); +assert.strictEqual(path.win32.isAbsolute('directory\\directory'), false); + +assert.strictEqual(path.posix.isAbsolute('/home/foo'), true); +assert.strictEqual(path.posix.isAbsolute('/home/foo/..'), true); +assert.strictEqual(path.posix.isAbsolute('bar/'), false); +assert.strictEqual(path.posix.isAbsolute('./baz'), false); diff --git a/test/parallel/test-path-join.js b/test/parallel/test-path-join.js new file mode 100644 index 00000000000000..691ba98f9bc095 --- /dev/null +++ b/test/parallel/test-path-join.js @@ -0,0 +1,142 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); + +const failures = []; +const backslashRE = /\\/g; + +const joinTests = [ + [ [path.posix.join, path.win32.join], + // arguments result + [[['.', 'x/b', '..', '/b/c.js'], 'x/b/c.js'], + [[], '.'], + [['/.', 'x/b', '..', '/b/c.js'], '/x/b/c.js'], + [['/foo', '../../../bar'], '/bar'], + [['foo', '../../../bar'], '../../bar'], + [['foo/', '../../../bar'], '../../bar'], + [['foo/x', '../../../bar'], '../bar'], + [['foo/x', './bar'], 'foo/x/bar'], + [['foo/x/', './bar'], 'foo/x/bar'], + [['foo/x/', '.', 'bar'], 'foo/x/bar'], + [['./'], './'], + [['.', './'], './'], + [['.', '.', '.'], '.'], + [['.', './', '.'], '.'], + [['.', '/./', '.'], '.'], + [['.', '/////./', '.'], '.'], + [['.'], '.'], + [['', '.'], '.'], + [['', 'foo'], 'foo'], + [['foo', '/bar'], 'foo/bar'], + [['', '/foo'], '/foo'], + [['', '', '/foo'], '/foo'], + [['', '', 'foo'], 'foo'], + [['foo', ''], 'foo'], + [['foo/', ''], 'foo/'], + [['foo', '', '/bar'], 'foo/bar'], + [['./', '..', '/foo'], '../foo'], + [['./', '..', '..', '/foo'], '../../foo'], + [['.', '..', '..', '/foo'], '../../foo'], + [['', '..', '..', '/foo'], '../../foo'], + [['/'], '/'], + [['/', '.'], '/'], + [['/', '..'], '/'], + [['/', '..', '..'], '/'], + [[''], '.'], + [['', ''], '.'], + [[' /foo'], ' /foo'], + [[' ', 'foo'], ' /foo'], + [[' ', '.'], ' '], + [[' ', '/'], ' /'], + [[' ', ''], ' '], + [['/', 'foo'], '/foo'], + [['/', '/foo'], '/foo'], + [['/', '//foo'], '/foo'], + [['/', '', '/foo'], '/foo'], + [['', '/', 'foo'], '/foo'], + [['', '/', '/foo'], '/foo'] + ] + ] +]; + +// Windows-specific join tests +joinTests.push([ + path.win32.join, + joinTests[0][1].slice(0).concat( + [// arguments result + // UNC path expected + [['//foo/bar'], '\\\\foo\\bar\\'], + [['\\/foo/bar'], '\\\\foo\\bar\\'], + [['\\\\foo/bar'], '\\\\foo\\bar\\'], + // UNC path expected - server and share separate + [['//foo', 'bar'], '\\\\foo\\bar\\'], + [['//foo/', 'bar'], '\\\\foo\\bar\\'], + [['//foo', '/bar'], '\\\\foo\\bar\\'], + // UNC path expected - questionable + [['//foo', '', 'bar'], '\\\\foo\\bar\\'], + [['//foo/', '', 'bar'], '\\\\foo\\bar\\'], + [['//foo/', '', '/bar'], '\\\\foo\\bar\\'], + // UNC path expected - even more questionable + [['', '//foo', 'bar'], '\\\\foo\\bar\\'], + [['', '//foo/', 'bar'], '\\\\foo\\bar\\'], + [['', '//foo/', '/bar'], '\\\\foo\\bar\\'], + // No UNC path expected (no double slash in first component) + [['\\', 'foo/bar'], '\\foo\\bar'], + [['\\', '/foo/bar'], '\\foo\\bar'], + [['', '/', '/foo/bar'], '\\foo\\bar'], + // No UNC path expected (no non-slashes in first component - + // questionable) + [['//', 'foo/bar'], '\\foo\\bar'], + [['//', '/foo/bar'], '\\foo\\bar'], + [['\\\\', '/', '/foo/bar'], '\\foo\\bar'], + [['//'], '/'], + // No UNC path expected (share name missing - questionable). + [['//foo'], '\\foo'], + [['//foo/'], '\\foo\\'], + [['//foo', '/'], '\\foo\\'], + [['//foo', '', '/'], '\\foo\\'], + // No UNC path expected (too many leading slashes - questionable) + [['///foo/bar'], '\\foo\\bar'], + [['////foo', 'bar'], '\\foo\\bar'], + [['\\\\\\/foo/bar'], '\\foo\\bar'], + // Drive-relative vs drive-absolute paths. This merely describes the + // status quo, rather than being obviously right + [['c:'], 'c:.'], + [['c:.'], 'c:.'], + [['c:', ''], 'c:.'], + [['', 'c:'], 'c:.'], + [['c:.', '/'], 'c:.\\'], + [['c:.', 'file'], 'c:file'], + [['c:', '/'], 'c:\\'], + [['c:', 'file'], 'c:\\file'] + ] + ) +]); +joinTests.forEach((test) => { + if (!Array.isArray(test[0])) + test[0] = [test[0]]; + test[0].forEach((join) => { + test[1].forEach((test) => { + const actual = join.apply(null, test[0]); + const expected = test[1]; + // For non-Windows specific tests with the Windows join(), we need to try + // replacing the slashes since the non-Windows specific tests' `expected` + // use forward slashes + let actualAlt; + let os; + if (join === path.win32.join) { + actualAlt = actual.replace(backslashRE, '/'); + os = 'win32'; + } else { + os = 'posix'; + } + const message = + `path.${os}.join(${test[0].map(JSON.stringify).join(',')})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected && actualAlt !== expected) + failures.push(`\n${message}`); + }); + }); +}); +assert.strictEqual(failures.length, 0, failures.join('')); diff --git a/test/parallel/test-path-makelong.js b/test/parallel/test-path-makelong.js index 769f81bdb81735..4e33d359e6cf11 100644 --- a/test/parallel/test-path-makelong.js +++ b/test/parallel/test-path-makelong.js @@ -23,3 +23,37 @@ assert.strictEqual(path._makeLong(100), 100); assert.strictEqual(path._makeLong(path), path); assert.strictEqual(path._makeLong(false), false); assert.strictEqual(path._makeLong(true), true); + +const emptyObj = {}; +assert.strictEqual(path.posix._makeLong('/foo/bar'), '/foo/bar'); +assert.strictEqual(path.posix._makeLong('foo/bar'), 'foo/bar'); +assert.strictEqual(path.posix._makeLong(null), null); +assert.strictEqual(path.posix._makeLong(true), true); +assert.strictEqual(path.posix._makeLong(1), 1); +assert.strictEqual(path.posix._makeLong(), undefined); +assert.strictEqual(path.posix._makeLong(emptyObj), emptyObj); +if (common.isWindows) { + // These tests cause resolve() to insert the cwd, so we cannot test them from + // non-Windows platforms (easily) + assert.strictEqual(path.win32._makeLong('foo\\bar').toLowerCase(), + `\\\\?\\${process.cwd().toLowerCase()}\\foo\\bar`); + assert.strictEqual(path.win32._makeLong('foo/bar').toLowerCase(), + `\\\\?\\${process.cwd().toLowerCase()}\\foo\\bar`); + const currentDeviceLetter = path.parse(process.cwd()).root.substring(0, 2); + assert.strictEqual(path.win32._makeLong(currentDeviceLetter).toLowerCase(), + `\\\\?\\${process.cwd().toLowerCase()}`); + assert.strictEqual(path.win32._makeLong('C').toLowerCase(), + `\\\\?\\${process.cwd().toLowerCase()}\\c`); +} +assert.strictEqual(path.win32._makeLong('C:\\foo'), '\\\\?\\C:\\foo'); +assert.strictEqual(path.win32._makeLong('C:/foo'), '\\\\?\\C:\\foo'); +assert.strictEqual(path.win32._makeLong('\\\\foo\\bar'), + '\\\\?\\UNC\\foo\\bar\\'); +assert.strictEqual(path.win32._makeLong('//foo//bar'), + '\\\\?\\UNC\\foo\\bar\\'); +assert.strictEqual(path.win32._makeLong('\\\\?\\foo'), '\\\\?\\foo'); +assert.strictEqual(path.win32._makeLong(null), null); +assert.strictEqual(path.win32._makeLong(true), true); +assert.strictEqual(path.win32._makeLong(1), 1); +assert.strictEqual(path.win32._makeLong(), undefined); +assert.strictEqual(path.win32._makeLong(emptyObj), emptyObj); diff --git a/test/parallel/test-path-normalize.js b/test/parallel/test-path-normalize.js new file mode 100644 index 00000000000000..db91432b3e0955 --- /dev/null +++ b/test/parallel/test-path-normalize.js @@ -0,0 +1,39 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); + +assert.strictEqual(path.win32.normalize('./fixtures///b/../b/c.js'), + 'fixtures\\b\\c.js'); +assert.strictEqual(path.win32.normalize('/foo/../../../bar'), '\\bar'); +assert.strictEqual(path.win32.normalize('a//b//../b'), 'a\\b'); +assert.strictEqual(path.win32.normalize('a//b//./c'), 'a\\b\\c'); +assert.strictEqual(path.win32.normalize('a//b//.'), 'a\\b'); +assert.strictEqual(path.win32.normalize('//server/share/dir/file.ext'), + '\\\\server\\share\\dir\\file.ext'); +assert.strictEqual(path.win32.normalize('/a/b/c/../../../x/y/z'), '\\x\\y\\z'); +assert.strictEqual(path.win32.normalize('C:'), 'C:.'); +assert.strictEqual(path.win32.normalize('C:..\\abc'), 'C:..\\abc'); +assert.strictEqual(path.win32.normalize('C:..\\..\\abc\\..\\def'), + 'C:..\\..\\def'); +assert.strictEqual(path.win32.normalize('C:\\.'), 'C:\\'); +assert.strictEqual(path.win32.normalize('file:stream'), 'file:stream'); +assert.strictEqual(path.win32.normalize('bar\\foo..\\..\\'), 'bar\\'); +assert.strictEqual(path.win32.normalize('bar\\foo..\\..'), 'bar'); +assert.strictEqual(path.win32.normalize('bar\\foo..\\..\\baz'), 'bar\\baz'); +assert.strictEqual(path.win32.normalize('bar\\foo..\\'), 'bar\\foo..\\'); +assert.strictEqual(path.win32.normalize('bar\\foo..'), 'bar\\foo..'); + +assert.strictEqual(path.posix.normalize('./fixtures///b/../b/c.js'), + 'fixtures/b/c.js'); +assert.strictEqual(path.posix.normalize('/foo/../../../bar'), '/bar'); +assert.strictEqual(path.posix.normalize('a//b//../b'), 'a/b'); +assert.strictEqual(path.posix.normalize('a//b//./c'), 'a/b/c'); +assert.strictEqual(path.posix.normalize('a//b//.'), 'a/b'); +assert.strictEqual(path.posix.normalize('/a/b/c/../../../x/y/z'), '/x/y/z'); +assert.strictEqual(path.posix.normalize('///..//./foo/.//bar'), '/foo/bar'); +assert.strictEqual(path.posix.normalize('bar/foo../../'), 'bar/'); +assert.strictEqual(path.posix.normalize('bar/foo../..'), 'bar'); +assert.strictEqual(path.posix.normalize('bar/foo../../baz'), 'bar/baz'); +assert.strictEqual(path.posix.normalize('bar/foo../'), 'bar/foo../'); +assert.strictEqual(path.posix.normalize('bar/foo..'), 'bar/foo..'); diff --git a/test/parallel/test-path-relative.js b/test/parallel/test-path-relative.js new file mode 100644 index 00000000000000..bd2c3f75a52dd2 --- /dev/null +++ b/test/parallel/test-path-relative.js @@ -0,0 +1,67 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const path = require('path'); + +const failures = []; + +const relativeTests = [ + [ path.win32.relative, + // arguments result + [['c:/blah\\blah', 'd:/games', 'd:\\games'], + ['c:/aaaa/bbbb', 'c:/aaaa', '..'], + ['c:/aaaa/bbbb', 'c:/cccc', '..\\..\\cccc'], + ['c:/aaaa/bbbb', 'c:/aaaa/bbbb', ''], + ['c:/aaaa/bbbb', 'c:/aaaa/cccc', '..\\cccc'], + ['c:/aaaa/', 'c:/aaaa/cccc', 'cccc'], + ['c:/', 'c:\\aaaa\\bbbb', 'aaaa\\bbbb'], + ['c:/aaaa/bbbb', 'd:\\', 'd:\\'], + ['c:/AaAa/bbbb', 'c:/aaaa/bbbb', ''], + ['c:/aaaaa/', 'c:/aaaa/cccc', '..\\aaaa\\cccc'], + ['C:\\foo\\bar\\baz\\quux', 'C:\\', '..\\..\\..\\..'], + ['C:\\foo\\test', 'C:\\foo\\test\\bar\\package.json', 'bar\\package.json'], + ['C:\\foo\\bar\\baz-quux', 'C:\\foo\\bar\\baz', '..\\baz'], + ['C:\\foo\\bar\\baz', 'C:\\foo\\bar\\baz-quux', '..\\baz-quux'], + ['\\\\foo\\bar', '\\\\foo\\bar\\baz', 'baz'], + ['\\\\foo\\bar\\baz', '\\\\foo\\bar', '..'], + ['\\\\foo\\bar\\baz-quux', '\\\\foo\\bar\\baz', '..\\baz'], + ['\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz-quux', '..\\baz-quux'], + ['C:\\baz-quux', 'C:\\baz', '..\\baz'], + ['C:\\baz', 'C:\\baz-quux', '..\\baz-quux'], + ['\\\\foo\\baz-quux', '\\\\foo\\baz', '..\\baz'], + ['\\\\foo\\baz', '\\\\foo\\baz-quux', '..\\baz-quux'], + ['C:\\baz', '\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz'], + ['\\\\foo\\bar\\baz', 'C:\\baz', 'C:\\baz'] + ] + ], + [ path.posix.relative, + // arguments result + [['/var/lib', '/var', '..'], + ['/var/lib', '/bin', '../../bin'], + ['/var/lib', '/var/lib', ''], + ['/var/lib', '/var/apache', '../apache'], + ['/var/', '/var/lib', 'lib'], + ['/', '/var/lib', 'var/lib'], + ['/foo/test', '/foo/test/bar/package.json', 'bar/package.json'], + ['/Users/a/web/b/test/mails', '/Users/a/web/b', '../..'], + ['/foo/bar/baz-quux', '/foo/bar/baz', '../baz'], + ['/foo/bar/baz', '/foo/bar/baz-quux', '../baz-quux'], + ['/baz-quux', '/baz', '../baz'], + ['/baz', '/baz-quux', '../baz-quux'] + ] + ] +]; +relativeTests.forEach((test) => { + const relative = test[0]; + test[1].forEach((test) => { + const actual = relative(test[0], test[1]); + const expected = test[2]; + const os = relative === path.win32.relative ? 'win32' : 'posix'; + const message = `path.${os}.relative(${ + test.slice(0, 2).map(JSON.stringify).join(',')})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected) + failures.push(`\n${message}`); + }); +}); +assert.strictEqual(failures.length, 0, failures.join('')); diff --git a/test/parallel/test-path-resolve.js b/test/parallel/test-path-resolve.js new file mode 100644 index 00000000000000..91a2807648fa40 --- /dev/null +++ b/test/parallel/test-path-resolve.js @@ -0,0 +1,70 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const child = require('child_process'); +const path = require('path'); + +const failures = []; +const slashRE = /\//g; +const backslashRE = /\\/g; + +const resolveTests = [ + [ path.win32.resolve, + // arguments result + [[['c:/blah\\blah', 'd:/games', 'c:../a'], 'c:\\blah\\a'], + [['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'], 'd:\\e.exe'], + [['c:/ignore', 'c:/some/file'], 'c:\\some\\file'], + [['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'], + [['.'], process.cwd()], + [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative'], + [['c:/', '//'], 'c:\\'], + [['c:/', '//dir'], 'c:\\dir'], + [['c:/', '//server/share'], '\\\\server\\share\\'], + [['c:/', '//server//share'], '\\\\server\\share\\'], + [['c:/', '///some//dir'], 'c:\\some\\dir'], + [['C:\\foo\\tmp.3\\', '..\\tmp.3\\cycles\\root.js'], + 'C:\\foo\\tmp.3\\cycles\\root.js'] + ] + ], + [ path.posix.resolve, + // arguments result + [[['/var/lib', '../', 'file/'], '/var/file'], + [['/var/lib', '/../', 'file/'], '/file'], + [['a/b/c/', '../../..'], process.cwd()], + [['.'], process.cwd()], + [['/some/dir', '.', '/absolute/'], '/absolute'], + [['/foo/tmp.3/', '../tmp.3/cycles/root.js'], '/foo/tmp.3/cycles/root.js'] + ] + ] +]; +resolveTests.forEach((test) => { + const resolve = test[0]; + test[1].forEach((test) => { + const actual = resolve.apply(null, test[0]); + let actualAlt; + const os = resolve === path.win32.resolve ? 'win32' : 'posix'; + if (resolve === path.win32.resolve && !common.isWindows) + actualAlt = actual.replace(backslashRE, '/'); + else if (resolve !== path.win32.resolve && common.isWindows) + actualAlt = actual.replace(slashRE, '\\'); + + const expected = test[1]; + const message = + `path.${os}.resolve(${test[0].map(JSON.stringify).join(',')})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected && actualAlt !== expected) + failures.push(`\n${message}`); + }); +}); +assert.strictEqual(failures.length, 0, failures.join('')); + +if (common.isWindows) { + // Test resolving the current Windows drive letter from a spawned process. + // See https://github.com/nodejs/node/issues/7215 + const currentDriveLetter = path.parse(process.cwd()).root.substring(0, 2); + const resolveFixture = path.join(common.fixturesDir, 'path-resolve.js'); + const spawnResult = child.spawnSync( + process.argv[0], [resolveFixture, currentDriveLetter]); + const resolvedPath = spawnResult.stdout.toString().trim(); + assert.strictEqual(resolvedPath.toLowerCase(), process.cwd().toLowerCase()); +} diff --git a/test/parallel/test-path.js b/test/parallel/test-path.js index 531b5d69e7b483..18eedbeb076681 100644 --- a/test/parallel/test-path.js +++ b/test/parallel/test-path.js @@ -1,368 +1,8 @@ 'use strict'; const common = require('../common'); const assert = require('assert'); -const child = require('child_process'); const path = require('path'); -const f = __filename; -const failures = []; - -const slashRE = /\//g; -const backslashRE = /\\/g; - -// path.basename tests -assert.strictEqual(path.basename(f), 'test-path.js'); -assert.strictEqual(path.basename(f, '.js'), 'test-path'); -assert.strictEqual(path.basename('.js', '.js'), ''); -assert.strictEqual(path.basename(''), ''); -assert.strictEqual(path.basename('/dir/basename.ext'), 'basename.ext'); -assert.strictEqual(path.basename('/basename.ext'), 'basename.ext'); -assert.strictEqual(path.basename('basename.ext'), 'basename.ext'); -assert.strictEqual(path.basename('basename.ext/'), 'basename.ext'); -assert.strictEqual(path.basename('basename.ext//'), 'basename.ext'); -assert.strictEqual(path.basename('aaa/bbb', '/bbb'), 'bbb'); -assert.strictEqual(path.basename('aaa/bbb', 'a/bbb'), 'bbb'); -assert.strictEqual(path.basename('aaa/bbb', 'bbb'), 'bbb'); -assert.strictEqual(path.basename('aaa/bbb//', 'bbb'), 'bbb'); -assert.strictEqual(path.basename('aaa/bbb', 'bb'), 'b'); -assert.strictEqual(path.basename('aaa/bbb', 'b'), 'bb'); -assert.strictEqual(path.basename('/aaa/bbb', '/bbb'), 'bbb'); -assert.strictEqual(path.basename('/aaa/bbb', 'a/bbb'), 'bbb'); -assert.strictEqual(path.basename('/aaa/bbb', 'bbb'), 'bbb'); -assert.strictEqual(path.basename('/aaa/bbb//', 'bbb'), 'bbb'); -assert.strictEqual(path.basename('/aaa/bbb', 'bb'), 'b'); -assert.strictEqual(path.basename('/aaa/bbb', 'b'), 'bb'); -assert.strictEqual(path.basename('/aaa/bbb'), 'bbb'); -assert.strictEqual(path.basename('/aaa/'), 'aaa'); -assert.strictEqual(path.basename('/aaa/b'), 'b'); -assert.strictEqual(path.basename('/a/b'), 'b'); -assert.strictEqual(path.basename('//a'), 'a'); - -// On Windows a backslash acts as a path separator. -assert.strictEqual(path.win32.basename('\\dir\\basename.ext'), 'basename.ext'); -assert.strictEqual(path.win32.basename('\\basename.ext'), 'basename.ext'); -assert.strictEqual(path.win32.basename('basename.ext'), 'basename.ext'); -assert.strictEqual(path.win32.basename('basename.ext\\'), 'basename.ext'); -assert.strictEqual(path.win32.basename('basename.ext\\\\'), 'basename.ext'); -assert.strictEqual(path.win32.basename('foo'), 'foo'); -assert.strictEqual(path.win32.basename('aaa\\bbb', '\\bbb'), 'bbb'); -assert.strictEqual(path.win32.basename('aaa\\bbb', 'a\\bbb'), 'bbb'); -assert.strictEqual(path.win32.basename('aaa\\bbb', 'bbb'), 'bbb'); -assert.strictEqual(path.win32.basename('aaa\\bbb\\\\\\\\', 'bbb'), 'bbb'); -assert.strictEqual(path.win32.basename('aaa\\bbb', 'bb'), 'b'); -assert.strictEqual(path.win32.basename('aaa\\bbb', 'b'), 'bb'); -assert.strictEqual(path.win32.basename('C:'), ''); -assert.strictEqual(path.win32.basename('C:.'), '.'); -assert.strictEqual(path.win32.basename('C:\\'), ''); -assert.strictEqual(path.win32.basename('C:\\dir\\base.ext'), 'base.ext'); -assert.strictEqual(path.win32.basename('C:\\basename.ext'), 'basename.ext'); -assert.strictEqual(path.win32.basename('C:basename.ext'), 'basename.ext'); -assert.strictEqual(path.win32.basename('C:basename.ext\\'), 'basename.ext'); -assert.strictEqual(path.win32.basename('C:basename.ext\\\\'), 'basename.ext'); -assert.strictEqual(path.win32.basename('C:foo'), 'foo'); -assert.strictEqual(path.win32.basename('file:stream'), 'file:stream'); - -// On unix a backslash is just treated as any other character. -assert.strictEqual(path.posix.basename('\\dir\\basename.ext'), - '\\dir\\basename.ext'); -assert.strictEqual(path.posix.basename('\\basename.ext'), '\\basename.ext'); -assert.strictEqual(path.posix.basename('basename.ext'), 'basename.ext'); -assert.strictEqual(path.posix.basename('basename.ext\\'), 'basename.ext\\'); -assert.strictEqual(path.posix.basename('basename.ext\\\\'), 'basename.ext\\\\'); -assert.strictEqual(path.posix.basename('foo'), 'foo'); - -// POSIX filenames may include control characters -// c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html -const controlCharFilename = `Icon${String.fromCharCode(13)}`; -assert.strictEqual(path.posix.basename(`/a/b/${controlCharFilename}`), - controlCharFilename); - - -// path.dirname tests -assert.strictEqual(path.dirname(f).substr(-13), - common.isWindows ? 'test\\parallel' : 'test/parallel'); - -assert.strictEqual(path.posix.dirname('/a/b/'), '/a'); -assert.strictEqual(path.posix.dirname('/a/b'), '/a'); -assert.strictEqual(path.posix.dirname('/a'), '/'); -assert.strictEqual(path.posix.dirname(''), '.'); -assert.strictEqual(path.posix.dirname('/'), '/'); -assert.strictEqual(path.posix.dirname('////'), '/'); -assert.strictEqual(path.posix.dirname('//a'), '//'); -assert.strictEqual(path.posix.dirname('foo'), '.'); - -assert.strictEqual(path.win32.dirname('c:\\'), 'c:\\'); -assert.strictEqual(path.win32.dirname('c:\\foo'), 'c:\\'); -assert.strictEqual(path.win32.dirname('c:\\foo\\'), 'c:\\'); -assert.strictEqual(path.win32.dirname('c:\\foo\\bar'), 'c:\\foo'); -assert.strictEqual(path.win32.dirname('c:\\foo\\bar\\'), 'c:\\foo'); -assert.strictEqual(path.win32.dirname('c:\\foo\\bar\\baz'), 'c:\\foo\\bar'); -assert.strictEqual(path.win32.dirname('\\'), '\\'); -assert.strictEqual(path.win32.dirname('\\foo'), '\\'); -assert.strictEqual(path.win32.dirname('\\foo\\'), '\\'); -assert.strictEqual(path.win32.dirname('\\foo\\bar'), '\\foo'); -assert.strictEqual(path.win32.dirname('\\foo\\bar\\'), '\\foo'); -assert.strictEqual(path.win32.dirname('\\foo\\bar\\baz'), '\\foo\\bar'); -assert.strictEqual(path.win32.dirname('c:'), 'c:'); -assert.strictEqual(path.win32.dirname('c:foo'), 'c:'); -assert.strictEqual(path.win32.dirname('c:foo\\'), 'c:'); -assert.strictEqual(path.win32.dirname('c:foo\\bar'), 'c:foo'); -assert.strictEqual(path.win32.dirname('c:foo\\bar\\'), 'c:foo'); -assert.strictEqual(path.win32.dirname('c:foo\\bar\\baz'), 'c:foo\\bar'); -assert.strictEqual(path.win32.dirname('file:stream'), '.'); -assert.strictEqual(path.win32.dirname('dir\\file:stream'), 'dir'); -assert.strictEqual(path.win32.dirname('\\\\unc\\share'), - '\\\\unc\\share'); -assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo'), - '\\\\unc\\share\\'); -assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\'), - '\\\\unc\\share\\'); -assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\bar'), - '\\\\unc\\share\\foo'); -assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\bar\\'), - '\\\\unc\\share\\foo'); -assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\bar\\baz'), - '\\\\unc\\share\\foo\\bar'); -assert.strictEqual(path.win32.dirname('/a/b/'), '/a'); -assert.strictEqual(path.win32.dirname('/a/b'), '/a'); -assert.strictEqual(path.win32.dirname('/a'), '/'); -assert.strictEqual(path.win32.dirname(''), '.'); -assert.strictEqual(path.win32.dirname('/'), '/'); -assert.strictEqual(path.win32.dirname('////'), '/'); -assert.strictEqual(path.win32.dirname('foo'), '.'); - - -// path.extname tests -[ - [f, '.js'], - ['', ''], - ['/path/to/file', ''], - ['/path/to/file.ext', '.ext'], - ['/path.to/file.ext', '.ext'], - ['/path.to/file', ''], - ['/path.to/.file', ''], - ['/path.to/.file.ext', '.ext'], - ['/path/to/f.ext', '.ext'], - ['/path/to/..ext', '.ext'], - ['/path/to/..', ''], - ['file', ''], - ['file.ext', '.ext'], - ['.file', ''], - ['.file.ext', '.ext'], - ['/file', ''], - ['/file.ext', '.ext'], - ['/.file', ''], - ['/.file.ext', '.ext'], - ['.path/file.ext', '.ext'], - ['file.ext.ext', '.ext'], - ['file.', '.'], - ['.', ''], - ['./', ''], - ['.file.ext', '.ext'], - ['.file', ''], - ['.file.', '.'], - ['.file..', '.'], - ['..', ''], - ['../', ''], - ['..file.ext', '.ext'], - ['..file', '.file'], - ['..file.', '.'], - ['..file..', '.'], - ['...', '.'], - ['...ext', '.ext'], - ['....', '.'], - ['file.ext/', '.ext'], - ['file.ext//', '.ext'], - ['file/', ''], - ['file//', ''], - ['file./', '.'], - ['file.//', '.'], -].forEach((test) => { - const expected = test[1]; - [path.posix.extname, path.win32.extname].forEach((extname) => { - let input = test[0]; - let os; - if (extname === path.win32.extname) { - input = input.replace(slashRE, '\\'); - os = 'win32'; - } else { - os = 'posix'; - } - const actual = extname(input); - const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${ - JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; - if (actual !== expected) - failures.push(`\n${message}`); - }); - { - const input = `C:${test[0].replace(slashRE, '\\')}`; - const actual = path.win32.extname(input); - const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${ - JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; - if (actual !== expected) - failures.push(`\n${message}`); - } -}); -assert.strictEqual(failures.length, 0, failures.join('')); - -// On Windows, backslash is a path separator. -assert.strictEqual(path.win32.extname('.\\'), ''); -assert.strictEqual(path.win32.extname('..\\'), ''); -assert.strictEqual(path.win32.extname('file.ext\\'), '.ext'); -assert.strictEqual(path.win32.extname('file.ext\\\\'), '.ext'); -assert.strictEqual(path.win32.extname('file\\'), ''); -assert.strictEqual(path.win32.extname('file\\\\'), ''); -assert.strictEqual(path.win32.extname('file.\\'), '.'); -assert.strictEqual(path.win32.extname('file.\\\\'), '.'); - -// On *nix, backslash is a valid name component like any other character. -assert.strictEqual(path.posix.extname('.\\'), ''); -assert.strictEqual(path.posix.extname('..\\'), '.\\'); -assert.strictEqual(path.posix.extname('file.ext\\'), '.ext\\'); -assert.strictEqual(path.posix.extname('file.ext\\\\'), '.ext\\\\'); -assert.strictEqual(path.posix.extname('file\\'), ''); -assert.strictEqual(path.posix.extname('file\\\\'), ''); -assert.strictEqual(path.posix.extname('file.\\'), '.\\'); -assert.strictEqual(path.posix.extname('file.\\\\'), '.\\\\'); - - -// path.join tests -const joinTests = [ - [ [path.posix.join, path.win32.join], - // arguments result - [[['.', 'x/b', '..', '/b/c.js'], 'x/b/c.js'], - [[], '.'], - [['/.', 'x/b', '..', '/b/c.js'], '/x/b/c.js'], - [['/foo', '../../../bar'], '/bar'], - [['foo', '../../../bar'], '../../bar'], - [['foo/', '../../../bar'], '../../bar'], - [['foo/x', '../../../bar'], '../bar'], - [['foo/x', './bar'], 'foo/x/bar'], - [['foo/x/', './bar'], 'foo/x/bar'], - [['foo/x/', '.', 'bar'], 'foo/x/bar'], - [['./'], './'], - [['.', './'], './'], - [['.', '.', '.'], '.'], - [['.', './', '.'], '.'], - [['.', '/./', '.'], '.'], - [['.', '/////./', '.'], '.'], - [['.'], '.'], - [['', '.'], '.'], - [['', 'foo'], 'foo'], - [['foo', '/bar'], 'foo/bar'], - [['', '/foo'], '/foo'], - [['', '', '/foo'], '/foo'], - [['', '', 'foo'], 'foo'], - [['foo', ''], 'foo'], - [['foo/', ''], 'foo/'], - [['foo', '', '/bar'], 'foo/bar'], - [['./', '..', '/foo'], '../foo'], - [['./', '..', '..', '/foo'], '../../foo'], - [['.', '..', '..', '/foo'], '../../foo'], - [['', '..', '..', '/foo'], '../../foo'], - [['/'], '/'], - [['/', '.'], '/'], - [['/', '..'], '/'], - [['/', '..', '..'], '/'], - [[''], '.'], - [['', ''], '.'], - [[' /foo'], ' /foo'], - [[' ', 'foo'], ' /foo'], - [[' ', '.'], ' '], - [[' ', '/'], ' /'], - [[' ', ''], ' '], - [['/', 'foo'], '/foo'], - [['/', '/foo'], '/foo'], - [['/', '//foo'], '/foo'], - [['/', '', '/foo'], '/foo'], - [['', '/', 'foo'], '/foo'], - [['', '/', '/foo'], '/foo'] - ] - ] -]; - -// Windows-specific join tests -joinTests.push([ - path.win32.join, - joinTests[0][1].slice(0).concat( - [// arguments result - // UNC path expected - [['//foo/bar'], '\\\\foo\\bar\\'], - [['\\/foo/bar'], '\\\\foo\\bar\\'], - [['\\\\foo/bar'], '\\\\foo\\bar\\'], - // UNC path expected - server and share separate - [['//foo', 'bar'], '\\\\foo\\bar\\'], - [['//foo/', 'bar'], '\\\\foo\\bar\\'], - [['//foo', '/bar'], '\\\\foo\\bar\\'], - // UNC path expected - questionable - [['//foo', '', 'bar'], '\\\\foo\\bar\\'], - [['//foo/', '', 'bar'], '\\\\foo\\bar\\'], - [['//foo/', '', '/bar'], '\\\\foo\\bar\\'], - // UNC path expected - even more questionable - [['', '//foo', 'bar'], '\\\\foo\\bar\\'], - [['', '//foo/', 'bar'], '\\\\foo\\bar\\'], - [['', '//foo/', '/bar'], '\\\\foo\\bar\\'], - // No UNC path expected (no double slash in first component) - [['\\', 'foo/bar'], '\\foo\\bar'], - [['\\', '/foo/bar'], '\\foo\\bar'], - [['', '/', '/foo/bar'], '\\foo\\bar'], - // No UNC path expected (no non-slashes in first component - - // questionable) - [['//', 'foo/bar'], '\\foo\\bar'], - [['//', '/foo/bar'], '\\foo\\bar'], - [['\\\\', '/', '/foo/bar'], '\\foo\\bar'], - [['//'], '/'], - // No UNC path expected (share name missing - questionable). - [['//foo'], '\\foo'], - [['//foo/'], '\\foo\\'], - [['//foo', '/'], '\\foo\\'], - [['//foo', '', '/'], '\\foo\\'], - // No UNC path expected (too many leading slashes - questionable) - [['///foo/bar'], '\\foo\\bar'], - [['////foo', 'bar'], '\\foo\\bar'], - [['\\\\\\/foo/bar'], '\\foo\\bar'], - // Drive-relative vs drive-absolute paths. This merely describes the - // status quo, rather than being obviously right - [['c:'], 'c:.'], - [['c:.'], 'c:.'], - [['c:', ''], 'c:.'], - [['', 'c:'], 'c:.'], - [['c:.', '/'], 'c:.\\'], - [['c:.', 'file'], 'c:file'], - [['c:', '/'], 'c:\\'], - [['c:', 'file'], 'c:\\file'] - ] - ) -]); -joinTests.forEach((test) => { - if (!Array.isArray(test[0])) - test[0] = [test[0]]; - test[0].forEach((join) => { - test[1].forEach((test) => { - const actual = join.apply(null, test[0]); - const expected = test[1]; - // For non-Windows specific tests with the Windows join(), we need to try - // replacing the slashes since the non-Windows specific tests' `expected` - // use forward slashes - let actualAlt; - let os; - if (join === path.win32.join) { - actualAlt = actual.replace(backslashRE, '/'); - os = 'win32'; - } else { - os = 'posix'; - } - const message = - `path.${os}.join(${test[0].map(JSON.stringify).join(',')})\n expect=${ - JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; - if (actual !== expected && actualAlt !== expected) - failures.push(`\n${message}`); - }); - }); -}); -assert.strictEqual(failures.length, 0, failures.join('')); - - // Test thrown TypeErrors const typeErrorTests = [true, false, 7, null, {}, undefined, [], NaN]; @@ -394,186 +34,6 @@ typeErrorTests.forEach((test) => { }); }); - -// path.normalize tests -assert.strictEqual(path.win32.normalize('./fixtures///b/../b/c.js'), - 'fixtures\\b\\c.js'); -assert.strictEqual(path.win32.normalize('/foo/../../../bar'), '\\bar'); -assert.strictEqual(path.win32.normalize('a//b//../b'), 'a\\b'); -assert.strictEqual(path.win32.normalize('a//b//./c'), 'a\\b\\c'); -assert.strictEqual(path.win32.normalize('a//b//.'), 'a\\b'); -assert.strictEqual(path.win32.normalize('//server/share/dir/file.ext'), - '\\\\server\\share\\dir\\file.ext'); -assert.strictEqual(path.win32.normalize('/a/b/c/../../../x/y/z'), '\\x\\y\\z'); -assert.strictEqual(path.win32.normalize('C:'), 'C:.'); -assert.strictEqual(path.win32.normalize('C:..\\abc'), 'C:..\\abc'); -assert.strictEqual(path.win32.normalize('C:..\\..\\abc\\..\\def'), - 'C:..\\..\\def'); -assert.strictEqual(path.win32.normalize('C:\\.'), 'C:\\'); -assert.strictEqual(path.win32.normalize('file:stream'), 'file:stream'); - -assert.strictEqual(path.posix.normalize('./fixtures///b/../b/c.js'), - 'fixtures/b/c.js'); -assert.strictEqual(path.posix.normalize('/foo/../../../bar'), '/bar'); -assert.strictEqual(path.posix.normalize('a//b//../b'), 'a/b'); -assert.strictEqual(path.posix.normalize('a//b//./c'), 'a/b/c'); -assert.strictEqual(path.posix.normalize('a//b//.'), 'a/b'); -assert.strictEqual(path.posix.normalize('/a/b/c/../../../x/y/z'), '/x/y/z'); -assert.strictEqual(path.posix.normalize('///..//./foo/.//bar'), '/foo/bar'); - - -// path.resolve tests -const resolveTests = [ - [ path.win32.resolve, - // arguments result - [[['c:/blah\\blah', 'd:/games', 'c:../a'], 'c:\\blah\\a'], - [['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'], 'd:\\e.exe'], - [['c:/ignore', 'c:/some/file'], 'c:\\some\\file'], - [['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'], - [['.'], process.cwd()], - [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative'], - [['c:/', '//'], 'c:\\'], - [['c:/', '//dir'], 'c:\\dir'], - [['c:/', '//server/share'], '\\\\server\\share\\'], - [['c:/', '//server//share'], '\\\\server\\share\\'], - [['c:/', '///some//dir'], 'c:\\some\\dir'], - [['C:\\foo\\tmp.3\\', '..\\tmp.3\\cycles\\root.js'], - 'C:\\foo\\tmp.3\\cycles\\root.js'] - ] - ], - [ path.posix.resolve, - // arguments result - [[['/var/lib', '../', 'file/'], '/var/file'], - [['/var/lib', '/../', 'file/'], '/file'], - [['a/b/c/', '../../..'], process.cwd()], - [['.'], process.cwd()], - [['/some/dir', '.', '/absolute/'], '/absolute'], - [['/foo/tmp.3/', '../tmp.3/cycles/root.js'], '/foo/tmp.3/cycles/root.js'] - ] - ] -]; -resolveTests.forEach((test) => { - const resolve = test[0]; - test[1].forEach((test) => { - const actual = resolve.apply(null, test[0]); - let actualAlt; - const os = resolve === path.win32.resolve ? 'win32' : 'posix'; - if (resolve === path.win32.resolve && !common.isWindows) - actualAlt = actual.replace(backslashRE, '/'); - else if (resolve !== path.win32.resolve && common.isWindows) - actualAlt = actual.replace(slashRE, '\\'); - - const expected = test[1]; - const message = - `path.${os}.resolve(${test[0].map(JSON.stringify).join(',')})\n expect=${ - JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; - if (actual !== expected && actualAlt !== expected) - failures.push(`\n${message}`); - }); -}); -assert.strictEqual(failures.length, 0, failures.join('')); - -if (common.isWindows) { - // Test resolving the current Windows drive letter from a spawned process. - // See https://github.com/nodejs/node/issues/7215 - const currentDriveLetter = path.parse(process.cwd()).root.substring(0, 2); - const resolveFixture = path.join(common.fixturesDir, 'path-resolve.js'); - const spawnResult = child.spawnSync( - process.argv[0], [resolveFixture, currentDriveLetter]); - const resolvedPath = spawnResult.stdout.toString().trim(); - assert.strictEqual(resolvedPath.toLowerCase(), process.cwd().toLowerCase()); -} - - -// path.isAbsolute tests -assert.strictEqual(path.win32.isAbsolute('/'), true); -assert.strictEqual(path.win32.isAbsolute('//'), true); -assert.strictEqual(path.win32.isAbsolute('//server'), true); -assert.strictEqual(path.win32.isAbsolute('//server/file'), true); -assert.strictEqual(path.win32.isAbsolute('\\\\server\\file'), true); -assert.strictEqual(path.win32.isAbsolute('\\\\server'), true); -assert.strictEqual(path.win32.isAbsolute('\\\\'), true); -assert.strictEqual(path.win32.isAbsolute('c'), false); -assert.strictEqual(path.win32.isAbsolute('c:'), false); -assert.strictEqual(path.win32.isAbsolute('c:\\'), true); -assert.strictEqual(path.win32.isAbsolute('c:/'), true); -assert.strictEqual(path.win32.isAbsolute('c://'), true); -assert.strictEqual(path.win32.isAbsolute('C:/Users/'), true); -assert.strictEqual(path.win32.isAbsolute('C:\\Users\\'), true); -assert.strictEqual(path.win32.isAbsolute('C:cwd/another'), false); -assert.strictEqual(path.win32.isAbsolute('C:cwd\\another'), false); -assert.strictEqual(path.win32.isAbsolute('directory/directory'), false); -assert.strictEqual(path.win32.isAbsolute('directory\\directory'), false); - -assert.strictEqual(path.posix.isAbsolute('/home/foo'), true); -assert.strictEqual(path.posix.isAbsolute('/home/foo/..'), true); -assert.strictEqual(path.posix.isAbsolute('bar/'), false); -assert.strictEqual(path.posix.isAbsolute('./baz'), false); - - -// path.relative tests -const relativeTests = [ - [ path.win32.relative, - // arguments result - [['c:/blah\\blah', 'd:/games', 'd:\\games'], - ['c:/aaaa/bbbb', 'c:/aaaa', '..'], - ['c:/aaaa/bbbb', 'c:/cccc', '..\\..\\cccc'], - ['c:/aaaa/bbbb', 'c:/aaaa/bbbb', ''], - ['c:/aaaa/bbbb', 'c:/aaaa/cccc', '..\\cccc'], - ['c:/aaaa/', 'c:/aaaa/cccc', 'cccc'], - ['c:/', 'c:\\aaaa\\bbbb', 'aaaa\\bbbb'], - ['c:/aaaa/bbbb', 'd:\\', 'd:\\'], - ['c:/AaAa/bbbb', 'c:/aaaa/bbbb', ''], - ['c:/aaaaa/', 'c:/aaaa/cccc', '..\\aaaa\\cccc'], - ['C:\\foo\\bar\\baz\\quux', 'C:\\', '..\\..\\..\\..'], - ['C:\\foo\\test', 'C:\\foo\\test\\bar\\package.json', 'bar\\package.json'], - ['C:\\foo\\bar\\baz-quux', 'C:\\foo\\bar\\baz', '..\\baz'], - ['C:\\foo\\bar\\baz', 'C:\\foo\\bar\\baz-quux', '..\\baz-quux'], - ['\\\\foo\\bar', '\\\\foo\\bar\\baz', 'baz'], - ['\\\\foo\\bar\\baz', '\\\\foo\\bar', '..'], - ['\\\\foo\\bar\\baz-quux', '\\\\foo\\bar\\baz', '..\\baz'], - ['\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz-quux', '..\\baz-quux'], - ['C:\\baz-quux', 'C:\\baz', '..\\baz'], - ['C:\\baz', 'C:\\baz-quux', '..\\baz-quux'], - ['\\\\foo\\baz-quux', '\\\\foo\\baz', '..\\baz'], - ['\\\\foo\\baz', '\\\\foo\\baz-quux', '..\\baz-quux'], - ['C:\\baz', '\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz'], - ['\\\\foo\\bar\\baz', 'C:\\baz', 'C:\\baz'] - ] - ], - [ path.posix.relative, - // arguments result - [['/var/lib', '/var', '..'], - ['/var/lib', '/bin', '../../bin'], - ['/var/lib', '/var/lib', ''], - ['/var/lib', '/var/apache', '../apache'], - ['/var/', '/var/lib', 'lib'], - ['/', '/var/lib', 'var/lib'], - ['/foo/test', '/foo/test/bar/package.json', 'bar/package.json'], - ['/Users/a/web/b/test/mails', '/Users/a/web/b', '../..'], - ['/foo/bar/baz-quux', '/foo/bar/baz', '../baz'], - ['/foo/bar/baz', '/foo/bar/baz-quux', '../baz-quux'], - ['/baz-quux', '/baz', '../baz'], - ['/baz', '/baz-quux', '../baz-quux'] - ] - ] -]; -relativeTests.forEach((test) => { - const relative = test[0]; - test[1].forEach((test) => { - const actual = relative(test[0], test[1]); - const expected = test[2]; - const os = relative === path.win32.relative ? 'win32' : 'posix'; - const message = `path.${os}.relative(${ - test.slice(0, 2).map(JSON.stringify).join(',')})\n expect=${ - JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; - if (actual !== expected) - failures.push(`\n${message}`); - }); -}); -assert.strictEqual(failures.length, 0, failures.join('')); - - // path.sep tests // windows assert.strictEqual(path.win32.sep, '\\'); @@ -586,43 +46,6 @@ assert.strictEqual(path.win32.delimiter, ';'); // posix assert.strictEqual(path.posix.delimiter, ':'); - -// path._makeLong tests -const emptyObj = {}; -assert.strictEqual(path.posix._makeLong('/foo/bar'), '/foo/bar'); -assert.strictEqual(path.posix._makeLong('foo/bar'), 'foo/bar'); -assert.strictEqual(path.posix._makeLong(null), null); -assert.strictEqual(path.posix._makeLong(true), true); -assert.strictEqual(path.posix._makeLong(1), 1); -assert.strictEqual(path.posix._makeLong(), undefined); -assert.strictEqual(path.posix._makeLong(emptyObj), emptyObj); -if (common.isWindows) { - // These tests cause resolve() to insert the cwd, so we cannot test them from - // non-Windows platforms (easily) - assert.strictEqual(path.win32._makeLong('foo\\bar').toLowerCase(), - `\\\\?\\${process.cwd().toLowerCase()}\\foo\\bar`); - assert.strictEqual(path.win32._makeLong('foo/bar').toLowerCase(), - `\\\\?\\${process.cwd().toLowerCase()}\\foo\\bar`); - const currentDeviceLetter = path.parse(process.cwd()).root.substring(0, 2); - assert.strictEqual(path.win32._makeLong(currentDeviceLetter).toLowerCase(), - `\\\\?\\${process.cwd().toLowerCase()}`); - assert.strictEqual(path.win32._makeLong('C').toLowerCase(), - `\\\\?\\${process.cwd().toLowerCase()}\\c`); -} -assert.strictEqual(path.win32._makeLong('C:\\foo'), '\\\\?\\C:\\foo'); -assert.strictEqual(path.win32._makeLong('C:/foo'), '\\\\?\\C:\\foo'); -assert.strictEqual(path.win32._makeLong('\\\\foo\\bar'), - '\\\\?\\UNC\\foo\\bar\\'); -assert.strictEqual(path.win32._makeLong('//foo//bar'), - '\\\\?\\UNC\\foo\\bar\\'); -assert.strictEqual(path.win32._makeLong('\\\\?\\foo'), '\\\\?\\foo'); -assert.strictEqual(path.win32._makeLong(null), null); -assert.strictEqual(path.win32._makeLong(true), true); -assert.strictEqual(path.win32._makeLong(1), 1); -assert.strictEqual(path.win32._makeLong(), undefined); -assert.strictEqual(path.win32._makeLong(emptyObj), emptyObj); - - if (common.isWindows) assert.deepStrictEqual(path, path.win32, 'should be win32 path module'); else