├── .github
└── workflows
│ └── gitleaks.yaml
├── CHANGELOG.md
├── README.md
├── accessRules.json
├── filesystem-server.js
└── package.json
/.github/workflows/gitleaks.yaml:
--------------------------------------------------------------------------------
1 | name: Secret Value found!!
2 | on:
3 | push:
4 | public:
5 | jobs:
6 | scan:
7 | name: gitleaks
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v3
12 | - name: Install the gitleaks
13 | run: wget https://github.com/zricethezav/gitleaks/releases/download/v8.15.2/gitleaks_8.15.2_linux_x64.tar.gz
14 | shell: pwsh
15 | - name: Extract the tar file
16 | run: tar xzvf gitleaks_8.15.2_linux_x64.tar.gz
17 | - name: Generate the report
18 | id: gitleaks
19 | run: $GITHUB_WORKSPACE/gitleaks detect -s $GITHUB_WORKSPACE -f json -r $GITHUB_WORKSPACE/leaksreport.json
20 | shell: bash
21 | continue-on-error: true
22 | - name: Setup NuGet.exe
23 | if: steps.gitleaks.outcome != 'success'
24 | uses: nuget/setup-nuget@v1
25 | with:
26 | nuget-version: latest
27 | - name: Install the dotnet
28 | if: steps.gitleaks.outcome != 'success'
29 | uses: actions/setup-dotnet@v3
30 | with:
31 | dotnet-version: '3.1.x'
32 | - name: Install the report tool packages
33 | if: steps.gitleaks.outcome != 'success'
34 | run: |
35 | nuget install "Syncfusion.Email" -source ${{ secrets.NexusFeedLink }} -ExcludeVersion
36 | dir $GITHUB_WORKSPACE/Syncfusion.Email/lib/netcoreapp3.1
37 | dotnet $GITHUB_WORKSPACE/Syncfusion.Email/lib/netcoreapp3.1/GitleaksReportMail.dll ${{ secrets.CITEAMCREDENTIALS }} "$GITHUB_REF_NAME" ${{ secrets.NETWORKCREDENTIALS }} ${{ secrets.NETWORKKEY }} "$GITHUB_WORKSPACE" ${{ secrets.ORGANIZATIONNAME }}
38 | exit 1
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [Unreleased]
4 |
5 | ## 18.1.52 (2020-05-13)
6 |
7 | ### NodeJS File System Provider
8 |
9 | #### Bug Fixes
10 |
11 | - `#266713` - The script error thrown while performing the GetImage operation has been fixed.
12 |
13 | ## 17.4.43 (2020-01-14)
14 |
15 | ### NodeJS File System Provider
16 |
17 | #### Bug Fixes
18 |
19 | - `#256589` - The issue with `Directory traversal vulnerability` has been fixed.
20 |
21 | ## 17.3.26 (2019-11-05)
22 |
23 | ### NodeJS File System Provider
24 |
25 | #### New Features
26 |
27 | - Access control support has been included.
28 |
29 | ## 17.2.34 (2019-10-03)
30 |
31 | ### NodeJS File System Provider
32 |
33 | #### Bug fixes
34 |
35 | - Issues with getting the details and searching the files or folders have been fixed.
36 |
37 | - Existing and replacing dialog will now be shown on copying or moving the files or folders.
38 |
39 | ## 17.2.34 (2019-07-12)
40 |
41 | ### NodeJS File System Provider
42 |
43 | #### New Features
44 |
45 | - Support for download, copy and moving files and folders within the file system has been provided.
46 |
47 | ## 17.2.28-beta (2019-06-27)
48 |
49 | ### NodeJS File System Provider
50 |
51 | #### New Features
52 |
53 | - Added filesystem provider support for NodeJS framework.
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NodeJS file system providers for Essential JS 2 File Manager
2 |
3 | This repository contains the nodeJS file system provider used for Essential JS 2 File Manager component.
4 |
5 | ## Key Features
6 |
7 | The Node.js file system provider module allows you to work with the physical file system. It also provides the methods for performing various file actions like creating a new folder, renaming files, and deleting files.
8 |
9 | NodeJs File System Provider Serves the file system providers support for the FileManager component with the NodeJS.
10 |
11 | The following actions can be performed with NodeJS file system provider.
12 |
13 | | **Actions** | **Description** |
14 | | --- | --- |
15 | | Read | Reads the files from NodeJS file system. |
16 | | Details | Gets the file's details which consists of Type, Size, Location and Modified date. |
17 | | Download | Downloads the selected file or folder from NodeJS file system. |
18 | | Upload | Uploads a file in NodeJS file system. It accepts uploaded media with the following characteristics:
- Maximum file size: 30MB
- Accepted Media MIME types: `*/*`
|
19 | | Create | Creates a New folder. |
20 | | Delete | Deletes a folder or file. |
21 | | Copy | Copys the selected files or folders from target. |
22 | | Move | Moves the files or folders to the desired location. |
23 | | Rename | Renames a folder or file. |
24 | | Search | Full-text questions perform linguistic searches against text data in full-text indexes by operating on words and phrases. |
25 |
26 | ## Access Control
27 |
28 | The EJ2 FileManager allows you to define access permissions for files and folders using a set of access rules to user(s). The rules and roles should be specified in the `accessRules.json` available in the root folder of the package. The following table represents the access rule properties available for the files and folders.
29 |
30 | | **Properties** | **Description** |
31 | | --- | --- |
32 | | Read | Allows access to read a file or folder. |
33 | | Write | Allows permission to edit a file or folder. |
34 | | WriteContents | Allows permission to edit the content of folder. |
35 | | Copy | Allows permission to copy a file or folder. |
36 | | Download | Allows permission to download a file or folder. |
37 | | Upload | Allows permission to upload into the folder. |
38 | | IsFile | Specifies whether the rule is specified for folder or file. |
39 | | Role | Specifies the role to which the rule is applied. |
40 | | Path | Specifies the path to apply the rules which are defined. |
41 |
42 | For Example
43 | ```sh
44 | {
45 | "role": "Administator",
46 | "rules":[
47 | //Denies downloading the 'Videos' folder.
48 | {"path":"/Videos/", "isFile": false,"role": "Administator", "download": "deny"},
49 | //Denies uploading files in all folders under 'Pictures' by displaying a custom access denied message.
50 | {"path":"/Pictures/*","isFile": false, "role": "Administator","upload": "deny","message":"you don't have permission for this, Contact admisinistrator for access." },
51 | //Denies deleting and renaming all files in 'Downloads' folder.
52 | {"path":"/Downloads/*.*","isFile":true, "role": "Administator","write": "deny", },
53 | //Denies opening all 'png' files in 'Employees' folder.
54 | {"path":"/Pictures/Employees/*.png","isFile":true, "role": "Administator","read": "deny" },
55 | //Denies downloading all files with name 'FileManager' in 'Documents' folder.
56 | {"path":"/Documents/FileManager.*","isFile":true, "role": "Administator", "download": "deny", "message":"you don't have permission for this, Contact admisinistrator for access." },
57 | ]
58 | }
59 | ```
60 |
61 |
62 | ## How to configure a web service
63 |
64 | Follow the below set of commands to configure the nodeJS file system providers.
65 |
66 | - To install ej2-filemanager-node-filesystem package, use the following command.
67 |
68 | ```sh
69 |
70 | npm install @syncfusion/ej2-filemanager-node-filesystem
71 |
72 | ```
73 |
74 | - To install the depend packages for the file system provider, navigate to @syncfusion/ej2-filemanager-node-filesystem folder within the node_modules and run the below command
75 |
76 | ```sh
77 |
78 | npm install
79 |
80 | ```
81 |
82 | * Now, run the below command line to check the Node API service in local and will be started in `http://localhost:8090/`. By default the nodeJS directory service is configured with `C:/Users`.
83 |
84 | ### To configure the directory
85 |
86 | * To change the directory use flag `-d` like this `-d D:/Projects`
87 |
88 | ### To configure the port
89 |
90 | * To change the port use like this `set PORT=3000`
91 |
92 | For example:
93 |
94 | ```sh
95 | set PORT=3000 && node filesystem-server.js -d D:/Projects
96 | ```
97 |
98 | ### start the service
99 |
100 | To start the service use this command,
101 |
102 | ```sh
103 | npm start
104 | ```
105 |
106 | ## File Manager AjaxSettings
107 |
108 | To access the basic actions like Read, Delete, Copy, Move, Rename, Search, and Get Details of File Manager using NodeJS file system service, just map the following code snippet in the Ajaxsettings property of File Manager.
109 |
110 | Here, the `hostUrl` will be your locally hosted port number.
111 |
112 | ```
113 | var hostUrl = http://localhost:8090/;
114 | ajaxSettings: {
115 | url: hostUrl,
116 | }
117 | ```
118 |
119 | ## File download AjaxSettings
120 |
121 | To perform download operation, initialize the `downloadUrl` property in ajaxSettings of the File Manager component.
122 |
123 | ```
124 | var hostUrl = http://localhost:8090/;
125 | ajaxSettings: {
126 | url: hostUrl,
127 | downloadUrl: hostUrl + 'Download'
128 | },
129 | ```
130 |
131 | ## File upload AjaxSettings
132 |
133 | To perform upload operation, initialize the `uploadUrl` property in ajaxSettings of the File Manager component.
134 |
135 | ```
136 | var hostUrl = http://localhost:8090/;
137 | ajaxSettings: {
138 | url: hostUrl,
139 | uploadUrl: hostUrl + 'Upload'
140 | },
141 | ```
142 |
143 | ## File image preview AjaxSettings
144 |
145 | To perform image preview support in the File Manager component, initialize the `getImageUrl` property in ajaxSettings of the File Manager component.
146 |
147 | ```
148 | var hostUrl = http://localhost:8090/;
149 | ajaxSettings: {
150 | url: hostUrl,
151 | getImageUrl: hostUrl + 'GetImage'
152 | },
153 | ```
154 |
155 | The FileManager will be rendered as follows.
156 |
157 | 
158 |
159 | ## Support
160 |
161 | Product support is available for through following mediums.
162 |
163 | * Creating incident in Syncfusion [Direct-trac](https://www.syncfusion.com/support/directtrac/incidents?utm_source=npm&utm_campaign=filemanager) support system or [Community forum](https://www.syncfusion.com/forums/essential-js2?utm_source=npm&utm_campaign=filemanager).
164 | * New [GitHub issue](https://github.com/syncfusion/ej2-javascript-ui-controls/issues/new).
165 | * Ask your query in [Stack Overflow](https://stackoverflow.com/?utm_source=npm&utm_campaign=filemanager) with tag `syncfusion` and `ej2`.
166 |
167 | ## License
168 |
169 | Check the license detail [here](https://github.com/syncfusion/ej2-javascript-ui-controls/blob/master/license).
170 |
171 | ## Changelog
172 |
173 | Check the changelog [here](https://github.com/syncfusion/ej2-javascript-ui-controls/blob/master/controls/filemanager/CHANGELOG.md)
174 |
175 | © Copyright 2020 Syncfusion, Inc. All Rights Reserved. The Syncfusion Essential Studio license and copyright applies to this distribution.
176 |
--------------------------------------------------------------------------------
/accessRules.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SyncfusionExamples/ej2-filemanager-node-filesystem/eebf1a853ea0d232087eb936fe39546c029c9e46/accessRules.json
--------------------------------------------------------------------------------
/filesystem-server.js:
--------------------------------------------------------------------------------
1 | /*jshint esversion: 8 */
2 | var express = require('express');
3 | var yargs = require('yargs');
4 | var app = express();
5 | const rateLimit = require("express-rate-limit");
6 | // Enable rate limiting
7 | const limiter = rateLimit({
8 | windowMs: 15 * 60 * 1000, // 15 minutes
9 | max: 100 // limit each IP to 100 requests per windowMs
10 | });
11 | var size = 0;
12 | var copyName = "";
13 | var location = "";
14 | var isRenameChecking = false;
15 | var accessDetails = null;
16 | const path = require('path');
17 | const bodyParser = require("body-parser");
18 | const archiver = require('archiver');
19 | const multer = require('multer');
20 | const fs = require('fs');
21 | var cors = require('cors');
22 | const pattern = /(\.\.\/)/g;
23 |
24 | var contentRootPath = yargs.argv.d;
25 |
26 | app.use(bodyParser.urlencoded({
27 | extended: true
28 | }));
29 | app.use(limiter);
30 | app.use(bodyParser.json());
31 | app.use(cors());
32 |
33 | var Permission = {
34 | Allow: "allow",
35 | Deny: "deny"
36 | };
37 |
38 | class AccessDetails {
39 | constructor(role, rules) {
40 | this.role = role;
41 | this.rules = rules;
42 | }
43 | }
44 |
45 | class AccessPermission {
46 | constructor(read, write, writeContents, copy, download, upload, message) {
47 | this.read = read;
48 | this.write = write;
49 | this.writeContents = writeContents;
50 | this.copy = copy;
51 | this.download = download;
52 | this.upload = upload;
53 | this.message = message
54 | }
55 | }
56 |
57 | class AccessRules {
58 | constructor(path, role, read, write, writeContents, copy, download, upload, isFile, message) {
59 | this.path = path;
60 | this.role = role;
61 | this.read = read;
62 | this.write = write;
63 | this.writeContents = writeContents;
64 | this.copy = copy;
65 | this.download = download;
66 | this.upload = upload;
67 | this.isFile = isFile;
68 | this.message = message
69 | }
70 | }
71 | /**
72 | * Reads text from the file asynchronously and returns a Promise.
73 | */
74 | function GetFiles(req, res) {
75 | return new Promise((resolve, reject) => {
76 | const sanitizedPath = path.normalize(req.body.path).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
77 | fs.readdir(contentRootPath + sanitizedPath, function (err, files) {
78 | //handling error
79 | if (err) {
80 | console.log(err);
81 | reject(err);
82 |
83 | } else
84 | resolve(files);
85 | });
86 | });
87 | }
88 | /**
89 | *
90 | * function to check for exising folder or file
91 | */
92 | function checkForDuplicates(directory, name, isFile) {
93 | var filenames = fs.readdirSync(directory);
94 | if (filenames.indexOf(name) == -1) {
95 | return false;
96 | } else {
97 | for (var i = 0; i < filenames.length; i++) {
98 | if (filenames[i] === name) {
99 | if (!isFile && fs.lstatSync(directory + "/" + filenames[i]).isDirectory()) {
100 | return true;
101 | } else if (isFile && !fs.lstatSync(directory + "/" + filenames[i]).isDirectory()) {
102 | return true;
103 | } else {
104 | return false;
105 | }
106 | }
107 | }
108 | }
109 | }
110 | /**
111 | * function to rename the folder
112 | */
113 | function renameFolder(req, res) {
114 | var oldName = req.body.data[0].name.split("/")[req.body.data[0].name.split("/").length - 1];
115 | var newName = req.body.newName.split("/")[req.body.newName.split("/").length - 1];
116 | const resolvedOldDirectoryPath = path.resolve(contentRootPath + req.body.data[0].filterPath, oldName);
117 | const resolvedNewDirectoryPath = path.resolve(contentRootPath + req.body.data[0].filterPath, newName);
118 | const fullOldPath = (contentRootPath + req.body.data[0].filterPath + oldName ).replace(/[\\/]/g, "\\");
119 | const fullNewPath = (contentRootPath + req.body.data[0].filterPath + newName ).replace(/[\\/]/g, "\\");
120 | const isValidateOldPath = fullOldPath == resolvedOldDirectoryPath ? true : false;
121 | const isValidateNewPath = fullNewPath == resolvedNewDirectoryPath ? true : false;
122 | if(!isValidateOldPath || !isValidateNewPath){
123 | var errorMsg = new Error();
124 | errorMsg.message = "Access denied for Directory-traversal";
125 | errorMsg.code = "401";
126 | response = { error: errorMsg };
127 | response = JSON.stringify(response);
128 | res.setHeader('Content-Type', 'application/json');
129 | res.json(response);
130 | }
131 | var permission = getPermission((contentRootPath + req.body.data[0].filterPath), oldName, req.body.data[0].isFile, contentRootPath, req.body.data[0].filterPath);
132 | if (permission != null && (!permission.read || !permission.write)) {
133 | var errorMsg = new Error();
134 | errorMsg.message = (permission.message !== "") ? permission.message : getFileName(contentRootPath + req.body.data[0].filterPath + oldName) + " is not accessible. is not accessible. You need permission to perform the write action.";
135 | errorMsg.code = "401";
136 | response = { error: errorMsg };
137 | response = JSON.stringify(response);
138 | res.setHeader('Content-Type', 'application/json');
139 | res.json(response);
140 | } else {
141 | var sanitizedPath = path.normalize(req.body.data[0].filterPath).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
142 | var oldDirectoryPath = path.join(contentRootPath + sanitizedPath, oldName);
143 | var newDirectoryPath = path.join(contentRootPath + sanitizedPath, newName);
144 | if (checkForDuplicates(contentRootPath + sanitizedPath, newName, req.body.data[0].isFile)) {
145 | var errorMsg = new Error();
146 | errorMsg.message = "A file or folder with the name " + req.body.name + " already exists.";
147 | errorMsg.code = "400";
148 | response = { error: errorMsg };
149 |
150 | response = JSON.stringify(response);
151 | res.setHeader('Content-Type', 'application/json');
152 | res.json(response);
153 | } else {
154 | fs.renameSync(oldDirectoryPath, newDirectoryPath);
155 | (async () => {
156 | await FileManagerDirectoryContent(req, res, newDirectoryPath + "/").then(data => {
157 | response = { files: data };
158 | response = JSON.stringify(response);
159 | res.setHeader('Content-Type', 'application/json');
160 | res.json(response);
161 | });
162 | })();
163 | }
164 | }
165 | }
166 | /**
167 | * function to delete the folder
168 | */
169 | function deleteFolder(req, res, contentRootPath) {
170 | var deleteFolderRecursive = function (path) {
171 | if (fs.existsSync(path)) {
172 | fs.readdirSync(path).forEach(function (file, index) {
173 | var curPath = path + "/" + file;
174 | if (fs.lstatSync(curPath).isDirectory()) { // recurse
175 | deleteFolderRecursive(curPath);
176 | } else { // delete file
177 | fs.unlinkSync(curPath);
178 | }
179 | });
180 | fs.rmdirSync(path);
181 | }
182 | };
183 | var permission; var permissionDenied = false;
184 | req.body.data.forEach(function (item) {
185 | const resolvedPath = path.join(contentRootPath + item.filterPath, item.name);
186 | const fullPath = (contentRootPath + item.filterPath + item.name ).replace(/[\\/]/g, "\\");
187 | const isValidatePath = fullPath == resolvedPath ? true : false;
188 | if(!isValidatePath){
189 | var errorMsg = new Error();
190 | errorMsg.message = "Access denied for Directory-traversal";
191 | errorMsg.code = "401";
192 | response = { error: errorMsg };
193 | response = JSON.stringify(response);
194 | res.setHeader('Content-Type', 'application/json');
195 | res.json(response);
196 | }
197 | var fromPath = contentRootPath + item.filterPath;
198 | permission = getPermission(fromPath, item.name, item.isFile, contentRootPath, item.filterPath);
199 | if (permission != null && (!permission.read || !permission.write)) {
200 | permissionDenied = true;
201 | var errorMsg = new Error();
202 | errorMsg.message = (permission.message !== "") ? permission.message : item.name + " is not accessible. You need permission to perform the write action.";
203 | errorMsg.code = "401";
204 | response = { error: errorMsg };
205 | response = JSON.stringify(response);
206 | res.setHeader('Content-Type', 'application/json');
207 | res.json(response);
208 | }
209 | });
210 | if (!permissionDenied) {
211 | var promiseList = [];
212 | for (var i = 0; i < req.body.data.length; i++) {
213 | var newDirectoryPath = path.join(contentRootPath + req.body.data[i].filterPath, req.body.data[i].name);
214 | if (fs.lstatSync(newDirectoryPath).isFile()) {
215 | promiseList.push(FileManagerDirectoryContent(req, res, newDirectoryPath, req.body.data[i].filterPath));
216 | } else {
217 | promiseList.push(FileManagerDirectoryContent(req, res, newDirectoryPath + "/", req.body.data[i].filterPath));
218 | }
219 | }
220 | Promise.all(promiseList).then(data => {
221 | data.forEach(function (files) {
222 | if (fs.lstatSync(path.join(contentRootPath + files.filterPath, files.name)).isFile()) {
223 | fs.unlinkSync(path.join(contentRootPath + files.filterPath, files.name));
224 | } else {
225 | const deleteFolderPath = path.join(contentRootPath + files.filterPath, files.name);
226 | const sanitizedPath = path.normalize(deleteFolderPath).replace(/^(\.\.[\/\\])+/, '');
227 | deleteFolderRecursive(path.join(sanitizedPath));
228 | }
229 | });
230 | response = { files: data };
231 | response = JSON.stringify(response);
232 | res.setHeader('Content-Type', 'application/json');
233 | res.json(response);
234 | });
235 | }
236 | }
237 | /**
238 | * function to create the folder
239 | */
240 | function createFolder(req, res, filepath, contentRootPath) {
241 | const sanitizedPath = path.normalize(req.body.path).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
242 | const sanitizedPathName = path.normalize(req.body.name).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
243 | var newDirectoryPath = path.join(contentRootPath + sanitizedPath, sanitizedPathName);
244 | const resolvedPath = newDirectoryPath.replace(/[\\/]/g, "\\\\");
245 | const fullPath = (contentRootPath + req.body.path+req.body.name).replace(/\//g, "\\\\");
246 | const isValidatePath = fullPath == resolvedPath ? true : false;
247 | if(!isValidatePath){
248 | var errorMsg = new Error();
249 | errorMsg.message = "Access denied for Directory-traversal";
250 | errorMsg.code = "401";
251 | response = { error: errorMsg };
252 | response = JSON.stringify(response);
253 | res.setHeader('Content-Type', 'application/json');
254 | res.json(response);
255 | }
256 | var pathPermission = getPathPermission(req.path, false, req.body.data[0].name, filepath, contentRootPath, req.body.data[0].filterPath);
257 | if (pathPermission != null && (!pathPermission.read || !pathPermission.writeContents)) {
258 | var errorMsg = new Error();
259 | errorMsg.message = (permission.message !== "") ? permission.message : req.body.data[0].name + " is not accessible. You need permission to perform the writeContents action.";
260 | errorMsg.code = "401";
261 | response = { error: errorMsg };
262 | response = JSON.stringify(response);
263 | res.setHeader('Content-Type', 'application/json');
264 | res.json(response);
265 | }
266 | else {
267 | if (fs.existsSync(newDirectoryPath)) {
268 | var errorMsg = new Error();
269 | errorMsg.message = "A file or folder with the name " + req.body.name + " already exists.";
270 | errorMsg.code = "400";
271 | response = { error: errorMsg };
272 |
273 | response = JSON.stringify(response);
274 | res.setHeader('Content-Type', 'application/json');
275 | res.json(response);
276 | } else {
277 | fs.mkdirSync(newDirectoryPath);
278 | (async () => {
279 | await FileManagerDirectoryContent(req, res, newDirectoryPath).then(data => {
280 | response = { files: data };
281 | response = JSON.stringify(response);
282 | res.setHeader('Content-Type', 'application/json');
283 | res.json(response);
284 | });
285 | })();
286 | }
287 | }
288 | }
289 | /**
290 | * function to get the file details like path, name and size
291 | */
292 | function fileDetails(req, res, filepath) {
293 | return new Promise((resolve, reject) => {
294 | var cwd = {};
295 | fs.stat(filepath, function (err, stats) {
296 | cwd.name = path.basename(filepath);
297 | cwd.size = getSize(stats.size);
298 | cwd.isFile = stats.isFile();
299 | cwd.modified = stats.ctime;
300 | cwd.created = stats.mtime;
301 | cwd.type = path.extname(filepath);
302 | cwd.location = req.body.data[0].filterPath
303 | resolve(cwd);
304 | });
305 | });
306 | }
307 |
308 | /**
309 | * function to get the folder size
310 | */
311 | function getFolderSize(req, res, directory, sizeValue) {
312 | size = sizeValue;
313 | var filenames = fs.readdirSync(directory);
314 | for (var i = 0; i < filenames.length; i++) {
315 | if (fs.lstatSync(directory + "/" + filenames[i]).isDirectory()) {
316 | getFolderSize(req, res, directory + "/" + filenames[i], size);
317 | } else {
318 | const stats = fs.statSync(directory + "/" + filenames[i]);
319 | size = size + stats.size;
320 | }
321 | }
322 | }
323 |
324 | /**
325 | * function to get the size in kb, MB
326 | */
327 | function getSize(size) {
328 | var hz;
329 | if (size < 1024) hz = size + ' B';
330 | else if (size < 1024 * 1024) hz = (size / 1024).toFixed(2) + ' KB';
331 | else if (size < 1024 * 1024 * 1024) hz = (size / 1024 / 1024).toFixed(2) + ' MB';
332 | else hz = (size / 1024 / 1024 / 1024).toFixed(2) + ' GB';
333 | return hz;
334 | }
335 |
336 | function checkForMultipleLocations(req, contentRootPath) {
337 | var previousLocation = "";
338 | var isMultipleLocation = false;
339 | req.body.data.forEach(function (item) {
340 | if (previousLocation == "") {
341 | previousLocation = item.filterPath;
342 | location = item.filterPath;
343 | } else if (previousLocation == item.filterPath && !isMultipleLocation) {
344 | isMultipleLocation = false;
345 | location = item.filterPath;
346 | } else {
347 | isMultipleLocation = true;
348 | location = "Various Location";
349 | }
350 | });
351 | if (!isMultipleLocation) {
352 | location = contentRootPath.split("/")[contentRootPath.split("/").length - 1] + location.substr(0, location.length - 2);
353 | }
354 | return isMultipleLocation;
355 | }
356 | function getFileDetails(req, res, contentRootPath, filterPath) {
357 | var isNamesAvailable = req.body.names.length > 0 ? true : false;
358 | if (req.body.names.length == 0 && req.body.data != 0) {
359 | var nameValues = [];
360 | req.body.data.forEach(function (item) {
361 | nameValues.push(item.name);
362 | });
363 | req.body.names = nameValues;
364 | }
365 | var sanitizedPathName = path.normalize(req.body.names[0]).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
366 | var sanitizedPath = path.normalize(req.body.path).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
367 | if (req.body.names.length == 1) {
368 | fileDetails(req, res, contentRootPath + (isNamesAvailable ? sanitizedPathName : "")).then(data => {
369 | if (!data.isFile) {
370 | getFolderSize(req, res, contentRootPath + (isNamesAvailable ? sanitizedPathName : ""), 0);
371 | data.size = getSize(size);
372 | size = 0;
373 | }
374 | if (filterPath == "") {
375 | data.location = path.join(filterPath, req.body.names[0]).substr(0, path.join(filterPath, req.body.names[0]).length);
376 | } else {
377 | data.location = path.join(rootName = (path.basename(contentRootPath + sanitizedPath)), filterPath, req.body.names[0]);
378 | }
379 | response = { details: data };
380 | response = JSON.stringify(response);
381 | res.setHeader('Content-Type', 'application/json');
382 | res.json(response);
383 | });
384 | } else {
385 | var isMultipleLocations = false;
386 | isMultipleLocations = checkForMultipleLocations(req, contentRootPath);
387 | req.body.names.forEach(function (item) {
388 | if (fs.lstatSync(contentRootPath + item).isDirectory()) {
389 | getFolderSize(req, res, contentRootPath + item, size);
390 | } else {
391 | const stats = fs.statSync(contentRootPath + item);
392 | size = size + stats.size;
393 | }
394 | });
395 | fileDetails(req, res, contentRootPath + sanitizedPathName).then(data => {
396 | var names = [];
397 | req.body.names.forEach(function (name) {
398 | if (name.split("/").length > 0) {
399 | names.push(name.split("/")[name.split("/").length - 1]);
400 | }
401 | else {
402 | names.push(name);
403 | }
404 | });
405 | data.name = names.join(", ");
406 | data.multipleFiles = true;
407 | data.size = getSize(size);
408 | size = 0;
409 | if (filterPath == "") {
410 | data.location = path.join(rootName = (path.basename(contentRootPath + sanitizedPath)), filterPath).substr(0, path.join(rootName = (path.basename(contentRootPath + sanitizedPath)), filterPath).length - 1);
411 | } else {
412 | data.location = path.join(rootName = (path.basename(contentRootPath + sanitizedPath)), filterPath).substr(0, path.join(rootName = (path.basename(contentRootPath + sanitizedPath)), filterPath).length - 1);
413 | }
414 | response = { details: data };
415 | response = JSON.stringify(response);
416 | res.setHeader('Content-Type', 'application/json');
417 | isMultipleLocations = false;
418 | location = "";
419 | res.json(response);
420 | });
421 | }
422 | }
423 |
424 | function copyFolder(source, dest) {
425 | return new Promise((resolve, reject) => {
426 | if (!fs.existsSync(dest)) {
427 | fs.mkdirSync(dest);
428 | }
429 |
430 | const files = fs.readdirSync(source);
431 | const copyOperations = files.map((file) => {
432 | return new Promise((innerResolve, innerReject) => {
433 | const curSource = path.join(source, file);
434 | const curDest = path.join(dest, file);
435 |
436 | if (fs.lstatSync(curSource).isDirectory()) {
437 | copyFolder(curSource, curDest)
438 | .then(innerResolve)
439 | .catch(innerReject);
440 | } else {
441 | fs.copyFile(curSource, curDest, (err) => {
442 | if (err) return innerReject(err);
443 | innerResolve();
444 | });
445 | }
446 | });
447 | });
448 |
449 | Promise.all(copyOperations)
450 | .then(resolve)
451 | .catch(reject);
452 | });
453 | }
454 |
455 | function updateCopyName(path, name, count, isFile) {
456 | var subName = "", extension = "";
457 | if (isFile) {
458 | extension = name.substr(name.lastIndexOf('.'), name.length - 1);
459 | subName = name.substr(0, name.lastIndexOf('.'));
460 | }
461 | copyName = !isFile ? name + "(" + count + ")" : (subName + "(" + count + ")" + extension);
462 | if (checkForDuplicates(path, copyName, isFile)) {
463 | count = count + 1;
464 | updateCopyName(path, name, count, isFile);
465 | }
466 | }
467 |
468 | function checkForFileUpdate(fromPath, toPath, item, contentRootPath, req) {
469 | var count = 1;
470 | var name = copyName = item.name;
471 | var sanitizedTargetPath = path.normalize(req.body.targetPath).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
472 | if (fromPath == toPath) {
473 | if (checkForDuplicates(contentRootPath + sanitizedTargetPath, name, item.isFile)) {
474 | updateCopyName(contentRootPath + sanitizedTargetPath, name, count, item.isFile);
475 | }
476 | } else {
477 | if (req.body.renameFiles.length > 0 && req.body.renameFiles.indexOf(item.name) >= 0) {
478 | updateCopyName(contentRootPath + sanitizedTargetPath, name, count, item.isFile);
479 | } else {
480 | if (checkForDuplicates(contentRootPath + sanitizedTargetPath, name, item.isFile)) {
481 | isRenameChecking = true;
482 | }
483 | }
484 | }
485 | }
486 | /**
487 | * function copyfile and folder
488 | */
489 | function CopyFiles(req, res, contentRootPath) {
490 | var fileList = [];
491 | var replaceFileList = [];
492 | var permission; var pathPermission; var permissionDenied = false;
493 | pathPermission = getPathPermission(req.path, false, req.body.targetData.name, contentRootPath + req.body.targetPath, contentRootPath, req.body.targetData.filterPath);
494 | req.body.data.forEach(function (item) {
495 | const resolvedPath = path.join(contentRootPath + item.filterPath, item.name);
496 | const fullPath = (contentRootPath + item.filterPath + item.name ).replace(/[\\/]/g, "\\");
497 | const isValidatePath = fullPath == resolvedPath ? true : false;
498 | if(!isValidatePath){
499 | var errorMsg = new Error();
500 | errorMsg.message = "Access denied for Directory-traversal";
501 | errorMsg.code = "401";
502 | response = { error: errorMsg };
503 | response = JSON.stringify(response);
504 | res.setHeader('Content-Type', 'application/json');
505 | res.json(response);
506 | }
507 | var fromPath = contentRootPath + item.filterPath;
508 | permission = getPermission(fromPath, item.name, item.isFile, contentRootPath, item.filterPath);
509 | var fileAccessDenied = (permission != null && (!permission.read || !permission.copy));
510 | var pathAccessDenied = (pathPermission != null && (!pathPermission.read || !pathPermission.writeContents));
511 | if (fileAccessDenied || pathAccessDenied) {
512 | permissionDenied = true;
513 | var errorMsg = new Error();
514 | errorMsg.message = fileAccessDenied ? ((permission.message !== "") ? permission.message :
515 | item.name + " is not accessible. You need permission to perform the copy action.") :
516 | ((pathPermission.message !== "") ? pathPermission.message :
517 | req.body.targetData.name + " is not accessible. You need permission to perform the writeContents action.");
518 | errorMsg.code = "401";
519 | response = { error: errorMsg };
520 | response = JSON.stringify(response);
521 | res.setHeader('Content-Type', 'application/json');
522 | res.json(response);
523 | }
524 | });
525 | if (!permissionDenied) {
526 | const copyPromises = req.body.data.map((item) => {
527 | return new Promise((resolve, reject) => {
528 | const fromPath = path.normalize(contentRootPath + item.filterPath + item.name).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
529 | let toPath = contentRootPath + req.body.targetPath + item.name;
530 | checkForFileUpdate(fromPath, toPath, item, contentRootPath, req);
531 | if (!isRenameChecking) {
532 | const sanitizedTargetPath = path.normalize(req.body.targetPath).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
533 | const sanitizedTargetName = path.normalize(copyName).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
534 | toPath = contentRootPath + sanitizedTargetPath + sanitizedTargetName;
535 |
536 | if (item.isFile) {
537 | // File copy operation using promises
538 | fs.copyFile(path.join(fromPath), path.join(toPath), (err) => {
539 | if (err) return reject(err);
540 | resolve();
541 | });
542 | } else {
543 | // Folder copy operation as a promise
544 | copyFolder(fromPath, toPath)
545 | .then(() => {
546 | resolve();
547 | })
548 | .catch((err) => reject(err));
549 | }
550 | var list = item;
551 | list.filterPath = sanitizedTargetPath;
552 | list.name = copyName;
553 | fileList.push(list);
554 | } else {
555 | replaceFileList.push(item.name);
556 | resolve();
557 | }
558 | });
559 | });
560 |
561 | Promise.all(copyPromises)
562 | .then(() => {
563 | if (replaceFileList.length === 0) {
564 | copyName = "";
565 | response = { files: fileList };
566 | res.setHeader('Content-Type', 'application/json');
567 | res.json(response);
568 | } else {
569 | isRenameChecking = false;
570 | const errorMsg = {
571 | message: "File Already Exists.",
572 | code: "400",
573 | fileExists: replaceFileList
574 | };
575 | response = { error: errorMsg, files: [] };
576 | res.setHeader('Content-Type', 'application/json');
577 | res.json(response);
578 | }
579 | })
580 | .catch((err) => {
581 | response = { error: err };
582 | res.setHeader('Content-Type', 'application/json');
583 | res.json(response);
584 | });
585 | }
586 | }
587 |
588 | function MoveFolder(source, dest) {
589 | return new Promise((resolve, reject) => {
590 | if (!fs.existsSync(dest)) {
591 | fs.mkdirSync(dest);
592 | }
593 |
594 | const files = fs.readdirSync(source);
595 | const fileOperations = files.map((file) => {
596 | return new Promise((innerResolve, innerReject) => {
597 | const curSource = path.join(source, file);
598 | const curDest = path.join(dest, file);
599 |
600 | if (fs.lstatSync(curSource).isDirectory()) {
601 | MoveFolder(curSource, curDest)
602 | .then(() => {
603 | fs.rmdirSync(curSource);
604 | innerResolve();
605 | })
606 | .catch(innerReject);
607 | } else {
608 | fs.copyFile(curSource, curDest, (err) => {
609 | if (err) return innerReject(err);
610 | fs.unlink(curSource, (unlinkErr) => {
611 | if (unlinkErr) return innerReject(unlinkErr);
612 | innerResolve();
613 | });
614 | });
615 | }
616 | });
617 | });
618 |
619 | Promise.all(fileOperations)
620 | .then(resolve)
621 | .catch(reject);
622 | });
623 | }
624 | /**
625 | * function move files and folder
626 | */
627 | function MoveFiles(req, res, contentRootPath) {
628 | var fileList = [];
629 | var replaceFileList = [];
630 | var permission; var pathPermission; var permissionDenied = false;
631 | pathPermission = getPathPermission(req.path, false, req.body.targetData.name, contentRootPath + req.body.targetPath, contentRootPath, req.body.targetData.filterPath);
632 | req.body.data.forEach(function (item) {
633 | const resolvedPath = path.join(contentRootPath + item.filterPath, item.name);
634 | const fullPath = (contentRootPath + item.filterPath + item.name ).replace(/[\\/]/g, "\\");
635 | const isValidatePath = fullPath == resolvedPath ? true : false;
636 | if(!isValidatePath){
637 | var errorMsg = new Error();
638 | errorMsg.message = "Access denied for Directory-traversal";
639 | errorMsg.code = "401";
640 | response = { error: errorMsg };
641 | response = JSON.stringify(response);
642 | res.setHeader('Content-Type', 'application/json');
643 | res.json(response);
644 | }
645 | var fromPath = contentRootPath + item.filterPath;
646 | permission = getPermission(fromPath, item.name, item.isFile, contentRootPath, item.filterPath);
647 | var fileAccessDenied = (permission != null && (!permission.read || !permission.write));
648 | var pathAccessDenied = (pathPermission != null && (!pathPermission.read || !pathPermission.writeContents));
649 | if (fileAccessDenied || pathAccessDenied) {
650 | permissionDenied = true;
651 | var errorMsg = new Error();
652 | errorMsg.message = fileAccessDenied ? ((permission.message !== "") ? permission.message :
653 | item.name + " is not accessible. You need permission to perform the write action.") :
654 | ((pathPermission.message !== "") ? pathPermission.message :
655 | req.body.targetData.name + " is not accessible. You need permission to perform the writeContents action.");
656 | errorMsg.code = "401";
657 | response = { error: errorMsg };
658 | response = JSON.stringify(response);
659 | res.setHeader('Content-Type', 'application/json');
660 | res.json(response);
661 | }
662 | });
663 | if (!permissionDenied) {
664 | const movePromises = req.body.data.map((item) => {
665 | return new Promise((resolve, reject) => {
666 | const fromPath = path.normalize(contentRootPath + item.filterPath + item.name).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
667 | let toPath = contentRootPath + req.body.targetPath + item.name;
668 | checkForFileUpdate(fromPath, toPath, item, contentRootPath, req);
669 | if (!isRenameChecking) {
670 | const sanitizedTargetPath = path.normalize(req.body.targetPath).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
671 | const sanitizedTargetName = path.normalize(copyName).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
672 | toPath = contentRootPath + sanitizedTargetPath + sanitizedTargetName;
673 |
674 | if (item.isFile) {
675 | const source = fs.createReadStream(path.join(fromPath));
676 | const desti = fs.createWriteStream(path.join(toPath));
677 |
678 | source.pipe(desti);
679 | source.on('end', () => {
680 | fs.unlink(path.join(fromPath), (err) => {
681 | if (err) return reject(err);
682 | resolve();
683 | });
684 | });
685 |
686 | source.on('error', (err) => reject(err));
687 | desti.on('error', (err) => reject(err));
688 | } else {
689 | MoveFolder(fromPath, toPath)
690 | .then(() => {
691 | fs.rmdirSync(fromPath);
692 | resolve();
693 | })
694 | .catch((err) => reject(err));
695 | }
696 | var list = item;
697 | list.name = copyName;
698 | list.filterPath = sanitizedTargetPath;
699 | fileList.push(list);
700 | } else {
701 | replaceFileList.push(item.name);
702 | resolve();
703 | }
704 | });
705 | });
706 |
707 | Promise.all(movePromises)
708 | .then(() => {
709 | if (replaceFileList.length === 0) {
710 | copyName = "";
711 | response = { files: fileList };
712 | res.setHeader('Content-Type', 'application/json');
713 | res.json(response);
714 | } else {
715 | isRenameChecking = false;
716 | const errorMsg = {
717 | message: "File Already Exists.",
718 | code: "400",
719 | fileExists: replaceFileList
720 | };
721 | response = { error: errorMsg, files: [] };
722 | res.setHeader('Content-Type', 'application/json');
723 | res.json(response);
724 | }
725 | })
726 | .catch((err) => {
727 | response = { error: err };
728 | res.setHeader('Content-Type', 'application/json');
729 | res.json(response);
730 | });
731 | }
732 | }
733 |
734 | function getRelativePath(rootDirectory, fullPath) {
735 | if (rootDirectory.substring(rootDirectory.length - 1) == "/") {
736 | if (fullPath.indexOf(rootDirectory) >= 0) {
737 | return fullPath.substring(rootDirectory.length - 1);
738 | }
739 | }
740 | else if (fullPath.indexOf(rootDirectory + "/") >= 0) {
741 | return "/" + fullPath.substring(rootDirectory.length + 1);
742 | }
743 | else {
744 | return "";
745 | }
746 | }
747 |
748 | function hasPermission(rule) {
749 | return ((rule == undefined) || (rule == null) || (rule == Permission.Allow)) ? true : false;
750 | }
751 |
752 | function getMessage(rule) {
753 | return ((rule.message == undefined) || (rule.message == null)) ? "" : rule.message;
754 | }
755 |
756 | function updateRules(filePermission, accessRule) {
757 | filePermission.download = hasPermission(accessRule.read) && hasPermission(accessRule.download);
758 | filePermission.write = hasPermission(accessRule.read) && hasPermission(accessRule.write);
759 | filePermission.writeContents = hasPermission(accessRule.read) && hasPermission(accessRule.writeContents);
760 | filePermission.copy = hasPermission(accessRule.read) && hasPermission(accessRule.copy);
761 | filePermission.read = hasPermission(accessRule.read);
762 | filePermission.upload = hasPermission(accessRule.read) && hasPermission(accessRule.upload);
763 | filePermission.message = getMessage(accessRule);
764 | return filePermission;
765 | }
766 |
767 | function getPathPermission(path, isFile, name, filepath, contentRootPath, filterPath) {
768 | return getPermission(filepath, name, isFile, contentRootPath, filterPath);
769 | }
770 |
771 | function getPermission(filepath, name, isFile, contentRootPath, filterPath) {
772 | var filePermission = new AccessPermission(true, true, true, true, true, true, "");
773 | if (accessDetails == null) {
774 | return null;
775 | } else {
776 | accessDetails.rules.forEach(function (accessRule) {
777 | if (isFile && accessRule.isFile) {
778 | var nameExtension = name.substr(name.lastIndexOf("."), name.length - 1).toLowerCase();
779 | var fileName = name.substr(0, name.lastIndexOf("."));
780 | var currentPath = contentRootPath + filterPath;
781 | if (accessRule.isFile && isFile && accessRule.path != "" && accessRule.path != null && (accessRule.role == null || accessRule.role == accessDetails.role)) {
782 | if (accessRule.path.indexOf("*.*") > -1) {
783 | var parentPath = accessRule.path.substr(0, accessRule.path.indexOf("*.*"));
784 | if (currentPath.indexOf(contentRootPath + parentPath) == 0 || parentPath == "") {
785 | filePermission = updateRules(filePermission, accessRule);
786 | }
787 | }
788 | else if (accessRule.path.indexOf("*.") > -1) {
789 | var pathExtension = accessRule.path.substr(accessRule.path.lastIndexOf("."), accessRule.path.length - 1).toLowerCase();
790 | var parentPath = accessRule.path.substr(0, accessRule.path.indexOf("*."));
791 | if (((contentRootPath + parentPath) == currentPath || parentPath == "") && nameExtension == pathExtension) {
792 | filePermission = updateRules(filePermission, accessRule);
793 | }
794 | }
795 | else if (accessRule.path.indexOf(".*") > -1) {
796 | var pathName = accessRule.path.substr(0, accessRule.path.lastIndexOf(".")).substr(accessRule.path.lastIndexOf("/") + 1, accessRule.path.length - 1);
797 | var parentPath = accessRule.path.substr(0, accessRule.path.indexOf(pathName + ".*"));
798 | if (((contentRootPath + parentPath) == currentPath || parentPath == "") && fileName == pathName) {
799 | filePermission = updateRules(filePermission, accessRule);
800 | }
801 | }
802 | else if (contentRootPath + accessRule.path == filepath) {
803 | filePermission = updateRules(filePermission, accessRule);
804 | }
805 | }
806 | } else {
807 | if (!accessRule.isFile && !isFile && accessRule.path != null && (accessRule.role == null || accessRule.role == accessDetails.role)) {
808 | var parentFolderpath = contentRootPath + filterPath;
809 | if (accessRule.path.indexOf("*") > -1) {
810 | var parentPath = accessRule.path.substr(0, accessRule.path.indexOf("*"));
811 | if (((parentFolderpath + (parentFolderpath[parentFolderpath.length - 1] == "/" ? "" : "/") + name).lastIndexOf(contentRootPath + parentPath) == 0) || parentPath == "") {
812 | filePermission = updateRules(filePermission, accessRule);
813 | }
814 | } else if (path.join(contentRootPath, accessRule.path) == path.join(parentFolderpath, name) || path.join(contentRootPath, accessRule.path) == path.join(parentFolderpath, name + "/")) {
815 | filePermission = updateRules(filePermission, accessRule);
816 | }
817 | else if (path.join(parentFolderpath, name).lastIndexOf(path.join(contentRootPath, accessRule.path)) == 0) {
818 | filePermission.write = hasPermission(accessRule.writeContents);
819 | filePermission.writeContents = hasPermission(accessRule.writeContents);
820 | filePermission.message = getMessage(accessRule);
821 | }
822 | }
823 | }
824 | });
825 | return filePermission;
826 | }
827 | }
828 | /**
829 | * returns the current working directories
830 | */
831 | function FileManagerDirectoryContent(req, res, filepath, searchFilterPath) {
832 | return new Promise((resolve, reject) => {
833 | var cwd = {};
834 | replaceRequestParams(req, res);
835 | fs.stat(filepath, function (err, stats) {
836 | cwd.name = path.basename(filepath);
837 | cwd.size = getSize(stats.size);
838 | cwd.isFile = stats.isFile();
839 | cwd.dateModified = stats.ctime;
840 | cwd.dateCreated = stats.mtime;
841 | cwd.type = path.extname(filepath);
842 | if (searchFilterPath) {
843 | cwd.filterPath = searchFilterPath;
844 | } else {
845 | cwd.filterPath = (req.body.data.length > 0 && req.body.path != "/") ? path.normalize(req.body.data[0].filterPath).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/') : "";
846 | }
847 | cwd.permission = getPathPermission(req.path, cwd.isFile, (req.body.path == "/") ? "" : cwd.name, filepath, contentRootPath, cwd.filterPath);
848 | if (fs.lstatSync(filepath).isFile()) {
849 | cwd.hasChild = false;
850 | resolve(cwd);
851 | }
852 | });
853 | if (fs.lstatSync(filepath).isDirectory()) {
854 | fs.readdir(filepath, function (err, stats) {
855 | var hasChild = stats.some(stat => {
856 | try {
857 | const fullPath = path.join(filepath, stat);
858 | return fs.lstatSync(fullPath).isDirectory();
859 | } catch (error) {
860 | return false;
861 | }
862 | });
863 | cwd.hasChild = hasChild;
864 | resolve(cwd);
865 | });
866 | }
867 | });
868 | }
869 | //Multer to upload the files to the server
870 | var fileName = [];
871 | //MULTER CONFIG: to get file photos to temp server storage
872 | const multerConfig = {
873 | //specify diskStorage (another option is memory)
874 | storage: multer.diskStorage({
875 | //specify destination
876 | destination: function (req, file, next) {
877 | next(null, './');
878 | },
879 |
880 | //specify the filename to be unique
881 | filename: function (req, file, next) {
882 | fileName.push(file.originalname);
883 | next(null, file.originalname);
884 |
885 | }
886 | }),
887 |
888 | // filter out and prevent non-image files.
889 | fileFilter: function (req, file, next) {
890 | next(null, true);
891 | }
892 | };
893 |
894 | function replaceRequestParams(req, res) {
895 | const sanitizedPath = (req.body.path ? path.normalize(req.body.path).replace(/\\/g, '/') : undefined);
896 | req.body.path = (req.body.path && sanitizedPath);
897 | }
898 | /**
899 | * Gets the imageUrl from the client
900 | */
901 | app.get('/GetImage', function (req, res) {
902 | replaceRequestParams(req, res);
903 | const sanitizedPath = path.normalize(req.query.path).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, "/");
904 | var image = sanitizedPath.split("/").length > 1 ? sanitizedPath : "/" + sanitizedPath;
905 | const resolvedPath = path.resolve(contentRootPath + image.substr(0, image.lastIndexOf("/")), image.substr(image.lastIndexOf("/") + 1, image.length - 1));
906 | const fullPath = (contentRootPath + image).replace(/[\\/]/g, "\\");
907 | const isValidatePath = fullPath == resolvedPath ? true : false;
908 | if(!isValidatePath){
909 | var errorMsg = new Error();
910 | errorMsg.message = "Access denied for Directory-traversal";
911 | errorMsg.code = "401";
912 | response = { error: errorMsg };
913 | response = JSON.stringify(response);
914 | res.setHeader('Content-Type', 'application/json');
915 | res.json(response);
916 | }
917 | var pathPermission = getPermission(contentRootPath + image.substr(0, image.lastIndexOf("/")), image.substr(image.lastIndexOf("/") + 1, image.length - 1), true, contentRootPath, image.substr(0, image.lastIndexOf("/")));
918 | if (pathPermission != null && !pathPermission.read) {
919 | return null;
920 | }
921 | else {
922 | fs.readFile(contentRootPath + image, function (err, content) {
923 | if (err) {
924 | res.writeHead(400, { 'Content-type': 'text/html' });
925 | res.end("No such image");
926 | } else {
927 | //specify the content type in the response will be an image
928 | res.writeHead(200, { 'Content-type': 'image/jpg' });
929 | res.end(content);
930 | }
931 | });
932 | }
933 | });
934 |
935 | /**
936 | * Handles the upload request
937 | */
938 | app.post('/Upload', multer(multerConfig).any('uploadFiles'), function (req, res) {
939 | replaceRequestParams(req, res);
940 | const checkTraversalPath = path.resolve(contentRootPath + req.body.path).replace(/[\\/]/g, "\\\\")+"\\\\";
941 | const actualPath = (contentRootPath + req.body.path).replace(/\//g, "\\\\");
942 | const isPathTraversal = checkTraversalPath == actualPath ? true : false;
943 | if(!isPathTraversal){
944 | var errorMsg = new Error();
945 | errorMsg.message = "Access denied for Directory-traversal";
946 | errorMsg.code = "401";
947 | response = { error: errorMsg };
948 | response = JSON.stringify(response);
949 | res.setHeader('Content-Type', 'application/json');
950 | res.json(response);
951 | }
952 | var pathPermission = req.body.data != null ? getPathPermission(req.path, true, JSON.parse(req.body.data).name, contentRootPath + req.body.path, contentRootPath, JSON.parse(req.body.data).filterPath) : null;
953 | if (pathPermission != null && (!pathPermission.read || !pathPermission.upload)) {
954 | var errorMsg = new Error();
955 | errorMsg.message = (permission.message !== "") ? permission.message :
956 | JSON.parse(req.body.data).name + " is not accessible. You need permission to perform the upload action.";
957 | errorMsg.code = "401";
958 | response = { error: errorMsg };
959 | response = JSON.stringify(response);
960 | res.setHeader('Content-Type', 'application/json');
961 | res.json(response);
962 | }
963 |
964 | if (req.body != null && req.body.path != null) {
965 | var errorValue = new Error();
966 | var existFiles = [];
967 |
968 | if (req.body.action === 'save' || req.body.action === 'keepboth' || req.body.action === 'replace') {
969 | var folders = (path.normalize(req.body.filename).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, "/")).split('/');
970 | var filepath = path.normalize(req.body.path).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, "/");
971 | var uploadedFileName = folders[folders.length - 1];
972 | // checking the folder upload
973 | if (folders.length > 1)
974 | {
975 | for (var i = 0; i < folders.length - 1; i++)
976 | {
977 | var newDirectoryPath = path.join(contentRootPath + filepath, folders[i]);
978 | const fullPath = (contentRootPath + filepath + folders[i]).replace(/[\\/]/g, "\\");
979 | const isValidatePath = fullPath == newDirectoryPath ? true : false;
980 | if(!isValidatePath){
981 | var errorMsg = new Error();
982 | errorMsg.message = "Access denied for Directory-traversal";
983 | errorMsg.code = "401";
984 | response = { error: errorMsg };
985 | response = JSON.stringify(response);
986 | res.setHeader('Content-Type', 'application/json');
987 | res.json(response);
988 | }
989 | if (!fs.existsSync(newDirectoryPath)) {
990 | fs.mkdirSync(newDirectoryPath);
991 | }
992 | filepath += folders[i] + "/";
993 | }
994 | }
995 |
996 | const fullFilePath = path.join(contentRootPath, filepath + uploadedFileName);
997 |
998 | if (req.body.action === 'save') {
999 | if (fs.existsSync(fullFilePath)) {
1000 | existFiles.push(fullFilePath);
1001 | } else {
1002 | fs.renameSync('./' + uploadedFileName, fullFilePath);
1003 | }
1004 | } else if (req.body.action === 'replace') {
1005 | if (fs.existsSync(fullFilePath)) {
1006 | fs.unlinkSync(fullFilePath);
1007 | }
1008 | fs.renameSync('./' + uploadedFileName, fullFilePath);
1009 | } else if (req.body.action === 'keepboth') {
1010 | let newName = fullFilePath;
1011 | let fileCount = 0;
1012 | while (fs.existsSync(newName)) {
1013 | fileCount++;
1014 | const parsedPath = path.parse(fullFilePath);
1015 | newName = path.join(parsedPath.dir, `${parsedPath.name}(${fileCount})${parsedPath.ext}`);
1016 | }
1017 | fs.renameSync('./' + uploadedFileName, newName);
1018 | }
1019 | } else if (req.body.action === 'remove') {
1020 | const normalizedPath = path.normalize(req.body.path).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
1021 | const cancelUploadValue = path.normalize(req.body['cancel-upload']).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
1022 | if (fs.existsSync(path.join(contentRootPath, normalizedPath + cancelUploadValue))) {
1023 | fs.unlinkSync(path.join(contentRootPath, normalizedPath + cancelUploadValue));
1024 | }
1025 | }
1026 |
1027 | if (existFiles.length > 0) {
1028 | errorValue.message = "File already exists.";
1029 | errorValue.code = "400";
1030 | errorValue.fileExists = existFiles;
1031 | response = { error: errorValue };
1032 | response = JSON.stringify(response);
1033 | res.setHeader('Content-Type', 'application/json');
1034 | return res.status(400).json(response);
1035 | }
1036 | res.send('Success');
1037 | fileName = [];
1038 | }
1039 | });
1040 |
1041 | /**
1042 | * Download a file or folder
1043 | */
1044 | app.post('/Download', function (req, res) {
1045 | replaceRequestParams(req, res);
1046 | var downloadObj = JSON.parse(req.body.downloadInput);
1047 | var permission; var permissionDenied = false;
1048 | downloadObj.data.forEach(function (item) {
1049 | const resolvedPath = path.join(contentRootPath + item.filterPath, item.name);
1050 | const fullPath = (contentRootPath + item.filterPath + item.name).replace(/\//g, "\\");
1051 | const isValidatePath = fullPath == resolvedPath ? true : false;
1052 | if(!isValidatePath){
1053 | var errorMsg = new Error();
1054 | errorMsg.message = "Access denied for Directory-traversal";
1055 | errorMsg.code = "401";
1056 | response = { error: errorMsg };
1057 | response = JSON.stringify(response);
1058 | res.setHeader('Content-Type', 'application/json');
1059 | res.json(response);
1060 | }
1061 | var filepath = (contentRootPath + item.filterPath).replace(/\\/g, "/");
1062 | permission = getPermission(filepath + item.name, item.name, item.isFile, contentRootPath, item.filterPath);
1063 | if (permission != null && (!permission.read || !permission.download)) {
1064 | permissionDenied = true;
1065 | var errorMsg = new Error();
1066 | errorMsg.message = (permission.message !== "") ? permission.message : getFileName(contentRootPath + item.filterPath + item.name) + " is not accessible. You need permission to perform the download action.";
1067 | errorMsg.code = "401";
1068 | response = { error: errorMsg };
1069 | response = JSON.stringify(response);
1070 | res.setHeader('Content-Type', 'application/json');
1071 | res.json(response);
1072 | }
1073 | });
1074 | if (!permissionDenied) {
1075 | if (downloadObj.names.length === 1 && downloadObj.data[0].isFile) {
1076 | var file = contentRootPath + downloadObj.path + downloadObj.names[0];
1077 | res.download(file);
1078 | } else {
1079 | var archive = archiver('zip', {
1080 | gzip: true,
1081 | zlib: { level: 9 } // Sets the compression level.
1082 | });
1083 | var output = fs.createWriteStream('./Files.zip');
1084 | downloadObj.data.forEach(function (item) {
1085 | archive.on('error', function (err) {
1086 | throw err;
1087 | });
1088 | if (item.isFile) {
1089 | archive.file(contentRootPath + item.filterPath + item.name, { name: item.name });
1090 | }
1091 | else {
1092 | archive.directory(contentRootPath + item.filterPath + item.name + "/", item.name);
1093 | }
1094 | });
1095 | archive.pipe(output);
1096 | archive.finalize();
1097 | output.on('close', function () {
1098 | var stat = fs.statSync(output.path);
1099 | res.writeHead(200, {
1100 | 'Content-disposition': 'attachment; filename=Files.zip; filename*=UTF-8',
1101 | 'Content-Type': 'APPLICATION/octet-stream',
1102 | 'Content-Length': stat.size
1103 | });
1104 | var filestream = fs.createReadStream(output.path);
1105 | filestream.pipe(res);
1106 | });
1107 | }
1108 | }
1109 | });
1110 |
1111 | /**
1112 | * Handles the read request
1113 | */
1114 | app.post('/', function (req, res) {
1115 | replaceRequestParams(req, res);
1116 | req.setTimeout(0);
1117 | function getRules() {
1118 | var details = new AccessDetails();
1119 | var accessRuleFile = "accessRules.json";
1120 | if (!fs.existsSync(accessRuleFile)) { return null; }
1121 | var rawData = fs.readFileSync(accessRuleFile);
1122 | if (rawData.length === 0) { return null; }
1123 | var parsedData = JSON.parse(rawData);
1124 | var data = parsedData.rules;
1125 | var accessRules = [];
1126 | for (var i = 0; i < data.length; i++) {
1127 | var rule = new AccessRules(data[i].path, data[i].role, data[i].read, data[i].write, data[i].writeContents, data[i].copy, data[i].download, data[i].upload, data[i].isFile, data[i].message);
1128 | accessRules.push(rule);
1129 | }
1130 | if (accessRules.length == 1 && accessRules[0].path == undefined) {
1131 | return null;
1132 | } else {
1133 | details.rules = accessRules;
1134 | details.role = parsedData.role;
1135 | return details;
1136 | }
1137 | }
1138 |
1139 | accessDetails = getRules();
1140 |
1141 | // Action for getDetails
1142 | if (req.body.action == "details") {
1143 | var sanitizedPath = path.normalize(req.body.path).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
1144 | getFileDetails(req, res, contentRootPath + sanitizedPath, req.body.data[0].filterPath);
1145 | }
1146 | // Action for copying files
1147 | if (req.body.action == "copy") {
1148 | CopyFiles(req, res, contentRootPath);
1149 | }
1150 | // Action for movinh files
1151 | if (req.body.action == "move") {
1152 | MoveFiles(req, res, contentRootPath);
1153 | }
1154 | // Action to create a new folder
1155 | if (req.body.action == "create") {
1156 | createFolder(req, res, contentRootPath + req.body.path, contentRootPath);
1157 | }
1158 | // Action to remove a file
1159 | if (req.body.action == "delete") {
1160 | deleteFolder(req, res, contentRootPath);
1161 | }
1162 | // Action to rename a file
1163 | if (req.body.action === "rename") {
1164 | renameFolder(req, res, contentRootPath + req.body.path);
1165 | }
1166 |
1167 | function addSearchList(filename, contentRootPath, fileList, files, index) {
1168 | var cwd = {};
1169 | var stats = fs.statSync(filename);
1170 | cwd.name = path.basename(filename);
1171 | cwd.size = stats.size;
1172 | cwd.isFile = stats.isFile();
1173 | cwd.dateModified = stats.mtime;
1174 | cwd.dateCreated = stats.ctime;
1175 | cwd.type = path.extname(filename);
1176 | cwd.filterPath = filename.substr((contentRootPath.length), filename.length).replace(files[index], "");
1177 | cwd.permission = getPermission(filename.replace(/\\/g, "/"), cwd.name, cwd.isFile, contentRootPath, cwd.filterPath);
1178 | var permission = parentsHavePermission(filename, contentRootPath, cwd.isFile, cwd.name, cwd.filterPath);
1179 | if (permission) {
1180 | if (fs.lstatSync(filename).isFile()) {
1181 | cwd.hasChild = false;
1182 | }
1183 | if (fs.lstatSync(filename).isDirectory()) {
1184 | var statsRead = fs.readdirSync(filename);
1185 | cwd.hasChild = statsRead.length > 0;
1186 | }
1187 | fileList.push(cwd);
1188 | }
1189 | }
1190 |
1191 | function parentsHavePermission(filepath, contentRootPath, isFile, name, filterPath) {
1192 | var parentPath = filepath.substr(contentRootPath.length, filepath.length - 1).replace(/\\/g, "/");
1193 | parentPath = parentPath.substr(0, parentPath.indexOf(name)) + (isFile ? "" : "/");
1194 | var parents = parentPath.split('/');
1195 | var currPath = "/";
1196 | var hasPermission = true;
1197 | var pathPermission;
1198 | for (var i = 0; i <= parents.length - 2; i++) {
1199 | currPath = (parents[i] == "") ? currPath : (currPath + parents[i] + "/");
1200 | pathPermission = getPathPermission(parentPath, false, parents[i], contentRootPath + (currPath == "/" ? "" : "/"), contentRootPath, filterPath);
1201 | if (pathPermission == null) {
1202 | break;
1203 | }
1204 | else if (pathPermission != null && !pathPermission.read) {
1205 | hasPermission = false;
1206 | break;
1207 | }
1208 | }
1209 | return hasPermission;
1210 | }
1211 |
1212 | function checkForSearchResult(casesensitive, filter, isFile, fileName, searchString) {
1213 | var isAddable = false;
1214 | if (searchString.substr(0, 1) == "*" && searchString.substr(searchString.length - 1, 1) == "*") {
1215 | if (casesensitive ? fileName.indexOf(filter) >= 0 : (fileName.indexOf(filter.toLowerCase()) >= 0 || fileName.indexOf(filter.toUpperCase()) >= 0)) {
1216 | isAddable = true
1217 | }
1218 | } else if (searchString.substr(searchString.length - 1, 1) == "*") {
1219 | if (casesensitive ? fileName.startsWith(filter) : (fileName.startsWith(filter.toLowerCase()) || fileName.startsWith(filter.toUpperCase()))) {
1220 | isAddable = true
1221 | }
1222 | } else {
1223 | if (casesensitive ? fileName.endsWith(filter) : (fileName.endsWith(filter.toLowerCase()) || fileName.endsWith(filter.toUpperCase()))) {
1224 | isAddable = true
1225 | }
1226 | }
1227 | return isAddable;
1228 | }
1229 |
1230 | function fromDir(startPath, filter, contentRootPath, casesensitive, searchString) {
1231 | if (!fs.existsSync(startPath)) {
1232 | return;
1233 | }
1234 | var files = fs.readdirSync(startPath);
1235 | for (var i = 0; i < files.length; i++) {
1236 | var filename = path.join(startPath, files[i]);
1237 | var stat = fs.lstatSync(filename);
1238 | if (stat.isDirectory()) {
1239 | if (checkForSearchResult(casesensitive, filter, false, files[i], searchString)) {
1240 | addSearchList(filename, contentRootPath, fileList, files, i);
1241 | }
1242 | fromDir(filename, filter, contentRootPath, casesensitive, searchString); //recurse
1243 | }
1244 | else if (checkForSearchResult(casesensitive, filter, true, files[i], searchString)) {
1245 | addSearchList(filename, contentRootPath, fileList, files, i);
1246 | }
1247 | }
1248 | }
1249 |
1250 | // Action to search a file
1251 | if (req.body.action === 'search') {
1252 | const resolvedPath = path.resolve(contentRootPath + req.body.path).replace(/[\\/]/g, "\\\\")+"\\\\";
1253 | const fullPath = (contentRootPath + req.body.path).replace(/\//g, "\\\\");
1254 | const isValidatePath = fullPath == resolvedPath ? true : false;
1255 | if(!isValidatePath){
1256 | var errorMsg = new Error();
1257 | errorMsg.message = "Access denied for Directory-traversal";
1258 | errorMsg.code = "401";
1259 | response = { error: errorMsg };
1260 | response = JSON.stringify(response);
1261 | res.setHeader('Content-Type', 'application/json');
1262 | res.json(response);
1263 | }
1264 | var fileList = [];
1265 | const sanitizedPath = path.resolve(contentRootPath + path.normalize(req.body.path).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/')).replace(/\\/g, '/') + '/';
1266 | fromDir(sanitizedPath, req.body.searchString.replace(/\*/g, ""), contentRootPath, req.body.caseSensitive, req.body.searchString);
1267 | (async () => {
1268 | const tes = await FileManagerDirectoryContent(req, res, contentRootPath + path.normalize(req.body.path).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/'));
1269 | if (tes.permission != null && !tes.permission.read) {
1270 | var errorMsg = new Error();
1271 | errorMsg.message = (permission.message !== "") ? permission.message :
1272 | "'" + getFileName(contentRootPath + (req.body.path.substring(0, req.body.path.length - 1))) + "' is not accessible. You need permission to perform the read action.";
1273 | errorMsg.code = "401";
1274 | response = { error: errorMsg };
1275 | response = JSON.stringify(response);
1276 | res.setHeader('Content-Type', 'application/json');
1277 | res.json(response);
1278 | } else {
1279 | response = { cwd: tes, files: fileList };
1280 | response = JSON.stringify(response);
1281 | res.setHeader('Content-Type', 'application/json');
1282 | res.json(response);
1283 | }
1284 | })();
1285 | }
1286 |
1287 | function ReadDirectories(file) {
1288 | var cwd = {};
1289 | var directoryList = [];
1290 | function stats(file) {
1291 | return new Promise((resolve, reject) => {
1292 | fs.stat(file, (err, cwd) => {
1293 | if (err) {
1294 | return reject(err);
1295 | }
1296 | cwd.name = path.basename(contentRootPath + req.body.path + file);
1297 | cwd.size = (cwd.size);
1298 | cwd.isFile = cwd.isFile();
1299 | cwd.dateModified = cwd.ctime;
1300 | cwd.dateCreated = cwd.mtime;
1301 | cwd.filterPath = getRelativePath(contentRootPath, contentRootPath + req.body.path, req);
1302 | cwd.type = path.extname(contentRootPath + req.body.path + file);
1303 | cwd.permission = getPermission(contentRootPath + req.body.path + cwd.name, cwd.name, cwd.isFile, contentRootPath, cwd.filterPath);
1304 | if (fs.lstatSync(file).isDirectory()) {
1305 | fs.readdirSync(file).forEach(function (items) {
1306 | if (fs.statSync(path.join(file, items)).isDirectory()) {
1307 | directoryList.push(items[i]);
1308 | }
1309 | if (directoryList.length > 0) {
1310 | cwd.hasChild = true;
1311 | } else {
1312 | cwd.hasChild = false;
1313 | directoryList = [];
1314 | }
1315 | });
1316 | } else {
1317 | cwd.hasChild = false;
1318 | dir = [];
1319 | }
1320 | directoryList = [];
1321 | resolve(cwd);
1322 | });
1323 | });
1324 | }
1325 | var promiseList = [];
1326 | for (var i = 0; i < file.length; i++) {
1327 | const sanitizedPath = path.normalize(req.body.path).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
1328 | promiseList.push(stats(path.join(contentRootPath + sanitizedPath, file[i])));
1329 | }
1330 | return Promise.all(promiseList);
1331 | }
1332 |
1333 | // Action to read a file
1334 | if (req.body.action == "read") {
1335 | (async () => {
1336 | const resolvedPath = path.resolve(contentRootPath + req.body.path).replace(/[\\/]/g, "\\\\")+"\\\\";
1337 | const fullPath = (contentRootPath + req.body.path).replace(/\//g, "\\\\");
1338 | const isValidatePath = fullPath == resolvedPath ? true : false;
1339 | const filesList = await GetFiles(req, res);
1340 | const sanitizedPath = path.normalize(req.body.path).replace(/^(\.\.[\/\\])+/, '').replace(/\\/g, '/');
1341 | const cwdFiles = await FileManagerDirectoryContent(req, res, contentRootPath + sanitizedPath);
1342 | cwdFiles.name = req.body.path == "/" ? rootName = (path.basename(contentRootPath + sanitizedPath)) : path.basename(contentRootPath + sanitizedPath)
1343 | var response = {};
1344 | if(!isValidatePath)
1345 | {
1346 | var errorMsg = new Error();
1347 | errorMsg.message = "Access denied for Directory-traversal.";
1348 | errorMsg.code = "401";
1349 | response = { cwd: cwdFiles, files: null, error: errorMsg };
1350 | response = JSON.stringify(response);
1351 | res.setHeader('Content-Type', 'application/json');
1352 | res.json(response);
1353 | }
1354 | if (cwdFiles.permission != null && !cwdFiles.permission.read) {
1355 | var errorMsg = new Error();
1356 | errorMsg.message = (cwdFiles.permission.message !== "") ? cwdFiles.permission.message :
1357 | "'" + cwdFiles.name + "' is not accessible. You need permission to perform the read action.";
1358 | errorMsg.code = "401";
1359 | response = { cwd: cwdFiles, files: null, error: errorMsg };
1360 | response = JSON.stringify(response);
1361 | res.setHeader('Content-Type', 'application/json');
1362 | res.json(response);
1363 | }
1364 | else {
1365 | ReadDirectories(filesList).then(data => {
1366 | response = { cwd: cwdFiles, files: data };
1367 | response = JSON.stringify(response);
1368 | res.setHeader('Content-Type', 'application/json');
1369 | res.json(response);
1370 | });
1371 | }
1372 | })();
1373 | }
1374 |
1375 | });
1376 | /**
1377 | * Server serving port
1378 | */
1379 | var runPort = process.env.PORT || 8090;
1380 | var server = app.listen(runPort, function () {
1381 | server.setTimeout(10 * 60 * 1000);
1382 | var host = server.address().address;
1383 | var port = server.address().port;
1384 | console.log("Example app listening at http://%s:%s", host, port);
1385 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@syncfusion/ej2-filemanager-node-filesystem",
3 | "version": "18.1.52",
4 | "description": "Essential JS 2 File Manager Node FileSystem",
5 | "author": "Syncfusion Inc.",
6 | "license": "SEE LICENSE IN license",
7 | "keywords": [
8 | "ej2",
9 | "syncfusion",
10 | "ej2 file manager",
11 | "Nodejs",
12 | "File system",
13 | "File explorer",
14 | "File access rule",
15 | "File system provider",
16 | "Local/physical file storage",
17 | "Folders/directory",
18 | "file path",
19 | "file upload",
20 | "File access permission",
21 | "file browser",
22 | "folder drag and drop"
23 | ],
24 | "repository": {
25 | "type": "git",
26 | "url": "https://github.com/SyncfusionExamples/ej2-filemanager-node-filesystem"
27 | },
28 | "dependencies": {
29 | "archiver": "^3.0.0",
30 | "body-parser": "^1.18.3",
31 | "cors": "^2.8.5",
32 | "express": "^4.16.4",
33 | "express-rate-limit": "^5.3.0",
34 | "file-system": "^2.2.2",
35 | "gulp": "^4.0.2",
36 | "jshint-stylish": "^2.2.1",
37 | "multer": "^1.4.1",
38 | "path": "^0.12.7",
39 | "yargs": "^14.2.0"
40 | },
41 | "devDependencies": {
42 | "gulp-jshint": "^2.1.0",
43 | "jshint": "^2.10.2",
44 | "gulp": "^4.0.2",
45 | "require-dir": "^0.3.2",
46 | "requirejs": "^2.2.0",
47 | "shelljs": "^0.8.5"
48 | },
49 | "scripts": {
50 | "test": "gulp lint",
51 | "start": "node filesystem-server.js -d C:/Users",
52 | "ci-publish": "gulp publish"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------