Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors

This commit is contained in:
Juan Picado 2017-04-23 20:02:26 +02:00
parent e3e1485369
commit f282941075
No known key found for this signature in database
GPG Key ID: 18AC54485952D158
40 changed files with 2430 additions and 2386 deletions

View File

@ -1,3 +1,4 @@
node_modules
lib/static
coverage/
coverage/
lib/GUI/

View File

@ -9,8 +9,11 @@
# Created to work with eslint@0.18.0
#
extends: ["eslint:recommended", "google"]
env:
node: true
browser: true
es6: true
rules:
@ -43,3 +46,22 @@ rules:
# useful for code clean-up
no-unused-vars: [1, {"vars": "all", "args": "none"}]
max-len: [1, 160]
# camelcase is standard, but this should be 1 and then 2 soon
camelcase: 0
# configuration that should be upgraded progresivelly
require-jsdoc: 1
valid-jsdoc: 1
prefer-spread: 1
no-constant-condition: 1
no-var: 1
no-empty: 1
guard-for-in: 1
no-invalid-this: 1
new-cap: 1
one-var: 1
no-redeclare: 1
prefer-rest-params: 1
no-console: [1, {"allow": ["log", "warn"]}]

View File

@ -4,36 +4,36 @@ module.exports = function(grunt) {
browserify: {
dist: {
files: {
'lib/static/main.js': [ 'lib/GUI/js/main.js' ]
'lib/static/main.js': ['lib/GUI/js/main.js'],
},
options: {
debug: true,
transform: [ 'browserify-handlebars' ]
}
}
transform: ['browserify-handlebars'],
},
},
},
less: {
dist: {
files: {
'lib/static/main.css': [ 'lib/GUI/css/main.less' ]
'lib/static/main.css': ['lib/GUI/css/main.less'],
},
options: {
sourceMap: false
}
}
sourceMap: false,
},
},
},
watch: {
files: [ 'lib/GUI/**/*' ],
tasks: [ 'default' ]
}
})
files: ['lib/GUI/**/*'],
tasks: ['default'],
},
});
grunt.loadNpmTasks('grunt-browserify')
grunt.loadNpmTasks('grunt-contrib-watch')
grunt.loadNpmTasks('grunt-contrib-less')
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.registerTask('default', [
'browserify',
'less'
])
}
'less',
]);
};

View File

@ -1,3 +1,3 @@
#!/usr/bin/env node
require('../lib/cli')
require('../lib/cli');

View File

@ -1,6 +1,6 @@
module.exports = require('./lib')
module.exports = require('./lib');
/**package
/** package
{ "name": "verdaccio",
"version": "0.0.0",
"dependencies": {"js-yaml": "*"},

View File

@ -7,275 +7,272 @@
* ======================================================================== */
+function ($) {
+function($) {
'use strict';
// MODAL CLASS DEFINITION
// ======================
var Modal = function (element, options) {
this.options = options
this.$body = $(document.body)
this.$element = $(element)
this.$backdrop =
this.isShown = null
this.scrollbarWidth = 0
let Modal = function(element, options) {
this.options = options;
this.$body = $(document.body);
this.$element = $(element);
this.$backdrop =
this.isShown = null;
this.scrollbarWidth = 0;
if (this.options.remote) {
this.$element
.find('.modal-content')
.load(this.options.remote, $.proxy(function () {
this.$element.trigger('loaded.bs.modal')
}, this))
.load(this.options.remote, $.proxy(function() {
this.$element.trigger('loaded.bs.modal');
}, this));
}
}
};
Modal.VERSION = '3.3.0'
Modal.VERSION = '3.3.0';
Modal.TRANSITION_DURATION = 300
Modal.BACKDROP_TRANSITION_DURATION = 150
Modal.TRANSITION_DURATION = 300;
Modal.BACKDROP_TRANSITION_DURATION = 150;
Modal.DEFAULTS = {
backdrop: true,
keyboard: true,
show: true
}
show: true,
};
Modal.prototype.toggle = function (_relatedTarget) {
return this.isShown ? this.hide() : this.show(_relatedTarget)
}
Modal.prototype.toggle = function(_relatedTarget) {
return this.isShown ? this.hide() : this.show(_relatedTarget);
};
Modal.prototype.show = function (_relatedTarget) {
var that = this
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
Modal.prototype.show = function(_relatedTarget) {
let that = this;
let e = $.Event('show.bs.modal', {relatedTarget: _relatedTarget});
this.$element.trigger(e)
this.$element.trigger(e);
if (this.isShown || e.isDefaultPrevented()) return
if (this.isShown || e.isDefaultPrevented()) return;
this.isShown = true
this.isShown = true;
this.checkScrollbar()
this.$body.addClass('modal-open')
this.checkScrollbar();
this.$body.addClass('modal-open');
this.setScrollbar()
this.escape()
this.setScrollbar();
this.escape();
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this));
this.backdrop(function () {
var transition = $.support.transition && that.$element.hasClass('fade')
this.backdrop(function() {
let transition = $.support.transition && that.$element.hasClass('fade');
if (!that.$element.parent().length) {
that.$element.appendTo(that.$body) // don't move modals dom position
that.$element.appendTo(that.$body); // don't move modals dom position
}
that.$element
.show()
.scrollTop(0)
.scrollTop(0);
if (transition) {
that.$element[0].offsetWidth // force reflow
that.$element[0].offsetWidth; // force reflow
}
that.$element
.addClass('in')
.attr('aria-hidden', false)
.attr('aria-hidden', false);
that.enforceFocus()
that.enforceFocus();
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
let e = $.Event('shown.bs.modal', {relatedTarget: _relatedTarget});
transition ?
that.$element.find('.modal-dialog') // wait for modal to slide in
.one('bsTransitionEnd', function () {
that.$element.trigger('focus').trigger(e)
.one('bsTransitionEnd', function() {
that.$element.trigger('focus').trigger(e);
})
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
that.$element.trigger('focus').trigger(e)
})
}
that.$element.trigger('focus').trigger(e);
});
};
Modal.prototype.hide = function (e) {
if (e) e.preventDefault()
Modal.prototype.hide = function(e) {
if (e) e.preventDefault();
e = $.Event('hide.bs.modal')
e = $.Event('hide.bs.modal');
this.$element.trigger(e)
this.$element.trigger(e);
if (!this.isShown || e.isDefaultPrevented()) return
if (!this.isShown || e.isDefaultPrevented()) return;
this.isShown = false
this.isShown = false;
this.escape()
this.escape();
$(document).off('focusin.bs.modal')
$(document).off('focusin.bs.modal');
this.$element
.removeClass('in')
.attr('aria-hidden', true)
.off('click.dismiss.bs.modal')
.off('click.dismiss.bs.modal');
$.support.transition && this.$element.hasClass('fade') ?
this.$element
.one('bsTransitionEnd', $.proxy(this.hideModal, this))
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
this.hideModal()
}
this.hideModal();
};
Modal.prototype.enforceFocus = function () {
Modal.prototype.enforceFocus = function() {
$(document)
.off('focusin.bs.modal') // guard against infinite focus loop
.on('focusin.bs.modal', $.proxy(function (e) {
.on('focusin.bs.modal', $.proxy(function(e) {
if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
this.$element.trigger('focus')
this.$element.trigger('focus');
}
}, this))
}
}, this));
};
Modal.prototype.escape = function () {
Modal.prototype.escape = function() {
if (this.isShown && this.options.keyboard) {
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
e.which == 27 && this.hide()
}, this))
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function(e) {
e.which == 27 && this.hide();
}, this));
} else if (!this.isShown) {
this.$element.off('keydown.dismiss.bs.modal')
this.$element.off('keydown.dismiss.bs.modal');
}
}
};
Modal.prototype.hideModal = function () {
var that = this
this.$element.hide()
this.backdrop(function () {
that.$body.removeClass('modal-open')
that.resetScrollbar()
that.$element.trigger('hidden.bs.modal')
})
}
Modal.prototype.hideModal = function() {
let that = this;
this.$element.hide();
this.backdrop(function() {
that.$body.removeClass('modal-open');
that.resetScrollbar();
that.$element.trigger('hidden.bs.modal');
});
};
Modal.prototype.removeBackdrop = function () {
this.$backdrop && this.$backdrop.remove()
this.$backdrop = null
}
Modal.prototype.removeBackdrop = function() {
this.$backdrop && this.$backdrop.remove();
this.$backdrop = null;
};
Modal.prototype.backdrop = function (callback) {
var that = this
var animate = this.$element.hasClass('fade') ? 'fade' : ''
Modal.prototype.backdrop = function(callback) {
let that = this;
let animate = this.$element.hasClass('fade') ? 'fade' : '';
if (this.isShown && this.options.backdrop) {
var doAnimate = $.support.transition && animate
let doAnimate = $.support.transition && animate;
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
.prependTo(this.$element)
.on('click.dismiss.bs.modal', $.proxy(function (e) {
if (e.target !== e.currentTarget) return
.on('click.dismiss.bs.modal', $.proxy(function(e) {
if (e.target !== e.currentTarget) return;
this.options.backdrop == 'static'
? this.$element[0].focus.call(this.$element[0])
: this.hide.call(this)
}, this))
: this.hide.call(this);
}, this));
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
if (doAnimate) this.$backdrop[0].offsetWidth; // force reflow
this.$backdrop.addClass('in')
this.$backdrop.addClass('in');
if (!callback) return
if (!callback) return;
doAnimate ?
this.$backdrop
.one('bsTransitionEnd', callback)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callback()
callback();
} else if (!this.isShown && this.$backdrop) {
this.$backdrop.removeClass('in')
this.$backdrop.removeClass('in');
var callbackRemove = function () {
that.removeBackdrop()
callback && callback()
}
let callbackRemove = function() {
that.removeBackdrop();
callback && callback();
};
$.support.transition && this.$element.hasClass('fade') ?
this.$backdrop
.one('bsTransitionEnd', callbackRemove)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callbackRemove()
callbackRemove();
} else if (callback) {
callback()
callback();
}
}
};
Modal.prototype.checkScrollbar = function () {
this.scrollbarWidth = this.measureScrollbar()
}
Modal.prototype.checkScrollbar = function() {
this.scrollbarWidth = this.measureScrollbar();
};
Modal.prototype.setScrollbar = function () {
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
if (this.scrollbarWidth) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
}
Modal.prototype.setScrollbar = function() {
let bodyPad = parseInt((this.$body.css('padding-right') || 0), 10);
if (this.scrollbarWidth) this.$body.css('padding-right', bodyPad + this.scrollbarWidth);
};
Modal.prototype.resetScrollbar = function () {
this.$body.css('padding-right', '')
}
Modal.prototype.resetScrollbar = function() {
this.$body.css('padding-right', '');
};
Modal.prototype.measureScrollbar = function () { // thx walsh
if (document.body.clientWidth >= window.innerWidth) return 0
var scrollDiv = document.createElement('div')
scrollDiv.className = 'modal-scrollbar-measure'
this.$body.append(scrollDiv)
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
this.$body[0].removeChild(scrollDiv)
return scrollbarWidth
}
Modal.prototype.measureScrollbar = function() { // thx walsh
if (document.body.clientWidth >= window.innerWidth) return 0;
let scrollDiv = document.createElement('div');
scrollDiv.className = 'modal-scrollbar-measure';
this.$body.append(scrollDiv);
let scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
this.$body[0].removeChild(scrollDiv);
return scrollbarWidth;
};
// MODAL PLUGIN DEFINITION
// =======================
function Plugin(option, _relatedTarget) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.modal')
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
return this.each(function() {
let $this = $(this);
let data = $this.data('bs.modal');
let options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option);
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
if (typeof option == 'string') data[option](_relatedTarget)
else if (options.show) data.show(_relatedTarget)
})
if (!data) $this.data('bs.modal', (data = new Modal(this, options)));
if (typeof option == 'string') data[option](_relatedTarget);
else if (options.show) data.show(_relatedTarget);
});
}
var old = $.fn.modal
let old = $.fn.modal;
$.fn.modal = Plugin
$.fn.modal.Constructor = Modal
$.fn.modal = Plugin;
$.fn.modal.Constructor = Modal;
// MODAL NO CONFLICT
// =================
$.fn.modal.noConflict = function () {
$.fn.modal = old
return this
}
$.fn.modal.noConflict = function() {
$.fn.modal = old;
return this;
};
// MODAL DATA-API
// ==============
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
var $this = $(this)
var href = $this.attr('href')
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function(e) {
let $this = $(this);
let href = $this.attr('href');
let $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))); // strip for ie7
let option = $target.data('bs.modal') ? 'toggle' : $.extend({remote: !/#/.test(href) && href}, $target.data(), $this.data());
if ($this.is('a')) e.preventDefault()
$target.one('show.bs.modal', function (showEvent) {
if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
$target.one('hidden.bs.modal', function () {
$this.is(':visible') && $this.trigger('focus')
})
})
Plugin.call($target, option, this)
})
if ($this.is('a')) e.preventDefault();
$target.one('show.bs.modal', function(showEvent) {
if (showEvent.isDefaultPrevented()) return; // only register focus restorer if modal will actually get shown
$target.one('hidden.bs.modal', function() {
$this.is(':visible') && $this.trigger('focus');
});
});
Plugin.call($target, option, this);
});
}(jQuery);

View File

@ -1,54 +1,53 @@
var $ = require('unopinionate').selector
var onClick = require('onclick')
var transitionComplete = require('transition-complete')
let $ = require('unopinionate').selector;
let onClick = require('onclick');
let transitionComplete = require('transition-complete');
$(function() {
onClick('.entry .name', function() {
var $this = $(this)
var $entry = $this.closest('.entry')
let $this = $(this);
let $entry = $this.closest('.entry');
if ($entry.hasClass('open')) {
// Close entry
$entry
.height($entry.outerHeight())
.removeClass('open')
.removeClass('open');
setTimeout(function() {
$entry.css('height', $entry.attr('data-height') + 'px')
}, 0)
$entry.css('height', $entry.attr('data-height') + 'px');
}, 0);
transitionComplete(function() {
$entry.find('.readme').remove()
$entry.css('height', 'auto')
})
$entry.find('.readme').remove();
$entry.css('height', 'auto');
});
} else {
// Open entry
$('.entry.open').each(function() {
// Close open entries
var $entry = $(this)
let $entry = $(this);
$entry
.height($entry.outerHeight())
.removeClass('open')
.removeClass('open');
setTimeout(function() {
$entry.css('height', $entry.attr('data-height') + 'px')
}, 0)
$entry.css('height', $entry.attr('data-height') + 'px');
}, 0);
transitionComplete(function() {
$entry.find('.readme').remove()
$entry.css('height', 'auto')
})
})
$entry.find('.readme').remove();
$entry.css('height', 'auto');
});
});
// Add the open class
$entry.addClass('open')
$entry.addClass('open');
// Explicitly set heights for transitions
var height = $entry.outerHeight()
let height = $entry.outerHeight();
$entry
.attr('data-height', height)
.css('height', height)
.css('height', height);
// Get the data
$.ajax({
@ -57,17 +56,17 @@ $(function() {
+ encodeURIComponent($entry.attr('data-version')),
dataType: 'text',
success: function(html) {
var $readme = $("<div class='readme'>")
let $readme = $('<div class=\'readme\'>')
.html(html)
.appendTo($entry)
.appendTo($entry);
$entry.height(height + $readme.outerHeight())
$entry.height(height + $readme.outerHeight());
transitionComplete(function() {
$entry.css('height', 'auto')
})
}
})
$entry.css('height', 'auto');
});
},
});
}
})
})
});
});

View File

@ -1,13 +1,13 @@
// twitter bootstrap stuff;
// not in static 'cause I want it to be bundled with the rest of javascripts
require('./bootstrap-modal')
require('./bootstrap-modal');
// our own files
require('./search')
require('./entry')
require('./search');
require('./entry');
var $ = require('unopinionate').selector
let $ = require('unopinionate').selector;
$(document).on('click', '.js-userLogoutBtn', function() {
$('#userLogoutForm').submit()
return false
})
$('#userLogoutForm').submit();
return false;
});

View File

@ -1,5 +1,5 @@
var $ = require('unopinionate').selector
var template = require('../entry.hbs')
let $ = require('unopinionate').selector;
let template = require('../entry.hbs');
$(function() {
;(function(window, document) {
@ -22,7 +22,8 @@ $(function() {
$form.bind('submit keyup', function(e) {
var query, isValidQuery
e.preventDefault()
e.preventDefault();
query = $input.val()
isValidQuery = (query !== '')
@ -31,35 +32,35 @@ $(function() {
if (!isValidQuery) {
if (request && typeof request.abort === 'function') {
request.abort()
request.abort();
}
$searchResults.html('')
return
return;
}
if (request && typeof request.abort === 'function') {
request.abort()
request.abort();
}
if (query !== lastQuery) {
lastQuery = query
$searchResults.html(
"<img class='search-ajax' src='-/static/ajax.gif' alt='Spinner'/>")
'<img class=\'search-ajax\' src=\'-/static/ajax.gif\' alt=\'Spinner\'/>');
}
request = $.getJSON('-/search/' + query, function( results ) {
if (results.length > 0) {
var html = ''
let html = '';
$.each(results, function(i, entry) {
html += template(entry)
})
html += template(entry);
});
$searchResults.html(html)
$searchResults.html(html);
} else {
$searchResults.html(
"<div class='no-results'><big>No Results</big></div>")
'<div class=\'no-results\'><big>No Results</big></div>');
}
}).fail(function () {
$searchResults.html(
@ -68,10 +69,9 @@ $(function() {
})
$(document).on('click', '.icon-cancel', function(e) {
e.preventDefault()
$input.val('')
$form.keyup()
})
})(window, window.document)
})
e.preventDefault();
$input.val('');
$form.keyup();
});
})(window, window.document);
});

View File

