<commit_before>/*
 * grunt
 * http://gruntjs.com/
 *
 * Copyright (c) 2012 "Cowboy" Ben Alman
 * Licensed under the MIT license.
 * http://benalman.com/about/license/
 */

'use strict';

var grunt = require('../grunt');

// Nodejs libs.
var fs = require('fs');
var path = require('path');
// In Nodejs 0.8.0, existsSync moved from path -> fs.
var existsSync = fs.existsSync || path.existsSync;

// The module to be exported.
var file = module.exports = {};

// External libs.
file.glob = require('glob-whatev');
file.findup = require('../util/findup');
var YAML = require('js-yaml');

// Windows?
var win32 = process.platform === 'win32';

// Normalize \\ paths to / paths.
function unixifyPath(filepath) {
  if (win32) {
    return filepath.replace(/\\/g, '/');
  } else {
    return filepath;
  }
}

// Change the current base path (ie, CWD) to the specified path.
file.setBase = function() {
  var dirpath = path.join.apply(path, arguments);
  process.chdir(dirpath);
};

// Match a filepath or filepaths against one or more wildcard patterns. Returns
// all matching filepaths.
file.match = function(options, patterns, filepaths) {
  if (grunt.util.kindOf(options) !== 'object') {
    filepaths = patterns;
    patterns = options;
    options = {};
  }
  // Return empty set if patterns or filepaths are completely empty.
  if (patterns == null || filepaths == null) { return []; }
  // Normalize patterns and filepaths to arrays.
  if (!Array.isArray(patterns)) { patterns = [patterns]; }
  if (!Array.isArray(filepaths)) { filepaths = [filepaths]; }
  // Return empty set if there are no patterns or filepaths.
  if (patterns.length === 0 || filepaths.length === 0) { return []; }
  // Filepaths to return.
  var result = [];
  // Flattened, Underscore.js-chainable set of patterns.
  grunt.util._(patterns).chain().flatten().map(function(pattern) {
    // Process any templates.
    return grunt.template.process(pattern);
  }).each(function(pattern) {
    // If the first character is ! it should be omitted
    var exclusion = pattern.indexOf('!') === 0;
    // If the pattern is an exclusion, remove the !
    if (exclusion) { pattern = pattern.slice(1); }
    // Find all matching files for this pattern.
    var matches = filepaths.filter(file.glob.minimatch.filter(pattern, options));
    if (exclusion) {
      // If an exclusion, remove matching files.
      result = grunt.util._.difference(result, matches);
    } else {
      // Otherwise add matching files.
      result = grunt.util._.union(result, matches);
    }
  });
  return result;
};

// Match a filepath or filepaths against one or more wildcard patterns. Returns
// true if any of the patterns match.
file.isMatch = function() {
  return file.match.apply(file, arguments).length > 0;
};

// List of changed / deleted file paths, generated by the watch task.
file.watchFiles = {changed: null, deleted: null};

// Return an array of all file paths that match the given wildcard patterns.
file.expand = function() {
  var args = grunt.util.toArray(arguments);
  // If the first argument is an options object, save those options to pass
  // into the file.glob.glob method for minimatch to use.
  var options = grunt.util.kindOf(args[0]) === 'object' ? args.shift() : {};
  // Use the first argument if it's an Array, otherwise convert the arguments
  // object to an array and use that.
  var patterns = Array.isArray(args[0]) ? args[0] : args;
  // Filepaths to return.
  var result = [];
  // Flattened, Underscore.js-chainable set of patterns.
  grunt.util._(patterns).chain().flatten().each(function(pattern) {
    // If pattern is a template, process it accordingly.
    pattern = grunt.template.process(pattern);
    // If the first character is ! it should be omitted
    var exclusion = pattern.indexOf('!') === 0;
    // If the pattern is an exclusion, remove the !
    if (exclusion) { pattern = pattern.slice(1); }
    // Find all matching files for this pattern.
    var matches = file.glob.glob(pattern, options);
    if (exclusion) {
      // If an exclusion, remove matching files.
      result = grunt.util._.difference(result, matches);
    } else {
      // Otherwise add matching files.
      result = result.concat(matches);
    }
  });
  return grunt.util._(result).uniq();
};

// Further filter file.expand.
function expandByType(type) {
  var args = grunt.util.toArray(arguments).slice(1);
  // If the first argument is an options object, grab it.
  var options = grunt.util.kindOf(args[0]) === 'object' ? args[0] : {};
  // Match, then filter filepaths.
  return file.expand.apply(file, args).filter(function(filepath) {
    try {
      // If the file is of the right type and exists, this should work.
      return fs.statSync(path.join(options.cwd, filepath))[type]();
    } catch(e) {
      // Otherwise, it's probably not the right type.
      return false;
    }
  });
}

// A few type-specific file expansion methods.
file.expandDirs = expandByType.bind(file, 'isDirectory');
file.expandFiles = expandByType.bind(file, 'isFile');

// Return an array of all file paths that match the given wildcard patterns,
// plus any URLs that were passed at the end.
file.expandFileURLs = function() {
  // Use the first argument if it's an Array, otherwise convert the arguments
  // object to an array and use that.
  var patterns = Array.isArray(arguments[0]) ? arguments[0] : grunt.util.toArray(arguments);
  var urls = [];
  // Filter all URLs out of patterns list and store them in a separate array.
  patterns = patterns.filter(function(pattern) {
    if (/^(?:file|https?):\/\//i.test(pattern)) {
      // Push onto urls array.
      urls.push(pattern);
      // Remove from patterns array.
      return false;
    }
    // Otherwise, keep pattern.
    return true;
  });
  // Return expanded filepaths with urls at end.
  return file.expandFiles(patterns).map(function(filepath) {
    var abspath = path.resolve(filepath);
    // Convert C:\foo\bar style paths to /C:/foo/bar.
    if (abspath.indexOf('/') !== 0) {
      abspath = unixifyPath('/' + abspath);
    }
    return 'file://' + abspath;
  }).concat(urls);
};

// Like mkdir -p. Create a directory and any intermediary directories.
file.mkdir = function(dirpath, mode) {
  if (grunt.option('no-write')) { return; }
  // Set directory mode in a strict-mode-friendly way.
  if (mode == null) {
    mode = parseInt('0777', 8) & (~process.umask());
  }
  dirpath.split(/[\/\\]/).reduce(function(parts, part) {
    parts += part + '/';
    var subpath = path.resolve(parts);
    if (!file.exists(subpath)) {
      try {
        fs.mkdirSync(subpath, mode);
      } catch(e) {
        throw grunt.util.error('Unable to create directory "' + subpath + '" (Error code: ' + e.code + ').', e);
      }
    }
    return parts;
  }, '');
};

// Recurse into a directory, executing callback for each file.
file.recurse = function recurse(rootdir, callback, subdir) {
  var abspath = subdir ? path.join(rootdir, subdir) : rootdir;
  fs.readdirSync(abspath).forEach(function(filename) {
    var filepath = path.join(abspath, filename);
    if (fs.statSync(filepath).isDirectory()) {
      recurse(rootdir, callback, unixifyPath(path.join(subdir, filename)));
    } else {
      callback(unixifyPath(filepath), rootdir, subdir, filename);
    }
  });
};

// Is a given file path absolute?
file.isPathAbsolute = function() {
  var filepath = path.join.apply(path, arguments);
  return path.resolve(filepath) === filepath;
};

// Write a file.
file.write = function(filepath, contents) {
  var nowrite = grunt.option('no-write');
  grunt.verbose.write((nowrite ? 'Not actually writing ' : 'Writing ') + filepath + '...');
  // Create path, if necessary.
  file.mkdir(path.dirname(filepath));
  try {
    if (!nowrite) {
      // Actually write file.
      fs.writeFileSync(filepath, contents);
    }
    grunt.verbose.ok();
    return true;
  } catch(e) {
    grunt.verbose.error();
    throw grunt.util.error('Unable to write "' + filepath + '" file (Error code: ' + e.code + ').', e);
  }
};

// Read a file, return its contents.
file.read = function(filepath, encoding) {
  var src;
  grunt.verbose.write('Reading ' + filepath + '...');
  try {
    src = fs.readFileSync(String(filepath), encoding ? null : 'utf8');
    // Strip any BOM that might exist.
    if (typeof src === 'string' && src.charCodeAt(0) === 0xFEFF) {
      src = src.substring(1);
    }
    grunt.verbose.ok();
    return src;
  } catch(e) {
    grunt.verbose.error();
    throw grunt.util.error('Unable to read "' + filepath + '" file (Error code: ' + e.code + ').', e);
  }
};

// Read a file, optionally processing its content, then write the output.
file.copy = function(srcpath, destpath, options) {
  if (!options) { options = {}; }
  var src = file.read(srcpath, true);
  if (options.process && options.noProcess !== true &&
    !(options.noProcess && file.isMatch(options.noProcess, srcpath))) {
    grunt.verbose.write('Processing source...');
    try {
      src = options.process(src.toString('utf8'));
      grunt.verbose.ok();
    } catch(e) {
      grunt.verbose.error();
      throw grunt.util.error('Error while processing "' + srcpath + '" file.', e);
    }
  }
  // Abort copy if the process function returns false.
  if (src === false) {
    grunt.verbose.writeln('Write aborted.');
  } else {
    file.write(destpath, src);
  }
};

// Read a file, parse its contents, return an object.
file.readJSON = function(filepath) {
  var src = this.read(String(filepath));
  var result;
  grunt.verbose.write('Parsing ' + filepath + '...');
  try {
    result = JSON.parse(src);
    grunt.verbose.ok();
    return result;
  } catch(e) {
    grunt.verbose.error();
    throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.message + ').', e);
  }
};

// Read a YAML file, parse its contents, return an object.
file.readYAML = function(filepath) {
  var src = this.read(String(filepath));
  var result;
  grunt.verbose.write('Parsing ' + filepath + '...');
  try {
    result = YAML.load(src);
    grunt.verbose.ok();
    return result;
  } catch(e) {
    grunt.verbose.error();
    throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.problem + ').', e);
  }
};

// Clear the require cache for all passed filepaths.
file.clearRequireCache = function() {
  // If a non-string argument is passed, it's an array of filepaths, otherwise
  // each filepath is passed individually.
  var filepaths = typeof arguments[0] !== 'string' ? arguments[0] : grunt.util.toArray(arguments);
  // For each filepath, clear the require cache, if necessary.
  filepaths.forEach(function(filepath) {
    var abspath = path.resolve(filepath);
    if (require.cache[abspath]) {
      grunt.verbose.write('Clearing require cache for "' + filepath + '" file...').ok();
      delete require.cache[abspath];
    }
  });
};

// Access files in the user's ".grunt" folder.
file.userDir = function() {
  var dirpath = path.join.apply(path, arguments);
  var homepath = process.env[win32 ? 'USERPROFILE' : 'HOME'];
  dirpath = path.resolve(homepath, '.grunt', dirpath);
  return file.exists(dirpath) ? dirpath : null;
};

// True if the file path exists.
file.exists = function() {
  var filepath = path.join.apply(path, arguments);
  return existsSync(filepath);
};

// True if the file is a symbolic link.
file.isLink = function() {
  var filepath = path.join.apply(path, arguments);
  return file.exists(filepath) && fs.lstatSync(filepath).isSymbolicLink();
};

// True if the path is a directory.
file.isDir = function() {
  var filepath = path.join.apply(path, arguments);
  return file.exists(filepath) && fs.statSync(filepath).isDirectory();
};

// True if the path is a file.
file.isFile = function() {
  var filepath = path.join.apply(path, arguments);
  return file.exists(filepath) && fs.statSync(filepath).isFile();
};
<commit_msg>Greatly simplified file.match and file.expand methods now that directives are gone.<commit_after>/*
 * grunt
 * http://gruntjs.com/
 *
 * Copyright (c) 2012 "Cowboy" Ben Alman
 * Licensed under the MIT license.
 * http://benalman.com/about/license/
 */

