aboutsummaryrefslogblamecommitdiffhomepage
path: root/src/files.js
blob: 1af4a18a3ece1f215342cd2b90fda8fa871d28ff (plain) (tree)
1
2
3
4
5
6
7
8

             

                             
                           
                        
                                      
                               


                                                          



                                                
 


                 
                   


                




                                                                                  


                                                   
 



                                             
 



                                              
 




                                     
 




                                
         
       

 






                                                



                                                               
                                    
                                                                        
 
                                                               


                            



                                            
                              
                                                     





                                                                               
                                       
 
                                                                                                           
                                                                   
 


















                                                                                     


       
                               
                                                     
 

                                                                                                                             
 

                             
                                                     
                                                                                                                



                                                                                     
                                                                                                  
                                                                                                          






                                                                       









                                                                                     























                                                                                                                      
                              
                                                     


                                          
                                                     
                                                                        
 

                                                                                           
                                                           
                                                                                                  



                                                          




                                                                                                
                                                                                      
                                                
                                                            


                                                         


           
'use strict';

var async = require('async'),
    fs = require('fs'),
    path = require('path'),
    rm = require('del'),
    debug = require('debug')('files'),
    mkdirp = require('mkdirp'),
    HttpError = require('connect-lastmile').HttpError,
    HttpSuccess = require('connect-lastmile').HttpSuccess;

var gBasePath;

exports = module.exports = function (basePath) {
    gBasePath = basePath;

    return {
        get: get,
        put: put,
        post: post,
        del: del
    };
};

// http://stackoverflow.com/questions/11293857/fastest-way-to-copy-file-in-node-js
function copyFile(source, target, cb) {
    var cbCalled = false;

    // ensure directory
    mkdirp(path.dirname(target), function (error) {
        if (error) return cb(error);

        var rd = fs.createReadStream(source);
            rd.on("error", function(err) {
            done(err);
        });

        var wr = fs.createWriteStream(target);
            wr.on("error", function(err) {
            done(err);
        });

        wr.on("close", function(ex) {
            done();
        });

        rd.pipe(wr);

        function done(err) {
            if (!cbCalled) {
                cb(err);
                cbCalled = true;
            }
        }
    });
}

function createDirectory(targetPath, callback) {
    mkdirp(targetPath, function (error) {
        if (error) return callback(error);
        callback(null);
    });
}

function isProtected(targetPath) {
    return targetPath.indexOf(getAbsolutePath('_admin')) === 0;
}

function getAbsolutePath(filePath) {
    var absoluteFilePath = path.resolve(path.join(gBasePath, filePath));

    if (absoluteFilePath.indexOf(gBasePath) !== 0) return null;
    return absoluteFilePath;
}

function removeBasePath(filePath) {
    return filePath.slice(gBasePath.length);
}

function get(req, res, next) {
    var filePath = decodeURIComponent(req.params[0]);
    var absoluteFilePath = getAbsolutePath(filePath);
    if (!absoluteFilePath) return next(new HttpError(403, 'Path not allowed'));

    fs.stat(absoluteFilePath, function (error, result) {
        if (error) return next(new HttpError(404, error));

        debug('get', absoluteFilePath);

        if (!result.isDirectory() && !result.isFile()) return next(new HttpError(500, 'unsupported type'));
        if (result.isFile()) return res.download(absoluteFilePath);

        async.map(fs.readdirSync(absoluteFilePath), function (filePath, callback) {
            fs.stat(path.join(absoluteFilePath, filePath), function (error, result) {
                if (error) return callback(error);

                callback(null, {
                    isDirectory: result.isDirectory(),
                    isFile: result.isFile(),
                    atime: result.atime,
                    mtime: result.mtime,
                    ctime: result.ctime,
                    birthtime: result.birthtime,
                    size: result.size,
                    filePath: filePath
                });
            });
        }, function (error, results) {
            if (error) return next(new HttpError(500, error));
            res.status(222).send({ entries: results });
        });
    });
}

function post(req, res, next) {
    var filePath = decodeURIComponent(req.params[0]);

    if (!(req.files && req.files.file) && !req.query.directory) return next(new HttpError(400, 'missing file or directory'));
    if ((req.files && req.files.file) && req.query.directory) return next(new HttpError(400, 'either file or directory'));

    debug('post:', filePath);

    var absoluteFilePath = getAbsolutePath(filePath);
    if (!absoluteFilePath || isProtected(absoluteFilePath)) return next(new HttpError(403, 'Path not allowed'));

    fs.stat(absoluteFilePath, function (error, result) {
        if (error && error.code !== 'ENOENT') return next(new HttpError(500, error));

        if (result && req.query.directory) return next(new HttpError(409, 'name already exists'));
        if (result && result.isDirectory()) return next(new HttpError(409, 'cannot post on directories'));

        if (req.query.directory) {
            return createDirectory(absoluteFilePath, function (error) {
                if (error) return next(new HttpError(500, error));
                next(new HttpSuccess(201, {}));
            });
        } else if (!result || result.isFile()) {
            return copyFile(req.files.file.path, absoluteFilePath, function (error) {
                if (error) return next(new HttpError(500, error));
                next(new HttpSuccess(201, {}));
            });
        }

        return next(new HttpError(500, 'unsupported type'));
    });
}

function put(req, res, next) {
    var oldFilePath = decodeURIComponent(req.params[0]);

    if (!req.body || !req.body.newFilePath) return next(new HttpError(400, 'missing newFilePath'));

    var newFilePath = decodeURIComponent(req.body.newFilePath);

    debug('put: %s -> %s', oldFilePath, newFilePath);

    var absoluteOldFilePath = getAbsolutePath(oldFilePath);
    if (!absoluteOldFilePath || isProtected(absoluteOldFilePath)) return next(new HttpError(403, 'Path not allowed'));

    var absoluteNewFilePath = getAbsolutePath(newFilePath);
    if (!absoluteNewFilePath || isProtected(absoluteNewFilePath)) return next(new HttpError(403, 'Path not allowed'));

    fs.rename(absoluteOldFilePath, absoluteNewFilePath, function (error) {
        if (error) return next (new HttpError(500, error));

        debug('put: successful');

        return next(new HttpSuccess(200, {}));
    });
}

function del(req, res, next) {
    var filePath = decodeURIComponent(req.params[0]);
    var recursive = !!req.query.recursive;
    var dryRun = !!req.query.dryRun;

    var absoluteFilePath = getAbsolutePath(filePath);
    if (!absoluteFilePath) return next(new HttpError(404, 'Not found'));

    if (isProtected(absoluteFilePath)) return next(new HttpError(403, 'Path not allowed'));

    // absoltueFilePath has to have the base path prepended
    if (absoluteFilePath.length <= gBasePath.length) return next(new HttpError(404, 'Not found'));

    fs.stat(absoluteFilePath, function (error, result) {
        if (error) return next(new HttpError(404, error));

        if (result.isDirectory() && !recursive) return next(new HttpError(403, 'Is directory'));

        // add globs to get file listing
        if (result.isDirectory()) absoluteFilePath += '/**';

        rm(absoluteFilePath, { dryRun: dryRun, force: true }).then(function (result) {
            result = result.map(removeBasePath);
            next(new HttpSuccess(200, { entries: result }));
        }, function (error) {
            console.error(error);
            next(new HttpError(500, 'Unable to remove'));
        });
    });
}