@ -1,33 +1,37 @@
const Crypto = require('crypto')
const jju = require('jju')
const Error = require('http-errors')
const Logger = require('./logger')
/* eslint prefer-spread: "off" */
'use strict';
const Crypto = require('crypto');
const jju = require('jju');
const Error = require('http-errors');
const Logger = require('./logger');
const load_plugins = require('./plugin-loader').load_plugins;
module.exports = Auth
module.exports = Auth;
function Auth(config) {
var self = Object.create(Auth.prototype)
self.config = config
self.logger = Logger.logger.child({ sub: 'auth' })
self.secret = config.secret
let self = Object.create(Auth.prototype);
self.config = config;
self.logger = Logger.logger.child({sub: 'auth'});
self.secret = config.secret;
var plugin_params = {
let plugin_params = {
config: config,
logger: self.logger
}
logger: self.logger,
};
if (config.users_file) {
if (!config.auth || !config.auth.htpasswd) {
// b/w compat
config.auth = config.auth || {}
config.auth.htpasswd = { file: config.users_file }
config.auth = config.auth || {};
config.auth.htpasswd = {file: config.users_file};
}
}
self.plugins = load_plugins(config, config.auth, plugin_params, function (p) {
return p.authenticate || p.allow_access || p.allow_publish
})
self.plugins = load_plugins(config, config.auth, plugin_params, function(p) {
return p.authenticate || p.allow_access || p.allow_publish;
});
self.plugins.unshift({
verdaccio_version: '1.1.0',
@ -38,342 +42,342 @@ function Auth(config) {
&& (Crypto.createHash('sha1').update(password).digest('hex')
=== config.users[user].password)
) {
return cb(null, [ user ])
return cb(null, [user]);
}
return cb()
return cb();
},
adduser: function(user, password, cb) {
if (config.users && config.users[user])
return cb( Error[403]('this user already exists') )
return cb( Error[403]('this user already exists') );
return cb()
return cb();
},
})
});
function allow_action(action) {
return function(user, package, cb) {
var ok = package[action].reduce(function(prev, curr) {
if (user.groups.indexOf(curr) !== -1) return true
return prev
}, false)
return function(user, pkg, cb) {
let ok = pkg[action].reduce(function(prev, curr) {
if (user.groups.indexOf(curr) !== -1) return true;
return prev;
}, false);
if (ok) return cb(null, true)
if (ok) return cb(null, true);
if (user.name) {
cb( Error[403]('user ' + user.name + ' is not allowed to ' + action + ' package ' + package.name) )
cb( Error[403]('user ' + user.name + ' is not allowed to ' + action + ' package ' + pkg.name) );
} else {
cb( Error[403]('unregistered users are not allowed to ' + action + ' package ' + package.name) )
cb( Error[403]('unregistered users are not allowed to ' + action + ' package ' + pkg.name) );
}
}
};
}
self.plugins.push({
authenticate: function(user, password, cb) {
return cb( Error[403]('bad username/password, access denied') )
return cb( Error[403]('bad username/password, access denied') );
},
add_user: function(user, password, cb) {
return cb( Error[409]('registration is disabled') )
return cb( Error[409]('registration is disabled') );
},
allow_access: allow_action('access'),
allow_publish: allow_action('publish'),
})
});
return self
return self;
}
Auth.prototype.authenticate = function(user, password, cb) {
var plugins = this.plugins.slice(0)
let plugins = this.plugins.slice(0)
;(function next() {
var p = plugins.shift()
let p = plugins.shift();
if (typeof(p.authenticate) !== 'function') {
return next()
return next();
}
p.authenticate(user, password, function(err, groups) {
if (err) return cb(err)
if (err) return cb(err);
if (groups != null && groups != false)
return cb(err, AuthenticatedUser(user, groups))
next()
})
})()
}
return cb(err, authenticatedUser(user, groups));
next();
});
})();
};
Auth.prototype.add_user = function(user, password, cb) {
var self = this
var plugins = this.plugins.slice(0)
let self = this;
let plugins = this.plugins.slice(0)
;(function next() {
var p = plugins.shift()
var n = 'adduser'
let p = plugins.shift();
let n = 'adduser';
if (typeof(p[n]) !== 'function') {
n = 'add_user'
n = 'add_user';
}
if (typeof(p[n]) !== 'function') {
next()
next();
} else {
p[n](user, password, function(err, ok) {
if (err) return cb(err)
if (ok) return self.authenticate(user, password, cb)
next()
})
if (err) return cb(err);
if (ok) return self.authenticate(user, password, cb);
next();
});
}
})()
}
})();
};
Auth.prototype.allow_access = function(package_name, user, callback) {
var plugins = this.plugins.slice(0)
var package = Object.assign({ name: package_name },
let plugins = this.plugins.slice(0);
let pkg = Object.assign({name: package_name},
this.config.get_package_spec(package_name))
;(function next() {
var p = plugins.shift()
let p = plugins.shift();
if (typeof(p.allow_access) !== 'function') {
return next()
return next();
}
p.allow_access(user, package, function(err, ok) {
if (err) return callback(err)
if (ok) return callback(null, ok)
next() // cb(null, false) causes next plugin to roll
})
})()
}
p.allow_access(user, pkg, function(err, ok) {
if (err) return callback(err);
if (ok) return callback(null, ok);
next(); // cb(null, false) causes next plugin to roll
});
})();
};
Auth.prototype.allow_publish = function(package_name, user, callback) {
var plugins = this.plugins.slice(0)
var package = Object.assign({ name: package_name },
let plugins = this.plugins.slice(0);
let pkg = Object.assign({name: package_name},
this.config.get_package_spec(package_name))
;(function next() {
var p = plugins.shift()
let p = plugins.shift();
if (typeof(p.allow_publish) !== 'function') {
return next()
return next();
}
p.allow_publish(user, package, function(err, ok) {
if (err) return callback(err)
if (ok) return callback(null, ok)
next() // cb(null, false) causes next plugin to roll
})
})()
}
p.allow_publish(user, pkg, function(err, ok) {
if (err) return callback(err);
if (ok) return callback(null, ok);
next(); // cb(null, false) causes next plugin to roll
});
})();
};
Auth.prototype.basic_middleware = function() {
var self = this
let self = this;
return function(req, res, _next) {
req.pause()
req.pause();
function next(err) {
req.resume()
req.resume();
// uncomment this to reject users with bad auth headers
//return _next.apply(null, arguments)
// return _next.apply(null, arguments)
// swallow error, user remains unauthorized
// set remoteUserError to indicate that user was attempting authentication
if (err) req.remote_user.error = err.message
return _next()
if (err) req.remote_user.error = err.message;
return _next();
}
if (req.remote_user != null && req.remote_user.name !== undefined)
return next()
req.remote_user = AnonymousUser()
return next();
req.remote_user = buildAnonymousUser();
var authorization = req.headers.authorization
if (authorization == null) return next()
let authorization = req.headers.authorization;
if (authorization == null) return next();
var parts = authorization.split(' ')
let parts = authorization.split(' ');
if (parts.length !== 2)
return next( Error[400]('bad authorization header') )
return next( Error[400]('bad authorization header') );
var scheme = parts[0]
let scheme = parts[0];
if (scheme === 'Basic') {
var credentials = new Buffer(parts[1], 'base64').toString()
var credentials = new Buffer(parts[1], 'base64').toString();
} else if (scheme === 'Bearer') {
var credentials = self.aes_decrypt(new Buffer(parts[1], 'base64')).toString('utf8')
if (!credentials) return next()
var credentials = self.aes_decrypt(new Buffer(parts[1], 'base64')).toString('utf8');
if (!credentials) return next();
} else {
return next()
return next();
}
var index = credentials.indexOf(':')
if (index < 0) return next()
let index = credentials.indexOf(':');
if (index < 0) return next();
var user = credentials.slice(0, index)
var pass = credentials.slice(index + 1)
let user = credentials.slice(0, index);
let pass = credentials.slice(index + 1);
self.authenticate(user, pass, function(err, user) {
if (!err) {
req.remote_user = user
next()
req.remote_user = user;
next();
} else {
req.remote_user = AnonymousUser()
next(err)
req.remote_user = buildAnonymousUser();
next(err);
}
})
}
}
});
};
};
Auth.prototype.bearer_middleware = function() {
var self = this
let self = this;
return function(req, res, _next) {
req.pause()
req.pause();
function next(_err) {
req.resume()
return _next.apply(null, arguments)
req.resume();
return _next.apply(null, arguments);
}
if (req.remote_user != null && req.remote_user.name !== undefined)
return next()
req.remote_user = AnonymousUser()
return next();
req.remote_user = buildAnonymousUser();
var authorization = req.headers.authorization
if (authorization == null) return next()
let authorization = req.headers.authorization;
if (authorization == null) return next();
var parts = authorization.split(' ')
let parts = authorization.split(' ');
if (parts.length !== 2)
return next( Error[400]('bad authorization header') )
return next( Error[400]('bad authorization header') );
var scheme = parts[0]
var token = parts[1]
let scheme = parts[0];
let token = parts[1];
if (scheme !== 'Bearer')
return next()
return next();
try {
var user = self.decode_token(token)
var user = self.decode_token(token);
} catch(err) {
return next(err)
return next(err);
}
req.remote_user = AuthenticatedUser(user.u, user.g)
req.remote_user.token = token
next()
}
}
req.remote_user = authenticatedUser(user.u, user.g);
req.remote_user.token = token;
next();
};
};
Auth.prototype.cookie_middleware = function() {
var self = this
let self = this;
return function(req, res, _next) {
req.pause()
req.pause();
function next(_err) {
req.resume()
return _next()
req.resume();
return _next();
}
if (req.remote_user != null && req.remote_user.name !== undefined)
return next()
return next();
req.remote_user = AnonymousUser()
req.remote_user = buildAnonymousUser();
var token = req.cookies.get('token')
if (token == null) return next()
let token = req.cookies.get('token');
if (token == null) return next();
/*try {
/* try {
var user = self.decode_token(token, 60*60)
} catch(err) {
return next()
}
req.remote_user = AuthenticatedUser(user.u, user.g)
req.remote_user = authenticatedUser(user.u, user.g)
req.remote_user.token = token
next()*/
var credentials = self.aes_decrypt(new Buffer(token, 'base64')).toString('utf8')
if (!credentials) return next()
let credentials = self.aes_decrypt(new Buffer(token, 'base64')).toString('utf8');
if (!credentials) return next();
var index = credentials.indexOf(':')
if (index < 0) return next()
let index = credentials.indexOf(':');
if (index < 0) return next();
var user = credentials.slice(0, index)
var pass = credentials.slice(index + 1)
let user = credentials.slice(0, index);
let pass = credentials.slice(index + 1);
self.authenticate(user, pass, function(err, user) {
if (!err) {
req.remote_user = user
next()
req.remote_user = user;
next();
} else {
req.remote_user = AnonymousUser()
next(err)
req.remote_user = buildAnonymousUser();
next(err);
}
})
}
}
});
};
};
Auth.prototype.issue_token = function(user) {
var data = jju.stringify({
let data = jju.stringify({
u: user.name,
g: user.real_groups && user.real_groups.length ? user.real_groups : undefined,
t: ~~(Date.now()/1000),
}, { indent: false })
}, {indent: false});
data = new Buffer(data, 'utf8')
var mac = Crypto.createHmac('sha256', this.secret).update(data).digest()
return Buffer.concat([ data, mac ]).toString('base64')
}
data = new Buffer(data, 'utf8');
let mac = Crypto.createHmac('sha256', this.secret).update(data).digest();
return Buffer.concat([data, mac]).toString('base64');
};
Auth.prototype.decode_token = function(str, expire_time) {
var buf = new Buffer(str, 'base64')
if (buf.length <= 32) throw Error[401]('invalid token')
let buf = new Buffer(str, 'base64');
if (buf.length <= 32) throw Error[401]('invalid token');
var data = buf.slice(0, buf.length - 32)
var their_mac = buf.slice(buf.length - 32)
var good_mac = Crypto.createHmac('sha256', this.secret).update(data).digest()
let data = buf.slice(0, buf.length - 32);
let their_mac = buf.slice(buf.length - 32);
let good_mac = Crypto.createHmac('sha256', this.secret).update(data).digest();
their_mac = Crypto.createHash('sha512').update(their_mac).digest('hex')
good_mac = Crypto.createHash('sha512').update(good_mac).digest('hex')
if (their_mac !== good_mac) throw Error[401]('bad signature')
their_mac = Crypto.createHash('sha512').update(their_mac).digest('hex');
good_mac = Crypto.createHash('sha512').update(good_mac).digest('hex');
if (their_mac !== good_mac) throw Error[401]('bad signature');
// make token expire in 24 hours
// TODO: make configurable?
expire_time = expire_time || 24*60*60
expire_time = expire_time || 24*60*60;
data = jju.parse(data.toString('utf8'))
data = jju.parse(data.toString('utf8'));
if (Math.abs(data.t - ~~(Date.now()/1000)) > expire_time)
throw Error[401]('token expired')
throw Error[401]('token expired');
return data
}
return data;
};
Auth.prototype.aes_encrypt = function(buf) {
var c = Crypto.createCipher('aes192', this.secret)
var b1 = c.update(buf)
var b2 = c.final()
return Buffer.concat([ b1, b2 ])
}
let c = Crypto.createCipher('aes192', this.secret);
let b1 = c.update(buf);
let b2 = c.final();
return Buffer.concat([b1, b2]);
};
Auth.prototype.aes_decrypt = function(buf) {
try {
var c = Crypto.createDecipher('aes192', this.secret)
var b1 = c.update(buf)
var b2 = c.final()
let c = Crypto.createDecipher('aes192', this.secret);
let b1 = c.update(buf);
let b2 = c.final();
return Buffer.concat([b1, b2]);
} catch(_) {
return new Buffer(0)
return new Buffer(0);
}
return Buffer.concat([ b1, b2 ])
}
};
function AnonymousUser() {
function buildAnonymousUser() {
return {
name: undefined,
// groups without '$' are going to be deprecated eventually
groups: [ '$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous' ],
groups: ['$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous'],
real_groups: [],
}
};
}
function AuthenticatedUser(name, groups) {
var _groups = (groups || []).concat([ '$all', '$authenticated', '@all', '@authenticated', 'all' ])
function authenticatedUser(name, groups) {
let _groups = (groups || []).concat(['$all', '$authenticated', '@all', '@authenticated', 'all']);
return {
name: name,
groups: _groups,
real_groups: groups,
}
};
}

View File

@ -1,113 +1,113 @@
#!/usr/bin/env node
/*eslint no-sync:0*/
/* eslint no-sync:0*/
'use strict';
if (process.getuid && process.getuid() === 0) {
global.console.error("Verdaccio doesn't need superuser privileges. Don't run it under root.")
global.console.error('Verdaccio doesn\'t need superuser privileges. Don\'t run it under root.');
}
process.title = 'verdaccio'
process.title = 'verdaccio';
try {
// for debugging memory leaks
// totally optional
require('heapdump')
require('heapdump');
} catch(err) {}
var logger = require('./logger')
logger.setup() // default setup
let logger = require('./logger');
logger.setup(); // default setup
var commander = require('commander')
var constants = require('constants')
var fs = require('fs')
var http = require('http')
var https = require('https')
var YAML = require('js-yaml')
var Path = require('path')
var URL = require('url')
var server = require('./index')
var Utils = require('./utils')
var pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars
var pkgVersion = module.exports.version
var pkgName = module.exports.name
let commander = require('commander');
let constants = require('constants');
let fs = require('fs');
let http = require('http');
let https = require('https');
let YAML = require('js-yaml');
let Path = require('path');
let URL = require('url');
let server = require('./index');
let Utils = require('./utils');
let pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars
let pkgVersion = module.exports.version;
let pkgName = module.exports.name;
commander
.option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)')
.option('-c, --config <config.yaml>', 'use this configuration file (default: ./config.yaml)')
.version(pkgVersion)
.parse(process.argv)
.parse(process.argv);
if (commander.args.length == 1 && !commander.config) {
// handling "verdaccio [config]" case if "-c" is missing in commandline
commander.config = commander.args.pop()
commander.config = commander.args.pop();
}
if (commander.args.length != 0) {
commander.help()
commander.help();
}
var config, config_path
let config, config_path;
try {
if (commander.config) {
config_path = Path.resolve(commander.config)
config_path = Path.resolve(commander.config);
} else {
config_path = require('./config-path')()
config_path = require('./config-path')();
}
config = YAML.safeLoad(fs.readFileSync(config_path, 'utf8'))
logger.logger.warn({ file: config_path }, 'config file - @{file}')
config = YAML.safeLoad(fs.readFileSync(config_path, 'utf8'));
logger.logger.warn({file: config_path}, 'config file - @{file}');
} catch (err) {
logger.logger.fatal({ file: config_path, err: err }, 'cannot open config file @{file}: @{!err.message}')
process.exit(1)
logger.logger.fatal({file: config_path, err: err}, 'cannot open config file @{file}: @{!err.message}');
process.exit(1);
}
afterConfigLoad()
afterConfigLoad();
function get_listen_addresses() {
// command line || config file || default
var addresses
let addresses;
if (commander.listen) {
addresses = [ commander.listen ]
addresses = [commander.listen];
} else if (Array.isArray(config.listen)) {
addresses = config.listen
addresses = config.listen;
} else if (config.listen) {
addresses = [ config.listen ]
addresses = [config.listen];
} else {
addresses = [ '4873' ]
addresses = ['4873'];
}
addresses = addresses.map(function(addr) {
var parsed_addr = Utils.parse_address(addr)
let parsed_addr = Utils.parse_address(addr);
if (!parsed_addr) {
logger.logger.warn({ addr: addr },
logger.logger.warn({addr: addr},
'invalid address - @{addr}, we expect a port (e.g. "4873"),'
+ ' host:port (e.g. "localhost:4873") or full url'
+ ' (e.g. "http://localhost:4873/")')
+ ' (e.g. "http://localhost:4873/")');
}
return parsed_addr
return parsed_addr;
}).filter(Boolean);
}).filter(Boolean)
return addresses
return addresses;
}
function afterConfigLoad() {
if (!config.self_path) config.self_path = Path.resolve(config_path)
if (!config.https) config.https = { enable: false };
if (!config.self_path) config.self_path = Path.resolve(config_path);
if (!config.https) config.https = {enable: false};
var app = server(config)
let app = server(config);
get_listen_addresses().forEach(function(addr) {
var webServer
let webServer;
if (addr.proto === 'https') { // https
if (!config.https || !config.https.key || !config.https.cert) {
var conf_path = function(file) {
if (!file) return config_path
return Path.resolve(Path.dirname(config_path), file)
}
let conf_path = function(file) {
if (!file) return config_path;
return Path.resolve(Path.dirname(config_path), file);
};
logger.logger.fatal([
'You need to specify "https.key" and "https.cert" to run https server',
@ -122,8 +122,8 @@ function afterConfigLoad() {
' https:',
' key: verdaccio-key.pem',
' cert: verdaccio-cert.pem',
].join('\n'))
process.exit(2)
].join('\n'));
process.exit(2);
}
try {
@ -132,12 +132,11 @@ function afterConfigLoad() {
secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3,
key: fs.readFileSync(config.https.key),
cert: fs.readFileSync(config.https.cert),
ca: fs.readFileSync(config.https.ca)
}, app)
ca: fs.readFileSync(config.https.ca),
}, app);
} catch (err) { // catch errors related to certificate loading
logger.logger.fatal({ err: err }, 'cannot create server: @{err.message}')
process.exit(2)
logger.logger.fatal({err: err}, 'cannot create server: @{err.message}');
process.exit(2);
}
} else { // http
webServer = http.createServer(app);
@ -146,9 +145,9 @@ function afterConfigLoad() {
webServer
.listen(addr.port || addr.path, addr.host)
.on('error', function(err) {
logger.logger.fatal({ err: err }, 'cannot create server: @{err.message}')
process.exit(2)
})
logger.logger.fatal({err: err}, 'cannot create server: @{err.message}');
process.exit(2);
});
logger.logger.warn({
addr: ( addr.path
@ -164,17 +163,17 @@ function afterConfigLoad() {
})
),
version: pkgName + '/' + pkgVersion,
}, 'http address - @{addr}')
})
}, 'http address - @{addr}');
});
// undocumented stuff for tests
if (typeof(process.send) === 'function') {
process.send({ verdaccio_started: true })
process.send({verdaccio_started: true});
}
}
process.on('uncaughtException', function(err) {
logger.logger.fatal( { err: err }
, 'uncaught exception, please report this\n@{err.stack}' )
process.exit(255)
})
logger.logger.fatal( {err: err}
, 'uncaught exception, please report this\n@{err.stack}' );
process.exit(255);
});

View File

@ -1,47 +1,49 @@
var fs = require('fs')
var Path = require('path')
var logger = require('./logger')
'use strict';
module.exports = find_config_file
let fs = require('fs');
let Path = require('path');
let logger = require('./logger');
module.exports = find_config_file;
function find_config_file() {
var paths = get_paths()
let paths = get_paths();
for (var i=0; i<paths.length; i++) {
if (file_exists(paths[i].path)) return paths[i].path
for (let i=0; i<paths.length; i++) {
if (file_exists(paths[i].path)) return paths[i].path;
}
create_config_file(paths[0])
return paths[0].path
create_config_file(paths[0]);
return paths[0].path;
}
function create_config_file(config_path) {
require('mkdirp').sync(Path.dirname(config_path.path))
logger.logger.info({ file: config_path.path }, 'Creating default config file in @{file}')
require('mkdirp').sync(Path.dirname(config_path.path));
logger.logger.info({file: config_path.path}, 'Creating default config file in @{file}');
var created_config = fs.readFileSync(require.resolve('../conf/default.yaml'), 'utf8')
let created_config = fs.readFileSync(require.resolve('../conf/default.yaml'), 'utf8');
if (config_path.type === 'xdg') {
var data_dir = process.env.XDG_DATA_HOME
|| Path.join(process.env.HOME, '.local', 'share')
let data_dir = process.env.XDG_DATA_HOME
|| Path.join(process.env.HOME, '.local', 'share');
if (folder_exists(data_dir)) {
data_dir = Path.resolve(Path.join(data_dir, 'verdaccio', 'storage'))
created_config = created_config.replace(/^storage: .\/storage$/m, 'storage: ' + data_dir)
data_dir = Path.resolve(Path.join(data_dir, 'verdaccio', 'storage'));
created_config = created_config.replace(/^storage: .\/storage$/m, 'storage: ' + data_dir);
}
}
fs.writeFileSync(config_path.path, created_config)
fs.writeFileSync(config_path.path, created_config);
}
function get_paths() {
var try_paths = []
var xdg_config = process.env.XDG_CONFIG_HOME
|| process.env.HOME && Path.join(process.env.HOME, '.config')
let try_paths = [];
let xdg_config = process.env.XDG_CONFIG_HOME
|| process.env.HOME && Path.join(process.env.HOME, '.config');
if (xdg_config && folder_exists(xdg_config)) {
try_paths.push({
path: Path.join(xdg_config, 'verdaccio', 'config.yaml'),
type: 'xdg',
})
});
}
if (process.platform === 'win32'
@ -50,40 +52,40 @@ function get_paths() {
try_paths.push({
path: Path.resolve(Path.join(process.env.APPDATA, 'verdaccio', 'config.yaml')),
type: 'win',
})
});
}
try_paths.push({
path: Path.resolve(Path.join('.', 'verdaccio', 'config.yaml')),
type: 'def',
})
});
// backward compatibility
try_paths.push({
path: Path.resolve(Path.join('.', 'config.yaml')),
type: 'old',
})
});
return try_paths
return try_paths;
}
function folder_exists(path) {
try {
var stat = fs.statSync(path)
var stat = fs.statSync(path);
} catch(_) {
return false
return false;
}
return stat.isDirectory()
return stat.isDirectory();
}
function file_exists(path) {
try {
var stat = fs.statSync(path)
var stat = fs.statSync(path);
} catch(_) {
return false
return false;
}
return stat.isFile()
return stat.isFile();
}

View File