'use strict';

var grunt = require('../grunt');

// Nodejs libs.
var fs = require('fs');
var path = require('path');
// In Nodejs 0.8.0, existsSync moved from path -> fs.
var existsSync = fs.existsSync || path.existsSync;

// The module to be exported.
var file = module.exports = {};

// External libs.
file.glob = require('glob-whatev');
file.findup = require('../util/findup');
var YAML = require('js-yaml');

// Windows?
var win32 = process.platform === 'win32';

// Normalize \\ paths to / paths.
function unixifyPath(filepath) {
  if (win32) {
    return filepath.replace(/\\/g, '/');
  } else {
    return filepath;
  }
}

// Change the current base path (ie, CWD) to the specified path.
file.setBase = function() {
  var dirpath = path.join.apply(path, arguments);
  process.chdir(dirpath);
};

// Process specified wildcard glob patterns or filenames against a
// callback, processing templates, excluding and uniquing files in the
// result set.
var processPatterns = function(patterns, fn) {
  // Filepaths to return.
  var result = [];
  // Flattened, Underscore.js-chainable set of patterns.
  grunt.util._(patterns).chain().flatten().each(function(pattern) {
    // If pattern is a template, process it accordingly.
    pattern = grunt.template.process(pattern);
    // If the first character is ! it should be omitted
    var exclusion = pattern.indexOf('!') === 0;
    // If the pattern is an exclusion, remove the !
    if (exclusion) { pattern = pattern.slice(1); }
    // Find all matching files for this pattern.
    var matches = fn(pattern);
    if (exclusion) {
      // If an exclusion, remove matching files.
      result = grunt.util._.difference(result, matches);
    } else {
      // Otherwise add matching files.
      result = grunt.util._.union(result, matches);
    }
  });
  return result;
};

// Match a filepath or filepaths against one or more wildcard patterns. Returns
// all matching filepaths.
file.match = function(options, patterns, filepaths) {
  if (grunt.util.kindOf(options) !== 'object') {
    filepaths = patterns;
    patterns = options;
    options = {};
  }
  // Return empty set if either patterns or filepaths was omitted.
  if (patterns == null || filepaths == null) { return []; }
  // Normalize patterns and filepaths to arrays.
  if (!Array.isArray(patterns)) { patterns = [patterns]; }
  if (!Array.isArray(filepaths)) { filepaths = [filepaths]; }
  // Return empty set if there are no patterns or filepaths.
  if (patterns.length === 0 || filepaths.length === 0) { return []; }
  // Return all matching filepaths.
  return processPatterns(patterns, function(pattern) {
    return filepaths.filter(file.glob.minimatch.filter(pattern, options));
  });
};

// Match a filepath or filepaths against one or more wildcard patterns. Returns
// true if any of the patterns match.
file.isMatch = function() {
  return file.match.apply(file, arguments).length > 0;
};

// List of changed / deleted file paths, generated by the watch task.
file.watchFiles = {changed: null, deleted: null};

// Return an array of all file paths that match the given wildcard patterns.
file.expand = function() {
  var args = grunt.util.toArray(arguments);
  // If the first argument is an options object, save those options to pass
  // into the file.glob.glob method for minimatch to use.
  var options = grunt.util.kindOf(args[0]) === 'object' ? args.shift() : {};
  // Use the first argument if it's an Array, otherwise convert the arguments
  // object to an array and use that.
  var patterns = Array.isArray(args[0]) ? args[0] : args;
  // Return empty set if there are no patterns or filepaths.
  if (patterns.length === 0) { return []; }
  // Return all matching filepaths.
  return processPatterns(patterns, function(pattern) {
    // Find all matching files for this pattern.
    return file.glob.glob(pattern, options);
  });
};

// Further filter file.expand.
function expandByType(type) {
  var args = grunt.util.toArray(arguments).slice(1);
  // If the first argument is an options object, grab it.
  var options = grunt.util.kindOf(args[0]) === 'object' ? args[0] : {};
  // Match, then filter filepaths.
  return file.expand.apply(file, args).filter(function(filepath) {
    try {
      // If the file is of the right type and exists, this should work.
      return fs.statSync(path.join(options.cwd, filepath))[type]();
    } catch(e) {
      // Otherwise, it's probably not the right type.
      return false;
    }
  });
}

// A few type-specific file expansion methods.
file.expandDirs = expandByType.bind(file, 'isDirectory');
file.expandFiles = expandByType.bind(file, 'isFile');

// Return an array of all file paths that match the given wildcard patterns,
// plus any URLs that were passed at the end.
file.expandFileURLs = function() {
  // Use the first argument if it's an Array, otherwise convert the arguments
  // object to an array and use that.
  var patterns = Array.isArray(arguments[0]) ? arguments[0] : grunt.util.toArray(arguments);
  var urls = [];
  // Filter all URLs out of patterns list and store them in a separate array.
  patterns = patterns.filter(function(pattern) {
    if (/^(?:file|https?):\/\//i.test(pattern)) {
      // Push onto urls array.
      urls.push(pattern);
      // Remove from patterns array.
      return false;
    }
    // Otherwise, keep pattern.
    return true;
  });
  // Return expanded filepaths with urls at end.
  return file.expandFiles(patterns).map(function(filepath) {
    var abspath = path.resolve(filepath);
    // Convert C:\foo\bar style paths to /C:/foo/bar.
    if (abspath.indexOf('/') !== 0) {
      abspath = unixifyPath('/' + abspath);
    }
    return 'file://' + abspath;
  }).concat(urls);
};

// Like mkdir -p. Create a directory and any intermediary directories.
file.mkdir = function(dirpath, mode) {
  if (grunt.option('no-write')) { return; }
  // Set directory mode in a strict-mode-friendly way.
  if (mode == null) {
    mode = parseInt('0777', 8) & (~process.umask());
  }
  dirpath.split(/[\/\\]/).reduce(function(parts, part) {
    parts += part + '/';
    var subpath = path.resolve(parts);
    if (!file.exists(subpath)) {
      try {
        fs.mkdirSync(subpath, mode);
      } catch(e) {
        throw grunt.util.error('Unable to create directory "' + subpath + '" (Error code: ' + e.code + ').', e);
      }
    }
    return parts;
  }, '');
};

// Recurse into a directory, executing callback for each file.
file.recurse = function recurse(rootdir, callback, subdir) {
  var abspath = subdir ? path.join(rootdir, subdir) : rootdir;
  fs.readdirSync(abspath).forEach(function(filename) {
    var filepath = path.join(abspath, filename);
    if (fs.statSync(filepath).isDirectory()) {
      recurse(rootdir, callback, unixifyPath(path.join(subdir, filename)));
    } else {
      callback(unixifyPath(filepath), rootdir, subdir, filename);
    }
  });
};

// Is a given file path absolute?
file.isPathAbsolute = function() {
  var filepath = path.join.apply(path, arguments);
  return path.resolve(filepath) === filepath;
};

// Write a file.
file.write = function(filepath, contents) {
  var nowrite = grunt.option('no-write');
  grunt.verbose.write((nowrite ? 'Not actually writing ' : 'Writing ') + filepath + '...');
  // Create path, if necessary.
  file.mkdir(path.dirname(filepath));
  try {
    if (!nowrite) {
      // Actually write file.
      fs.writeFileSync(filepath, contents);
    }
    grunt.verbose.ok();
    return true;
  } catch(e) {
    grunt.verbose.error();
    throw grunt.util.error('Unable to write "' + filepath + '" file (Error code: ' + e.code + ').', e);
  }
};

// Read a file, return its contents.
file.read = function(filepath, encoding) {
  var src;
  grunt.verbose.write('Reading ' + filepath + '...');
  try {
    src = fs.readFileSync(String(filepath), encoding ? null : 'utf8');
    // Strip any BOM that might exist.
    if (typeof src === 'string' && src.charCodeAt(0) === 0xFEFF) {
      src = src.substring(1);
    }
    grunt.verbose.ok();
    return src;
  } catch(e) {
    grunt.verbose.error();
    throw grunt.util.error('Unable to read "' + filepath + '" file (Error code: ' + e.code + ').', e);
  }
};

// Read a file, optionally processing its content, then write the output.
file.copy = function(srcpath, destpath, options) {
  if (!options) { options = {}; }
  var src = file.read(srcpath, true);
  if (options.process && options.noProcess !== true &&
    !(options.noProcess && file.isMatch(options.noProcess, srcpath))) {
    grunt.verbose.write('Processing source...');
    try {
      src = options.process(src.toString('utf8'));
      grunt.verbose.ok();
    } catch(e) {
      grunt.verbose.error();
      throw grunt.util.error('Error while processing "' + srcpath + '" file.', e);
    }
  }
  // Abort copy if the process function returns false.
  if (src === false) {
    grunt.verbose.writeln('Write aborted.');
  } else {
    file.write(destpath, src);
  }
};

// Read a file, parse its contents, return an object.
file.readJSON = function(filepath) {
  var src = this.read(String(filepath));
  var result;
  grunt.verbose.write('Parsing ' + filepath + '...');
  try {
    result = JSON.parse(src);
    grunt.verbose.ok();
    return result;
  } catch(e) {
    grunt.verbose.error();
    throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.message + ').', e);
  }
};

// Read a YAML file, parse its contents, return an object.
file.readYAML = function(filepath) {
  var src = this.read(String(filepath));
  var result;
  grunt.verbose.write('Parsing ' + filepath + '...');
  try {
    result = YAML.load(src);
    grunt.verbose.ok();
    return result;
  } catch(e) {
    grunt.verbose.error();
    throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.problem + ').', e);
  }
};

// Clear the require cache for all passed filepaths.
file.clearRequireCache = function() {
  // If a non-string argument is passed, it's an array of filepaths, otherwise
  // each filepath is passed individually.
  var filepaths = typeof arguments[0] !== 'string' ? arguments[0] : grunt.util.toArray(arguments);
  // For each filepath, clear the require cache, if necessary.
  filepaths.forEach(function(filepath) {
    var abspath = path.resolve(filepath);
    if (require.cache[abspath]) {
      grunt.verbose.write('Clearing require cache for "' + filepath + '" file...').ok();
      delete require.cache[abspath];
    }
  });
};

// Access files in the user's ".grunt" folder.
file.userDir = function() {
  var dirpath = path.join.apply(path, arguments);
  var homepath = process.env[win32 ? 'USERPROFILE' : 'HOME'];
  dirpath = path.resolve(homepath, '.grunt', dirpath);
  return file.exists(dirpath) ? dirpath : null;
};

// True if the file path exists.
file.exists = function() {
  var filepath = path.join.apply(path, arguments);
  return existsSync(filepath);
};

// True if the file is a symbolic link.
file.isLink = function() {
  var filepath = path.join.apply(path, arguments);
  return file.exists(filepath) && fs.lstatSync(filepath).isSymbolicLink();
};

// True if the path is a directory.
file.isDir = function() {
  var filepath = path.join.apply(path, arguments);
  return file.exists(filepath) && fs.statSync(filepath).isDirectory();
};

// True if the path is a file.
file.isFile = function() {
  var filepath = path.join.apply(path, arguments);
  return file.exists(filepath) && fs.statSync(filepath).isFile();
};
<|endoftext|>


<commit_before>/*
	The Cedric's Swiss Knife (CSK) - CSK REST Query

	Copyright (c) 2015 Cédric Ronvel

	The MIT License (MIT)

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
*/

// Load modules
var url = require( 'url' ) ;
var server = require( 'server-kit' ) ;
var string = require( 'string-kit' ) ;
var ErrorStatus = require( 'error-status' ) ;

var restQuery = require( './restQuery.js' ) ;

			/* HTTP request parser */

var httpModule = {} ;
module.exports = httpModule ;

