From 3ef5b6c57dff58cde124732f1e31b84dc9f793eb Mon Sep 17 00:00:00 2001
From: loveky <ylzcylx@gmail.com>
Date: Sat, 2 Jun 2018 07:10:00 +0800
Subject: [PATCH] fix: allow square brackets in path (#264)

* fix #231 allow brackets in path

* only need take care of [

* use character sets to escape special character to avoid issue on Windows

* [fix 220] create special directory/file through scripts

* rm helpers directory on every test

* add new line to the end of scripts

* update code, add tests
---
 package-lock.json                             |  6 +--
 package.json                                  | 10 ++--
 scripts/createSpecialDirectory.js             | 20 ++++++++
 src/utils/escape.js                           |  3 +-
 .../nested/nestedfile.txt => [!]/hello.txt}   |  0
 .../[special?directory]/(special-*file).txt   |  1 -
 .../[special?directory]/directoryfile.txt     |  1 -
 tests/index.js                                | 48 +++++++++++++++++--
 .../utils/removeIllegalCharacterForWindows.js |  4 ++
 9 files changed, 78 insertions(+), 15 deletions(-)
 create mode 100644 scripts/createSpecialDirectory.js
 rename tests/helpers/{[special?directory]/nested/nestedfile.txt => [!]/hello.txt} (100%)
 delete mode 100644 tests/helpers/[special?directory]/(special-*file).txt
 delete mode 100644 tests/helpers/[special?directory]/directoryfile.txt
 create mode 100644 tests/utils/removeIllegalCharacterForWindows.js

diff --git a/package-lock.json b/package-lock.json
index b71bb516..198fe596 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3900,7 +3900,7 @@
     },
     "mkdirp": {
       "version": "0.5.1",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "resolved": "http://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.1.tgz",
       "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
       "requires": {
         "minimist": "0.0.8"
@@ -4722,8 +4722,8 @@
     },
     "rimraf": {
       "version": "2.6.2",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
-      "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+      "resolved": "http://registry.npm.taobao.org/rimraf/download/rimraf-2.6.2.tgz",
+      "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=",
       "requires": {
         "glob": "7.1.2"
       }
diff --git a/package.json b/package.json
index 4cfc23a5..a6ed4948 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
     "pretest": "npm run lint && npm run build && npm run build:tests",
     "test": "mocha compiled_tests/",
     "build": "babel src/ --out-dir dist/",
-    "build:tests": "babel tests/ --out-dir compiled_tests/ && ncp tests/helpers compiled_tests/helpers"
+    "build:tests": "babel tests/ --out-dir compiled_tests/ && rimraf compiled_tests/helpers && ncp tests/helpers compiled_tests/helpers && node scripts/createSpecialDirectory.js"
   },
   "dependencies": {
     "globby": "^7.1.1",
@@ -34,12 +34,14 @@
     "babel-cli": "^6.8.0",
     "babel-preset-es2015": "^6.6.0",
     "chai": "^3.4.0",
-    "eslint": "^2.9.0",
     "enhanced-resolve": "^3.4.1",
+    "eslint": "^2.9.0",
+    "is-gzip": "^2.0.0",
+    "mkdirp": "^0.5.1",
     "mocha": "^2.4.5",
     "ncp": "^2.0.0",
-    "standard-version": "^4.2.0",
-    "is-gzip": "^2.0.0"
+    "rimraf": "^2.6.2",
+    "standard-version": "^4.2.0"
   },
   "homepage": "https://github.com/webpack-contrib/copy-webpack-plugin",
   "bugs": "https://github.com/webpack-contrib/copy-webpack-plugin/issues",
diff --git a/scripts/createSpecialDirectory.js b/scripts/createSpecialDirectory.js
new file mode 100644
index 00000000..831f336e
--- /dev/null
+++ b/scripts/createSpecialDirectory.js
@@ -0,0 +1,20 @@
+const mkdirp = require('mkdirp');
+const path = require('path');
+const fs = require('fs');
+const removeIllegalCharacterForWindows = require('../tests/utils/removeIllegalCharacterForWindows');
+
+const baseDir = 'compiled_tests/helpers';
+
+const specialFiles = {
+    '[special?directory]/nested/nestedfile.txt': '',
+    '[special?directory]/(special-*file).txt': 'special',
+    '[special?directory]/directoryfile.txt': 'new'
+};
+
+Object.keys(specialFiles).forEach(function (originFile) {
+    const file = removeIllegalCharacterForWindows(originFile);
+    const dir = path.dirname(file);
+    mkdirp.sync(path.join(baseDir, dir));
+    fs.writeFileSync(path.join(baseDir, file), specialFiles[originFile]);
+});
+
diff --git a/src/utils/escape.js b/src/utils/escape.js
index 447f376b..07ef750f 100644
--- a/src/utils/escape.js
+++ b/src/utils/escape.js
@@ -7,8 +7,7 @@ export default function escape(context, from) {
         // Ensure context is escaped before globbing
         // Handles special characters in paths
         const absoluteContext = path.resolve(context)
-            .replace(/\\/, '/')
-            .replace(/[\*|\?|\!|\(|\)|\[|\]|\{|\}]/g, (substring) => `\\${substring}`);
+            .replace(/[\*|\?|\!|\(|\)|\[|\]|\{|\}]/g, (substring) => `[${substring}]`);
 
         if (!from) {
             return absoluteContext;
diff --git a/tests/helpers/[special?directory]/nested/nestedfile.txt b/tests/helpers/[!]/hello.txt
similarity index 100%
rename from tests/helpers/[special?directory]/nested/nestedfile.txt
rename to tests/helpers/[!]/hello.txt
diff --git a/tests/helpers/[special?directory]/(special-*file).txt b/tests/helpers/[special?directory]/(special-*file).txt
deleted file mode 100644
index 065d0200..00000000
--- a/tests/helpers/[special?directory]/(special-*file).txt
+++ /dev/null
@@ -1 +0,0 @@
-special
\ No newline at end of file
diff --git a/tests/helpers/[special?directory]/directoryfile.txt b/tests/helpers/[special?directory]/directoryfile.txt
deleted file mode 100644
index 3e5126c4..00000000
--- a/tests/helpers/[special?directory]/directoryfile.txt
+++ /dev/null
@@ -1 +0,0 @@
-new
\ No newline at end of file
diff --git a/tests/index.js b/tests/index.js
index fefd44ec..bcb8497e 100644
--- a/tests/index.js
+++ b/tests/index.js
@@ -15,6 +15,8 @@ import cacache from 'cacache';
 import isGzip from 'is-gzip';
 import zlib from 'zlib';
 
+import removeIllegalCharacterForWindows from './utils/removeIllegalCharacterForWindows';
+
 const BUILD_DIR = path.join(__dirname, 'build');
 const HELPER_DIR = path.join(__dirname, 'helpers');
 const TEMP_DIR = path.join(__dirname, 'tempdir');
@@ -58,6 +60,13 @@ describe('apply function', () => {
     // Ideally we pass in patterns and confirm the resulting assets
     const run = (opts) => {
         return new Promise((resolve, reject) => {
+            if (Array.isArray(opts.patterns)) {
+                opts.patterns.forEach(function (pattern) {
+                    if (pattern.context) {
+                        pattern.context = removeIllegalCharacterForWindows(pattern.context);
+                    }
+                });
+            }
             const plugin = CopyWebpackPlugin(opts.patterns, opts.options);
 
             // Get a mock compiler to pass to plugin.apply
@@ -109,7 +118,7 @@ describe('apply function', () => {
         return run(opts)
             .then((compilation) => {
                 if (opts.expectedAssetKeys && opts.expectedAssetKeys.length > 0) {
-                    expect(compilation.assets).to.have.all.keys(opts.expectedAssetKeys);
+                    expect(compilation.assets).to.have.all.keys(opts.expectedAssetKeys.map(removeIllegalCharacterForWindows));
                 } else {
                     expect(compilation.assets).to.deep.equal({});
                 }
@@ -268,6 +277,7 @@ describe('apply function', () => {
         it('can use a glob to move multiple files to the root directory', (done) => {
             runEmit({
                 expectedAssetKeys: [
+                    '[!]/hello.txt',
                     'binextension.bin',
                     'file.txt',
                     'file.txt.gz',
@@ -289,6 +299,7 @@ describe('apply function', () => {
         it('can use a glob to move multiple files to a non-root directory', (done) => {
             runEmit({
                 expectedAssetKeys: [
+                    'nested/[!]/hello.txt',
                     'nested/binextension.bin',
                     'nested/file.txt',
                     'nested/file.txt.gz',
@@ -405,6 +416,7 @@ describe('apply function', () => {
         it('can use a glob with a full path to move multiple files to the root directory', (done) => {
             runEmit({
                 expectedAssetKeys: [
+                    '[!]/hello.txt',
                     'file.txt',
                     'directory/directoryfile.txt',
                     'directory/nested/nestedfile.txt',
@@ -423,6 +435,7 @@ describe('apply function', () => {
         it('can use a glob to move multiple files to a non-root directory with name, hash and ext', (done) => {
             runEmit({
                 expectedAssetKeys: [
+                    'nested/[!]/hello-d41d8c.txt',
                     'nested/binextension-d41d8c.bin',
                     'nested/file-22af64.txt',
                     'nested/file.txt-5b311c.gz',
@@ -445,13 +458,14 @@ describe('apply function', () => {
         it('can flatten or normalize glob matches', (done) => {
             runEmit({
                 expectedAssetKeys: [
+                    '[!]-hello.txt',
                     '[special?directory]-(special-*file).txt',
                     '[special?directory]-directoryfile.txt',
                     'directory-directoryfile.txt'
                 ],
                 patterns: [{
                     from: '*/*.*',
-                    test: /([^\/]+)\/([^\/]+)\.\w+$/,
+                    test: `([^\\${path.sep}]+)\\${path.sep}([^\\${path.sep}]+)\\.\\w+$`,
                     to: '[1]-[2].[ext]'
                 }]
             })
@@ -887,6 +901,7 @@ describe('apply function', () => {
         it('ignores files in pattern', (done) => {
             runEmit({
                 expectedAssetKeys: [
+                    '[!]/hello.txt',
                     'binextension.bin',
                     'directory/directoryfile.txt',
                     'directory/nested/nestedfile.txt',
@@ -984,7 +999,7 @@ describe('apply function', () => {
                     'nested/nestedfile.txt'
                 ],
                 patterns: [{
-                    from: '[special?directory]'
+                    from: (path.sep === '/' ? '[special?directory]' : '[specialdirectory]')
                 }]
             })
             .then(done)
@@ -1324,6 +1339,7 @@ describe('apply function', () => {
             it('ignores files that start with a dot', (done) => {
                 runEmit({
                     expectedAssetKeys: [
+                        '[!]/hello.txt',
                         'binextension.bin',
                         'file.txt',
                         'file.txt.gz',
@@ -1384,13 +1400,14 @@ describe('apply function', () => {
             it('ignores nested directory', (done) => {
                 runEmit({
                     expectedAssetKeys: [
+                        '[!]/hello.txt',
                         'binextension.bin',
                         'file.txt',
                         'file.txt.gz',
                         'noextension'
                     ],
                     options: {
-                        ignore: ['directory/**/*', '\\[special\\?directory\\]/**/*']
+                        ignore: ['directory/**/*', `[[]special${process.platform === 'win32' ? '' : '[?]'}directory]/**/*`]
                     },
                     patterns: [{
                         from: '.'
@@ -1401,6 +1418,29 @@ describe('apply function', () => {
                 .catch(done);
             });
 
+            if (path.sep === '/') {
+                it('ignores nested directory(can use "\\" to escape if path.sep is "/")', (done) => {
+                    runEmit({
+                        expectedAssetKeys: [
+                            '[!]/hello.txt',
+                            'binextension.bin',
+                            'file.txt',
+                            'file.txt.gz',
+                            'noextension'
+                        ],
+                        options: {
+                            ignore: ['directory/**/*', '\\[special\\?directory\\]/**/*']
+                        },
+                        patterns: [{
+                            from: '.'
+                        }]
+    
+                    })
+                    .then(done)
+                    .catch(done);
+                });
+            }
+
             it('ignores nested directory (glob)', (done) => {
                 runEmit({
                     expectedAssetKeys: [
diff --git a/tests/utils/removeIllegalCharacterForWindows.js b/tests/utils/removeIllegalCharacterForWindows.js
new file mode 100644
index 00000000..0754abbf
--- /dev/null
+++ b/tests/utils/removeIllegalCharacterForWindows.js
@@ -0,0 +1,4 @@
+module.exports = function (string) {
+    return process.platform !== 'win32' ? string : string.replace(/[*?"<>|]/g, '');
+};
+