@ -1,32 +1,34 @@
var assert = require('assert')
var Crypto = require('crypto')
var Error = require('http-errors')
var minimatch = require('minimatch')
var Path = require('path')
var LocalData = require('./local-data')
var Utils = require('./utils')
var Utils = require('./utils')
var pkginfo = require('pkginfo')(module) // eslint-disable-line no-unused-vars
var pkgVersion = module.exports.version
var pkgName = module.exports.name
'use strict';
let assert = require('assert');
let Crypto = require('crypto');
let Error = require('http-errors');
let minimatch = require('minimatch');
let Path = require('path');
let LocalData = require('./local-data');
var Utils = require('./utils');
var Utils = require('./utils');
let pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars
let pkgVersion = module.exports.version;
let pkgName = module.exports.name;
// [[a, [b, c]], d] -> [a, b, c, d]
function flatten(array) {
var result = []
for (var i=0; i<array.length; i++) {
let result = [];
for (let i=0; i<array.length; i++) {
if (Array.isArray(array[i])) {
result.push.apply(result, flatten(array[i]))
result.push.apply(result, flatten(array[i]));
} else {
result.push(array[i])
result.push(array[i]);
}
}
return result
return result;
}
function Config(config) {
var self = Object.create(Config.prototype)
let self = Object.create(Config.prototype);
for (var i in config) {
if (self[i] == null) self[i] = config[i]
if (self[i] == null) self[i] = config[i];
}
if (!self.user_agent) {
@ -34,44 +36,44 @@ function Config(config) {
}
// some weird shell scripts are valid yaml files parsed as string
assert.equal(typeof(config), 'object', 'CONFIG: it doesn\'t look like a valid config file')
assert.equal(typeof(config), 'object', 'CONFIG: it doesn\'t look like a valid config file');
assert(self.storage, 'CONFIG: storage path not defined')
assert(self.storage, 'CONFIG: storage path not defined');
self.localList = new LocalData(
Path.join(
Path.resolve(Path.dirname(self.self_path || ''), self.storage),
'.sinopia-db.json'
)
)
);
if (!self.secret) {
self.secret = self.localList.data.secret
self.secret = self.localList.data.secret;
if (!self.secret) {
self.secret = Crypto.pseudoRandomBytes(32).toString('hex')
self.localList.data.secret = self.secret
self.localList.sync()
self.secret = Crypto.pseudoRandomBytes(32).toString('hex');
self.localList.data.secret = self.secret;
self.localList.sync();
}
}
var users = {
all: true,
anonymous: true,
let users = {
'all': true,
'anonymous': true,
'undefined': true,
owner: true,
none: true
'owner': true,
'none': true,
};
var check_user_or_uplink = function(arg) {
assert(arg !== 'all' && arg !== 'owner' && arg !== 'anonymous' && arg !== 'undefined' && arg !== 'none', 'CONFIG: reserved user/uplink name: ' + arg)
assert(!arg.match(/\s/), 'CONFIG: invalid user name: ' + arg)
assert(users[arg] == null, 'CONFIG: duplicate user/uplink name: ' + arg)
users[arg] = true
let check_user_or_uplink = function(arg) {
assert(arg !== 'all' && arg !== 'owner' && arg !== 'anonymous' && arg !== 'undefined' && arg !== 'none', 'CONFIG: reserved user/uplink name: ' + arg);
assert(!arg.match(/\s/), 'CONFIG: invalid user name: ' + arg);
assert(users[arg] == null, 'CONFIG: duplicate user/uplink name: ' + arg);
users[arg] = true;
}
;[ 'users', 'uplinks', 'packages' ].forEach(function(x) {
if (self[x] == null) self[x] = {}
assert(Utils.is_object(self[x]), 'CONFIG: bad "'+x+'" value (object expected)')
})
;['users', 'uplinks', 'packages'].forEach(function(x) {
if (self[x] == null) self[x] = {};
assert(Utils.is_object(self[x]), 'CONFIG: bad "'+x+'" value (object expected)');
});
for (var i in self.users) {
check_user_or_uplink(i);
@ -81,127 +83,127 @@ function Config(config) {
}
for (var i in self.users) {
assert(self.users[i].password, 'CONFIG: no password for user: ' + i)
assert(self.users[i].password, 'CONFIG: no password for user: ' + i);
assert(typeof(self.users[i].password) === 'string' &&
self.users[i].password.match(/^[a-f0-9]{40}$/)
, 'CONFIG: wrong password format for user: ' + i + ', sha1 expected')
, 'CONFIG: wrong password format for user: ' + i + ', sha1 expected');
}
for (var i in self.uplinks) {
assert(self.uplinks[i].url, 'CONFIG: no url for uplink: ' + i)
assert(self.uplinks[i].url, 'CONFIG: no url for uplink: ' + i);
assert( typeof(self.uplinks[i].url) === 'string'
, 'CONFIG: wrong url format for uplink: ' + i)
self.uplinks[i].url = self.uplinks[i].url.replace(/\/$/, '')
, 'CONFIG: wrong url format for uplink: ' + i);
self.uplinks[i].url = self.uplinks[i].url.replace(/\/$/, '');
}
function normalize_userlist() {
var result = []
let result = [];
for (var i=0; i<arguments.length; i++) {
if (arguments[i] == null) continue
for (let i=0; i<arguments.length; i++) {
if (arguments[i] == null) continue;
// if it's a string, split it to array
if (typeof(arguments[i]) === 'string') {
result.push(arguments[i].split(/\s+/))
result.push(arguments[i].split(/\s+/));
} else if (Array.isArray(arguments[i])) {
result.push(arguments[i])
result.push(arguments[i]);
} else {
throw Error('CONFIG: bad package acl (array or string expected): ' + JSON.stringify(arguments[i]))
throw Error('CONFIG: bad package acl (array or string expected): ' + JSON.stringify(arguments[i]));
}
}
return flatten(result)
return flatten(result);
}
// add a default rule for all packages to make writing plugins easier
if (self.packages['**'] == null) {
self.packages['**'] = {}
self.packages['**'] = {};
}
for (var i in self.packages) {
assert(
typeof(self.packages[i]) === 'object' &&
!Array.isArray(self.packages[i])
, 'CONFIG: bad "'+i+'" package description (object expected)')
, 'CONFIG: bad "'+i+'" package description (object expected)');
self.packages[i].access = normalize_userlist(
self.packages[i].allow_access,
self.packages[i].access
);
delete self.packages[i].allow_access
delete self.packages[i].allow_access;
self.packages[i].publish = normalize_userlist(
self.packages[i].allow_publish,
self.packages[i].publish
);
delete self.packages[i].allow_publish
delete self.packages[i].allow_publish;
self.packages[i].proxy = normalize_userlist(
self.packages[i].proxy_access,
self.packages[i].proxy
);
delete self.packages[i].proxy_access
delete self.packages[i].proxy_access;
}
// loading these from ENV if aren't in config
;[ 'http_proxy', 'https_proxy', 'no_proxy' ].forEach((function(v) {
['http_proxy', 'https_proxy', 'no_proxy'].forEach((function(v) {
if (!(v in self)) {
self[v] = process.env[v] || process.env[v.toUpperCase()]
self[v] = process.env[v] || process.env[v.toUpperCase()];
}
}).bind(self))
}));
// unique identifier of self server (or a cluster), used to avoid loops
if (!self.server_id) {
self.server_id = Crypto.pseudoRandomBytes(6).toString('hex')
self.server_id = Crypto.pseudoRandomBytes(6).toString('hex');
}
return self
return self;
}
Config.prototype.can_proxy_to = function(package, uplink) {
return (this.get_package_spec(package).proxy || []).reduce(function(prev, curr) {
if (uplink === curr) return true
return prev
}, false)
}
Config.prototype.can_proxy_to = function(pkg, uplink) {
return (this.get_package_spec(pkg).proxy || []).reduce(function(prev, curr) {
if (uplink === curr) return true;
return prev;
}, false);
};
Config.prototype.get_package_spec = function(package) {
for (var i in this.packages) {
if (minimatch.makeRe(i).exec(package)) {
return this.packages[i]
Config.prototype.get_package_spec = function(pkg) {
for (let i in this.packages) {
if (minimatch.makeRe(i).exec(pkg)) {
return this.packages[i];
}
}
return {}
}
return {};
};
module.exports = Config
module.exports = Config;
var parse_interval_table = {
let parse_interval_table = {
'': 1000,
ms: 1,
s: 1000,
m: 60*1000,
h: 60*60*1000,
d: 86400000,
w: 7*86400000,
M: 30*86400000,
y: 365*86400000,
}
'ms': 1,
's': 1000,
'm': 60*1000,
'h': 60*60*1000,
'd': 86400000,
'w': 7*86400000,
'M': 30*86400000,
'y': 365*86400000,
};
module.exports.parse_interval = function(interval) {
if (typeof(interval) === 'number') return interval * 1000
if (typeof(interval) === 'number') return interval * 1000;
var result = 0
var last_suffix = Infinity
let result = 0;
let last_suffix = Infinity;
interval.split(/\s+/).forEach(function(x) {
if (!x) return
var m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/)
if (!x) return;
let m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/);
if (!m
|| parse_interval_table[m[4]] >= last_suffix
|| (m[4] === '' && last_suffix !== Infinity)) {
throw Error('invalid interval: ' + interval)
|| parse_interval_table[m[4]] >= last_suffix
|| (m[4] === '' && last_suffix !== Infinity)) {
throw Error('invalid interval: ' + interval);
}
last_suffix = parse_interval_table[m[4]]
result += Number(m[1]) * parse_interval_table[m[4]]
})
return result
}
last_suffix = parse_interval_table[m[4]];
result += Number(m[1]) * parse_interval_table[m[4]];
});
return result;
};

View File

@ -1,15 +1,17 @@
'use strict';
/**
* file-locking.js - file system locking (replaces fs-ext)
*/
var async = require('async'),
let async = require('async'),
locker = require('lockfile'),
fs = require('fs'),
path = require('path')
path = require('path');
// locks a file by creating a lock file
function lockFile(name, next) {
var lockFileName = name + '.lock',
let lockFileName = name + '.lock',
lockOpts = {
wait: 1000, // time (ms) to wait when checking for stale locks
pollPeriod: 100, // how often (ms) to re-check stale locks
@ -17,65 +19,64 @@ function lockFile(name, next) {
stale: 5 * 60 * 1000, // locks are considered stale after 5 minutes
retries: 100, // number of times to attempt to create a lock
retryWait: 100 // time (ms) between tries
}
retryWait: 100, // time (ms) between tries
};
async.series({
statdir: function (callback) {
statdir: function(callback) {
// test to see if the directory exists
fs.stat(path.dirname(name), function (err, stats) {
fs.stat(path.dirname(name), function(err, stats) {
if (err) {
callback(err)
callback(err);
} else if (!stats.isDirectory()) {
callback(new Error(path.dirname(name) + ' is not a directory'))
callback(new Error(path.dirname(name) + ' is not a directory'));
} else {
callback(null)
}
})
},
statfile: function (callback) {
// test to see if the file to lock exists
fs.stat(name, function (err, stats) {
if (err) {
callback(err)
} else if (!stats.isFile()) {
callback(new Error(path.dirname(name) + ' is not a file'))
} else {
callback(null)
callback(null);
}
});
},
lockfile: function (callback) {
// try to lock the file
locker.lock(lockFileName, lockOpts, callback)
}
statfile: function(callback) {
// test to see if the file to lock exists
fs.stat(name, function(err, stats) {
if (err) {
callback(err);
} else if (!stats.isFile()) {
callback(new Error(path.dirname(name) + ' is not a file'));
} else {
callback(null);
}
});
},
}, function (err) {
lockfile: function(callback) {
// try to lock the file
locker.lock(lockFileName, lockOpts, callback);
},
}, function(err) {
if (err) {
// lock failed
return next(err)
return next(err);
}
// lock succeeded
return next(null);
})
});
}
// unlocks file by removing existing lock file
function unlockFile(name, next) {
var lockFileName = name + '.lock'
let lockFileName = name + '.lock';
locker.unlock(lockFileName, function (err) {
locker.unlock(lockFileName, function(err) {
if (err) {
return next(err)
return next(err);
}
return next(null)
})
return next(null);
});
}
/**
@ -87,63 +88,62 @@ function unlockFile(name, next) {
function readFile(name, options, next) {
if (typeof options === 'function' && next === null) {
next = options;
options = {}
options = {};
}
options = options || {}
options.lock = options.lock || false
options.parse = options.parse || false
options = options || {};
options.lock = options.lock || false;
options.parse = options.parse || false;
function lock(callback) {
if (!options.lock) {
return callback(null)
return callback(null);
}
lockFile(name, function (err) {
lockFile(name, function(err) {
if (err) {
return callback(err)
return callback(err);
}
return callback(null)
})
return callback(null);
});
}
function read(callback) {
fs.readFile(name, 'utf8', function (err, contents) {
fs.readFile(name, 'utf8', function(err, contents) {
if (err) {
return callback(err)
return callback(err);
}
callback(null, contents)
})
callback(null, contents);
});
}
function parseJSON(contents, callback) {
if (!options.parse) {
return callback(null, contents)
return callback(null, contents);
}
try {
contents = JSON.parse(contents)
return callback(null, contents)
contents = JSON.parse(contents);
return callback(null, contents);
} catch (err) {
return callback(err)
return callback(err);
}
}
async.waterfall([
lock,
read,
parseJSON
parseJSON,
],
function (err, result) {
function(err, result) {
if (err) {
return next(err)
return next(err);
} else {
return next(null, result)
return next(null, result);
}
})
});
}
exports.lockFile = lockFile;

View File

@ -1,99 +1,102 @@
var Cookies = require('cookies')
var express = require('express')
var bodyParser = require('body-parser')
var Error = require('http-errors')
var Path = require('path')
var Middleware = require('./middleware')
var Notify = require('./notify')
var Utils = require('./utils')
var expect_json = Middleware.expect_json
var match = Middleware.match
var media = Middleware.media
var validate_name = Middleware.validate_name
var validate_pkg = Middleware.validate_package
'use strict';
let Cookies = require('cookies');
let express = require('express');
let bodyParser = require('body-parser');
let Error = require('http-errors');
let Path = require('path');
let Middleware = require('./middleware');
let Notify = require('./notify');
let Utils = require('./utils');
let expect_json = Middleware.expect_json;
let match = Middleware.match;
let media = Middleware.media;
let validate_name = Middleware.validate_name;
let validate_pkg = Middleware.validate_package;
module.exports = function(config, auth, storage) {
var app = express.Router()
var can = Middleware.allow(auth)
var notify = Notify.notify;
let app = express.Router();
let can = Middleware.allow(auth);
let notify = Notify.notify;
// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble
app.param('package', validate_pkg)
app.param('filename', validate_name)
app.param('tag', validate_name)
app.param('version', validate_name)
app.param('revision', validate_name)
app.param('token', validate_name)
app.param('package', validate_pkg);
app.param('filename', validate_name);
app.param('tag', validate_name);
app.param('version', validate_name);
app.param('revision', validate_name);
app.param('token', validate_name);
// these can't be safely put into express url for some reason
app.param('_rev', match(/^-rev$/))
app.param('org_couchdb_user', match(/^org\.couchdb\.user:/))
app.param('anything', match(/.*/))
app.param('_rev', match(/^-rev$/));
app.param('org_couchdb_user', match(/^org\.couchdb\.user:/));
app.param('anything', match(/.*/));
app.use(auth.basic_middleware())
//app.use(auth.bearer_middleware())
app.use(bodyParser.json({ strict: false, limit: config.max_body_size || '10mb' }))
app.use(Middleware.anti_loop(config))
app.use(auth.basic_middleware());
// app.use(auth.bearer_middleware())
app.use(bodyParser.json({strict: false, limit: config.max_body_size || '10mb'}));
app.use(Middleware.anti_loop(config));
// encode / in a scoped package name to be matched as a single parameter in routes
app.use(function(req, res, next) {
if (req.url.indexOf('@') != -1) {
// e.g.: /@org/pkg/1.2.3 -> /@org%2Fpkg/1.2.3, /@org%2Fpkg/1.2.3 -> /@org%2Fpkg/1.2.3
req.url = req.url.replace(/^(\/@[^\/%]+)\/(?!$)/, '$1%2F')
req.url = req.url.replace(/^(\/@[^\/%]+)\/(?!$)/, '$1%2F');
}
next()
})
next();
});
// for "npm whoami"
app.get('/whoami', function(req, res, next) {
if (req.headers.referer === 'whoami') {
next({ username: req.remote_user.name })
next({username: req.remote_user.name});
} else {
next('route')
next('route');
}
})
});
app.get('/-/whoami', function(req, res, next) {
next({ username: req.remote_user.name })
})
next({username: req.remote_user.name});
});
// TODO: anonymous user?
app.get('/:package/:version?', can('access'), function(req, res, next) {
storage.get_package(req.params.package, { req: req }, function(err, info) {
if (err) return next(err)
info = Utils.filter_tarball_urls(info, req, config)
storage.get_package(req.params.package, {req: req}, function(err, info) {
if (err) return next(err);
info = Utils.filter_tarball_urls(info, req, config);
var version = req.params.version
if (!version) return next(info)
let version = req.params.version;
if (!version) return next(info);
var t = Utils.get_version(info, version)
if (t != null) return next(t)
let t = Utils.get_version(info, version);
if (t != null) return next(t);
if (info['dist-tags'] != null) {
if (info['dist-tags'][version] != null) {
version = info['dist-tags'][version]
t = Utils.get_version(info, version)
if (t != null) return next(t)
version = info['dist-tags'][version];
t = Utils.get_version(info, version);
if (t != null) return next(t);
}
}
return next( Error[404]('version not found: ' + req.params.version) )
})
})
return next( Error[404]('version not found: ' + req.params.version) );
});
});
app.get('/:package/-/:filename', can('access'), function(req, res, next) {
var stream = storage.get_tarball(req.params.package, req.params.filename)
let stream = storage.get_tarball(req.params.package, req.params.filename);
stream.on('content-length', function(v) {
res.header('Content-Length', v)
})
res.header('Content-Length', v);
});
stream.on('error', function(err) {
return res.report_error(err)
})
res.header('Content-Type', 'application/octet-stream')
stream.pipe(res)
})
return res.report_error(err);
});
res.header('Content-Type', 'application/octet-stream');
stream.pipe(res);
});
// searching packages
<<<<<<< HEAD
app.get('/-/all(\/since)?', function(req, res, next) {
var received_end = false
var response_finished = false
@ -128,26 +131,36 @@ module.exports = function(config, auth, storage) {
} else {
res.write('{"_updated":' + 99999);
}
=======
app.get('/-/all/:anything?', function(req, res, next) {
let received_end = false;
let response_finished = false;
let processing_pkgs = 0;
var stream = storage.search(req.query.startkey || 0, { req: req })
res.status(200);
res.write('{"_updated":' + Date.now());
>>>>>>> Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors
let stream = storage.search(req.query.startkey || 0, {req: req});
stream.on('data', function each(pkg) {
processing_pkgs++
processing_pkgs++;
auth.allow_access(pkg.name, req.remote_user, function(err, allowed) {
processing_pkgs--
processing_pkgs--;
if (err) {
if (err.status && String(err.status).match(/^4\d\d$/)) {
// auth plugin returns 4xx user error,
// that's equivalent of !allowed basically
allowed = false
allowed = false;
} else {
stream.abort(err)
stream.abort(err);
}
}
if (allowed) {
<<<<<<< HEAD
if (respShouldBeArray) {
res.write(`${firstPackage ? '' : ','}${JSON.stringify(pkg)}\n`)
if (firstPackage) {
@ -156,69 +169,77 @@ module.exports = function(config, auth, storage) {
} else {
res.write(',\n' + JSON.stringify(pkg.name) + ':' + JSON.stringify(pkg))
}
=======
res.write(',\n' + JSON.stringify(pkg.name) + ':' + JSON.stringify(pkg));
>>>>>>> Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors
}
check_finish()
})
})
check_finish();
});
});
stream.on('error', function (_err) {
res.socket.destroy()
})
stream.on('error', function(_err) {
res.socket.destroy();
});
stream.on('end', function () {
received_end = true
check_finish()
})
stream.on('end', function() {
received_end = true;
check_finish();
});
function check_finish() {
if (!received_end) return
if (processing_pkgs) return
if (response_finished) return
if (!received_end) return;
if (processing_pkgs) return;
if (response_finished) return;
<<<<<<< HEAD
response_finished = true
if (respShouldBeArray) {
res.end(']\n')
} else {
res.end('}\n')
}
=======
response_finished = true;
res.end('}\n');
>>>>>>> Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors
}
})
});
// placeholder 'cause npm require to be authenticated to publish
// we do not do any real authentication yet
app.post('/_session', Cookies.express(), function(req, res, next) {
res.cookies.set('AuthSession', String(Math.random()), {
// npmjs.org sets 10h expire
expires: new Date(Date.now() + 10*60*60*1000)
})
next({ ok: true, name: 'somebody', roles: [] })
})
expires: new Date(Date.now() + 10*60*60*1000),
});
next({ok: true, name: 'somebody', roles: []});
});
app.get('/-/user/:org_couchdb_user', function(req, res, next) {
res.status(200)
res.status(200);
next({
ok: 'you are authenticated as "' + req.remote_user.name + '"',
})
})
});
});
app.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req, res, next) {
var token = (req.body.name && req.body.password)
let token = (req.body.name && req.body.password)
? auth.aes_encrypt(req.body.name + ':' + req.body.password).toString('base64')
: undefined
: undefined;
if (req.remote_user.name != null) {
res.status(201)
res.status(201);
return next({
ok: "you are authenticated as '" + req.remote_user.name + "'",
//token: auth.issue_token(req.remote_user),
ok: 'you are authenticated as \'' + req.remote_user.name + '\'',
// token: auth.issue_token(req.remote_user),
token: token,
})
});
} else {
if (typeof(req.body.name) !== 'string' || typeof(req.body.password) !== 'string') {
if (typeof(req.body.password_sha)) {
return next( Error[422]('your npm version is outdated\nPlease update to npm@1.4.5 or greater.\nSee https://github.com/rlidwka/sinopia/issues/93 for details.') )
return next( Error[422]('your npm version is outdated\nPlease update to npm@1.4.5 or greater.\nSee https://github.com/rlidwka/sinopia/issues/93 for details.') );
} else {
return next( Error[422]('user/password is not found in request (npm issue?)') )
return next( Error[422]('user/password is not found in request (npm issue?)') );
}
}
auth.add_user(req.body.name, req.body.password, function(err, user) {
@ -227,133 +248,130 @@ module.exports = function(config, auth, storage) {
// With npm registering is the same as logging in,
// and npm accepts only an 409 error.
// So, changing status code here.
return next( Error[409](err.message) )
return next( Error[409](err.message) );
}
return next(err)
return next(err);
}
req.remote_user = user
res.status(201)
req.remote_user = user;
res.status(201);
return next({
ok: "user '" + req.body.name + "' created",
//token: auth.issue_token(req.remote_user),
ok: 'user \'' + req.body.name + '\' created',
// token: auth.issue_token(req.remote_user),
token: token,
})
})
});
});
}
})
});
app.delete('/-/user/token/*', function(req, res, next) {
res.status(200)
res.status(200);
next({
ok: 'Logged out',
})
})
});
});
function tag_package_version(req, res, next) {
if (typeof(req.body) !== 'string') return next('route')
if (typeof(req.body) !== 'string') return next('route');
var tags = {}
tags[req.params.tag] = req.body
let tags = {};
tags[req.params.tag] = req.body;
storage.merge_tags(req.params.package, tags, function(err) {
if (err) return next(err)
res.status(201)
return next({ ok: 'package tagged' })
})
if (err) return next(err);
res.status(201);
return next({ok: 'package tagged'});
});
}
// tagging a package
app.put('/:package/:tag',
can('publish'), media('application/json'), tag_package_version)
can('publish'), media('application/json'), tag_package_version);
app.post('/-/package/:package/dist-tags/:tag',
can('publish'), media('application/json'), tag_package_version)
can('publish'), media('application/json'), tag_package_version);
app.put('/-/package/:package/dist-tags/:tag',
can('publish'), media('application/json'), tag_package_version)
can('publish'), media('application/json'), tag_package_version);
app.delete('/-/package/:package/dist-tags/:tag', can('publish'), function (req, res, next) {
var tags = {}
tags[req.params.tag] = null
app.delete('/-/package/:package/dist-tags/:tag', can('publish'), function(req, res, next) {
let tags = {};
tags[req.params.tag] = null;
storage.merge_tags(req.params.package, tags, function(err) {
if (err) return next(err)
res.status(201)
return next({ ok: 'tag removed' })
})
})
if (err) return next(err);
res.status(201);
return next({ok: 'tag removed'});
});
});
app.get('/-/package/:package/dist-tags', can('access'), function(req, res, next) {
storage.get_package(req.params.package, { req: req }, function(err, info) {
if (err) return next(err)
storage.get_package(req.params.package, {req: req}, function(err, info) {
if (err) return next(err);
next(info['dist-tags'])
})
})
next(info['dist-tags']);
});
});
app.post('/-/package/:package/dist-tags',
can('publish'), media('application/json'), expect_json,
function(req, res, next) {
storage.merge_tags(req.params.package, req.body, function(err) {
if (err) return next(err)
res.status(201)
return next({ ok: 'tags updated' })
})
})
if (err) return next(err);
res.status(201);
return next({ok: 'tags updated'});
});
});
app.put('/-/package/:package/dist-tags',
can('publish'), media('application/json'), expect_json,
function(req, res, next) {
storage.replace_tags(req.params.package, req.body, function(err) {
if (err) return next(err)
res.status(201)
return next({ ok: 'tags updated' })
})
})
if (err) return next(err);
res.status(201);
return next({ok: 'tags updated'});
});
});
app.delete('/-/package/:package/dist-tags',
can('publish'), media('application/json'),
function(req, res, next) {
storage.replace_tags(req.params.package, {}, function(err) {
if (err) return next(err)
res.status(201)
return next({ ok: 'tags removed' })
})
})
if (err) return next(err);
res.status(201);
return next({ok: 'tags removed'});
});
});
// publishing a package
app.put('/:package/:_rev?/:revision?', can('publish'), media('application/json'), expect_json, function(req, res, next) {
var name = req.params.package
let name = req.params.package;
if (Object.keys(req.body).length == 1 && Utils.is_object(req.body.users)) {
// 501 status is more meaningful, but npm doesn't show error message for 5xx
return next( Error[404]('npm star|unstar calls are not implemented') )
return next( Error[404]('npm star|unstar calls are not implemented') );
}
try {
var metadata = Utils.validate_metadata(req.body, name)
var metadata = Utils.validate_metadata(req.body, name);
} catch(err) {
return next( Error[422]('bad incoming package data') )
return next( Error[422]('bad incoming package data') );
}
if (req.params._rev) {
storage.change_package(name, metadata, req.params.revision, function(err) {
after_change(err, 'package changed')
})
after_change(err, 'package changed');
});
} else {
storage.add_package(name, metadata, function(err) {
after_change(err, 'created new package')
})
after_change(err, 'created new package');
});
}
function after_change(err, ok_message) {
// old npm behaviour
if (metadata._attachments == null) {
if (err) return next(err)
res.status(201)
return next({ ok: ok_message })
if (err) return next(err);
res.status(201);
return next({ok: ok_message});
}
// npm-registry-client 0.3+ embeds tarball into the json upload
@ -361,121 +379,120 @@ module.exports = function(config, auth, storage) {
// issue #31, dealing with it here:
if (typeof(metadata._attachments) !== 'object'
|| Object.keys(metadata._attachments).length !== 1
|| typeof(metadata.versions) !== 'object'
|| Object.keys(metadata.versions).length !== 1) {
|| Object.keys(metadata._attachments).length !== 1
|| typeof(metadata.versions) !== 'object'
|| Object.keys(metadata.versions).length !== 1) {
// npm is doing something strange again
// if this happens in normal circumstances, report it as a bug
return next( Error[400]('unsupported registry call') )
return next( Error[400]('unsupported registry call') );
}
if (err && err.status != 409) return next(err)
if (err && err.status != 409) return next(err);
// at this point document is either created or existed before
var t1 = Object.keys(metadata._attachments)[0]
let t1 = Object.keys(metadata._attachments)[0];
create_tarball(Path.basename(t1), metadata._attachments[t1], function(err) {
if (err) return next(err)
if (err) return next(err);
var t2 = Object.keys(metadata.versions)[0]
metadata.versions[t2].readme = metadata.readme != null ? String(metadata.readme) : ''
let t2 = Object.keys(metadata.versions)[0];
metadata.versions[t2].readme = metadata.readme != null ? String(metadata.readme) : '';
create_version(t2, metadata.versions[t2], function(err) {
if (err) return next(err)
if (err) return next(err);
add_tags(metadata['dist-tags'], function(err) {
if (err) return next(err)
notify(metadata, config)
res.status(201)
return next({ ok: ok_message })
})
})
})
if (err) return next(err);
notify(metadata, config);
res.status(201);
return next({ok: ok_message});
});
});
});
}
function create_tarball(filename, data, cb) {
var stream = storage.add_tarball(name, filename)
let stream = storage.add_tarball(name, filename);
stream.on('error', function(err) {
cb(err)
})
cb(err);
});
stream.on('success', function() {
cb()
})
cb();
});
// this is dumb and memory-consuming, but what choices do we have?
stream.end(new Buffer(data.data, 'base64'))
stream.done()
stream.end(new Buffer(data.data, 'base64'));
stream.done();
}
function create_version(version, data, cb) {
storage.add_version(name, version, data, null, cb)
storage.add_version(name, version, data, null, cb);
}
function add_tags(tags, cb) {
storage.merge_tags(name, tags, cb)
storage.merge_tags(name, tags, cb);
}
})
});
// unpublishing an entire package
app.delete('/:package/-rev/*', can('publish'), function(req, res, next) {
storage.remove_package(req.params.package, function(err) {
if (err) return next(err)
res.status(201)
return next({ ok: 'package removed' })
})
})
if (err) return next(err);
res.status(201);
return next({ok: 'package removed'});
});
});
// removing a tarball
app.delete('/:package/-/:filename/-rev/:revision', can('publish'), function(req, res, next) {
storage.remove_tarball(req.params.package, req.params.filename, req.params.revision, function(err) {
if (err) return next(err)
res.status(201)
return next({ ok: 'tarball removed' })
})
})
if (err) return next(err);
res.status(201);
return next({ok: 'tarball removed'});
});
});
// uploading package tarball
app.put('/:package/-/:filename/*', can('publish'), media('application/octet-stream'), function(req, res, next) {
var name = req.params.package
let name = req.params.package;
var stream = storage.add_tarball(name, req.params.filename)
req.pipe(stream)
let stream = storage.add_tarball(name, req.params.filename);
req.pipe(stream);
// checking if end event came before closing
var complete = false
let complete = false;
req.on('end', function() {
complete = true
stream.done()
})
complete = true;
stream.done();
});
req.on('close', function() {
if (!complete) {
stream.abort()
stream.abort();
}
})
});
stream.on('error', function(err) {
return res.report_error(err)
})
return res.report_error(err);
});
stream.on('success', function() {
res.status(201)
res.status(201);
return next({
ok: 'tarball uploaded successfully'
})
})
})
ok: 'tarball uploaded successfully',
});
});
});
// adding a version
app.put('/:package/:version/-tag/:tag', can('publish'), media('application/json'), expect_json, function(req, res, next) {
var name = req.params.package
var version = req.params.version
var tag = req.params.tag
let name = req.params.package;
let version = req.params.version;
let tag = req.params.tag;
storage.add_version(name, version, req.body, tag, function(err) {
if (err) return next(err)
res.status(201)
return next({ ok: 'package published' })
})
})
if (err) return next(err);
res.status(201);
return next({ok: 'package published'});
});
});
return app
}
return app;
};