httpModule.MAX_NON_MULTIPART_SIZE = 10000 ;	// 10k max, if it's not a multipart

var deb = function( v ) { console.log( string.inspect( { style: 'color' , depth:2 } , v ) ) ; } ;	// jshint ignore:line

httpModule.methods = [ 'options' , 'get' , 'put' , 'patch' , 'delete' , 'post' ] ;

httpModule.createServer = function httpCreateServer()
{
	var self = this , appServer ;

	appServer = server.createServer( {
			port: this.serverPort ,
			http: true ,
			multipart: true ,	// Activate multipart body in server-kit
			ws: true	// TMP
		} ,
		httpModule.requestHandler.bind( this )
	) ;

	appServer.on( 'error' , function( error ) {

		switch ( error.code )
		{
			case 'EACCES' :
				console.error( "Error -- Can't open port " + self.serverPort + ": forbidden" ) ;
				break ;
			case 'EADDRINUSE' :
				console.error( "Error -- Can't open port " + self.serverPort + ": already in use" ) ;
				break ;
			default :
				console.error( 'Error: ' , error ) ;
		}

		process.exit( 1 ) ;
	} ) ;
} ;

/*
	httpRequestHandler( client )
	httpRequestHandler( httpRequest , httpResponse )
*/
httpModule.requestHandler = function httpRequestHandler( client )
{
	var self = this , performer , status , location ;

	if ( arguments.length > 1 )
	{
		client = {
			request: arguments[ 0 ] ,
			response:  arguments[ 1 ]
		} ;
	}

	httpModule.parseRequest( client.request , function( error , message ) {

		// First fix some header things
		client.response.setHeader( 'Content-Type' , 'application/json' ) ;

		// CORS
		if ( self.httpOptions.allowOrigin )
		{
			if ( typeof self.httpOptions.allowOrigin === 'string' )
			{
				client.response.setHeader( 'Access-Control-Allow-Origin' , self.httpOptions.allowOrigin ) ;
			}
			else if ( typeof self.httpOptions.allowOrigin === 'function' )
			{
				client.response.setHeader( 'Access-Control-Allow-Origin' , self.httpOptions.allowOrigin( client.request.headers.origin ) ) ;
			}

			client.response.setHeader( 'Access-Control-Allow-Methods' , 'OPTIONS, GET, PUT, PATCH, DELETE, POST, MOVE' ) ;
			client.response.setHeader( 'Access-Control-Allow-Headers' , [ 'Content-Type' , 'X-Token' ] ) ;
		}

		// If an error occurs parsing the request, abort now
		if ( error ) { return httpModule.errorHandler.call( self , client.response , error ) ; }

		//# debug : console.log( string.inspect( { style: 'color' } , message ) ) ;

		// Create the performer for this request
		performer = self.createPerformer( httpModule.getAuth( client.request , message ) ) ;

		switch ( message.method )
		{
			case 'options' :
				// We assume here a "pre-flight request" for checking CORS here...
				client.response.writeHeader( 200 ) ;
				client.response.end() ;
				break ;

			case 'get' :
				self.root.get( message.path , { performer: performer } , function( error , rawDocument ) {
					if ( error ) { return httpModule.errorHandler.call( self , client.response , error ) ; }

					client.response.writeHeader( 200 ) ;
					client.response.end( JSON.stringify( rawDocument ) ) ;

				} ) ;
				break ;

			case 'post' :
				// Methods do not always need a request body
				//if ( ! message.data ) { return httpModule.errorHandler.call( self , client.response , new Error( 'No message body...' ) ) ; }
				if ( ! message.data ) { message.data = {} ; }

				self.root.post( message.path , message.data , { performer: performer } , function( error , rawDocument , details ) {
					if ( error ) { return httpModule.errorHandler.call( self , client.response , error ) ; }

					status = details.status || 200 ;

					if ( status === 201 && details.name )
					{
						location = self.serverAbsoluteUrl + client.request.url ;
						if ( location[ location.length - 1 ] !== '/' ) { location += '/' ; }
						location += details.name ;
						client.response.setHeader( 'Location' , location ) ;
					}

					client.response.writeHeader( status ) ;
					client.response.end( JSON.stringify( rawDocument ) ) ;

				} ) ;
				break ;

			case 'put' :
				if ( ! message.data ) { return httpModule.errorHandler.call( self , client.response , new Error( 'No message body...' ) ) ; }

				self.root.put( message.path , message.data , { performer: performer } , function( error , rawDocument , details ) {
					if ( error ) { return httpModule.errorHandler.call( self , client.response , error ) ; }

					status = details.status || 200 ;

					client.response.writeHeader( status ) ;
					client.response.end() ;	// JSON.stringify( rawDocument ) ) ;

				} ) ;
				break ;

			case 'patch' :
				if ( ! message.data ) { return httpModule.errorHandler.call( self , client.response , new Error( 'No message body...' ) ) ; }

				self.root.patch( message.path , message.data , { performer: performer } , function( error , rawDocument ) {
					if ( error ) { return httpModule.errorHandler.call( self , client.response , error ) ; }

					client.response.writeHeader( 200 ) ;
					client.response.end() ;	// JSON.stringify( rawDocument ) ) ;

				} ) ;
				break ;

			case 'delete' :
				self.root.delete( message.path , { performer: performer } , function( error , rawDocument ) {
					if ( error ) { return httpModule.errorHandler.call( self , client.response , error ) ; }

					client.response.writeHeader( 200 ) ;
					client.response.end() ;	// JSON.stringify( rawDocument ) ) ;

				} ) ;
				break ;

			default:
				httpModule.errorHandler.call( self , client.response , error ) ;
		}

	} ) ;
} ;

httpModule.errorHandler = function httpErrorHandler( httpResponse , error )
{
	if ( error instanceof ErrorStatus || typeof error.sendHttpHeaders === 'function' )
	{
		error.sendHttpHeaders( httpResponse ) ;
	}
	else
	{
		ErrorStatus.badRequest( { message: error.toString() } ).sendHttpHeaders( httpResponse ) ;
	}

	console.log( '[clientHandler] Error:' , error ) ;

	// Do it better later
	httpResponse.end() ;
} ;

httpModule.getAuth = function getAuth( request , message )
{
	if ( request.headers['x-token'] )
	{
		console.log( "Auth by header:" , {
			type: 'header' ,
			token: request.headers['x-token']
		} ) ;
		return {
			type: 'header' ,
			token: request.headers['x-token']
		} ;
	}

	return null ;
} ;

var methodWithoutBody = {
	get: true ,
	head: true ,
	delete: true
} ;

// Parse an HTTP request and return a message
httpModule.parseRequest = function parseRequest( httpRequest , callback )
{
	var length , message = {} , parsed ;
	//deb( httpRequest ) ;

	// First, parse the URL
	parsed = url.parse( httpRequest.url , true ) ;
	//deb( parsed ) ;

	// Store the whole path
	message.path = parsed.pathname ;

	/* This is now obsolete: RestQuery is a service that only return data, not HTML
	// Split the path to separate the extension part
	splitted = parsed.pathname.split( /\.(?!.*[\/.])/ ) ;
	message.path = splitted[ 0 ] ;
	message.type = splitted[ 1 ] || 'json' ;
	//*/

	// Get the host without the port
	message.host = httpRequest.headers.host.split( ':' )[ 0 ] ;

	// Populate the method, lowercased
	if ( parsed.query.method ) { message.method = parsed.query.method.toLowerCase() ; delete parsed.query.method ; }
	else { message.method = httpRequest.method.toLowerCase() ; }

	// If it is not a httpModule method, leave now with an error message
	if ( httpModule.methods.indexOf( message.method ) === -1 )
	{
		callback( ErrorStatus.methodNotAllowed( "Method: '" + message.method + "' is not allowed." ) ) ;
		return ;
	}

	// Reference the remaining query's variable into the message
	message.params = parsed.query ;

	length = parseInt( httpRequest.headers['content-length'] , 10 ) ||
		httpRequest.headers['transfer-encoding'] === 'chunked' ? undefined : 0 ;

	if ( ! methodWithoutBody[ httpRequest.method ] && length !== 0 )
	{
		if ( length && length > httpModule.MAX_NON_MULTIPART_SIZE )
		{
			callback( new Error( '[restQuery] parseRequest() MAX_NON_MULTIPART_SIZE reached' ) ) ;
			return ;
		}

		if ( httpRequest.multipart )
		{
			console.error( '[restQuery] Multipart body received!' ) ;

			httpRequest.multipart.on( 'part' , function( part ) {

				var headers ;

				console.error( 'New part!' ) ;

				part.on( 'header' , function( headers_ ) {
					var contentDisposition ;

					console.error( 'Part headers:' , headers_ ) ;
					headers = headers_ ;
					contentDisposition = httpModule.parseComplexHeader( headers['content-disposition'] ) ;
					console.error( "contentDisposition:" , contentDisposition ) ;
					//message.
				} ) ;

				part.on( 'data' , function( data ) {
					console.error( 'Part data: ' , data.toString() ) ;
				} ) ;

				part.on( 'end' , function() {
					console.error( 'Part: End of part\n' ) ;
				} ) ;
			} ) ;

			httpRequest.multipart.on( 'finish' , function() {
				console.error( 'Multipart: End of *ALL* parts' ) ;

				// To do...
				callback( undefined , message ) ;
			} ) ;
		}
		else
		{
			// Normal, non-multipart body: they have a constraint in size, because every thing is loaded in memory
			httpModule.getBody( httpRequest , length || httpModule.MAX_NON_MULTIPART_SIZE , function( error , body ) {

				//console.log( '>>> body: "' + body + '"' ) ;
				if ( error ) { callback( error ) ; return ; }

				if ( body.length )
				{
					try {
						message.data = JSON.parse( body ) ;
					}
					catch ( error ) {
						callback( new Error( '[restQuery] parseRequest() content is not a valid JSON' ) ) ;
						return ;
					}
				}

				callback( undefined , message ) ;
			} ) ;
		}

		return ;
	}

	callback( undefined , message ) ;
} ;

// Get the whole body of a request
httpModule.getBody = function getBody( stream , maxLength , callback )
{
	var body = '' , length = 0 ;

	var onData = function( chunk ) {

		length += chunk.length ;

		if ( length > maxLength )
		{
			stream.pause() ;
			stream.removeListener( 'data' , onData ) ;
			stream.removeListener( 'end' , onEnd ) ;
			callback( new Error( 'HTTP body too large' ) ) ;
			return ;
		}

		body += chunk.toString() ;
	} ;

	var onEnd = function() {
		callback( undefined , body ) ;
	} ;

	stream.on( 'data' , onData ) ;
	stream.on( 'end' , onEnd ) ;
} ;

// A very naive Content-Disposition header parser, yet it should works in 99.9% of cases
httpModule.parseComplexHeader = function parseComplexHeader( header )
{
	var i , iMax , parts , match , parsed = {} ;

	parts = header.split( /; */ ) ;
	iMax = parts.length ;

	for ( i = 0 ; i < iMax ; i ++ )
	{
		match = parts[ i ].match( /^([a-zA-Z0-9_-]+)(?:="([^"]*)")?$/ ) ;

		if ( ! match ) { continue ; }

		if ( match[ 2 ] ) { parsed[ match[ 1 ] ] = match[ 2 ] ; }
		else if ( i === 0 ) { parsed.__type = match[ 1 ] ; }
	}

	return parsed ;
} ;

<commit_msg>multipart wip<commit_after>/*
	The Cedric's Swiss Knife (CSK) - CSK REST Query

	Copyright (c) 2015 Cédric Ronvel

	The MIT License (MIT)

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
*/

// Load modules
var url = require( 'url' ) ;
var server = require( 'server-kit' ) ;
var string = require( 'string-kit' ) ;
var ErrorStatus = require( 'error-status' ) ;

var restQuery = require( './restQuery.js' ) ;

			/* HTTP request parser */

var httpModule = {} ;
module.exports = httpModule ;

httpModule.MAX_NON_MULTIPART_SIZE = 10000 ;	// 10k max, if it's not a multipart

var deb = function( v ) { console.log( string.inspect( { style: 'color' , depth:2 } , v ) ) ; } ;	// jshint ignore:line

httpModule.methods = [ 'options' , 'get' , 'put' , 'patch' , 'delete' , 'post' ] ;

httpModule.createServer = function httpCreateServer()
{
	var self = this , appServer ;

	appServer = server.createServer( {
			port: this.serverPort ,
			http: true ,
			multipart: true ,	// Activate multipart body in server-kit
			ws: true	// TMP
		} ,
		httpModule.requestHandler.bind( this )
	) ;

	appServer.on( 'error' , function( error ) {

		switch ( error.code )
		{
			case 'EACCES' :
				console.error( "Error -- Can't open port " + self.serverPort + ": forbidden" ) ;
				break ;
			case 'EADDRINUSE' :
				console.error( "Error -- Can't open port " + self.serverPort + ": already in use" ) ;
				break ;
			default :
				console.error( 'Error: ' , error ) ;
		}

		process.exit( 1 ) ;
	} ) ;
} ;

/*
	httpRequestHandler( client )
	httpRequestHandler( httpRequest , httpResponse )
*/
httpModule.requestHandler = function httpRequestHandler( client )
{
	var self = this , performer , status , location ;

	if ( arguments.length > 1 )
	{
		client = {
			request: arguments[ 0 ] ,
			response:  arguments[ 1 ]
		} ;
	}

	httpModule.parseRequest( client.request , function( error , message ) {

		// First fix some header things
		client.response.setHeader( 'Content-Type' , 'application/json' ) ;

		// CORS
		if ( self.httpOptions.allowOrigin )
		{
			if ( typeof self.httpOptions.allowOrigin === 'string' )
			{
				client.response.setHeader( 'Access-Control-Allow-Origin' , self.httpOptions.allowOrigin ) ;
			}
			else if ( typeof self.httpOptions.allowOrigin === 'function' )
			{
				client.response.setHeader( 'Access-Control-Allow-Origin' , self.httpOptions.allowOrigin( client.request.headers.origin ) ) ;
			}

			client.response.setHeader( 'Access-Control-Allow-Methods' , 'OPTIONS, GET, PUT, PATCH, DELETE, POST, MOVE' ) ;
			client.response.setHeader( 'Access-Control-Allow-Headers' , [ 'Content-Type' , 'X-Token' ] ) ;
		}

		// If an error occurs parsing the request, abort now
		if ( error ) { return httpModule.errorHandler.call( self , client.response , error ) ; }

		//# debug : console.log( string.inspect( { style: 'color' } , message ) ) ;

		// Create the performer for this request
		performer = self.createPerformer( httpModule.getAuth( client.request , message ) ) ;

		switch ( message.method )
		{
			case 'options' :
				// We assume here a "pre-flight request" for checking CORS here...
				client.response.writeHeader( 200 ) ;
				client.response.end() ;
				break ;

			case 'get' :
				self.root.get( message.path , { performer: performer } , function( error , rawDocument ) {
					if ( error ) { return httpModule.errorHandler.call( self , client.response , error ) ; }

					client.response.writeHeader( 200 ) ;
					client.response.end( JSON.stringify( rawDocument ) ) ;

				} ) ;
				break ;

			case 'post' :
				// Methods do not always need a request body
				//if ( ! message.data ) { return httpModule.errorHandler.call( self , client.response , new Error( 'No message body...' ) ) ; }
				if ( ! message.data ) { message.data = {} ; }

				self.root.post( message.path , message.data , { performer: performer } , function( error , rawDocument , details ) {
					if ( error ) { return httpModule.errorHandler.call( self , client.response , error ) ; }

					status = details.status || 200 ;

					if ( status === 201 && details.name )
					{
						location = self.serverAbsoluteUrl + client.request.url ;
						if ( location[ location.length - 1 ] !== '/' ) { location += '/' ; }
						location += details.name ;
						client.response.setHeader( 'Location' , location ) ;
					}

					client.response.writeHeader( status ) ;
					client.response.end( JSON.stringify( rawDocument ) ) ;

				} ) ;
				break ;

			case 'put' :
				if ( ! message.data ) { return httpModule.errorHandler.call( self , client.response , new Error( 'No message body...' ) ) ; }

				self.root.put( message.path , message.data , { performer: performer } , function( error , rawDocument , details ) {
					if ( error ) { return httpModule.errorHandler.call( self , client.response , error ) ; }

					status = details.status || 200 ;

					client.response.writeHeader( status ) ;
					client.response.end() ;	// JSON.stringify( rawDocument ) ) ;

				} ) ;
				break ;

			case 'patch' :
				if ( ! message.data ) { return httpModule.errorHandler.call( self , client.response , new Error( 'No message body...' ) ) ; }

				self.root.patch( message.path , message.data , { performer: performer } , function( error , rawDocument ) {
					if ( error ) { return httpModule.errorHandler.call( self , client.response , error ) ; }

					client.response.writeHeader( 200 ) ;
					client.response.end() ;	// JSON.stringify( rawDocument ) ) ;

				} ) ;
				break ;

			case 'delete' :
				self.root.delete( message.path , { performer: performer } , function( error , rawDocument ) {
					if ( error ) { return httpModule.errorHandler.call( self , client.response , error ) ; }

					client.response.writeHeader( 200 ) ;
					client.response.end() ;	// JSON.stringify( rawDocument ) ) ;

				} ) ;
				break ;

			default:
				httpModule.errorHandler.call( self , client.response , error ) ;
		}

	} ) ;
} ;

httpModule.errorHandler = function httpErrorHandler( httpResponse , error )
{
	if ( error instanceof ErrorStatus || typeof error.sendHttpHeaders === 'function' )
	{
		error.sendHttpHeaders( httpResponse ) ;
	}
	else
	{
		ErrorStatus.badRequest( { message: error.toString() } ).sendHttpHeaders( httpResponse ) ;
	}

	console.log( '[clientHandler] Error:' , error ) ;

	// Do it better later
	httpResponse.end() ;
} ;

httpModule.getAuth = function getAuth( request , message )
{
	if ( request.headers['x-token'] )
	{
		console.log( "Auth by header:" , {
			type: 'header' ,
			token: request.headers['x-token']
		} ) ;
		return {
			type: 'header' ,
			token: request.headers['x-token']
		} ;
	}

	return null ;
} ;

var methodWithoutBody = {
	get: true ,
	head: true ,
	delete: true
} ;

// Parse an HTTP request and return a message
httpModule.parseRequest = function parseRequest( httpRequest , callback )
{
	var length , message = {} , parsed , fileMode ;
	//deb( httpRequest ) ;

	// First, parse the URL
	parsed = url.parse( httpRequest.url , true ) ;
	//deb( parsed ) ;

	// Store the whole path
	message.path = parsed.pathname ;

	/* This is now obsolete: RestQuery is a service that only return data, not HTML
	// Split the path to separate the extension part
	splitted = parsed.pathname.split( /\.(?!.*[\/.])/ ) ;
	message.path = splitted[ 0 ] ;
	message.type = splitted[ 1 ] || 'json' ;
	//*/

	// Get the host without the port
	message.host = httpRequest.headers.host.split( ':' )[ 0 ] ;

	// Populate the method, lowercased
	if ( parsed.query.method ) { message.method = parsed.query.method.toLowerCase() ; delete parsed.query.method ; }
	else { message.method = httpRequest.method.toLowerCase() ; }

	// If it is not a httpModule method, leave now with an error message
	if ( httpModule.methods.indexOf( message.method ) === -1 )
	{
		callback( ErrorStatus.methodNotAllowed( "Method: '" + message.method + "' is not allowed." ) ) ;
		return ;
	}

	// Reference the remaining query's variable into the message
	message.params = parsed.query ;

	length = parseInt( httpRequest.headers['content-length'] , 10 ) ||
		httpRequest.headers['transfer-encoding'] === 'chunked' ? undefined : 0 ;

	if ( ! methodWithoutBody[ httpRequest.method ] && length !== 0 )
	{
		if ( length && length > httpModule.MAX_NON_MULTIPART_SIZE )
		{
			callback( new Error( '[restQuery] parseRequest() MAX_NON_MULTIPART_SIZE reached' ) ) ;
			return ;
		}

		if ( httpRequest.multipart )
		{
			console.error( '[restQuery] Multipart body received!' ) ;

			message.data = {} ;
			message.fileStream = {} ;

			httpRequest.multipart.on( 'part' , function( part ) {

				var headers ;

				console.error( 'New part!' ) ;

				part.on( 'header' , function( headers_ ) {
					var contentDisposition ;

					console.error( 'Part headers:' , headers_ ) ;
					headers = headers_ ;
					contentDisposition = httpModule.parseComplexHeader( headers['content-disposition'] ) ;
					console.error( "contentDisposition:" , contentDisposition ) ;

					if ( ! contentDisposition ) { return ; }

					if (
						contentDisposition.name &&
						contentDisposition.filename &&
						headers['content-type'] === 'application/octet-stream'
					)
					{
						// This is a file!
						message.fileStream[ contentDisposition.name ] = part ;
						fileMode = true ;
					}
					else if ( ! fileMode && ! contentDisposition.filename )
					{
						//message.data[ contentDisposition.name ] =
// --------------------------------------------------------------------------- HERE!!! .getBody() needed!!!
					}

					//message.
				} ) ;

				part.on( 'data' , function( data ) {
					console.error( 'Part data: ' , data.toString() ) ;
				} ) ;

				part.on( 'end' , function() {
					console.error( 'Part: End of part\n' ) ;
				} ) ;
			} ) ;

			httpRequest.multipart.on( 'finish' , function() {
				console.error( 'Multipart: End of *ALL* parts' ) ;

				// To do...
				callback( undefined , message ) ;
			} ) ;
		}
		else
		{
			// Normal, non-multipart body: they have a constraint in size, because every thing is loaded in memory
			httpModule.getBody( httpRequest , length || httpModule.MAX_NON_MULTIPART_SIZE , function( error , body ) {

				//console.log( '>>> body: "' + body + '"' ) ;
				if ( error ) { callback( error ) ; return ; }

				if ( body.length )
				{
					try {
						message.data = JSON.parse( body ) ;
					}
					catch ( error ) {
						callback( new Error( '[restQuery] parseRequest() content is not a valid JSON' ) ) ;
						return ;
					}
				}

				callback( undefined , message ) ;
			} ) ;
		}

		return ;
	}

	callback( undefined , message ) ;
} ;

// Get the whole body of a request
httpModule.getBody = function getBody( stream , maxLength , callback )
{
	var body = '' , length = 0 ;

	var onData = function( chunk ) {

		length += chunk.length ;

		if ( length > maxLength )
		{
			stream.pause() ;
			stream.removeListener( 'data' , onData ) ;
			stream.removeListener( 'end' , onEnd ) ;
			callback( new Error( 'HTTP body too large' ) ) ;
			return ;
		}

		body += chunk.toString() ;
	} ;

	var onEnd = function() {
		callback( undefined , body ) ;
	} ;

	stream.on( 'data' , onData ) ;
	stream.on( 'end' , onEnd ) ;
} ;

// A very naive Content-Disposition header parser, yet it should works in 90% of cases
httpModule.parseComplexHeader = function parseComplexHeader( headerValue )
{
	var i , iMax , parts , match , parsed = {} ;

	if ( ! headerValue ) { return null ; }

	parts = headerValue.split( /; */ ) ;
	iMax = parts.length ;

	for ( i = 0 ; i < iMax ; i ++ )
	{
		match = parts[ i ].match( /^([a-zA-Z0-9_-]+)(?:="([^"]*)")?$/ ) ;

		if ( ! match ) { continue ; }

		if ( match[ 2 ] ) { parsed[ match[ 1 ] ] = match[ 2 ] ; }
		else if ( i === 0 ) { parsed.__type = match[ 1 ] ; }
	}

	return parsed ;
} ;