View File

@ -1,167 +1,167 @@
var async = require('async')
var bodyParser = require('body-parser')
var Cookies = require('cookies')
var express = require('express')
var fs = require('fs')
var Handlebars = require('handlebars')
var renderReadme = require('render-readme')
var Search = require('./search')
var Middleware = require('./middleware')
var match = Middleware.match
var validate_name = Middleware.validate_name
var validate_pkg = Middleware.validate_package
'use strict';
let async = require('async');
let bodyParser = require('body-parser');
let Cookies = require('cookies');
let express = require('express');
let fs = require('fs');
let Handlebars = require('handlebars');
let renderReadme = require('render-readme');
let Search = require('./search');
let Middleware = require('./middleware');
let match = Middleware.match;
let validate_name = Middleware.validate_name;
let validate_pkg = Middleware.validate_package;
module.exports = function(config, auth, storage) {
var app = express.Router()
var can = Middleware.allow(auth)
let app = express.Router();
let can = Middleware.allow(auth);
// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble
app.param('package', validate_pkg)
app.param('filename', validate_name)
app.param('version', validate_name)
app.param('anything', match(/.*/))
app.param('package', validate_pkg);
app.param('filename', validate_name);
app.param('version', validate_name);
app.param('anything', match(/.*/));
app.use(Cookies.express())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(auth.cookie_middleware())
app.use(Cookies.express());
app.use(bodyParser.urlencoded({extended: false}));
app.use(auth.cookie_middleware());
app.use(function(req, res, next) {
// disable loading in frames (clickjacking, etc.)
res.header('X-Frame-Options', 'deny')
next()
})
res.header('X-Frame-Options', 'deny');
next();
});
Search.configureStorage(storage)
Search.configureStorage(storage);
Handlebars.registerPartial('entry', fs.readFileSync(require.resolve('./GUI/entry.hbs'), 'utf8'))
if(config.web && config.web.template) {
var template = Handlebars.compile(fs.readFileSync(config.web.template, 'utf8'));
}
else {
var template = Handlebars.compile(fs.readFileSync(require.resolve('./GUI/index.hbs'), 'utf8'))
Handlebars.registerPartial('entry', fs.readFileSync(require.resolve('./GUI/entry.hbs'), 'utf8'));
let template;
if (config.web && config.web.template) {
template = Handlebars.compile(fs.readFileSync(config.web.template, 'utf8'));
} else {
template = Handlebars.compile(fs.readFileSync(require.resolve('./GUI/index.hbs'), 'utf8'));
}
app.get('/', function(req, res, next) {
var base = config.url_prefix
let base = config.url_prefix
? config.url_prefix.replace(/\/$/, '')
: req.protocol + '://' + req.get('host')
res.setHeader('Content-Type', 'text/html')
: req.protocol + '://' + req.get('host');
res.setHeader('Content-Type', 'text/html');
storage.get_local(function(err, packages) {
if (err) throw err // that function shouldn't produce any
async.filterSeries(packages, function(package, cb) {
auth.allow_access(package.name, req.remote_user, function(err, allowed) {
setImmediate(function () {
if (err) throw err; // that function shouldn't produce any
async.filterSeries(packages, function(pkg, cb) {
auth.allow_access(pkg.name, req.remote_user, function(err, allowed) {
setImmediate(function() {
if (err) {
cb(null, false);
} else {
cb(err, allowed)
cb(err, allowed);
}
})
})
});
});
}, function(err, packages) {
if (err) throw err
if (err) throw err;
packages.sort(function(p1, p2) {
if (p1.name < p2.name) {
return -1;
}
else {
} else {
return 1;
}
});
next(template({
name: config.web && config.web.title ? config.web.title : 'Verdaccio',
tagline: config.web && config.web.tagline ? config.web.tagline : '',
packages: packages,
baseUrl: base,
username: req.remote_user.name,
}))
})
})
})
name: config.web && config.web.title ? config.web.title : 'Verdaccio',
tagline: config.web && config.web.tagline ? config.web.tagline : '',
packages: packages,
baseUrl: base,
username: req.remote_user.name,
}));
});
});
});
// Static
app.get('/-/static/:filename', function(req, res, next) {
var file = __dirname + '/static/' + req.params.filename
let file = __dirname + '/static/' + req.params.filename;
res.sendFile(file, function(err) {
if (!err) return
if (!err) return;
if (err.status === 404) {
next()
next();
} else {
next(err)
next(err);
}
})
})
});
});
app.get('/-/logo', function(req, res, next) {
res.sendFile( config.web && config.web.logo
? config.web.logo
: __dirname + '/static/logo-sm.png' )
})
: __dirname + '/static/logo-sm.png' );
});
app.post('/-/login', function(req, res, next) {
auth.authenticate(req.body.user, req.body.pass, function(err, user) {
if (!err) {
req.remote_user = user
//res.cookies.set('token', auth.issue_token(req.remote_user))
req.remote_user = user;
// res.cookies.set('token', auth.issue_token(req.remote_user))
var str = req.body.user + ':' + req.body.pass
res.cookies.set('token', auth.aes_encrypt(str).toString('base64'))
let str = req.body.user + ':' + req.body.pass;
res.cookies.set('token', auth.aes_encrypt(str).toString('base64'));
}
var base = config.url_prefix
let base = config.url_prefix
? config.url_prefix.replace(/\/$/, '')
: req.protocol + '://' + req.get('host')
res.redirect(base)
})
})
: req.protocol + '://' + req.get('host');
res.redirect(base);
});
});
app.post('/-/logout', function(req, res, next) {
var base = config.url_prefix
let base = config.url_prefix
? config.url_prefix.replace(/\/$/, '')
: req.protocol + '://' + req.get('host')
res.cookies.set('token', '')
res.redirect(base)
})
: req.protocol + '://' + req.get('host');
res.cookies.set('token', '');
res.redirect(base);
});
// Search
app.get('/-/search/:anything', function(req, res, next) {
var results = Search.query(req.params.anything)
var packages = []
const results = Search.query(req.params.anything);
const packages = [];
var getData = function(i) {
const getData = function(i) {
storage.get_package(results[i].ref, function(err, entry) {
if (!err && entry) {
auth.allow_access(entry.name, req.remote_user, function(err, allowed) { // TODO: This may cause performance issue?
if (err || !allowed) return
if (err || !allowed) return;
packages.push(entry.versions[entry['dist-tags'].latest])
})
packages.push(entry.versions[entry['dist-tags'].latest]);
});
}
if (i >= results.length - 1) {
next(packages)
next(packages);
} else {
getData(i + 1)
getData(i + 1);
}
})
}
});
};
if (results.length) {
getData(0)
getData(0);
} else {
next([])
next([]);
}
})
});
app.get('/-/readme(/@:scope?)?/:package/:version?', can('access'), function(req, res, next) {
var packageName = req.params.package;
if (req.params.scope) packageName = "@"+ req.params.scope + "/" + packageName;
let packageName = req.params.package;
if (req.params.scope) packageName = '@'+ req.params.scope + '/' + packageName;
storage.get_package(packageName, {req: req}, function(err, info) {
if (err) return next(err)
next( renderReadme(info.readme || 'ERROR: No README data found!') )
})
})
return app
}
if (err) return next(err);
next( renderReadme(info.readme || 'ERROR: No README data found!') );
});
});
return app;
};

View File

@ -1,105 +1,107 @@
var express = require('express')
var Error = require('http-errors')
var compression = require('compression')
var Auth = require('./auth')
var Logger = require('./logger')
var Config = require('./config')
var Middleware = require('./middleware')
var Cats = require('./status-cats')
var Storage = require('./storage')
'use strict';
let express = require('express');
let Error = require('http-errors');
let compression = require('compression');
let Auth = require('./auth');
let Logger = require('./logger');
let Config = require('./config');
let Middleware = require('./middleware');
let Cats = require('./status-cats');
let Storage = require('./storage');
module.exports = function(config_hash) {
Logger.setup(config_hash.logs)
Logger.setup(config_hash.logs);
var config = Config(config_hash);
var storage = new Storage(config);
var auth = Auth(config);
var app = express();
let config = Config(config_hash);
let storage = new Storage(config);
let auth = Auth(config);
let app = express();
// run in production mode by default, just in case
// it shouldn't make any difference anyway
app.set('env', process.env.NODE_ENV || 'production')
app.set('env', process.env.NODE_ENV || 'production');
function error_reporting_middleware(req, res, next) {
res.report_error = res.report_error || function(err) {
if (err.status && err.status >= 400 && err.status < 600) {
if (!res.headersSent) {
res.status(err.status)
next({ error: err.message || 'unknown error' })
res.status(err.status);
next({error: err.message || 'unknown error'});
}
} else {
Logger.logger.error( { err: err }
, 'unexpected error: @{!err.message}\n@{err.stack}')
Logger.logger.error( {err: err}
, 'unexpected error: @{!err.message}\n@{err.stack}');
if (!res.status || !res.send) {
Logger.logger.error('this is an error in express.js, please report this')
res.destroy()
Logger.logger.error('this is an error in express.js, please report this');
res.destroy();
} else if (!res.headersSent) {
res.status(500)
next({ error: 'internal server error' })
res.status(500);
next({error: 'internal server error'});
} else {
// socket should be already closed
}
}
}
next()
};
next();
}
app.use(Middleware.log)
app.use(error_reporting_middleware)
app.use(Middleware.log);
app.use(error_reporting_middleware);
app.use(function(req, res, next) {
res.setHeader('X-Powered-By', config.user_agent)
next()
})
app.use(Cats.middleware)
app.use(compression())
res.setHeader('X-Powered-By', config.user_agent);
next();
});
app.use(Cats.middleware);
app.use(compression());
app.get('/favicon.ico', function(req, res, next) {
req.url = '/-/static/favicon.png'
next()
})
req.url = '/-/static/favicon.png';
next();
});
// hook for tests only
if (config._debug) {
app.get('/-/_debug', function(req, res, next) {
var do_gc = typeof(global.gc) !== 'undefined'
if (do_gc) global.gc()
let do_gc = typeof(global.gc) !== 'undefined';
if (do_gc) global.gc();
next({
pid : process.pid,
main : process.mainModule.filename,
conf : config.self_path,
mem : process.memoryUsage(),
gc : do_gc,
})
})
pid: process.pid,
main: process.mainModule.filename,
conf: config.self_path,
mem: process.memoryUsage(),
gc: do_gc,
});
});
}
app.use(require('./index-api')(config, auth, storage))
app.use(require('./index-api')(config, auth, storage));
if (config.web && config.web.enable === false) {
app.get('/', function(req, res, next) {
next( Error[404]('web interface is disabled in the config file') )
})
next( Error[404]('web interface is disabled in the config file') );
});
} else {
app.use(require('./index-web')(config, auth, storage))
app.use(require('./index-web')(config, auth, storage));
}
app.get('/*', function(req, res, next) {
next( Error[404]('file not found') )
})
next( Error[404]('file not found') );
});
app.use(function(err, req, res, next) {
if (Object.prototype.toString.call(err) !== '[object Error]') return next(err)
if (err.code === 'ECONNABORT' && res.statusCode === 304) return next()
if (Object.prototype.toString.call(err) !== '[object Error]') return next(err);
if (err.code === 'ECONNABORT' && res.statusCode === 304) return next();
if (typeof(res.report_error) !== 'function') {
// in case of very early error this middleware may not be loaded before error is generated
// fixing that
error_reporting_middleware(req, res, function(){})
error_reporting_middleware(req, res, function() {});
}
res.report_error(err)
})
res.report_error(err);
});
app.use(Middleware.final)
app.use(Middleware.final);
return app
}
return app;
};

View File

@ -1,44 +1,44 @@
"use strict";
'use strict';
const fs = require('fs');
const fs = require('fs');
const Path = require('path');
class LocalData {
constructor(path) {
this.path = path
this.path = path;
try {
this.data = JSON.parse(fs.readFileSync(this.path, 'utf8'))
this.data = JSON.parse(fs.readFileSync(this.path, 'utf8'));
} catch(_) {
this.data = { list: [] }
this.data = {list: []};
}
}
add(name) {
if (this.data.list.indexOf(name) === -1) {
this.data.list.push(name)
this.sync()
this.data.list.push(name);
this.sync();
}
}
remove(name) {
const i = this.data.list.indexOf(name)
const i = this.data.list.indexOf(name);
if (i !== -1) {
this.data.list.splice(i, 1)
this.data.list.splice(i, 1);
}
this.sync()
this.sync();
}
get() {
return this.data.list
return this.data.list;
}
sync() {
// Uses sync to prevent ugly race condition
try {
require('mkdirp').sync(Path.dirname(this.path))
require('mkdirp').sync(Path.dirname(this.path));
} catch(err) {}
fs.writeFileSync(this.path, JSON.stringify(this.data))
fs.writeFileSync(this.path, JSON.stringify(this.data));
}
}

View File