<|endoftext|>


<commit_before>var fs = require('fs');
var url = require('url');
var log = require('sslog');
var path = require('path');
var minimatch = require('minimatch');
var sprintf = require('util').format;
var cachedir = require('./cachedir.js');
var imagemagick = require('./imagemagick');
var config = require('../config');

// create the new cache_dir instance to be used my image cache
var imgcache = cachedir.create({
  cache_dir: config.cache_dir,
  work_dir: config.work_dir
});

function logError(err) {
  var msg = err.toString();
  if (err.stack) {
    msg += ' err.stack=' + err.stack;
  }
  log.error(msg);
}

// constructor
var Imgresizer = function() {};

// will get called via connect
Imgresizer.prototype.handle = function(req, res, next) {
  isRequestValid(req, function(method) {
    if (!method) {
      log.warn('request ' + req.url + ' is not valid');
      return next();
    }

    // get query string from requests
    var params = req.query;

    console.log('PARAMS', params);
    // check that 'url' param was passeed
    if (!params.url) return res.send(400, 'missing a "url" parameter');

    // prepend http:// if not there already
    if (!url.parse(params.url).protocol) params.url = 'http://' + params.url;

    // proceed only if hostname was whitelisted
    var hostname = url.parse(params.url).hostname || '';
    isHostAllowed(hostname, function(err, allowed) {
      if (err) logError(err);
      if (!allowed) {
        log.warn('hostname ' + hostname + ' is not allowed');
        return res.send(401);
      }

      // afterl all check we ready to process the image URL
      processImage(req.query, method, function(err, output, info) {
        if (err) return res.send(500, err.message);

        // let's serve the image :)
        log.verbose(JSON.stringify(info));
        res.contentType('image/' + info.type.toLowerCase());
        res.sendfile(output, {
          maxAge: (config.cache_max_age || 2419200) * 1000
        }); // default is 28 days
      });
    });
  });
};

/**
 * Checks if the request should be processed by Imgresizer
 * if yes returns the requested method (resize, stretch or crop)
 */
function isRequestValid(req, callback) {
  if (typeof callback !== 'function') callback = function() {};

  var pattern, regex, path, method;

  // catch only GET/HEAD
  if (req.method != 'GET' && req.method != 'HEAD') return callback(false);

  pattern = '\/(chop|cache|resize|stretch|crop)\/?$';
  if (config.namespace) pattern = "\/" + config.namespace + pattern;

  regex = new RegExp('^' + pattern, 'i');
  path = url.parse(req.url).pathname || '';
  method = path.match(regex);
  callback(method ? method[1].toLowerCase() : false);
}

/**
 * Checks if the requested image URL has a whitelisted host
 * whitelist is an array of "glob" patterns
 * callback has boolean parameter.
 */
function isHostAllowed(hostname, callback) {
  var globs = config.allowed_hosts || [''];

  // some sanity
  if (typeof callback !== 'function') callback = function() {};
  if (!hostname) return callback(new Error('hostname must not be empty'), false);
  if (!Array.isArray(globs)) return callback(new TypeError('allowed_hosts option must be an array of glob patterns'), false);

  for (i = 0; i < globs.length; i++) {
    if (minimatch(hostname, globs[i])) return callback(null, true);
  }

  return callback(null, false);
}

/**
 * Takes original request query-string params and manipulation method, then processes it
 * - default method is 'resize'
 * - callback function gets the error
 */
function processImage(params, method, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = method;
    method = 'resize';
  }

  // if it still not a function, just create a dummy one
  if (typeof callback !== 'function') callback = function() {};

  // is it forced or not
  var force = params.force || false;
  if (force) log.verbose('forced request, ignoring any cached image url = ' + params.url);

  // download
  imgcache.download(params.url, force, function(err, downloaded_image) {
    if (err) {
      logError(err);
      callback(err);
      return;
    }

    // If method is 'cache' means we just want to download the image and cache it; no transformation.
    if (method === 'cache') {
      // NOTE! this method can be used to delete downloaded/cached images that are not valid image files.
      imagemagick.info(downloaded_image, force, function(err, info) {
        if (err) {
          fs.unlink(downloaded_image); // Do not keep invalid image files in cache
          logError(err);
          return callback(err);
        }
        callback(null, downloaded_image, info);
      });
      return;
    }

    // normalize query params
    var width = params.w || '';
    var height = params.h || '';
    var quality = params.q || 69; // 92 was the default but based on CL-4237 we decided to reduce the quality of jpeg compression for faster load
    var filename = sprintf('%s_%s_q%s_%sx%s', path.basename(downloaded_image), method, quality, width, height); // <img-name>_<method>_q<quality>_<width>x<height>

    imgcache.get_cached_path(filename, function(dest, exists) {
      if (exists && !force) {
        // We already have a cached transform; just return image info.
        imagemagick.info(dest, function(err, info) {
          if (err) {
            logError(err);
            callback(err);
            return;
          }
          log.verbose('The image "' + path.basename(dest) + '" is already converted and cached, skipping...');
          callback(null, dest, info);
        });
        return;
      }

      imgcache.get_working_path(filename, function(workingPath, workingExists) {
        imgcache.ensure_path(workingPath, function(err) {
          if (err) {
            logError(err);
            callback(err);
            return;
          }

          log.verbose('Transforming... src=' + downloaded_image + ', dst=' + workingPath);

          imagemagick[method]({
            src: downloaded_image,
            dst: workingPath,
            width: width,
            height: height,
            quality: quality
          }, force, function(err, info) {
            if (err) {
              logError(err);
              callback(err);
              return;
            }
            imgcache.promote_working_path_to_cache(workingPath, function(err, cachePath) {
              if (err) {
                logError(err);
                callback(err);
                return;
              }
              // Success! Return the full path to cached transform result and image info.
              callback(null, cachePath, info);
            });
          });
        });
      });
    });
  });
}

module.exports = new Imgresizer();
<commit_msg>work-around hack for returning correct status code<commit_after>var fs = require('fs');
var url = require('url');
var log = require('sslog');
var path = require('path');
var minimatch = require('minimatch');
var sprintf = require('util').format;
var cachedir = require('./cachedir.js');
var imagemagick = require('./imagemagick');
var config = require('../config');

// create the new cache_dir instance to be used my image cache
var imgcache = cachedir.create({
  cache_dir: config.cache_dir,
  work_dir: config.work_dir
});

function logError(err) {
  var msg = err.toString();
  if (err.stack) {
    msg += ' err.stack=' + err.stack;
  }
  log.error(msg);
}

// constructor
var Imgresizer = function() {};

// will get called via connect
Imgresizer.prototype.handle = function(req, res, next) {
  isRequestValid(req, function(method) {
    if (!method) {
      log.warn('request ' + req.url + ' is not valid');
      return next();
    }

    // get query string from requests
    var params = req.query;

    console.log('PARAMS', params);
    // check that 'url' param was passeed
    if (!params.url) return res.send(400, 'missing a "url" parameter');

    // prepend http:// if not there already
    if (!url.parse(params.url).protocol) params.url = 'http://' + params.url;

    // proceed only if hostname was whitelisted
    var hostname = url.parse(params.url).hostname || '';
    isHostAllowed(hostname, function(err, allowed) {
      if (err) logError(err);
      if (!allowed) {
        log.warn('hostname ' + hostname + ' is not allowed');
        return res.send(401);
      }

      // afterl all check we ready to process the image URL
      processImage(req.query, method, function(err, output, info) {
        if (err) {
          var statusCode = err.message.split(",")[1].split("=")[1];
          return res.send(statusCode, err.message);
        }

        // let's serve the image :)
        log.verbose(JSON.stringify(info));
        res.contentType('image/' + info.type.toLowerCase());
        res.sendfile(output, {
          maxAge: (config.cache_max_age || 2419200) * 1000
        }); // default is 28 days
      });
    });
  });
};

/**
 * Checks if the request should be processed by Imgresizer
 * if yes returns the requested method (resize, stretch or crop)
 */
function isRequestValid(req, callback) {
  if (typeof callback !== 'function') callback = function() {};

  var pattern, regex, path, method;

  // catch only GET/HEAD
  if (req.method != 'GET' && req.method != 'HEAD') return callback(false);

  pattern = '\/(chop|cache|resize|stretch|crop)\/?$';
  if (config.namespace) pattern = "\/" + config.namespace + pattern;

  regex = new RegExp('^' + pattern, 'i');
  path = url.parse(req.url).pathname || '';
  method = path.match(regex);
  callback(method ? method[1].toLowerCase() : false);
}

/**
 * Checks if the requested image URL has a whitelisted host
 * whitelist is an array of "glob" patterns
 * callback has boolean parameter.
 */
function isHostAllowed(hostname, callback) {
  var globs = config.allowed_hosts || [''];

  // some sanity
  if (typeof callback !== 'function') callback = function() {};
  if (!hostname) return callback(new Error('hostname must not be empty'), false);
  if (!Array.isArray(globs)) return callback(new TypeError('allowed_hosts option must be an array of glob patterns'), false);

  for (i = 0; i < globs.length; i++) {
    if (minimatch(hostname, globs[i])) return callback(null, true);
  }

  return callback(null, false);
}

/**
 * Takes original request query-string params and manipulation method, then processes it
 * - default method is 'resize'
 * - callback function gets the error
 */
function processImage(params, method, callback) {
  // make sure we have a proper callback passed
  if (typeof callback !== 'function') {
    callback = method;
    method = 'resize';
  }

  // if it still not a function, just create a dummy one
  if (typeof callback !== 'function') callback = function() {};

  // is it forced or not
  var force = params.force || false;
  if (force) log.verbose('forced request, ignoring any cached image url = ' + params.url);

  // download
  imgcache.download(params.url, force, function(err, downloaded_image) {
    if (err) {
      logError(err);
      callback(err);
      return;
    }

    // If method is 'cache' means we just want to download the image and cache it; no transformation.
    if (method === 'cache') {
      // NOTE! this method can be used to delete downloaded/cached images that are not valid image files.
      imagemagick.info(downloaded_image, force, function(err, info) {
        if (err) {
          fs.unlink(downloaded_image); // Do not keep invalid image files in cache
          logError(err);
          return callback(err);
        }
        callback(null, downloaded_image, info);
      });
      return;
    }

    // normalize query params
    var width = params.w || '';
    var height = params.h || '';
    var quality = params.q || 69; // 92 was the default but based on CL-4237 we decided to reduce the quality of jpeg compression for faster load
    var filename = sprintf('%s_%s_q%s_%sx%s', path.basename(downloaded_image), method, quality, width, height); // <img-name>_<method>_q<quality>_<width>x<height>

    imgcache.get_cached_path(filename, function(dest, exists) {
      if (exists && !force) {
        // We already have a cached transform; just return image info.
        imagemagick.info(dest, function(err, info) {
          if (err) {
            logError(err);
            callback(err);
            return;
          }
          log.verbose('The image "' + path.basename(dest) + '" is already converted and cached, skipping...');
          callback(null, dest, info);
        });
        return;
      }

      imgcache.get_working_path(filename, function(workingPath, workingExists) {
        imgcache.ensure_path(workingPath, function(err) {
          if (err) {
            logError(err);
            callback(err);
            return;
          }

          log.verbose('Transforming... src=' + downloaded_image + ', dst=' + workingPath);

          imagemagick[method]({
            src: downloaded_image,
            dst: workingPath,
            width: width,
            height: height,
            quality: quality
          }, force, function(err, info) {
            if (err) {
              logError(err);
              callback(err);
              return;
            }
            imgcache.promote_working_path_to_cache(workingPath, function(err, cachePath) {
              if (err) {
                logError(err);
                callback(err);
                return;
              }
              // Success! Return the full path to cached transform result and image info.
              callback(null, cachePath, info);
            });
          });
        });
      });
    });
  });
}

module.exports = new Imgresizer();
<|endoftext|>


<commit_before>'use strict';

const nunjucks = require('nunjucks');
const path = require('path');

const views = [
  path.join(process.cwd(), 'views'),
  path.join(process.cwd(), 'templates'),
  path.join(__dirname, 'views'),
  path.join(__dirname, 'templates'),
];

module.exports = (app) => {
  return new Promise((res) => {
    nunjucks.configure(views, {
      'autoescape': true,
      'express': app,
    });
    app.set('view engine', 'html');

    res(app);
  });
};
<commit_msg>:bug: Fix views locations<commit_after>'use strict';

const nunjucks = require('nunjucks');
const path = require('path');

const views = [
  path.join(process.cwd(), 'views'),
  path.join(process.cwd(), 'templates'),
  path.join(__dirname, '..', '..', 'views'),
  path.join(__dirname, '..', '..', 'templates'),
];

module.exports = (app) => {
  return new Promise((res) => {
    nunjucks.configure(views, {
      'autoescape': true,
      'express': app,
    });
    app.set('view engine', 'html');

    res(app);
  });
};
<|endoftext|>


<commit_before>var fs = require("fs");
var path = require("path");
var stream = require("stream");
var crypto = require("crypto");
var HTMLProcessor = require("../lib/htmlprocessor");

function combineCss(sources) {
    var cleanCSS = require("clean-css");
    var concatenatedCss = "";

    console.log("Concatenating CSS");

    concatenatedCss = sources
        .map(function (source) {
            return fs.readFileSync(source).toString();
        })
        .reduce(function (concatenated, content) {
            return concatenated + content;
        }, "");

    console.log("Minifying CSS");

    return cleanCSS.process(concatenatedCss);
}

function combineJs(sources) {
    console.log("Minifying JS");
    var uglify = require("uglify-js");
    var uglified = uglify.minify(sources, {compress:false});
    return uglified.code;
}

function hashFilename(filename, content) {
    console.log("Getting hash of file");
    var hash = crypto.createHash("md5").update(content).digest("hex");
    return path.join(path.dirname(filename), hash.substr(0, 8) + "." + path.basename(filename));
}

var hashcatify = function (settings) {
    var options = settings;
    options.htmlFile = path.join(process.cwd(),settings.htmlFile);
    options.outputHtmlFile = path.join(process.cwd(),settings.outputHtmlFile);
    console.log(options);
    begin(options);
};

var begin = function (options) {
    fs.readFile(options.htmlFile, "utf8", function (err, sourceHtml) {
        if (err) {
            console.log("Error while reading the file contents: ");
            console.log(err);
            return;
        }

        if (sourceHtml.length <= 0) {
            console.log("ERROR reading " + options.htmlFile + ", it appears to be empty");
            return;
        }

        var proc = new HTMLProcessor(path.dirname(options.htmlFile), null, sourceHtml);
        proc.blocks.forEach(function (block) {
            console.log("-----");
            console.log("Processing block for " + block.dest);
            var fileContents;

            try {
                fileContents = (block.type === "css")
                    ? combineCss(block.src)
                    : combineJs(block.src);
            }
            catch (error) {
                console.log("ERROR while minifying " + block.type.toUpperCase() + ": ");
                console.log(error);
                return;
            }

            try {
                var revPath = hashFilename(block.dest, fileContents);

                console.log("Creating copy with hashed filename");
                fs.writeFileSync(revPath, fileContents);
            }
            catch (error) {
                console.log("ERROR while hashing: ");
                console.log(error);
                return;
            }

            try {
                var blockLine = block.raw.join(proc.linefeed);
                var replacement = proc.replaceWith(block);
                replacement = replacement.replace(path.basename(block.relativeDest), path.basename(revPath));
                console.log("New reference: " + replacement);

                sourceHtml = sourceHtml.replace(blockLine, replacement);
            }
            catch (error) {
                console.log("ERROR while creating new reference: ");
                console.log(error);
                return;
            }
        });

        console.log("Writing to HTML source file");
        fs.writeFileSync(options.outputHtmlFile, sourceHtml, "utf8");
        console.log("Hashcat complete");
    });
};

exports.hashcatify = hashcatify;
<commit_msg>Extract function to process block of HTML<commit_after>var fs = require("fs");
var path = require("path");
var stream = require("stream");
var crypto = require("crypto");
var HTMLProcessor = require("../lib/htmlprocessor");

function combineCss(sources) {
    var cleanCSS = require("clean-css");
    var concatenatedCss = "";

    console.log("Concatenating CSS");

    concatenatedCss = sources
        .map(function (source) {
            return fs.readFileSync(source).toString();
        })
        .reduce(function (concatenated, content) {
            return concatenated + content;
        }, "");

    console.log("Minifying CSS");

    return cleanCSS.process(concatenatedCss);
}

function combineJs(sources) {
    console.log("Minifying JS");
    var uglify = require("uglify-js");
    var uglified = uglify.minify(sources, {compress:false});
    return uglified.code;
}

function hashFilename(filename, content) {
    console.log("Getting hash of file");
    var hash = crypto.createHash("md5").update(content).digest("hex");
    return path.join(path.dirname(filename), hash.substr(0, 8) + "." + path.basename(filename));
}

function processBlock(proc) {
    return function (block) {
        console.log("-----");
        console.log("Processing block for " + block.dest);
        var fileContents;

        try {
            fileContents = (block.type === "css")
                ? combineCss(block.src)
                : combineJs(block.src);
        }
        catch (error) {
            console.log("ERROR while minifying " + block.type.toUpperCase() + ": ");
            console.log(error);
            return;
        }

        try {
            var revPath = hashFilename(block.dest, fileContents);

            console.log("Creating copy with hashed filename");
            fs.writeFileSync(revPath, fileContents);
        }
        catch (error) {
            console.log("ERROR while hashing: ");
            console.log(error);
            return;
        }

        try {
            var blockLine = block.raw.join(proc.linefeed);
            var replacement = proc.replaceWith(block);
            replacement = replacement.replace(path.basename(block.relativeDest), path.basename(revPath));
            console.log("New reference: " + replacement);

            return {
                source: blockLine,
                dest: replacement
            };
        }
        catch (error) {
            console.log("ERROR while creating new reference: ");
            console.log(error);
            return;
        }
    };
}

function replace(string, replacement) {
    return string.replace(replacement.source, replacement.dest);
}

var hashcatify = function (settings) {
    var options = settings;
    options.htmlFile = path.join(process.cwd(),settings.htmlFile);
    options.outputHtmlFile = path.join(process.cwd(),settings.outputHtmlFile);
    console.log(options);
    begin(options);
};

var begin = function (options) {
    fs.readFile(options.htmlFile, "utf8", function (err, sourceHtml) {
        if (err) {
            console.log("Error while reading the file contents: ");
            console.log(err);
            return;
        }

        if (sourceHtml.length <= 0) {
            console.log("ERROR reading " + options.htmlFile + ", it appears to be empty");
            return;
        }

        var proc = new HTMLProcessor(path.dirname(options.htmlFile), null, sourceHtml);
        var replacements = proc.blocks.map(processBlock(proc));
        var destHtml = replacements.reduce(replace, sourceHtml);

        console.log("Writing to HTML source file");
        fs.writeFileSync(options.outputHtmlFile, destHtml, "utf8");
        console.log("Hashcat complete");
    });
};

exports.hashcatify = hashcatify;
<|endoftext|>


<commit_before>var cheerio = require('cheerio'),
  url = require('url')

function extractLinks (pageUrl, pageHtml, callback) {
  var $ = cheerio.load(pageHtml),
    links = [],
    parsedUrl = url.parse(pageUrl)

  $('a').each(function (i, el) {
    var absoluteUrl = normalizeLink(parsedUrl, $(el).attr('href')),
      link = {}

    link.text = $(el).text()
    link.html = $(el).html()
    link.href = $(el).attr('href')
    link.element = el
    link.link = absoluteUrl
    links.push(link)
  })

  callback(links, $)
}

function normalizeLink (parsedUrl, scrapedHref) {
  if (!scrapedHref || !parsedUrl) return null
  if (scrapedHref.indexOf('javascript:') === 0) return null
  if (scrapedHref.indexOf('#') === 0) return null

  var scrapedUrl = url.parse(scrapedHref)
  if (scrapedUrl.host != null) return scrapedHref
  if (scrapedHref.indexOf('//') === 0) return parsedUrl.protocol + scrapedHref
  if (scrapedHref.indexOf('/') === 0) return parsedUrl.protocol + '//' + parsedUrl.host + scrapedHref
  if (scrapedHref.indexOf('(') > 0 && scrapedHref.indexOf(')') > 0) return null

  var pos = parsedUrl.href.lastIndexOf('/')
  if (pos >= 0) {
    var surl = parsedUrl.href.substring(0, pos + 1)
    return surl + scrapedHref
  } else {
    return parsedUrl.href + '/' + scrapedHref
  }
}

module.exports = extractLinks
<commit_msg>lib/linkscrape: fix var decls<commit_after>var cheerio = require('cheerio')
var url = require('url')

function extractLinks (pageUrl, pageHtml, callback) {
  var $ = cheerio.load(pageHtml)
  var links = []
  var parsedUrl = url.parse(pageUrl)

  $('a').each(function (i, el) {
    var absoluteUrl = normalizeLink(parsedUrl, $(el).attr('href')),
      link = {}

    link.text = $(el).text()
    link.html = $(el).html()
    link.href = $(el).attr('href')
    link.element = el
    link.link = absoluteUrl
    links.push(link)
  })

  callback(links, $)
}

function normalizeLink (parsedUrl, scrapedHref) {
  if (!scrapedHref || !parsedUrl) return null
  if (scrapedHref.indexOf('javascript:') === 0) return null
  if (scrapedHref.indexOf('#') === 0) return null

  var scrapedUrl = url.parse(scrapedHref)
  if (scrapedUrl.host != null) return scrapedHref
  if (scrapedHref.indexOf('//') === 0) return parsedUrl.protocol + scrapedHref
  if (scrapedHref.indexOf('/') === 0) return parsedUrl.protocol + '//' + parsedUrl.host + scrapedHref
  if (scrapedHref.indexOf('(') > 0 && scrapedHref.indexOf(')') > 0) return null

  var pos = parsedUrl.href.lastIndexOf('/')
  if (pos >= 0) {
    var surl = parsedUrl.href.substring(0, pos + 1)
    return surl + scrapedHref
  } else {
    return parsedUrl.href + '/' + scrapedHref
  }
}

module.exports = extractLinks
<|endoftext|>


<commit_before>/*
 * GET users listing.
 */

var _ = require('underscore')
  , mongodb = require("mongodb")
  , ObjectID = mongodb.ObjectID
  , fs = require('fs')
  , async = require('async')
  , moment = require("moment")
  , MailParser = require("mailparser").MailParser
  , mongo = require("../db")
  , spawn = require('child_process').spawn

  ;

exports.http_add = function (req, res) {

  // アップロードファイル
  var file = req.files.mail.path;

  add(fs.createReadStream(file), function (err, result) {

    // ファイル削除
    fs.unlink(file);

    var info = {
      subject: result.subject,
      to: result.to
    }
    res.send(info);

  });
}

/**
 *
 * @param stream RFC2822(eml形式) stream
 * @param callback
 */