@ -1,215 +1,215 @@
"use strict";
'use strict';
const fs = require('fs')
const Error = require('http-errors')
const mkdirp = require('mkdirp')
const Path = require('path')
const MyStreams = require('./streams')
const fs = require('fs');
const Error = require('http-errors');
const mkdirp = require('mkdirp');
const Path = require('path');
const MyStreams = require('./streams');
function FSError(code) {
var err = Error(code)
err.code = code
return err
let err = Error(code);
err.code = code;
return err;
}
var locker = require('./file-locking')
let locker = require('./file-locking');
function tempFile(str) {
return str + '.tmp' + String(Math.random()).substr(2)
return str + '.tmp' + String(Math.random()).substr(2);
}
function renameTmp(src, dst, _cb) {
function cb(err) {
if (err) fs.unlink(src, function() {})
_cb(err)
if (err) fs.unlink(src, function() {});
_cb(err);
}
if (process.platform !== 'win32') {
return fs.rename(src, dst, cb)
return fs.rename(src, dst, cb);
}
// windows can't remove opened file,
// but it seem to be able to rename it
var tmp = tempFile(dst)
let tmp = tempFile(dst);
fs.rename(dst, tmp, function(err) {
fs.rename(src, dst, cb)
if (!err) fs.unlink(tmp, function () {})
})
fs.rename(src, dst, cb);
if (!err) fs.unlink(tmp, () => {});
});
}
function write(dest, data, cb) {
var safe_write = function(cb) {
var tmpname = tempFile(dest)
let safe_write = function(cb) {
let tmpname = tempFile(dest);
fs.writeFile(tmpname, data, function(err) {
if (err) return cb(err)
renameTmp(tmpname, dest, cb)
})
}
if (err) return cb(err);
renameTmp(tmpname, dest, cb);
});
};
safe_write(function(err) {
if (err && err.code === 'ENOENT') {
mkdirp(Path.dirname(dest), function(err) {
if (err) return cb(err)
safe_write(cb)
})
if (err) return cb(err);
safe_write(cb);
});
} else {
cb(err)
cb(err);
}
})
});
}
function write_stream(name) {
var stream = MyStreams.UploadTarballStream()
let stream = MyStreams.UploadTarballStream();
var _ended = 0
let _ended = 0;
stream.on('end', function() {
_ended = 1
})
_ended = 1;
});
fs.exists(name, function(exists) {
if (exists) return stream.emit('error', FSError('EEXISTS'))
if (exists) return stream.emit('error', FSError('EEXISTS'));
var tmpname = name + '.tmp-'+String(Math.random()).replace(/^0\./, '')
var file = fs.createWriteStream(tmpname)
var opened = false
stream.pipe(file)
let tmpname = name + '.tmp-'+String(Math.random()).replace(/^0\./, '');
let file = fs.createWriteStream(tmpname);
let opened = false;
stream.pipe(file);
stream.done = function() {
function onend() {
file.on('close', function() {
renameTmp(tmpname, name, function(err) {
if (err) {
stream.emit('error', err)
stream.emit('error', err);
} else {
stream.emit('success')
stream.emit('success');
}
})
})
file.destroySoon()
});
});
file.destroySoon();
}
if (_ended) {
onend()
onend();
} else {
stream.on('end', onend)
stream.on('end', onend);
}
}
};
stream.abort = function() {
if (opened) {
opened = false
opened = false;
file.on('close', function() {
fs.unlink(tmpname, function(){})
})
fs.unlink(tmpname, function() {});
});
}
file.destroySoon()
}
file.destroySoon();
};
file.on('open', function() {
opened = true
opened = true;
// re-emitting open because it's handled in storage.js
stream.emit('open')
})
stream.emit('open');
});
file.on('error', function(err) {
stream.emit('error', err)
})
})
return stream
stream.emit('error', err);
});
});
return stream;
}
function read_stream(name, stream, callback) {
var rstream = fs.createReadStream(name)
let rstream = fs.createReadStream(name);
rstream.on('error', function(err) {
stream.emit('error', err)
})
stream.emit('error', err);
});
rstream.on('open', function(fd) {
fs.fstat(fd, function(err, stats) {
if (err) return stream.emit('error', err)
stream.emit('content-length', stats.size)
stream.emit('open')
rstream.pipe(stream)
})
})
if (err) return stream.emit('error', err);
stream.emit('content-length', stats.size);
stream.emit('open');
rstream.pipe(stream);
});
});
stream = MyStreams.ReadTarballStream()
stream = MyStreams.ReadTarballStream();
stream.abort = function() {
rstream.close()
}
return stream
rstream.close();
};
return stream;
}
function create(name, contents, callback) {
fs.exists(name, function(exists) {
if (exists) return callback( FSError('EEXISTS') )
write(name, contents, callback)
})
if (exists) return callback( FSError('EEXISTS') );
write(name, contents, callback);
});
}
function update(name, contents, callback) {
fs.exists(name, function(exists) {
if (!exists) return callback( FSError('ENOENT') )
write(name, contents, callback)
})
if (!exists) return callback( FSError('ENOENT') );
write(name, contents, callback);
});
}
function read(name, callback) {
fs.readFile(name, callback)
fs.readFile(name, callback);
}
module.exports.read = read
module.exports.read = read;
module.exports.read_json = function(name, cb) {
read(name, function(err, res) {
if (err) return cb(err)
if (err) return cb(err);
var args = []
let args = [];
try {
args = [ null, JSON.parse(res.toString('utf8')) ]
args = [null, JSON.parse(res.toString('utf8'))];
} catch(err) {
args = [ err ]
args = [err];
}
cb.apply(null, args)
})
}
cb.apply(null, args);
});
};
module.exports.lock_and_read = function(name, cb) {
locker.readFile(name, {lock: true}, function(err, res) {
if (err) return cb(err)
return cb(null, res)
})
}
if (err) return cb(err);
return cb(null, res);
});
};
module.exports.lock_and_read_json = function(name, cb) {
locker.readFile(name, {lock: true, parse: true}, function(err, res) {
if (err) return cb(err)
if (err) return cb(err);
return cb(null, res);
})
}
});
};
module.exports.unlock_file = function (name, cb) {
locker.unlockFile(name, cb)
}
module.exports.unlock_file = function(name, cb) {
locker.unlockFile(name, cb);
};
module.exports.create = create
module.exports.create = create;
module.exports.create_json = function(name, value, cb) {
create(name, JSON.stringify(value, null, '\t'), cb)
}
create(name, JSON.stringify(value, null, '\t'), cb);
};
module.exports.update = update
module.exports.update = update;
module.exports.update_json = function(name, value, cb) {
update(name, JSON.stringify(value, null, '\t'), cb)
}
update(name, JSON.stringify(value, null, '\t'), cb);
};
module.exports.write = write
module.exports.write = write;
module.exports.write_json = function(name, value, cb) {
write(name, JSON.stringify(value, null, '\t'), cb)
}
write(name, JSON.stringify(value, null, '\t'), cb);
};
module.exports.write_stream = write_stream
module.exports.write_stream = write_stream;
module.exports.read_stream = read_stream
module.exports.read_stream = read_stream;
module.exports.unlink = fs.unlink
module.exports.unlink = fs.unlink;
module.exports.rmdir = fs.rmdir
module.exports.rmdir = fs.rmdir;

File diff suppressed because it is too large Load Diff

View File

@ -1,157 +1,178 @@
var Logger = require('bunyan')
var Error = require('http-errors')
var Stream = require('stream')
var Utils = require('./utils')
'use strict';
const Logger = require('bunyan');
const Error = require('http-errors');
const Stream = require('stream');
const chalk = require('chalk');
const Utils = require('./utils');
const pkgJSON = require('../package.json');
/**
*
* @param {*} x
*/
function getlvl(x) {
switch(true) {
case x < 15 : return 'trace'
case x < 25 : return 'debug'
case x < 35 : return 'info'
case x == 35 : return 'http'
case x < 45 : return 'warn'
case x < 55 : return 'error'
default : return 'fatal'
}
switch(true) {
case x < 15 : return 'trace';
case x < 25 : return 'debug';
case x < 35 : return 'info';
case x == 35 : return 'http';
case x < 45 : return 'warn';
case x < 55 : return 'error';
default : return 'fatal';
}
}
module.exports.setup = function(logs) {
var streams = []
if (logs == null) logs = [{ type: 'stdout', format: 'pretty', level: 'http' }]
let streams = [];
if (logs == null) logs = [{type: 'stdout', format: 'pretty', level: 'http'}];
logs.forEach(function(target) {
var stream = new Stream()
stream.writable = true
logs.forEach(function(target) {
const stream = new Stream();
stream.writable = true;
if (target.type === 'stdout' || target.type === 'stderr') {
// destination stream
var dest = target.type === 'stdout' ? process.stdout : process.stderr
if (target.type === 'stdout' || target.type === 'stderr') {
// destination stream
const dest = target.type === 'stdout' ? process.stdout : process.stderr;
if (target.format === 'pretty') {
// making fake stream for prettypritting
stream.write = function(obj) {
dest.write(print(obj.level, obj.msg, obj, dest.isTTY) + '\n')
}
} else if (target.format === 'pretty-timestamped') {
// making fake stream for prettypritting
stream.write = function(obj) {
dest.write(obj.time.toISOString() + print(obj.level, obj.msg, obj, dest.isTTY) + '\n')
}
} else {
stream.write = function(obj) {
dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n')
}
}
} else if (target.type === 'file') {
var dest = require('fs').createWriteStream(target.path, {flags: 'a', encoding: 'utf8'})
dest.on('error', function (err) {
Logger.emit('error', err)
})
stream.write = function(obj) {
if (target.format === 'pretty') {
dest.write(print(obj.level, obj.msg, obj, false) + '\n')
} else {
dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n')
}
}
} else {
throw Error('wrong target type for a log')
}
if (target.format === 'pretty') {
// making fake stream for prettypritting
stream.write = function(obj) {
dest.write(print(obj.level, obj.msg, obj, dest.isTTY) + '\n');
};
} else if (target.format === 'pretty-timestamped') {
// making fake stream for prettypritting
stream.write = function(obj) {
dest.write(obj.time.toISOString() + print(obj.level, obj.msg, obj, dest.isTTY) + '\n');
};
} else {
stream.write = function(obj) {
dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n');
};
}
} else if (target.type === 'file') {
const dest = require('fs').createWriteStream(target.path, {flags: 'a', encoding: 'utf8'});
dest.on('error', function(err) {
Logger.emit('error', err);
});
stream.write = function(obj) {
if (target.format === 'pretty') {
dest.write(print(obj.level, obj.msg, obj, false) + '\n');
} else {
dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n');
}
};
} else {
throw Error('wrong target type for a log');
}
if (target.level === 'http') target.level = 35
streams.push({
type: 'raw',
level: target.level || 35,
stream: stream,
})
})
if (target.level === 'http') target.level = 35;
streams.push({
type: 'raw',
level: target.level || 35,
stream: stream,
});
});
var logger = new Logger({
name: 'verdaccio',
streams: streams,
serializers: {
err: Logger.stdSerializers.err,
req: Logger.stdSerializers.req,
res: Logger.stdSerializers.res,
},
})
let logger = new Logger({
name: pkgJSON.name,
streams: streams,
serializers: {
err: Logger.stdSerializers.err,
req: Logger.stdSerializers.req,
res: Logger.stdSerializers.res,
},
});
module.exports.logger = logger
}
module.exports.logger = logger;
};
// adopted from socket.io
// this part was converted to coffee-script and back again over the years,
// so it might look weird
// level to color
var levels = {
fatal : 31,
error : 31,
warn : 33,
http : 35,
info : 36,
debug : 90,
trace : 90,
}
var max = 0
for (var l in levels) {
max = Math.max(max, l.length)
let levels = {
fatal: chalk.red,
error: chalk.red,
warn: chalk.yellow,
http: chalk.magenta,
info: chalk.cyan,
debug: chalk.black,
trace: chalk.white,
};
let max = 0;
for (let l in levels) {
max = Math.max(max, l.length);
}
/**
*
* @param {*} str
*/
function pad(str) {
if (str.length < max) return str + ' '.repeat(max - str.length)
return str
if (str.length < max) return str + ' '.repeat(max - str.length);
return str;
}
var subsystems = [{
in : '\033[32m<--\033[39m',
out : '\033[33m-->\033[39m',
fs : '\033[90m-=-\033[39m',
default : '\033[34m---\033[39m',
}, {
in : '<--',
out : '-->',
fs : '-=-',
default : '---',
}]
/**
* Build a string
* @param {*} type
* @param {*} msg
* @param {*} obj
* @param {*} colors
*/
function print(type, msg, obj, colors) {
if (typeof type === 'number') type = getlvl(type)
var finalmsg = msg.replace(/@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g, function(_, name) {
var str = obj, is_error
if (name[0] === '!') {
name = name.substr(1)
is_error = true
}
if (typeof type === 'number') type = getlvl(type);
let finalmsg = msg.replace(/@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g, function(_, name) {
let str = obj;
let is_error;
if (name[0] === '!') {
name = name.substr(1);
is_error = true;
}
var _ref = name.split('.')
for (var _i = 0; _i < _ref.length; _i++) {
var id = _ref[_i]
if (Utils.is_object(str) || Array.isArray(str)) {
str = str[id]
} else {
str = undefined
}
}
let _ref = name.split('.');
for (let _i = 0; _i < _ref.length; _i++) {
let id = _ref[_i];
if (Utils.is_object(str) || Array.isArray(str)) {
str = str[id];
} else {
str = undefined;
}
}
if (typeof(str) === 'string') {
if (!colors || str.includes('\n')) {
return str
} else if (is_error) {
return '\033[31m' + str + '\033[39m'
} else {
return '\033[32m' + str + '\033[39m'
}
} else {
return require('util').inspect(str, null, null, colors)
}
})
if (typeof(str) === 'string') {
if (!colors || str.includes('\n')) {
return str;
} else if (is_error) {
return chalk.red(str);
} else {
return chalk.green(str);
}
} else {
return require('util').inspect(str, null, null, colors);
}
});
var sub = subsystems[colors ? 0 : 1][obj.sub] || subsystems[+!colors].default
if (colors) {
return ' \033[' + levels[type] + 'm' + (pad(type)) + '\033[39m ' + sub + ' ' + finalmsg
} else {
return ' ' + (pad(type)) + ' ' + sub + ' ' + finalmsg
}
const subsystems = [{
in: chalk.green('<--'),
out: chalk.yellow('-->'),
fs: chalk.black('-=-'),
default: chalk.blue('---'),
}, {
in: '<--',
out: '-->',
fs: '-=-',
default: '---',
}];
let sub = subsystems[colors ? 0 : 1][obj.sub] || subsystems[+!colors].default;
if (colors) {
// return ' \033[' + levels[type] + 'm' + (pad(type)) + '\033[39m ' + sub + ' ' + finalmsg
return ` ${levels[type]((pad(type)))}${chalk.white(`${sub} ${finalmsg}`)}`;
} else {
return ` ${(pad(type))}${sub} ${finalmsg}`;
}
}

View File

@ -1,79 +1,81 @@
var crypto = require('crypto')
var Error = require('http-errors')
var utils = require('./utils')
var Logger = require('./logger')
'use strict';
let crypto = require('crypto');
let Error = require('http-errors');
let utils = require('./utils');
let Logger = require('./logger');
module.exports.match = function match(regexp) {
return function(req, res, next, value, name) {
if (regexp.exec(value)) {
next()
next();
} else {
next('route')
next('route');
}
}
}
};
};
module.exports.validate_name = function validate_name(req, res, next, value, name) {
if (value.charAt(0) === '-') {
// special case in couchdb usually
next('route')
next('route');
} else if (utils.validate_name(value)) {
next()
next();
} else {
next( Error[403]('invalid ' + name) )
next( Error[403]('invalid ' + name) );
}
}
};
module.exports.validate_package = function validate_package(req, res, next, value, name) {
if (value.charAt(0) === '-') {
// special case in couchdb usually
next('route')
next('route');
} else if (utils.validate_package(value)) {
next()
next();
} else {
next( Error[403]('invalid ' + name) )
next( Error[403]('invalid ' + name) );
}
}
};
module.exports.media = function media(expect) {
return function(req, res, next) {
if (req.headers['content-type'] !== expect) {
next( Error[415]('wrong content-type, expect: ' + expect
+ ', got: '+req.headers['content-type']) )
+ ', got: '+req.headers['content-type']) );
} else {
next()
next();
}
}
}
};
};
module.exports.expect_json = function expect_json(req, res, next) {
if (!utils.is_object(req.body)) {
return next( Error[400]("can't parse incoming json") )
return next( Error[400]('can\'t parse incoming json') );
}
next()
}
next();
};
module.exports.anti_loop = function(config) {
return function(req, res, next) {
if (req.headers.via != null) {
var arr = req.headers.via.split(',')
let arr = req.headers.via.split(',');
for (var i=0; i<arr.length; i++) {
var m = arr[i].match(/\s*(\S+)\s+(\S+)/)
for (let i=0; i<arr.length; i++) {
let m = arr[i].match(/\s*(\S+)\s+(\S+)/);
if (m && m[2] === config.server_id) {
return next( Error[508]('loop detected') )
return next( Error[508]('loop detected') );
}
}
}
next()
}
}
next();
};
};
// express doesn't do etags with requests <= 1024b
// we use md5 here, it works well on 1k+ bytes, but sucks with fewer data
// could improve performance using crc32 after benchmarks
function md5sum(data) {
return crypto.createHash('md5').update(data).digest('hex')
return crypto.createHash('md5').update(data).digest('hex');
}
module.exports.allow = function(auth) {
@ -83,41 +85,41 @@ module.exports.allow = function(auth) {
auth['allow_'+action](req.params.package, req.remote_user, function(error, allowed) {
req.resume();
if (error) {
next(error)
next(error);
} else if (allowed) {
next()
next();
} else {
// last plugin (that's our built-in one) returns either
// cb(err) or cb(null, true), so this should never happen
throw Error('bug in the auth plugin system')
throw Error('bug in the auth plugin system');
}
})
}
}
}
});
};
};
};
module.exports.final = function(body, req, res, next) {
if (res.statusCode === 401 && !res.getHeader('WWW-Authenticate')) {
// they say it's required for 401, so...
res.header('WWW-Authenticate', 'Basic, Bearer')
res.header('WWW-Authenticate', 'Basic, Bearer');
}
try {
if (typeof(body) === 'string' || typeof(body) === 'object') {
if (!res.getHeader('Content-type')) {
res.header('Content-type', 'application/json')
res.header('Content-type', 'application/json');
}
if (typeof(body) === 'object' && body != null) {
if (typeof(body.error) === 'string') {
res._verdaccio_error = body.error
res._verdaccio_error = body.error;
}
body = JSON.stringify(body, undefined, ' ') + '\n'
body = JSON.stringify(body, undefined, ' ') + '\n';
}
// don't send etags with errors
if (!res.statusCode || (res.statusCode >= 200 && res.statusCode < 300)) {
res.header('ETag', '"' + md5sum(body) + '"')
res.header('ETag', '"' + md5sum(body) + '"');
}
} else {
// send(null), send(204), etc.
@ -127,77 +129,77 @@ module.exports.final = function(body, req, res, next) {
// as an error handler, we can't report error properly,
// and should just close socket
if (err.message.match(/set headers after they are sent/)) {
if (res.socket != null) res.socket.destroy()
return
if (res.socket != null) res.socket.destroy();
return;
} else {
throw err
throw err;
}
}
res.send(body)
}
res.send(body);
};
module.exports.log = function(req, res, next) {
// logger
req.log = Logger.logger.child({ sub: 'in' })
req.log = Logger.logger.child({sub: 'in'});
var _auth = req.headers.authorization
if (_auth != null) req.headers.authorization = '<Classified>'
var _cookie = req.headers.cookie
if (_cookie != null) req.headers.cookie = '<Classified>'
let _auth = req.headers.authorization;
if (_auth != null) req.headers.authorization = '<Classified>';
let _cookie = req.headers.cookie;
if (_cookie != null) req.headers.cookie = '<Classified>';
req.url = req.originalUrl
req.log.info( { req: req, ip: req.ip }
, '@{ip} requested \'@{req.method} @{req.url}\'' )
req.originalUrl = req.url
req.url = req.originalUrl;
req.log.info( {req: req, ip: req.ip}
, '@{ip} requested \'@{req.method} @{req.url}\'' );
req.originalUrl = req.url;
if (_auth != null) req.headers.authorization = _auth
if (_cookie != null) req.headers.cookie = _cookie
if (_auth != null) req.headers.authorization = _auth;
if (_cookie != null) req.headers.cookie = _cookie;
var bytesin = 0
let bytesin = 0;
req.on('data', function(chunk) {
bytesin += chunk.length
})
bytesin += chunk.length;
});
var bytesout = 0
var _write = res.write
let bytesout = 0;
let _write = res.write;
res.write = function(buf) {
bytesout += buf.length
_write.apply(res, arguments)
}
bytesout += buf.length;
_write.apply(res, arguments);
};
function log() {
var message = "@{status}, user: @{user}, req: '@{request.method} @{request.url}'"
let message = '@{status}, user: @{user}, req: \'@{request.method} @{request.url}\'';
if (res._verdaccio_error) {
message += ', error: @{!error}'
message += ', error: @{!error}';
} else {
message += ', bytes: @{bytes.in}/@{bytes.out}'
message += ', bytes: @{bytes.in}/@{bytes.out}';
}
req.url = req.originalUrl
req.url = req.originalUrl;
req.log.warn({
request : { method: req.method, url: req.url },
level : 35, // http
user : req.remote_user && req.remote_user.name,
status : res.statusCode,
error : res._verdaccio_error,
bytes : {
in : bytesin,
out : bytesout,
}
}, message)
req.originalUrl = req.url
request: {method: req.method, url: req.url},
level: 35, // http
user: req.remote_user && req.remote_user.name,
status: res.statusCode,
error: res._verdaccio_error,
bytes: {
in: bytesin,
out: bytesout,
},
}, message);
req.originalUrl = req.url;
}
req.on('close', function() {
log(true)
})
log(true);
});
var _end = res.end
let _end = res.end;
res.end = function(buf) {
if (buf) bytesout += buf.length
_end.apply(res, arguments)
log()
}
next()
}
if (buf) bytesout += buf.length;
_end.apply(res, arguments);
log();
};
next();
};

View File

@ -1,24 +1,24 @@
var Handlebars = require('handlebars')
var request = require('request')
var Logger = require('./logger')
'use strict';
let Handlebars = require('handlebars');
let request = require('request');
let Logger = require('./logger');
module.exports.notify = function(metadata, config) {
if (config.notify && config.notify.content) {
let template = Handlebars.compile(config.notify.content);
let content = template( metadata );
var template = Handlebars.compile(config.notify.content)
var content = template( metadata )
var options = {
body: content
}
let options = {
body: content,
};
// provides fallback support, it's accept an Object {} and Array of {}
if ( config.notify.headers && Array.isArray(config.notify.headers) ) {
var header = {};
let header = {};
config.notify.headers.map(function(item) {
if (Object.is(item, item)) {
for (var key in item) {
for (let key in item) {
header[key] = item[key];
}
}
@ -31,19 +31,18 @@ module.exports.notify = function(metadata, config) {
options.method = config.notify.method;
if (config.notify.endpoint) {
options.url = config.notify.endpoint
options.url = config.notify.endpoint;
}
request(options, function(err, response, body) {
if (err) {
Logger.logger.error( { err: err }, ' notify error: @{err.message}' );
Logger.logger.error( {err: err}, ' notify error: @{err.message}' );
} else {
Logger.logger.info({ content: content}, 'A notification has been shipped: @{content}')
Logger.logger.info({content: content}, 'A notification has been shipped: @{content}');
if (body) {
Logger.logger.debug( { body: body }, ' body: @{body}' );
Logger.logger.debug( {body: body}, ' body: @{body}' );
}
}
});
}
}
};

View File

@ -1,57 +1,59 @@
var Path = require('path')
'use strict';
let Path = require('path');
function try_load(path) {
try {
return require(path)
return require(path);
} catch(err) {
if (err.code === 'MODULE_NOT_FOUND') {
return null
return null;
}
throw err
throw err;
}
}
function load_plugins(config, plugin_configs, params, sanity_check) {
var plugins = Object.keys(plugin_configs || {}).map(function(p) {
var plugin
let plugins = Object.keys(plugin_configs || {}).map(function(p) {
let plugin;
// try local plugins first
plugin = try_load(Path.resolve(__dirname + '/plugins', p))
plugin = try_load(Path.resolve(__dirname + '/plugins', p));
// npm package
if (plugin === null && p.match(/^[^\.\/]/)) {
plugin = try_load(`verdaccio-${p}`)
plugin = try_load(`verdaccio-${p}`);
// compatibility for old sinopia plugins
if (!plugin) {
plugin = try_load(`sinopia-${p}`)
plugin = try_load(`sinopia-${p}`);
}
}
if (plugin === null) {
plugin = try_load(p)
plugin = try_load(p);
}
// relative to config path
if (plugin === null && p.match(/^\.\.?($|\/)/)) {
plugin = try_load(Path.resolve(Path.dirname(config.self_path), p))
plugin = try_load(Path.resolve(Path.dirname(config.self_path), p));
}
if (plugin === null) {
throw new Error('"' + p + '" plugin not found\ntry "npm install verdaccio-' + p + '"')
throw Error('"' + p + '" plugin not found\ntry "npm install verdaccio-' + p + '"');
}
if (typeof(plugin) !== 'function')
throw new Error('"' + p + '" doesn\'t look like a valid plugin')
throw Error('"' + p + '" doesn\'t look like a valid plugin');
plugin = plugin(plugin_configs[p], params)
plugin = plugin(plugin_configs[p], params);
if (plugin === null || !sanity_check(plugin))
throw new Error('"' + p + '" doesn\'t look like a valid plugin')
throw Error('"' + p + '" doesn\'t look like a valid plugin');
return plugin;
})
});
return plugins
return plugins;
}
exports.load_plugins = load_plugins;

View File

@ -1,3 +1,5 @@
'use strict';
/** Node.js Crypt(3) Library
Inspired by (and intended to be compatible with) sendanor/crypt3
@ -10,7 +12,7 @@
*/
var crypt = require('unix-crypt-td-js'),
let crypt = require('unix-crypt-td-js'),
crypto = require('crypto');
function createSalt(type) {
@ -33,7 +35,6 @@ function createSalt(type) {
default:
throw new TypeError('Unknown salt type at crypt3.createSalt: ' + type);
}
}
function crypt3(key, salt) {

View File

@ -1,49 +1,51 @@
var fs = require('fs')
var Path = require('path')
var utils = require('./utils')
'use strict';
module.exports = HTPasswd
let fs = require('fs');
let Path = require('path');
let utils = require('./utils');
module.exports = HTPasswd;
function HTPasswd(config, stuff) {
var self = Object.create(HTPasswd.prototype)
self._users = {}
let self = Object.create(HTPasswd.prototype);
self._users = {};
// config for this module
self._config = config
self._config = config;
// verdaccio logger
self._logger = stuff.logger
self._logger = stuff.logger;
// verdaccio main config object
self._verdaccio_config = stuff.config
self._verdaccio_config = stuff.config;
// all this "verdaccio_config" stuff is for b/w compatibility only
self._maxusers = self._config.max_users
if (!self._maxusers) self._maxusers = self._verdaccio_config.max_users
self._maxusers = self._config.max_users;
if (!self._maxusers) self._maxusers = self._verdaccio_config.max_users;
// set maxusers to Infinity if not specified
if (!self._maxusers) self._maxusers = Infinity
if (!self._maxusers) self._maxusers = Infinity;
self._last_time = null
var file = self._config.file
if (!file) file = self._verdaccio_config.users_file
if (!file) throw new Error('should specify "file" in config')
self._path = Path.resolve(Path.dirname(self._verdaccio_config.self_path), file)
return self
self._last_time = null;
let file = self._config.file;
if (!file) file = self._verdaccio_config.users_file;
if (!file) throw new Error('should specify "file" in config');
self._path = Path.resolve(Path.dirname(self._verdaccio_config.self_path), file);
return self;
}
HTPasswd.prototype.authenticate = function (user, password, cb) {
var self = this
self._reload(function (err) {
if (err) return cb(err.code === 'ENOENT' ? null : err)
if (!self._users[user]) return cb(null, false)
if (!utils.verify_password(user, password, self._users[user])) return cb(null, false)
HTPasswd.prototype.authenticate = function(user, password, cb) {
let self = this;
self._reload(function(err) {
if (err) return cb(err.code === 'ENOENT' ? null : err);
if (!self._users[user]) return cb(null, false);
if (!utils.verify_password(user, password, self._users[user])) return cb(null, false);
// authentication succeeded!
// return all usergroups this user has access to;
// (this particular package has no concept of usergroups, so just return user herself)
return cb(null, [user])
})
}
return cb(null, [user]);
});
};
// hopefully race-condition-free way to add users:
// 1. lock file for writing (other processes can still read)
@ -52,85 +54,82 @@ HTPasswd.prototype.authenticate = function (user, password, cb) {
// 4. move .htpasswd.tmp to .htpasswd
// 5. reload .htpasswd
// 6. unlock file
HTPasswd.prototype.adduser = function (user, password, real_cb) {
var self = this
HTPasswd.prototype.adduser = function(user, password, real_cb) {
let self = this;
function sanity_check() {
var err = null
let err = null;
if (self._users[user]) {
err = Error('this user already exists')
err = Error('this user already exists');
} else if (Object.keys(self._users).length >= self._maxusers) {
err = Error('maximum amount of users reached')
err = Error('maximum amount of users reached');
}
if (err) err.status = 403
return err
if (err) err.status = 403;
return err;
}
// preliminary checks, just to ensure that file won't be reloaded if it's not needed
var s_err = sanity_check()
if (s_err) return real_cb(s_err, false)
let s_err = sanity_check();
if (s_err) return real_cb(s_err, false);
utils.lock_and_read(self._path, function (err, res) {
var locked = false
utils.lock_and_read(self._path, function(err, res) {
let locked = false;
// callback that cleans up lock first
function cb(err) {
if (locked) {
utils.unlock_file(self._path, function () {
utils.unlock_file(self._path, function() {
// ignore any error from the unlock
real_cb(err, !err)
})
real_cb(err, !err);
});
} else {
real_cb(err, !err)
real_cb(err, !err);
}
}
if (!err) {
locked = true
locked = true;
}
// ignore ENOENT errors, we'll just create .htpasswd in that case
if (err && err.code !== 'ENOENT') return cb(err)
if (err && err.code !== 'ENOENT') return cb(err);
var body = (res || '').toString('utf8')
self._users = utils.parse_htpasswd(body)
let body = (res || '').toString('utf8');
self._users = utils.parse_htpasswd(body);
// real checks, to prevent race conditions
var s_err = sanity_check()
if (s_err) return cb(s_err)
let s_err = sanity_check();
if (s_err) return cb(s_err);
try {
body = utils.add_user_to_htpasswd(body, user, password)
body = utils.add_user_to_htpasswd(body, user, password);
} catch (err) {
return cb(err)
return cb(err);
}
fs.writeFile(self._path, body, function (err) {
if (err) return cb(err)
self._reload(function () {
cb(null, true)
})
})
})
}
HTPasswd.prototype._reload = function (_callback) {
var self = this
fs.stat(self._path, function (err, stats) {
if (err) return _callback(err)
if (self._last_time === stats.mtime) return _callback()
self._last_time = stats.mtime
fs.readFile(self._path, 'utf8', function (err, buffer) {
if (err) return _callback(err)
self._users = utils.parse_htpasswd(buffer)
_callback()
fs.writeFile(self._path, body, function(err) {
if (err) return cb(err);
self._reload(function() {
cb(null, true);
});
});
});
};
}
HTPasswd.prototype._reload = function(_callback) {
let self = this;
fs.stat(self._path, function(err, stats) {
if (err) return _callback(err);
if (self._last_time === stats.mtime) return _callback();
self._last_time = stats.mtime;
fs.readFile(self._path, 'utf8', function(err, buffer) {
if (err) return _callback(err);
self._users = utils.parse_htpasswd(buffer);
_callback();
});
});
};

View File

@ -1,68 +1,70 @@
var crypto = require('crypto')
var crypt3 = require('./crypt3')
var md5 = require('apache-md5')
var locker = require('../../file-locking')
'use strict';
let crypto = require('crypto');
let crypt3 = require('./crypt3');
let md5 = require('apache-md5');
let locker = require('../../file-locking');
// this function neither unlocks file nor closes it
// it'll have to be done manually later
function lock_and_read(name, cb) {
locker.readFile(name, {lock: true}, function (err, res) {
locker.readFile(name, {lock: true}, function(err, res) {
if (err) {
return cb(err)
return cb(err);
}
return cb(null, res)
})
return cb(null, res);
});
}
// close and unlock file
function unlock_file(name, cb) {
locker.unlockFile(name, cb)
locker.unlockFile(name, cb);
}
function parse_htpasswd(input) {
var result = {}
let result = {};
input.split('\n').forEach(function(line) {
var args = line.split(':', 3)
if (args.length > 1) result[args[0]] = args[1]
})
return result
let args = line.split(':', 3);
if (args.length > 1) result[args[0]] = args[1];
});
return result;
}
function verify_password(user, passwd, hash) {
if (hash.indexOf('{PLAIN}') === 0) {
return passwd === hash.substr(7)
return passwd === hash.substr(7);
} else if (hash.indexOf('{SHA}') === 0) {
return crypto.createHash('sha1').update(passwd, 'binary').digest('base64') === hash.substr(5)
return crypto.createHash('sha1').update(passwd, 'binary').digest('base64') === hash.substr(5);
} else {
return (
// for backwards compatibility, first check md5 then check crypt3
md5(passwd, hash) === hash ||
crypt3(passwd, hash) === hash
)
);
}
}
function add_user_to_htpasswd(body, user, passwd) {
if (user !== encodeURIComponent(user)) {
var err = Error('username should not contain non-uri-safe characters')
err.status = 409
throw err
let err = Error('username should not contain non-uri-safe characters');
err.status = 409;
throw err;
}
if (crypt3) {
passwd = crypt3(passwd)
passwd = crypt3(passwd);
} else {
passwd = '{SHA}' + crypto.createHash('sha1').update(passwd, 'binary').digest('base64')
passwd = '{SHA}' + crypto.createHash('sha1').update(passwd, 'binary').digest('base64');
}
var comment = 'autocreated ' + (new Date()).toJSON()
let comment = 'autocreated ' + (new Date()).toJSON();
var newline = user + ':' + passwd + ':' + comment + '\n'
if (body.length && body[body.length-1] !== '\n') newline = '\n' + newline
return body + newline
let newline = user + ':' + passwd + ':' + comment + '\n';
if (body.length && body[body.length-1] !== '\n') newline = '\n' + newline;
return body + newline;
}
module.exports.parse_htpasswd = parse_htpasswd
module.exports.verify_password = verify_password
module.exports.add_user_to_htpasswd = add_user_to_htpasswd
module.exports.lock_and_read = lock_and_read
module.exports.unlock_file = unlock_file
module.exports.parse_htpasswd = parse_htpasswd;
module.exports.verify_password = verify_password;
module.exports.add_user_to_htpasswd = add_user_to_htpasswd;
module.exports.lock_and_read = lock_and_read;
module.exports.unlock_file = unlock_file;

View File

@ -1,60 +1,51 @@
"use strict";
'use strict';
const lunr = require('lunr')
const lunr = require('lunr');
class Search {
constructor() {
this.index = lunr(function() {
this.field('name' , { boost: 10 })
this.field('description' , { boost: 4 })
this.field('author' , { boost: 6 })
this.field('readme')
})
this.field('name', {boost: 10});
this.field('description', {boost: 4});
this.field('author', {boost: 6});
this.field('readme');
});
}
query(q) {
return q === '*'
? this.storage.config.localList.get().map( function( pkg ) {
return { ref: pkg, score: 1 };
return {ref: pkg, score: 1};
}) : this.index.search(q);
}
add(pkg) {
this.index.add({
id: pkg.name,
name: pkg.name,
description: pkg.description,
author: pkg._npmUser ? pkg._npmUser.name : '???',
})
}
add(pkg) {
this.index.add({
id: pkg.name,
name: pkg.name,
description: pkg.description,
author: pkg._npmUser ? pkg._npmUser.name : '???',
})
id: pkg.name,
name: pkg.name,
description: pkg.description,
author: pkg._npmUser ? pkg._npmUser.name : '???',
});
}
remove(name) {
this.index.remove({ id: name })
this.index.remove({id: name});
}
reindex() {
var self = this
let self = this;
this.storage.get_local(function(err, packages) {
if (err) throw err // that function shouldn't produce any
var i = packages.length
if (err) throw err; // that function shouldn't produce any
let i = packages.length;
while (i--) {
self.add(packages[i])
self.add(packages[i]);
}
})
});
}
configureStorage(storage) {
this.storage = storage
this.reindex()
this.storage = storage;
this.reindex();
}
}

View File

@ -1,3 +1,4 @@
'use strict';
// see https://secure.flickr.com/photos/girliemac/sets/72157628409467125
@ -51,24 +52,24 @@ const images = {
508: 'aVdnYa', // '6509400445', // 508 - Loop Detected
509: 'aXXg1V', // '6540399865', // 509 - Bandwidth Limit Exceeded
599: 'aVdo7v', // '6509400929', // 599 - Network connect timeout error
}
};
module.exports.get_image = function(status) {
if (status in images) {
return 'http://flic.kr/p/' + images[status]
//return 'https://secure.flickr.com/photos/girliemac/'+images[status]+'/in/set-72157628409467125/lightbox/'
return 'http://flic.kr/p/' + images[status];
// return 'https://secure.flickr.com/photos/girliemac/'+images[status]+'/in/set-72157628409467125/lightbox/'
}
}
};
module.exports.middleware = function(req, res, next) {
var _writeHead = res.writeHead
let _writeHead = res.writeHead;
res.writeHead = function(status) {
if (status in images) {
res.setHeader('X-Status-Cat', module.exports.get_image(status))
res.setHeader('X-Status-Cat', module.exports.get_image(status));
}
_writeHead.apply(res, arguments)
}
_writeHead.apply(res, arguments);
};
next()
}
next();
};

View File