function add(stream, callback) {

  async.waterfall([

    // パース
    function (done) {

      var attachments = [];
      var attach_count = 0;
      var has_attachments = false;

      var bodies = [];
      var bodies_count = 0;
      var has_bodies = false;

      var mailInfo = null;
      var parser = new MailParser({streamAttachments: true});
      stream.pipe(parser);
      parser.on("end", function (mail) {
        mailInfo = {
          from: mail.from,
          to: mail.to,
          cc: mail.cc,
          subject: mail.subject,
          messageId: mail.messageId,
          date: (new moment(mail.date)).toDate()
        }
        if (mail.text) {
          parser.emit('body', {buffer: mail.text, contentType: 'text/plain'});
        }
        if (mail.html) {
          parser.emit('body', {buffer: mail.html, contentType: 'text/html'});
        }
        end();
      });
      parser.on("body", function (attachment) {
        has_bodies = true;
        bodies_count++;
        add(attachment, function (attach) {
          bodies.push(attach);
          bodies_count--;
          end();
        });
      });
      parser.on("attachment", function (attachment) {
        has_attachments = true;
        attach_count++;
        add(attachment, function (attach) {
          attachments.push(attach);
          attach_count--;
          end();
        });
      });
      function add(attachment, callback) {
        var _id = new ObjectID();
        var attach = {
          _id: _id,
          filename: attachment.generatedFileName,
          contentType: attachment.contentType,
          contentId: attachment.contentId,
          path: '/tmp/' + _id.toString() + '.%%%'
        }
        if (attachment.stream) {
          var ws = fs.createWriteStream(attach.path);
          attachment.stream.pipe(ws);
          ws.on('close', function () {
            callback(attach);
          });
        }
        if (attachment.buffer) {
          fs.writeFile(attach.path, attachment.buffer, function (err) {
            callback(attach);
          });
        }
      }
      function end() {
        var attach = (!has_attachments || (has_attachments && attach_count === 0));
        var body = (!has_bodies || (has_bodies && bodies_count === 0));
        if (attach && body) {
          mailInfo.attachments = attachments;
          mailInfo.bodies = bodies;
          done(null, mailInfo);
        }
      }
    },

    // DB登録
    function (mailInfo, done) {
      async.waterfall([
        function (done) {
          mongo.open(function (err, db) {
            done(err, db);
          });
        },
        function (db, done) {
          store(db, mailInfo.bodies, function (err) {
            done(err, db);
          });
        },
        function (db, done) {
          store(db, mailInfo.attachments, function (err) {
            done(err, db);
          });
        },
        function (db, done) {
          var collection = db.collection('mails');
          collection.insert(mailInfo, function (err, result) {
            done(null, db, mailInfo);
          });
        },
        function (db, meilInfo, done) {
          db.close();
          done(null, mailInfo);
        }
      ], function (err, mailInfo) {
        done(null, mailInfo);
      });
      function store(db, array, callback) {
        async.each(array, function (attach, next) {
          var option = {content_type: attach.contentType};
          var gs = new mongodb.GridStore(db, attach._id, attach.filename, "w", option);
          gs.writeFile(attach.path, function (err, result) {
            fs.unlink(attach.path);
            delete attach.path;
            attach.length = result.position;
            next();
          });
        }, function (err) {
          callback(err);
        });
      }
    }
  ], function (err, mailInfo) {
    callback(err, mailInfo);
  });

}

<commit_msg>すこし、うまくいっている<commit_after>/*
 * GET users listing.
 */

var _ = require('underscore')
  , mongodb = require("mongodb")
  , ObjectID = mongodb.ObjectID
  , fs = require('fs')
  , async = require('async')
  , moment = require("moment")
  , MailParser = require("mailparser").MailParser
  , mongo = require("../db")
  , spawn = require('child_process').spawn

  ;

exports.http_add = function (req, res) {

  // アップロードファイル
  var file = req.files.mail.path;

  add(fs.createReadStream(file), function (err, result) {

    // ファイル削除
    fs.unlink(file);

    var info = {
      subject: result.subject,
      to: result.to
    }
    res.send(info);

  });
}

/**
 *
 * @param stream RFC2822(eml形式) stream
 * @param callback
 */
function add(stream, callback) {

  async.waterfall([

    // パース
    function (done) {
      function StuructureInfo() {
        this.count = 0;
        this.array = [];
        this.flag = false;
      }
      StuructureInfo.prototype.end = function() {
        return !this.flag || (this.flag && this.count === 0);
      }
      var bodiesInfo = new StuructureInfo(); // 本文
      var attachInfo = new StuructureInfo(); // 添付ファイル
      var mailInfo = null;
      var parser = new MailParser({streamAttachments: true});
      stream.pipe(parser);
      parser.on("end", function (mail) {
        mailInfo = {
          from: mail.from,
          to: mail.to,
          cc: mail.cc,
          subject: mail.subject,
          messageId: mail.messageId,
          date: (new moment(mail.date)).toDate()
        }
        if (mail.text) {
          parser.emit('attachment', {info: bodiesInfo, buffer: mail.text, contentType: 'text/plain'});
        }
        if (mail.html) {
          parser.emit('attachment', {info: bodiesInfo, buffer: mail.html, contentType: 'text/html'});
        }
        end();
      });
      parser.on("attachment", function (attachment) {
        var info = attachment.info || attachInfo;
        info.flag = true;
        info.count++;
        add(attachment, function (attach) {
          info.array.push(attach);
          info.count--;
          end();
        });
      });
      function add(attachment, callback) {
        var _id = new ObjectID();
        var attach = {
          _id: _id,
          filename: attachment.generatedFileName,
          contentType: attachment.contentType,
          contentId: attachment.contentId,
          path: '/tmp/' + _id.toString() + '.%%%'
        }
        if (attachment.stream) {
          var ws = fs.createWriteStream(attach.path);
          attachment.stream.pipe(ws);
          ws.on('close', function () {
            callback(attach);
          });
        }
        if (attachment.buffer) {
          fs.writeFile(attach.path, attachment.buffer, function (err) {
            callback(attach);
          });
        }
      }
      function end() {
        if (bodiesInfo.end() && attachInfo.end()) {
          mailInfo.attachments = attachInfo.array;
          mailInfo.bodies = bodiesInfo.array;
          done(null, mailInfo);
        }
      }
    },

    // DB登録
    function (mailInfo, done) {
      async.waterfall([
        function (done) {
          mongo.open(function (err, db) {
            done(err, db);
          });
        },
        function (db, done) {
          store(db, mailInfo.bodies, function (err) {
            done(err, db);
          });
        },
        function (db, done) {
          store(db, mailInfo.attachments, function (err) {
            done(err, db);
          });
        },
        function (db, done) {
          var collection = db.collection('mails');
          collection.insert(mailInfo, function (err, result) {
            done(null, db, mailInfo);
          });
        },
        function (db, meilInfo, done) {
          db.close();
          done(null, mailInfo);
        }
      ], function (err, mailInfo) {
        done(null, mailInfo);
      });
      function store(db, array, callback) {
        async.each(array, function (attach, next) {
          var option = {content_type: attach.contentType};
          var gs = new mongodb.GridStore(db, attach._id, attach.filename, "w", option);
          gs.writeFile(attach.path, function (err, result) {
            fs.unlink(attach.path);
            delete attach.path;
            attach.length = result.position;
            next();
          });
        }, function (err) {
          callback(err);
        });
      }
    }
  ], function (err, mailInfo) {
    callback(err, mailInfo);
  });

}

<|endoftext|>


<commit_before>//
// Reusable Ripple layout
//
// - [Props](#props)
// - [Defaults](#defaults)
// - [Built-in builders](#builders)
//
// Created by ywu on 15/8/2.
//
const React = require('react-native');
const MKPropTypes = require('../MKPropTypes');
const MKTouchable = require('../internal/MKTouchable');

const {
  Component,
  Animated,
  View,
  PropTypes,
  Platform,
} = React;

//
// ##
Ripple

// Reusable `Ripple` effect.
//
class Ripple extends Component {
  constructor(props) {
    super(props);
    this._animatedAlpha = new Animated.Value(0);
    this._animatedRippleScale = new Animated.Value(0);

    // [Android] set initial size > 0 to avoid NPE at `ReactViewBackgroundDrawable.drawRoundedBackgroun`
    // @see https://github.com/facebook/react-native/issues/3069
    this.state = {
      width: 1,
      height: 1,
      maskBorderRadius: 0,
      shadowOffsetY: 1,
      ripple: {radii: 0, dia: 0, offset: {top: 0, left: 0}},
    };
  }

  // Touch events handling
  _onTouchEvent(evt) {
    switch (evt.type) {
      case 'TOUCH_DOWN':
        this._onPointerDown(evt);
        break;
      case 'TOUCH_UP':
      case 'TOUCH_CANCEL':
        this._onPointerUp();
        break;
    }
  }

  measure(cb) {
    return this.refs.container.measure(cb);
  }

  _onLayout(evt) {
    this._onLayoutChange(evt.nativeEvent.layout);

    if (this.props.onLayout) {
      this.props.onLayout(evt);
    }
  }

  _onLayoutChange({width, height}) {
    this.setState({
      width,
      height,
      ...this._calcLayers(width / 2, height / 2, width, height),
    });
  }

  _calcLayers(x0, y0, width, height) {
    // mask layer
    const maskRadiiPercent = this.props.maskBorderRadiusInPercent;
    let maskBorderRadius = this.props.maskBorderRadius;

    if (maskRadiiPercent) {
      maskBorderRadius = Math.min(width, height) * maskRadiiPercent / 100;
    }

    // ripple layer
    let offsetX, offsetY, radii;
    let hotSpotX = x0, hotSpotY = y0;
    if (this.props.rippleLocation === 'center') {
      hotSpotX = width / 2;
      hotSpotY = height / 2;
    }
    offsetX = Math.max(hotSpotX, (width - hotSpotX));
    offsetY = Math.max(hotSpotY, (height - hotSpotY));

    // FIXME Workaround for Android not respect `overflow`
    // @see https://github.com/facebook/react-native/issues/3198
    if (Platform.OS === 'android'
        && this.props.rippleLocation === 'center'
        && this.props.maskEnabled && maskRadiiPercent > 0) {
      // limit ripple to the bounds of mask
      radii = maskBorderRadius;
    } else {
      radii = Math.sqrt(offsetX * offsetX + offsetY * offsetY);
    }

    return {
      maskBorderRadius,
      ripple: {
        radii,
        dia: radii * 2,
        offset: {
          top: hotSpotY - radii,
          left: hotSpotX - radii,
        },
      },
    };
  }

  _onPointerDown(evt) {
    this.setState({
      ...this._calcLayers(evt.x, evt.y, this.state.width, this.state.height),
    });
    this.showRipple();
  }

  _onPointerUp() {
    this.hideRipple();
  }

  // Start the ripple effect
  showRipple() {
    this._animatedAlpha.setValue(1);
    this._animatedRippleScale.setValue(.3);

    // scaling up the ripple layer
    this._rippleAni = Animated.timing(this._animatedRippleScale, {
      toValue: 1,
      duration: this.props.rippleDuration || 200,
    });

    // enlarge the shadow, if enabled
    if (this.props.shadowAniEnabled) {
      this.setState({shadowOffsetY: 1.5});
    }

    this._rippleAni.start(() => {
      this._rippleAni = undefined;

      // if any pending animation, do it
      if (this._pendingRippleAni) {
        this._pendingRippleAni();
      }
    });
  }

  // Stop the ripple effect
  hideRipple() {
    this._pendingRippleAni = () => {
      // hide the ripple layer
      Animated.timing(this._animatedAlpha, {
        toValue: 0,
        duration: this.props.maskDuration || 200,
      }).start();

      // scale down the shadow
      if (this.props.shadowAniEnabled) {
        this.setState({shadowOffsetY: 1});
      }

      this._pendingRippleAni = undefined;
    };

    if (!this._rippleAni) {
      // previous ripple animation is done, good to go
      this._pendingRippleAni();
    }
  }

  render() {
    const shadowStyle = {};
    if (this.props.shadowAniEnabled) {
      shadowStyle.shadowOffset = {
        width: 0,
        height: this.state.shadowOffsetY,
      };
    }

    return (
      <MKTouchable ref="container"
                   {...this.props}
                   style={[this.props.style, shadowStyle]}
                   onTouchEvent={this._onTouchEvent.bind(this)}
                   onLayout={this._onLayout.bind(this)}
        >
        {this.props.children}
        <Animated.View
          ref="maskLayer"
          style={{
            position: 'absolute',
            backgroundColor: this.props.maskColor,
            opacity: this._animatedAlpha,
            top: 0,
            left: 0,
            width: this.state.width,
            height: this.state.height,
            borderRadius: this.state.maskBorderRadius,
            overflow: this.props.maskEnabled ? 'hidden' : 'visible',
          }}
          >
          <Animated.View
            ref="rippleLayer"
            style={{
              // position: 'absolute',
              backgroundColor: this.props.rippleColor,
              width: this.state.ripple.dia,
              height: this.state.ripple.dia,
              ...this.state.ripple.offset,
              borderRadius: this.state.ripple.radii,
              transform: [
                {scale: this._animatedRippleScale},
              ],
            }}
            />
        </Animated.View>
      </MKTouchable>
    );
  }
}

// ##
Props

Ripple.propTypes = {
  // [RN.View Props](https://facebook.github.io/react-native/docs/view.html#props)...
  ...View.propTypes,

  // Color of the `Ripple` layer
  rippleColor: PropTypes.string,

  // Duration of the ripple effect, in milliseconds
  rippleDuration: PropTypes.number,

  // Hot-spot position of the ripple effect, [available values](../MKPropTypes.html#rippleLocation)
  rippleLocation: MKPropTypes.rippleLocation,

  // Whether a `Mask` layer should be used, to clip the ripple to the container’s bounds,
  // default is `true`
  maskEnabled: PropTypes.bool,

  // Color of the `Mask` layer
  maskColor: PropTypes.string,

  // Border radius of the `Mask` layer
  maskBorderRadius: PropTypes.number,

  // Border radius of the `Mask` layer, in percentage (of min(width, height))
  maskBorderRadiusInPercent: PropTypes.number,

  // Duration of the mask effect (alpha), in milliseconds
  maskDuration: PropTypes.number,

  // Animating the shadow (on pressed/released) or not
  shadowAniEnabled: PropTypes.bool,
};

// ##
Defaults

Ripple.defaultProps = {
  rippleColor: 'rgba(255,255,255,0.2)',
  rippleDuration: 200,
  rippleLocation: 'tapLocation',
  maskEnabled: true,
  maskColor: 'rgba(255,255,255,0.15)',
  maskBorderRadius: 2,
  maskDuration: 200,
  shadowAniEnabled: true,
};

// --------------------------
// Builder
//
const {Builder} = require('../builder');

//
// ## Ripple builder
// - @see [Builder](../builder.html#Builder)
//
class RippleBuilder extends Builder {
  build() {
    const BuiltRipple = class extends Ripple {};
    BuiltRipple.defaultProps = Object.assign({}, Ripple.defaultProps, this.toProps());
    return BuiltRipple;
  }
}

// define builder method for each prop
RippleBuilder.defineProps(Ripple.propTypes);

// ----------
// ##
Built-in builders

//
function ripple() {
  return new RippleBuilder();
}

// ## Public interface
module.exports = Ripple;
Ripple.Builder = RippleBuilder;
Ripple.ripple = ripple;
<commit_msg>fix #26 update ripple position only on touch events<commit_after>//
// Reusable Ripple layout
//
// - [Props](#props)
// - [Defaults](#defaults)
// - [Built-in builders](#builders)
//
// Created by ywu on 15/8/2.
//
const React = require('react-native');
const MKPropTypes = require('../MKPropTypes');
const MKTouchable = require('../internal/MKTouchable');

const {
  Component,
  Animated,
  View,
  PropTypes,
  Platform,
} = React;

//
// ##
Ripple

// Reusable `Ripple` effect.
//
class Ripple extends Component {
  constructor(props) {
    super(props);
    this._animatedAlpha = new Animated.Value(0);
    this._animatedRippleScale = new Animated.Value(0);

    // [Android] set initial size > 0 to avoid NPE at `ReactViewBackgroundDrawable.drawRoundedBackgroun`
    // @see https://github.com/facebook/react-native/issues/3069
    this.state = {
      width: 1,
      height: 1,
      maskBorderRadius: 0,
      shadowOffsetY: 1,
      ripple: {radii: 0, dia: 0, offset: {top: 0, left: 0}},
    };
  }

  // Touch events handling
  _onTouchEvent(evt) {
    switch (evt.type) {
      case 'TOUCH_DOWN':
        this._onPointerDown(evt);
        break;
      case 'TOUCH_UP':
      case 'TOUCH_CANCEL':
        this._onPointerUp();
        break;
    }
  }

  measure(cb) {
    return this.refs.container.measure(cb);
  }

  _onLayout(evt) {
    this._onLayoutChange(evt.nativeEvent.layout);

    if (this.props.onLayout) {
      this.props.onLayout(evt);
    }
  }

  _onLayoutChange({width, height}) {
    if (width === this.state.width && height === this.state.height) {
      return;
    }

    this.setState({
      width,
      height,
      ...this._calcMaskLayer(width, height),
    });
  }

  // update Mask layer's dimen
  _calcMaskLayer(width, height) {
    const maskRadiiPercent = this.props.maskBorderRadiusInPercent;
    let maskBorderRadius = this.props.maskBorderRadius;

    if (maskRadiiPercent) {
      maskBorderRadius = Math.min(width, height) * maskRadiiPercent / 100;
    }

    return {maskBorderRadius};
  }

  // update Ripple layer's dimen
  _calcRippleLayer(x0, y0) {
    const {width, height, maskBorderRadius} = this.state;
    const {maskBorderRadiusInPercent} = this.props;
    let offsetX, offsetY, radii, hotSpotX = x0, hotSpotY = y0;

    if (this.props.rippleLocation === 'center') {
      hotSpotX = width / 2;
      hotSpotY = height / 2;
    }
    offsetX = Math.max(hotSpotX, (width - hotSpotX));
    offsetY = Math.max(hotSpotY, (height - hotSpotY));

    // FIXME Workaround for Android not respect `overflow`
    // @see https://github.com/facebook/react-native/issues/3198
    if (Platform.OS === 'android'
        && this.props.rippleLocation === 'center'
        && this.props.maskEnabled && maskBorderRadiusInPercent > 0) {
      // limit ripple to the bounds of mask
      radii = maskBorderRadius;
    } else {
      radii = Math.sqrt(offsetX * offsetX + offsetY * offsetY);
    }

    return {
      ripple: {
        radii,
        dia: radii * 2,
        offset: {
          top: hotSpotY - radii,
          left: hotSpotX - radii,
        },
      },
    };
  }

  _onPointerDown(evt) {
    this.setState({
      ...this._calcRippleLayer(evt.x, evt.y),
    });
    this.showRipple();
  }

  _onPointerUp() {
    this.hideRipple();
  }

  // Start the ripple effect
  showRipple() {
    this._animatedAlpha.setValue(1);
    this._animatedRippleScale.setValue(.3);

    // scaling up the ripple layer
    this._rippleAni = Animated.timing(this._animatedRippleScale, {
      toValue: 1,
      duration: this.props.rippleDuration || 200,
    });

    // enlarge the shadow, if enabled
    if (this.props.shadowAniEnabled) {
      this.setState({shadowOffsetY: 1.5});
    }

    this._rippleAni.start(() => {
      this._rippleAni = undefined;

      // if any pending animation, do it
      if (this._pendingRippleAni) {
        this._pendingRippleAni();
      }
    });
  }

  // Stop the ripple effect
  hideRipple() {
    this._pendingRippleAni = () => {
      // hide the ripple layer
      Animated.timing(this._animatedAlpha, {
        toValue: 0,
        duration: this.props.maskDuration || 200,
      }).start();

      // scale down the shadow
      if (this.props.shadowAniEnabled) {
        this.setState({shadowOffsetY: 1});
      }

      this._pendingRippleAni = undefined;
    };

    if (!this._rippleAni) {
      // previous ripple animation is done, good to go
      this._pendingRippleAni();
    }
  }

  render() {
    const shadowStyle = {};
    if (this.props.shadowAniEnabled) {
      shadowStyle.shadowOffset = {
        width: 0,
        height: this.state.shadowOffsetY,
      };
    }

    return (
      <MKTouchable ref="container"
                   {...this.props}
                   style={[this.props.style, shadowStyle]}
                   onTouchEvent={this._onTouchEvent.bind(this)}
                   onLayout={this._onLayout.bind(this)}
        >
        {this.props.children}
        <Animated.View
          ref="maskLayer"
          style={{
            position: 'absolute',
            backgroundColor: this.props.maskColor,
            opacity: this._animatedAlpha,
            top: 0,
            left: 0,
            width: this.state.width,
            height: this.state.height,
            borderRadius: this.state.maskBorderRadius,
            overflow: this.props.maskEnabled ? 'hidden' : 'visible',
          }}
          >
          <Animated.View
            ref="rippleLayer"
            style={{
              // position: 'absolute',
              backgroundColor: this.props.rippleColor,
              width: this.state.ripple.dia,
              height: this.state.ripple.dia,
              ...this.state.ripple.offset,
              borderRadius: this.state.ripple.radii,
              transform: [
                {scale: this._animatedRippleScale},
              ],
            }}
            />
        </Animated.View>
      </MKTouchable>
    );
  }
}

// ##
Props

Ripple.propTypes = {
  // [RN.View Props](https://facebook.github.io/react-native/docs/view.html#props)...
  ...View.propTypes,

  // Color of the `Ripple` layer
  rippleColor: PropTypes.string,

  // Duration of the ripple effect, in milliseconds
  rippleDuration: PropTypes.number,

  // Hot-spot position of the ripple effect, [available values](../MKPropTypes.html#rippleLocation)
  rippleLocation: MKPropTypes.rippleLocation,

  // Whether a `Mask` layer should be used, to clip the ripple to the container’s bounds,
  // default is `true`
  maskEnabled: PropTypes.bool,

  // Color of the `Mask` layer
  maskColor: PropTypes.string,

  // Border radius of the `Mask` layer
  maskBorderRadius: PropTypes.number,

  // Border radius of the `Mask` layer, in percentage (of min(width, height))
  maskBorderRadiusInPercent: PropTypes.number,

  // Duration of the mask effect (alpha), in milliseconds
  maskDuration: PropTypes.number,

  // Animating the shadow (on pressed/released) or not
  shadowAniEnabled: PropTypes.bool,
};

// ##
Defaults

Ripple.defaultProps = {
  rippleColor: 'rgba(255,255,255,0.2)',
  rippleDuration: 200,
  rippleLocation: 'tapLocation',
  maskEnabled: true,
  maskColor: 'rgba(255,255,255,0.15)',
  maskBorderRadius: 2,
  maskDuration: 200,
  shadowAniEnabled: true,
};

// --------------------------
// Builder
//
const {Builder} = require('../builder');

//
// ## Ripple builder
// - @see [Builder](../builder.html#Builder)
//
class RippleBuilder extends Builder {
  build() {
    const BuiltRipple = class extends Ripple {};
    BuiltRipple.defaultProps = Object.assign({}, Ripple.defaultProps, this.toProps());
    return BuiltRipple;
  }
}

// define builder method for each prop
RippleBuilder.defineProps(Ripple.propTypes);

// ----------
// ##
Built-in builders

//
function ripple() {
  return new RippleBuilder();
}

// ## Public interface
module.exports = Ripple;
Ripple.Builder = RippleBuilder;
Ripple.ripple = ripple;
<|endoftext|>