@ -1,14 +1,14 @@
"use strict";
'use strict';
const assert = require('assert')
const async = require('async')
const Error = require('http-errors')
const Stream = require('stream')
const Local = require('./local-storage')
const Logger = require('./logger')
const MyStreams = require('./streams')
const Proxy = require('./up-storage')
const Utils = require('./utils')
const assert = require('assert');
const async = require('async');
const Error = require('http-errors');
const Stream = require('stream');
const Local = require('./local-storage');
const Logger = require('./logger');
const MyStreams = require('./streams');
const Proxy = require('./up-storage');
const Utils = require('./utils');
//
// Implements Storage interface
@ -21,17 +21,17 @@ class Storage {
* @param {*} config
*/
constructor(config) {
this.config = config
this.config = config;
// we support a number of uplinks, but only one local storage
// Proxy and Local classes should have similar API interfaces
this.uplinks = {}
this.uplinks = {};
for (let p in config.uplinks) {
// instance for each up-link definition
this.uplinks[p] = new Proxy(config.uplinks[p], config)
this.uplinks[p].upname = p
this.uplinks[p] = new Proxy(config.uplinks[p], config);
this.uplinks[p].upname = p;
}
// an instance for local storage
this.local = new Local(config)
this.local = new Local(config);
this.logger = Logger.logger.child();
}
@ -45,7 +45,7 @@ class Storage {
* @param {*} callback
*/
add_package(name, metadata, callback) {
var self = this
let self = this;
// NOTE:
// - when we checking package for existance, we ask ALL uplinks
@ -53,52 +53,52 @@ class Storage {
// so all requests are necessary
check_package_local(function(err) {
if (err) return callback(err)
if (err) return callback(err);
check_package_remote(function(err) {
if (err) return callback(err)
if (err) return callback(err);
publish_package(function(err) {
if (err) return callback(err)
callback()
})
})
})
if (err) return callback(err);
callback();
});
});
});
function check_package_local(cb) {
self.local.get_package(name, {}, function(err, results) {
if (err && err.status !== 404) return cb(err)
if (err && err.status !== 404) return cb(err);
if (results) return cb( Error[409]('this package is already present') )
if (results) return cb( Error[409]('this package is already present') );
cb()
})
cb();
});
}
function check_package_remote(cb) {
self._sync_package_with_uplinks(name, null, {}, function(err, results, err_results) {
// something weird
if (err && err.status !== 404) return cb(err)
if (err && err.status !== 404) return cb(err);
// checking package
if (results) return cb( Error[409]('this package is already present') )
if (results) return cb( Error[409]('this package is already present') );
for (var i=0; i<err_results.length; i++) {
for (let i=0; i<err_results.length; i++) {
// checking error
// if uplink fails with a status other than 404, we report failure
if (err_results[i][0] != null) {
if (err_results[i][0].status !== 404) {
return cb( Error[503]('one of the uplinks is down, refuse to publish') )
return cb( Error[503]('one of the uplinks is down, refuse to publish') );
}
}
}
return cb()
})
return cb();
});
}
function publish_package(cb) {
self.local.add_package(name, metadata, callback)
self.local.add_package(name, metadata, callback);
}
}
@ -112,7 +112,7 @@ class Storage {
* @param {*} callback
*/
add_version(name, version, metadata, tag, callback) {
return this.local.add_version(name, version, metadata, tag, callback)
return this.local.add_version(name, version, metadata, tag, callback);
}
/**
@ -123,7 +123,7 @@ class Storage {
* @param {*} callback
*/
merge_tags(name, tag_hash, callback) {
return this.local.merge_tags(name, tag_hash, callback)
return this.local.merge_tags(name, tag_hash, callback);
}
/**
@ -134,7 +134,7 @@ class Storage {
* @param {*} callback
*/
replace_tags(name, tag_hash, callback) {
return this.local.replace_tags(name, tag_hash, callback)
return this.local.replace_tags(name, tag_hash, callback);
}
/**
@ -147,7 +147,7 @@ class Storage {
* @param {*} callback
*/
change_package(name, metadata, revision, callback) {
return this.local.change_package(name, metadata, revision, callback)
return this.local.change_package(name, metadata, revision, callback);
}
/**
@ -158,7 +158,7 @@ class Storage {
* @param {*} callback
*/
remove_package(name, callback) {
return this.local.remove_package(name, callback)
return this.local.remove_package(name, callback);
}
/**
@ -173,7 +173,7 @@ class Storage {
* @param {*} callback
*/
remove_tarball(name, filename, revision, callback) {
return this.local.remove_tarball(name, filename, revision, callback)
return this.local.remove_tarball(name, filename, revision, callback);
}
/**
@ -184,7 +184,7 @@ class Storage {
* @param {*} filename
*/
add_tarball(name, filename) {
return this.local.add_tarball(name, filename)
return this.local.add_tarball(name, filename);
}
/**
@ -197,101 +197,100 @@ class Storage {
* @param {*} filename
*/
get_tarball(name, filename) {
var stream = MyStreams.ReadTarballStream()
stream.abort = function() {}
let stream = MyStreams.ReadTarballStream();
stream.abort = function() {};
var self = this
let self = this;
// if someone requesting tarball, it means that we should already have some
// information about it, so fetching package info is unnecessary
// trying local first
var rstream = self.local.get_tarball(name, filename)
var is_open = false
let rstream = self.local.get_tarball(name, filename);
let is_open = false;
rstream.on('error', function(err) {
if (is_open || err.status !== 404) {
return stream.emit('error', err)
return stream.emit('error', err);
}
// local reported 404
var err404 = err
rstream.abort()
rstream = null // gc
let err404 = err;
rstream.abort();
rstream = null; // gc
self.local.get_package(name, function(err, info) {
if (!err && info._distfiles && info._distfiles[filename] != null) {
// information about this file exists locally
serve_file(info._distfiles[filename])
serve_file(info._distfiles[filename]);
} else {
// we know nothing about this file, trying to get information elsewhere
self._sync_package_with_uplinks(name, info, {}, function(err, info) {
if (err) return stream.emit('error', err)
if (err) return stream.emit('error', err);
if (!info._distfiles || info._distfiles[filename] == null) {
return stream.emit('error', err404)
return stream.emit('error', err404);
}
serve_file(info._distfiles[filename])
})
serve_file(info._distfiles[filename]);
});
}
})
})
});
});
rstream.on('content-length', function(v) {
stream.emit('content-length', v)
})
stream.emit('content-length', v);
});
rstream.on('open', function() {
is_open = true
rstream.pipe(stream)
})
return stream
is_open = true;
rstream.pipe(stream);
});
return stream;
function serve_file(file) {
var uplink = null
for (var p in self.uplinks) {
let uplink = null;
for (let p in self.uplinks) {
if (self.uplinks[p].can_fetch_url(file.url)) {
uplink = self.uplinks[p]
uplink = self.uplinks[p];
}
}
if (uplink == null) {
uplink = Proxy({
url: file.url,
_autogenerated: true,
}, self.config)
}, self.config);
}
var savestream = self.local.add_tarball(name, filename)
let savestream = self.local.add_tarball(name, filename);
var on_open = function() {
on_open = function(){} // prevent it from being called twice
var rstream2 = uplink.get_url(file.url)
on_open = function() {}; // prevent it from being called twice
let rstream2 = uplink.get_url(file.url);
rstream2.on('error', function(err) {
if (savestream) savestream.abort()
savestream = null
stream.emit('error', err)
})
if (savestream) savestream.abort();
savestream = null;
stream.emit('error', err);
});
rstream2.on('end', function() {
if (savestream) savestream.done()
})
if (savestream) savestream.done();
});
rstream2.on('content-length', function(v) {
stream.emit('content-length', v)
if (savestream) savestream.emit('content-length', v)
})
rstream2.pipe(stream)
if (savestream) rstream2.pipe(savestream)
}
stream.emit('content-length', v);
if (savestream) savestream.emit('content-length', v);
});
rstream2.pipe(stream);
if (savestream) rstream2.pipe(savestream);
};
savestream.on('open', function() {
on_open()
})
on_open();
});
savestream.on('error', function(err) {
self.logger.warn( { err: err }
, 'error saving file: @{err.message}\n@{err.stack}' )
if (savestream) savestream.abort()
savestream = null
on_open()
})
self.logger.warn( {err: err}
, 'error saving file: @{err.message}\n@{err.stack}' );
if (savestream) savestream.abort();
savestream = null;
on_open();
});
}
}
@ -313,24 +312,24 @@ class Storage {
this.local.get_package(name, options, (err, data) => {
if (err && (!err.status || err.status >= 500)) {
// report internal errors right away
return callback(err)
return callback(err);
}
this._sync_package_with_uplinks(name, data, options, function(err, result, uplink_errors) {
if (err) return callback(err)
const whitelist = [ '_rev', 'name', 'versions', 'dist-tags', 'readme' ]
for (var i in result) {
if (whitelist.indexOf(i) === -1) delete result[i]
if (err) return callback(err);
const whitelist = ['_rev', 'name', 'versions', 'dist-tags', 'readme'];
for (let i in result) {
if (whitelist.indexOf(i) === -1) delete result[i];
}
Utils.normalize_dist_tags(result)
Utils.normalize_dist_tags(result);
// npm can throw if this field doesn't exist
result._attachments = {}
result._attachments = {};
callback(null, result, uplink_errors)
})
})
callback(null, result, uplink_errors);
});
});
}
/**
@ -345,37 +344,39 @@ class Storage {
* @param {*} options
*/
search(startkey, options) {
var self = this
let self = this;
var stream = new Stream.PassThrough({ objectMode: true })
let stream = new Stream.PassThrough({objectMode: true});
async.eachSeries(Object.keys(this.uplinks), function(up_name, cb) {
// shortcut: if `local=1` is supplied, don't call uplinks
if (options.req.query.local !== undefined) return cb()
if (options.req.query.local !== undefined) return cb();
var lstream = self.uplinks[up_name].search(startkey, options)
lstream.pipe(stream, { end: false })
lstream.on('error', function (err) {
self.logger.error({ err: err }, 'uplink error: @{err.message}')
cb(), cb = function () {}
})
lstream.on('end', function () {
cb(), cb = function () {}
})
let lstream = self.uplinks[up_name].search(startkey, options);
lstream.pipe(stream, {end: false});
lstream.on('error', function(err) {
self.logger.error({err: err}, 'uplink error: @{err.message}');
cb(), cb = function() {};
});
lstream.on('end', function() {
cb(), cb = function() {};
});
stream.abort = function () {
if (lstream.abort) lstream.abort()
cb(), cb = function () {}
}
}, function () {
var lstream = self.local.search(startkey, options)
stream.abort = function () { lstream.abort() }
lstream.pipe(stream, { end: true })
lstream.on('error', function (err) {
self.logger.error({ err: err }, 'search error: @{err.message}')
stream.end()
})
})
stream.abort = function() {
if (lstream.abort) lstream.abort();
cb(), cb = function() {};
};
}, function() {
let lstream = self.local.search(startkey, options);
stream.abort = function() {
lstream.abort();
};
lstream.pipe(stream, {end: true});
lstream.on('error', function(err) {
self.logger.error({err: err}, 'search error: @{err.message}');
stream.end();
});
});
return stream;
}
@ -385,34 +386,34 @@ class Storage {
* @param {*} callback
*/
get_local(callback) {
var self = this
var locals = this.config.localList.get()
var packages = []
let self = this;
let locals = this.config.localList.get();
let packages = [];
var getPackage = function(i) {
self.local.get_package(locals[i], function(err, info) {
if (!err) {
var latest = info['dist-tags'].latest
let latest = info['dist-tags'].latest;
if (latest && info.versions[latest]) {
packages.push(info.versions[latest])
packages.push(info.versions[latest]);
} else {
self.logger.warn( { package: locals[i] }
, 'package @{package} does not have a "latest" tag?' )
self.logger.warn( {package: locals[i]}
, 'package @{package} does not have a "latest" tag?' );
}
}
if (i >= locals.length - 1) {
callback(null, packages)
callback(null, packages);
} else {
getPackage(i + 1)
getPackage(i + 1);
}
})
}
});
};
if (locals.length) {
getPackage(0)
getPackage(0);
} else {
callback(null, [])
callback(null, []);
}
}
@ -426,68 +427,68 @@ class Storage {
* @param {*} callback
*/
_sync_package_with_uplinks(name, pkginfo, options, callback) {
var self = this
let self = this;
let exists = false;
if (!pkginfo) {
exists = false
exists = false;
pkginfo = {
name : name,
versions : {},
'dist-tags' : {},
_uplinks : {},
}
'name': name,
'versions': {},
'dist-tags': {},
'_uplinks': {},
};
} else {
exists = true
exists = true;
}
var uplinks = []
let uplinks = [];
for (let i in self.uplinks) {
if (self.config.can_proxy_to(name, i)) {
uplinks.push(self.uplinks[i])
uplinks.push(self.uplinks[i]);
}
}
async.map(uplinks, function(up, cb) {
var _options = Object.assign({}, options)
let _options = Object.assign({}, options);
if (Utils.is_object(pkginfo._uplinks[up.upname])) {
var fetched = pkginfo._uplinks[up.upname].fetched
let fetched = pkginfo._uplinks[up.upname].fetched;
if (fetched && fetched > (Date.now() - up.maxage)) {
return cb()
return cb();
}
_options.etag = pkginfo._uplinks[up.upname].etag
_options.etag = pkginfo._uplinks[up.upname].etag;
}
up.get_package(name, _options, function(err, up_res, etag) {
if (err && err.status === 304)
pkginfo._uplinks[up.upname].fetched = Date.now()
pkginfo._uplinks[up.upname].fetched = Date.now();
if (err || !up_res) return cb(null, [err || Error('no data')])
if (err || !up_res) return cb(null, [err || Error('no data')]);
try {
Utils.validate_metadata(up_res, name)
Utils.validate_metadata(up_res, name);
} catch(err) {
self.logger.error({
sub: 'out',
err: err,
}, 'package.json validating error @{!err.message}\n@{err.stack}')
return cb(null, [ err ])
}, 'package.json validating error @{!err.message}\n@{err.stack}');
return cb(null, [err]);
}
pkginfo._uplinks[up.upname] = {
etag: etag,
fetched: Date.now()
}
fetched: Date.now(),
};
for (let i in up_res.versions) {
// this won't be serialized to json,
// kinda like an ES6 Symbol
//FIXME: perhaps Symbol('_verdaccio_uplink') here?
// FIXME: perhaps Symbol('_verdaccio_uplink') here?
Object.defineProperty(up_res.versions[i], '_verdaccio_uplink', {
value : up.upname,
enumerable : false,
configurable : false,
writable : true,
value: up.upname,
enumerable: false,
configurable: false,
writable: true,
});
}
@ -497,29 +498,29 @@ class Storage {
self.logger.error({
sub: 'out',
err: err,
}, 'package.json parsing error @{!err.message}\n@{err.stack}')
return cb(null, [ err ])
}, 'package.json parsing error @{!err.message}\n@{err.stack}');
return cb(null, [err]);
}
// if we got to this point, assume that the correct package exists
// on the uplink
exists = true
cb()
})
exists = true;
cb();
});
}, function(err, uplink_errors) {
assert(!err && Array.isArray(uplink_errors))
assert(!err && Array.isArray(uplink_errors));
if (!exists) {
return callback( Error[404]('no such package available')
, null
, uplink_errors )
, uplink_errors );
}
self.local.update_versions(name, pkginfo, function(err, pkginfo) {
if (err) return callback(err)
return callback(null, pkginfo, uplink_errors)
})
})
if (err) return callback(err);
return callback(null, pkginfo, uplink_errors);
});
});
}
/**
@ -534,17 +535,17 @@ class Storage {
// NOTE: if a certain version was updated, we can't refresh it reliably
for (var i in up.versions) {
if (local.versions[i] == null) {
local.versions[i] = up.versions[i]
local.versions[i] = up.versions[i];
}
}
// refresh dist-tags
for (var i in up['dist-tags']) {
if (local['dist-tags'][i] !== up['dist-tags'][i]) {
local['dist-tags'][i] = up['dist-tags'][i]
local['dist-tags'][i] = up['dist-tags'][i];
if (i === 'latest') {
// if remote has more fresh package, we should borrow its readme
local.readme = up.readme
local.readme = up.readme;
}
}
}

View File

@ -1,60 +1,62 @@
var Stream = require('stream')
var Util = require('util')
'use strict';
module.exports.ReadTarballStream = ReadTarball
module.exports.UploadTarballStream = UploadTarball
let Stream = require('stream');
let Util = require('util');
module.exports.ReadTarballStream = ReadTarball;
module.exports.UploadTarballStream = UploadTarball;
//
// This stream is used to read tarballs from repository
//
function ReadTarball(options) {
var self = new Stream.PassThrough(options)
Object.setPrototypeOf(self, ReadTarball.prototype)
let self = new Stream.PassThrough(options);
Object.setPrototypeOf(self, ReadTarball.prototype);
// called when data is not needed anymore
add_abstract_method(self, 'abort')
add_abstract_method(self, 'abort');
return self
return self;
}
Util.inherits(ReadTarball, Stream.PassThrough)
Util.inherits(ReadTarball, Stream.PassThrough);
//
// This stream is used to upload tarballs to a repository
//
function UploadTarball(options) {
var self = new Stream.PassThrough(options)
Object.setPrototypeOf(self, UploadTarball.prototype)
let self = new Stream.PassThrough(options);
Object.setPrototypeOf(self, UploadTarball.prototype);
// called when user closes connection before upload finishes
add_abstract_method(self, 'abort')
add_abstract_method(self, 'abort');
// called when upload finishes successfully
add_abstract_method(self, 'done')
add_abstract_method(self, 'done');
return self
return self;
}
Util.inherits(UploadTarball, Stream.PassThrough)
Util.inherits(UploadTarball, Stream.PassThrough);
//
// This function intercepts abstract calls and replays them allowing
// us to attach those functions after we are ready to do so
//
function add_abstract_method(self, name) {
self._called_methods = self._called_methods || {}
self._called_methods = self._called_methods || {};
self.__defineGetter__(name, function() {
return function() {
self._called_methods[name] = true
}
})
self._called_methods[name] = true;
};
});
self.__defineSetter__(name, function(fn) {
delete self[name]
self[name] = fn
delete self[name];
self[name] = fn;
if (self._called_methods && self._called_methods[name]) {
delete self._called_methods[name]
self[name]()
delete self._called_methods[name];
self[name]();
}
})
});
}

View File

@ -1,32 +1,32 @@
"use strict";
'use strict';
const JSONStream = require('JSONStream')
const Error = require('http-errors')
const request = require('request')
const Stream = require('readable-stream')
const URL = require('url')
const parse_interval = require('./config').parse_interval
const Logger = require('./logger')
const MyStreams = require('./streams')
const Utils = require('./utils')
const encode = function(thing) {
const JSONStream = require('JSONStream');
const Error = require('http-errors');
const request = require('request');
const Stream = require('readable-stream');
const URL = require('url');
const parse_interval = require('./config').parse_interval;
const Logger = require('./logger');
const MyStreams = require('./streams');
const Utils = require('./utils');
const encode = function(thing) {
return encodeURIComponent(thing).replace(/^%40/, '@');
};
const _setupProxy = function(hostname, config, mainconfig, isHTTPS) {
var no_proxy
var proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy'
let no_proxy;
let proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy';
// get http_proxy and no_proxy configs
if (proxy_key in config) {
this.proxy = config[proxy_key]
this.proxy = config[proxy_key];
} else if (proxy_key in mainconfig) {
this.proxy = mainconfig[proxy_key]
this.proxy = mainconfig[proxy_key];
}
if ('no_proxy' in config) {
no_proxy = config.no_proxy
no_proxy = config.no_proxy;
} else if ('no_proxy' in mainconfig) {
no_proxy = mainconfig.no_proxy
no_proxy = mainconfig.no_proxy;
}
// use wget-like algorithm to determine if proxy shouldn't be used
@ -34,17 +34,17 @@ const _setupProxy = function(hostname, config, mainconfig, isHTTPS) {
hostname = '.' + hostname;
}
if (typeof(no_proxy) === 'string' && no_proxy.length) {
no_proxy = no_proxy.split(',')
no_proxy = no_proxy.split(',');
}
if (Array.isArray(no_proxy)) {
for (let i=0; i<no_proxy.length; i++) {
var no_proxy_item = no_proxy[i]
if (no_proxy_item[0] !== '.') no_proxy_item = '.' + no_proxy_item
let no_proxy_item = no_proxy[i];
if (no_proxy_item[0] !== '.') no_proxy_item = '.' + no_proxy_item;
if (hostname.lastIndexOf(no_proxy_item) === hostname.length - no_proxy_item.length) {
if (this.proxy) {
this.logger.debug({url: this.url.href, rule: no_proxy_item},
'not using proxy for @{url}, excluded by @{rule} rule')
this.proxy = false
'not using proxy for @{url}, excluded by @{rule} rule');
this.proxy = false;
}
break;
}
@ -53,12 +53,12 @@ const _setupProxy = function(hostname, config, mainconfig, isHTTPS) {
// if it's non-string (i.e. "false"), don't use it
if (typeof(this.proxy) !== 'string') {
delete this.proxy
delete this.proxy;
} else {
this.logger.debug( { url: this.url.href, proxy: this.proxy }
, 'using proxy @{proxy} for @{url}' )
this.logger.debug( {url: this.url.href, proxy: this.proxy}
, 'using proxy @{proxy} for @{url}' );
}
}
};
//
// Implements Storage interface
@ -72,35 +72,35 @@ class Storage {
* @param {*} mainconfig
*/
constructor(config, mainconfig) {
this.config = config
this.failed_requests = 0
this.userAgent = mainconfig.user_agent
this.ca = config.ca
this.logger = Logger.logger.child({sub: 'out'})
this.server_id = mainconfig.server_id
this.config = config;
this.failed_requests = 0;
this.userAgent = mainconfig.user_agent;
this.ca = config.ca;
this.logger = Logger.logger.child({sub: 'out'});
this.server_id = mainconfig.server_id;
this.url = URL.parse(this.config.url)
this.url = URL.parse(this.config.url);
_setupProxy.call(this, this.url.hostname, config, mainconfig, this.url.protocol === 'https:')
_setupProxy.call(this, this.url.hostname, config, mainconfig, this.url.protocol === 'https:');
this.config.url = this.config.url.replace(/\/$/, '')
this.config.url = this.config.url.replace(/\/$/, '');
if (Number(this.config.timeout) >= 1000) {
this.logger.warn([ 'Too big timeout value: ' + this.config.timeout,
this.logger.warn(['Too big timeout value: ' + this.config.timeout,
'We changed time format to nginx-like one',
'(see http://wiki.nginx.org/ConfigNotation)',
'so please update your config accordingly' ].join('\n'))
'so please update your config accordingly'].join('\n'));
}
// a bunch of different configurable timers
this.maxage = parse_interval(config_get('maxage' , '2m' ))
this.timeout = parse_interval(config_get('timeout' , '30s'))
this.max_fails = Number(config_get('max_fails' , 2 ))
this.fail_timeout = parse_interval(config_get('fail_timeout', '5m' ))
return this
this.maxage = parse_interval(config_get('maxage', '2m' ));
this.timeout = parse_interval(config_get('timeout', '30s'));
this.max_fails = Number(config_get('max_fails', 2 ));
this.fail_timeout = parse_interval(config_get('fail_timeout', '5m' ));
return this;
// just a helper (`config[key] || default` doesn't work because of zeroes)
function config_get(key, def) {
return config[key] != null ? config[key] : def
return config[key] != null ? config[key] : def;
}
}
}
@ -108,15 +108,15 @@ class Storage {
Storage.prototype.request = function(options, cb) {
if (!this.status_check()) {
var req = new Stream.Readable()
var req = new Stream.Readable();
process.nextTick(function() {
if (typeof(cb) === 'function') cb(Error('uplink is offline'))
req.emit('error', Error('uplink is offline'))
})
req._read = function(){}
if (typeof(cb) === 'function') cb(Error('uplink is offline'));
req.emit('error', Error('uplink is offline'));
});
req._read = function() {};
// preventing 'Uncaught, unspecified "error" event'
req.on('error', function(){})
return req
req.on('error', function() {});
return req;
}
var self = this
@ -129,256 +129,257 @@ Storage.prototype.request = function(options, cb) {
// add/override headers specified in the config
for (let key in this.config.headers) {
headers[key] = this.config.headers[key]
headers[key] = this.config.headers[key];
}
var method = options.method || 'GET'
var uri = options.uri_full || (this.config.url + options.uri)
let method = options.method || 'GET';
let uri = options.uri_full || (this.config.url + options.uri);
self.logger.info({
method : method,
headers : headers,
uri : uri,
}, "making request: '@{method} @{uri}'")
method: method,
headers: headers,
uri: uri,
}, 'making request: \'@{method} @{uri}\'');
if (Utils.is_object(options.json)) {
var json = JSON.stringify(options.json)
headers['Content-Type'] = headers['Content-Type'] || 'application/json'
var json = JSON.stringify(options.json);
headers['Content-Type'] = headers['Content-Type'] || 'application/json';
}
var request_callback = cb ? (function (err, res, body) {
var error
var res_length = err ? 0 : body.length
let request_callback = cb ? (function(err, res, body) {
let error;
let res_length = err ? 0 : body.length;
do_decode()
do_log()
cb(err, res, body)
do_decode();
do_log();
cb(err, res, body);
function do_decode() {
if (err) {
error = err.message
return
error = err.message;
return;
}
if (options.json && res.statusCode < 300) {
try {
body = JSON.parse(body.toString('utf8'))
body = JSON.parse(body.toString('utf8'));
} catch(_err) {
body = {}
err = _err
error = err.message
body = {};
err = _err;
error = err.message;
}
}
if (!err && Utils.is_object(body)) {
if (typeof(body.error) === 'string') {
error = body.error
error = body.error;
}
}
}
function do_log() {
var message = '@{!status}, req: \'@{request.method} @{request.url}\''
let message = '@{!status}, req: \'@{request.method} @{request.url}\'';
message += error
? ', error: @{!error}'
: ', bytes: @{bytes.in}/@{bytes.out}'
: ', bytes: @{bytes.in}/@{bytes.out}';
self.logger.warn({
err : err,
request : { method: method, url: uri },
level : 35, // http
status : res != null ? res.statusCode : 'ERR',
error : error,
bytes : {
in : json ? json.length : 0,
out : res_length || 0,
}
}, message)
err: err,
request: {method: method, url: uri},
level: 35, // http
status: res != null ? res.statusCode : 'ERR',
error: error,
bytes: {
in: json ? json.length : 0,
out: res_length || 0,
},
}, message);
}
}) : undefined
}) : undefined;
var req = request({
url : uri,
method : method,
headers : headers,
body : json,
ca : this.ca,
proxy : this.proxy,
encoding : null,
gzip : true,
timeout : this.timeout,
}, request_callback)
url: uri,
method: method,
headers: headers,
body: json,
ca: this.ca,
proxy: this.proxy,
encoding: null,
gzip: true,
timeout: this.timeout,
}, request_callback);
var status_called = false
let status_called = false;
req.on('response', function(res) {
if (!req._verdaccio_aborted && !status_called) {
status_called = true
self.status_check(true)
status_called = true;
self.status_check(true);
}
if (!request_callback) {
;(function do_log() {
var message = '@{!status}, req: \'@{request.method} @{request.url}\' (streaming)'
(function do_log() {
let message = '@{!status}, req: \'@{request.method} @{request.url}\' (streaming)';
self.logger.warn({
request : { method: method, url: uri },
level : 35, // http
status : res != null ? res.statusCode : 'ERR',
}, message)
})()
request: {method: method, url: uri},
level: 35, // http
status: res != null ? res.statusCode : 'ERR',
}, message);
})();
}
})
});
req.on('error', function(_err) {
if (!req._verdaccio_aborted && !status_called) {
status_called = true
self.status_check(false)
status_called = true;
self.status_check(false);
}
})
return req
}
});
return req;
};
Storage.prototype.status_check = function(alive) {
if (arguments.length === 0) {
if (this.failed_requests >= this.max_fails
&& Math.abs(Date.now() - this.last_request_time) < this.fail_timeout) {
return false
return false;
} else {
return true
return true;
}
} else {
if (alive) {
if (this.failed_requests >= this.max_fails) {
this.logger.warn({ host: this.url.host }, 'host @{host} is back online')
this.logger.warn({host: this.url.host}, 'host @{host} is back online');
}
this.failed_requests = 0
this.failed_requests = 0;
} else {
this.failed_requests++
this.failed_requests++;
if (this.failed_requests === this.max_fails) {
this.logger.warn({ host: this.url.host }, 'host @{host} is now offline')
this.logger.warn({host: this.url.host}, 'host @{host} is now offline');
}
}
this.last_request_time = Date.now()
this.last_request_time = Date.now();
}
}
};
Storage.prototype.can_fetch_url = function(url) {
url = URL.parse(url)
url = URL.parse(url);
return url.protocol === this.url.protocol
&& url.host === this.url.host
&& url.path.indexOf(this.url.path) === 0
}
&& url.path.indexOf(this.url.path) === 0;
};
Storage.prototype.get_package = function(name, options, callback) {
if (typeof(options) === 'function') callback = options, options = {}
if (typeof(options) === 'function') callback = options, options = {};
var headers = {}
let headers = {};
if (options.etag) {
headers['If-None-Match'] = options.etag
headers['Accept'] = 'application/octet-stream'
headers['If-None-Match'] = options.etag;
headers['Accept'] = 'application/octet-stream';
}
this.request({
uri : '/' + encode(name),
json : true,
headers : headers,
req : options.req,
uri: '/' + encode(name),
json: true,
headers: headers,
req: options.req,
}, function(err, res, body) {
if (err) return callback(err)
if (err) return callback(err);
if (res.statusCode === 404) {
return callback( Error[404]("package doesn't exist on uplink") )
return callback( Error[404]('package doesn\'t exist on uplink') );
}
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
var error = Error('bad status code: ' + res.statusCode)
error.remoteStatus = res.statusCode
return callback(error)
let error = Error('bad status code: ' + res.statusCode);
error.remoteStatus = res.statusCode;
return callback(error);
}
callback(null, body, res.headers.etag)
})
}
callback(null, body, res.headers.etag);
});
};
Storage.prototype.get_tarball = function(name, options, filename) {
if (!options) options = {}
return this.get_url(this.config.url + '/' + name + '/-/' + filename)
}
if (!options) options = {};
return this.get_url(this.config.url + '/' + name + '/-/' + filename);
};
Storage.prototype.get_url = function(url) {
var stream = MyStreams.ReadTarballStream()
stream.abort = function() {}
var current_length = 0, expected_length
let stream = MyStreams.ReadTarballStream();
stream.abort = function() {};
let current_length = 0, expected_length;
var rstream = this.request({
let rstream = this.request({
uri_full: url,
encoding: null,
headers: { Accept: 'application/octet-stream' },
})
headers: {Accept: 'application/octet-stream'},
});
rstream.on('response', function(res) {
if (res.statusCode === 404) {
return stream.emit('error', Error[404]("file doesn't exist on uplink"))
return stream.emit('error', Error[404]('file doesn\'t exist on uplink'));
}
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
return stream.emit('error', Error('bad uplink status code: ' + res.statusCode))
return stream.emit('error', Error('bad uplink status code: ' + res.statusCode));
}
if (res.headers['content-length']) {
expected_length = res.headers['content-length']
stream.emit('content-length', res.headers['content-length'])
expected_length = res.headers['content-length'];
stream.emit('content-length', res.headers['content-length']);
}
rstream.pipe(stream)
})
rstream.pipe(stream);
});
rstream.on('error', function(err) {
stream.emit('error', err)
})
stream.emit('error', err);
});
rstream.on('data', function(d) {
current_length += d.length
})
current_length += d.length;
});
rstream.on('end', function(d) {
if (d) current_length += d.length
if (d) current_length += d.length;
if (expected_length && current_length != expected_length)
stream.emit('error', Error('content length mismatch'))
})
return stream
}
stream.emit('error', Error('content length mismatch'));
});
return stream;
};
Storage.prototype.search = function(startkey, options) {
var self = this
let self = this;
var stream = new Stream.PassThrough({ objectMode: true })
let stream = new Stream.PassThrough({objectMode: true});
var req = self.request({
uri: options.req.url,
req: options.req,
headers: {
referer: options.req.headers.referer
}
})
referer: options.req.headers.referer,
},
});
req.on('response', function (res) {
req.on('response', (res) => {
if (!String(res.statusCode).match(/^2\d\d$/)) {
return stream.emit('error', Error('bad status code ' + res.statusCode + ' from uplink'))
return stream.emit('error', Error('bad status code ' + res.statusCode + ' from uplink'));
}
res.pipe(JSONStream.parse('*')).on('data', function (pkg) {
res.pipe(JSONStream.parse('*')).on('data', (pkg) => {
if (Utils.is_object(pkg)) {
stream.emit('data', pkg)
stream.emit('data', pkg);
}
})
res.on('end', function () {
stream.emit('end')
})
})
});
res.on('end', () => {
stream.emit('end');
});
});
req.on('error', function (err) {
stream.emit('error', err)
})
req.on('error', (err) => {
stream.emit('error', err);
});
stream.abort = function () {
req.abort()
stream.emit('end')
}
stream.abort = () => {
req.abort();
stream.emit('end');
};
return stream
}
return stream;
};
Storage.prototype._add_proxy_headers = function(req, headers) {
if (req) {
@ -393,7 +394,7 @@ Storage.prototype._add_proxy_headers = function(req, headers) {
req && req.headers['x-forwarded-for']
? req.headers['x-forwarded-for'] + ', '
: ''
) + req.connection.remoteAddress
) + req.connection.remoteAddress;
}
}
@ -401,10 +402,10 @@ Storage.prototype._add_proxy_headers = function(req, headers) {
headers['Via'] =
req && req.headers['via']
? req.headers['via'] + ', '
: ''
: '';
headers['Via'] += '1.1 ' + this.server_id + ' (Verdaccio)'
}
headers['Via'] += '1.1 ' + this.server_id + ' (Verdaccio)';
};
module.exports = Storage;

View File

@ -1,25 +1,27 @@
var assert = require('assert')
var Semver = require('semver')
var URL = require('url')
var Logger = require('./logger')
'use strict';
let assert = require('assert');
let Semver = require('semver');
let URL = require('url');
let Logger = require('./logger');
module.exports.validate_package = function(name) {
name = name.split('/', 2)
name = name.split('/', 2);
if (name.length === 1) {
// normal package
return module.exports.validate_name(name[0])
return module.exports.validate_name(name[0]);
} else {
// scoped package
return name[0][0] === '@'
&& module.exports.validate_name(name[0].slice(1))
&& module.exports.validate_name(name[1])
&& module.exports.validate_name(name[1]);
}
}
};
// from normalize-package-data/lib/fixer.js
module.exports.validate_name = function(name) {
if (typeof(name) !== 'string') return false
name = name.toLowerCase()
if (typeof(name) !== 'string') return false;
name = name.toLowerCase();
// all URL-safe characters and "@" for issue #75
if (!name.match(/^[-a-zA-Z0-9_.!~*'()@]+$/)
@ -30,88 +32,88 @@ module.exports.validate_name = function(name) {
|| name === 'package.json'
|| name === 'favicon.ico'
) {
return false
return false;
} else {
return true
return true;
}
}
};
module.exports.is_object = function(obj) {
return typeof(obj) === 'object' && obj !== null && !Array.isArray(obj)
}
return typeof(obj) === 'object' && obj !== null && !Array.isArray(obj);
};
module.exports.validate_metadata = function(object, name) {
assert(module.exports.is_object(object), 'not a json object')
assert.equal(object.name, name)
assert(module.exports.is_object(object), 'not a json object');
assert.equal(object.name, name);
if (!module.exports.is_object(object['dist-tags'])) {
object['dist-tags'] = {}
object['dist-tags'] = {};
}
if (!module.exports.is_object(object['versions'])) {
object['versions'] = {}
object['versions'] = {};
}
return object;
}
};
module.exports.filter_tarball_urls = function(pkg, req, config) {
function filter(_url) {
if (!req.headers.host) return _url
if (!req.headers.host) return _url;
var filename = URL.parse(_url).pathname.replace(/^.*\//, '')
let filename = URL.parse(_url).pathname.replace(/^.*\//, '');
if (config.url_prefix != null) {
var result = config.url_prefix.replace(/\/$/, '')
var result = config.url_prefix.replace(/\/$/, '');
} else {
var result = req.protocol + '://' + req.headers.host
var result = req.protocol + '://' + req.headers.host;
}
return `${result}/${pkg.name.replace(/\//g, '%2f')}/-/${filename}`;
}
for (var ver in pkg.versions) {
var dist = pkg.versions[ver].dist
for (let ver in pkg.versions) {
let dist = pkg.versions[ver].dist;
if (dist != null && dist.tarball != null) {
//dist.__verdaccio_orig_tarball = dist.tarball
dist.tarball = filter(dist.tarball)
// dist.__verdaccio_orig_tarball = dist.tarball
dist.tarball = filter(dist.tarball);
}
}
return pkg
}
return pkg;
};
module.exports.tag_version = function(data, version, tag) {
if (tag) {
if (data['dist-tags'][tag] !== version) {
if (Semver.parse(version, true)) {
// valid version - store
data['dist-tags'][tag] = version
return true
data['dist-tags'][tag] = version;
return true;
}
}
Logger.logger.warn({ver: version, tag: tag}, 'ignoring bad version @{ver} in @{tag}')
Logger.logger.warn({ver: version, tag: tag}, 'ignoring bad version @{ver} in @{tag}');
if (tag && data['dist-tags'][tag]) {
delete data['dist-tags'][tag]
delete data['dist-tags'][tag];
}
}
return false
}
return false;
};
// gets version from a package object taking into account semver weirdness
module.exports.get_version = function(object, version) {
if (object.versions[version] != null) return object.versions[version]
if (object.versions[version] != null) return object.versions[version];
try {
version = Semver.parse(version, true)
for (var k in object.versions) {
version = Semver.parse(version, true);
for (let k in object.versions) {
if (version.compare(Semver.parse(k, true)) === 0) {
return object.versions[k]
return object.versions[k];
}
}
} catch (err) {
return undefined
return undefined;
}
}
};
module.exports.parse_address = function parse_address(addr) {
//
@ -129,68 +131,67 @@ module.exports.parse_address = function parse_address(addr) {
// TODO: refactor it to something more reasonable?
//
// protocol : // ( host )|( ipv6 ): port /
var m = /^((https?):(\/\/)?)?((([^\/:]*)|\[([^\[\]]+)\]):)?(\d+)\/?$/.exec(addr)
var m = /^((https?):(\/\/)?)?((([^\/:]*)|\[([^\[\]]+)\]):)?(\d+)\/?$/.exec(addr);
if (m) return {
proto: m[2] || 'http',
host: m[6] || m[7] || 'localhost',
port: m[8] || '4873',
}
host: m[6] || m[7] || 'localhost',
port: m[8] || '4873',
};
var m = /^((https?):(\/\/)?)?unix:(.*)$/.exec(addr)
var m = /^((https?):(\/\/)?)?unix:(.*)$/.exec(addr);
if (m) return {
proto: m[2] || 'http',
path: m[4],
}
path: m[4],
};
return null
}
return null;
};
// function filters out bad semver versions and sorts the array
module.exports.semver_sort = function semver_sort(array) {
return array
.filter(function(x) {
if (!Semver.parse(x, true)) {
Logger.logger.warn( {ver: x}, 'ignoring bad version @{ver}' )
return false
Logger.logger.warn( {ver: x}, 'ignoring bad version @{ver}' );
return false;
}
return true
return true;
})
.sort(Semver.compareLoose)
.map(String)
}
.map(String);
};
// flatten arrays of tags
module.exports.normalize_dist_tags = function (data) {
var sorted
module.exports.normalize_dist_tags = function(data) {
let sorted;
if (!data['dist-tags'].latest) {
// overwrite latest with highest known version based on semver sort
sorted = module.exports.semver_sort(Object.keys(data.versions))
sorted = module.exports.semver_sort(Object.keys(data.versions));
if (sorted && sorted.length) {
data['dist-tags'].latest = sorted.pop()
data['dist-tags'].latest = sorted.pop();
}
}
for (var tag in data['dist-tags']) {
for (let tag in data['dist-tags']) {
if (Array.isArray(data['dist-tags'][tag])) {
if (data['dist-tags'][tag].length) {
// sort array
sorted = module.exports.semver_sort(data['dist-tags'][tag])
sorted = module.exports.semver_sort(data['dist-tags'][tag]);
if (sorted.length) {
// use highest version based on semver sort
data['dist-tags'][tag] = sorted.pop()
data['dist-tags'][tag] = sorted.pop();
}
} else {
delete data['dist-tags'][tag]
delete data['dist-tags'][tag];
}
} else if (typeof data['dist-tags'][tag] === 'string') {
if (!Semver.parse(data['dist-tags'][tag], true)) {
// if the version is invalid, delete the dist-tag entry
delete data['dist-tags'][tag]
delete data['dist-tags'][tag];
}
}
}
}
};

View File

@ -20,6 +20,7 @@
"async": "^2.0.1",
"body-parser": "^1.15.0",
"bunyan": "^1.8.0",
"chalk": "^1.1.3",
"commander": "^2.9.0",
"compression": "^1.6.1",
"cookies": "^0.6.1",
@ -44,6 +45,7 @@
"browserify": "^13.0.0",
"browserify-handlebars": "^1.0.0",
"eslint": "^3.19.0",
"eslint-config-google": "^0.7.1",
"grunt": "^1.0.1",
"grunt-browserify": "^5.0.0",
"grunt-cli": "^1.2.0",
@ -66,9 +68,9 @@
"server"
],
"scripts": {
"test": "eslint . && mocha ./test/functional ./test/unit",
"test": "npm run lint && mocha ./test/functional ./test/unit",
"test:coverage": "nyc --reporter=html --reporter=text mocha -R spec ./test/functional ./test/unit",
"test-travis": "eslint . && npm run test:coverage",
"test-travis": "npm run lint && npm run test:coverage",
"test-only": "mocha ./test/functional ./test/unit",
"lint": "eslint .",
"build-docker": "docker build -t verdaccio .",

View File

@ -1,5 +1,12 @@
# vim: syntax=yaml
extends: ["eslint:recommended"]
env:
node: true
mocha: true
es6: true
valid-jsdoc: 0
no-redeclare: 1
no-console: 1

View File

@ -18,12 +18,12 @@ Plugin.prototype.allow_access = function(user, pkg, cb) {
return cb(null, false);
}
if (user.name !== self._config.allow_user) {
var err = Error('i don\'t know anything about you');
let err = Error('i don\'t know anything about you');
err.status = 403;
return cb(err);
}
if (pkg.name !== self._config.to_access) {
var err = Error('you\'re not allowed here');
let err = Error('you\'re not allowed here');
err.status = 403;
return cb(err);
}

View File

@ -59,7 +59,7 @@ module.exports = function() {
it('uploading 10 diff versions', function(callback) {
let fns = [];
for (let i=0; i<10; i++) {
;(function(i) {
(function(i) {
fns.push(function(cb_) {
let _res;
server.put_version('race', '0.1.'+String(i), require('./lib/package')('race'))

View File

@ -22,13 +22,13 @@ describe('Use proxy', function() {
});
it('no_proxy is invalid', function() {
var x = setup('http://x/x', {http_proxy: '123', no_proxy: false}, {});
let x = setup('http://x/x', {http_proxy: '123', no_proxy: false}, {});
assert.equal(x.proxy, '123');
var x = setup('http://x/x', {http_proxy: '123', no_proxy: null}, {});
x = setup('http://x/x', {http_proxy: '123', no_proxy: null}, {});
assert.equal(x.proxy, '123');
var x = setup('http://x/x', {http_proxy: '123', no_proxy: []}, {});
x = setup('http://x/x', {http_proxy: '123', no_proxy: []}, {});
assert.equal(x.proxy, '123');
var x = setup('http://x/x', {http_proxy: '123', no_proxy: ''}, {});
x = setup('http://x/x', {http_proxy: '123', no_proxy: ''}, {});
assert.equal(x.proxy, '123');
});
@ -43,48 +43,48 @@ describe('Use proxy', function() {
});
it('no_proxy - various, single string', function() {
var x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'blah'});
let x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'blah'});
assert.equal(x.proxy, '123');
var x = setup('http://blah.blah', {}, {http_proxy: '123', no_proxy: 'blah'});
x = setup('http://blah.blah', {}, {http_proxy: '123', no_proxy: 'blah'});
assert.equal(x.proxy, null);
var x = setup('http://blahblah', {}, {http_proxy: '123', no_proxy: '.blah'});
x = setup('http://blahblah', {}, {http_proxy: '123', no_proxy: '.blah'});
assert.equal(x.proxy, '123');
var x = setup('http://blah.blah', {http_proxy: '123', no_proxy: '.blah'}, {});
x = setup('http://blah.blah', {http_proxy: '123', no_proxy: '.blah'}, {});
assert.equal(x.proxy, null);
var x = setup('http://blah', {http_proxy: '123', no_proxy: '.blah'}, {});
x = setup('http://blah', {http_proxy: '123', no_proxy: '.blah'}, {});
assert.equal(x.proxy, null);
var x = setup('http://blahh', {http_proxy: '123', no_proxy: 'blah'}, {});
x = setup('http://blahh', {http_proxy: '123', no_proxy: 'blah'}, {});
assert.equal(x.proxy, '123');
});
it('no_proxy - various, array', function() {
var x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
let x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
assert.equal(x.proxy, '123');
var x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
assert.equal(x.proxy, null);
var x = setup('http://blah.foo', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
x = setup('http://blah.foo', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
assert.equal(x.proxy, null);
var x = setup('http://foo.baz', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
x = setup('http://foo.baz', {http_proxy: '123'}, {no_proxy: 'foo,bar,blah'});
assert.equal(x.proxy, '123');
var x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']});
x = setup('http://blahblah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']});
assert.equal(x.proxy, '123');
var x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']});
x = setup('http://blah.blah', {http_proxy: '123'}, {no_proxy: ['foo', 'bar', 'blah']});
assert.equal(x.proxy, null);
});
it('no_proxy - hostport', function() {
var x = setup('http://localhost:80', {http_proxy: '123'}, {no_proxy: 'localhost'});
let x = setup('http://localhost:80', {http_proxy: '123'}, {no_proxy: 'localhost'});
assert.equal(x.proxy, null);
var x = setup('http://localhost:8080', {http_proxy: '123'}, {no_proxy: 'localhost'});
x = setup('http://localhost:8080', {http_proxy: '123'}, {no_proxy: 'localhost'});
assert.equal(x.proxy, null);
});
it('no_proxy - secure', function() {
var x = setup('https://something', {http_proxy: '123'}, {});
let x = setup('https://something', {http_proxy: '123'}, {});
assert.equal(x.proxy, null);
var x = setup('https://something', {https_proxy: '123'}, {});
x = setup('https://something', {https_proxy: '123'}, {});
assert.equal(x.proxy, '123');
var x = setup('https://something', {http_proxy: '456', https_proxy: '123'}, {});
x = setup('https://something', {http_proxy: '456', https_proxy: '123'}, {});
assert.equal(x.proxy, '123');
});
});

View File

@ -56,10 +56,10 @@ describe('search', function() {
},
};
Search.add(item);
var result = Search.query('test6');
let result = Search.query('test6');
assert(result.length === 1);
Search.remove(item.name);
var result = Search.query('test6');
result = Search.query('test6');
assert(result.length === 0);
});
});

View File

@ -23,9 +23,10 @@ function test(file) {
var t;
inner.split('/').forEach(function(x) {
t = x.match(/^:([^?:]*)\??$/);
if (m[1] === 'param') {
params[x] = 'ok';
} else if (t = x.match(/^:([^?:]*)\??$/)) {
} else if (t) {
params[t[1]] = params[t[1]] || m[0].trim();
}
});