0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-04-01 02:42:23 -05:00

change code style to jshttp

close #155, see reasons there

This is a huge commit, so let me know if it will cause
any trouble, I might consider reverting it if it's the case.
This commit is contained in:
Alex Kocharin 2014-11-12 14:14:37 +03:00
parent bac62f6969
commit 6a778e8c17
51 changed files with 4403 additions and 4420 deletions

View file

@ -6,16 +6,7 @@ root = true
end_of_line = lf
insert_final_newline = true
# Tab indentation (no size specified)
[*.js]
indent_style = tab
# 2 space indentation
[{.,}*.y{a,}ml]
[{.,}*.{js,yml,yaml}]
indent_style = space
indent_size = 2
# Indentation override for Gruntfile
[Gruntfile.js]
indent_style = space
indent_size = 4

View file

@ -1,39 +1,39 @@
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readYAML('package.yaml'),
browserify: {
dist: {
files: {
'lib/static/main.js': ['lib/GUI/js/main.js']
},
options: {
debug: true,
transform: ['browserify-handlebars']
}
}
grunt.initConfig({
pkg: grunt.file.readYAML('package.yaml'),
browserify: {
dist: {
files: {
'lib/static/main.js': [ 'lib/GUI/js/main.js' ]
},
less: {
dist: {
files: {
'lib/static/main.css': ['lib/GUI/css/main.less']
},
options: {
sourceMap: true
}
}
},
watch: {
files: [ "lib/GUI/**/*"],
tasks: [ 'default' ]
options: {
debug: true,
transform: [ 'browserify-handlebars' ]
}
});
}
},
less: {
dist: {
files: {
'lib/static/main.css': [ 'lib/GUI/css/main.less' ]
},
options: {
sourceMap: true
}
}
},
watch: {
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'
]);
};
grunt.registerTask('default', [
'browserify',
'less'
])
}

View file

@ -1,38 +1,38 @@
@media (max-width: 992px) {
.body {
.main-header {
.npm-logo {
width: 100px;
float: left;
}
.body {
.main-header {
.npm-logo {
width: 100px;
float: left;
}
.packages-header {
border-bottom: none;
}
}
}
.packages-header {
border-bottom: none;
}
}
}
}
@media (max-width: 768px) {
.body {
.content {
padding-top: @mainHeaderHeight + @packagesHeaderHeight + @smRegistryInfoHeight + 10;
.body {
.content {
padding-top: @mainHeaderHeight + @packagesHeaderHeight + @smRegistryInfoHeight + 10;
.entry {
.title {
margin-bottom: 0;
}
.entry {
.title {
margin-bottom: 0;
}
.author {
float: none !important;
clear: both;
padding: 0 0 5px 18px;
}
}
}
.author {
float: none !important;
clear: both;
padding: 0 0 5px 18px;
}
}
}
.no-results {
margin: 10px 0 0;
}
}
.no-results {
margin: 10px 0 0;
}
}
}

View file

@ -9,194 +9,194 @@
/*** Main Styles ***/
.body {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left:0;
margin: 0;
padding: 0;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left:0;
margin: 0;
padding: 0;
.main-header {
position: fixed;
top: 0;
left: 0;
right: 0;
background: @white;
z-index: 1;
.main-header {
position: fixed;
top: 0;
left: 0;
right: 0;
background: @white;
z-index: 1;
.navbar {
margin-bottom: 0;
}
.navbar {
margin-bottom: 0;
}
.npm-logo {
width: 79px;
height: @mainHeaderHeight;
background-image: url( -/logo-sm );
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE8AAAAoCAYAAAC/xadkAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMDA4QUE4NzQ1QkMxMUU0QTVCMUYwQjdEREYxNENGMiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozMDA4QUE4ODQ1QkMxMUU0QTVCMUYwQjdEREYxNENGMiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjMwMDhBQTg1NDVCQzExRTRBNUIxRjBCN0RERjE0Q0YyIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjMwMDhBQTg2NDVCQzExRTRBNUIxRjBCN0RERjE0Q0YyIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+ypkYZgAACelJREFUeNrsWmtsHFcZPXd2Zh9+r+31+5W4tSPSoKZUKgpIgdAWqorSULVFqJREKpVAAqRKhfIPKfCDtP2TKilpRRACUpBC+UlbQRG0RUmo0gBtWoyT2HHstR0/Yq/X9j5mh/PdmfX7te42tZyMdO2dmTt3vnvu+Z53lHOm9VcwQntRuvMq0qMKmTSgDNw4Fh4OG3Gxyh3E/l2GZOxVE0UtDShqLUbCLkb9o0D6KmAnbgA4D7cMcbMIXAUw+BoQ2QXEu5pMAjqB2q8Cl34BXDwC1D3IzkkgNUEAfTeAE+B8IWJhAl0vAKECoOa7wIVDEwbsaQUMA03fAMJbgJ5fAxlS1Crjg/Z1DhznbxbzB1l3+SWguBpo2cdzamd6wjBcdqXYeoHa+8m8T/PncSBJ5gUiHCB9nQLHefvLCRK1sPe3QNUtQAO1ElE2MWumcJHEg881iE43ULGbQIdJ0RNA5V1EuxWY7r++VFiAC5JlcRLqyp+olV8CSm938RGn4foDwyR2xowzcQy3Q8l24KYi6vVviHyc6ryTAPa5OLt/Nq9HddiCddTM94GxvwFbHwIKb6YpE+CEa9n5K2HewoOXMj1AAQfY9h2g8xi1mjpevYdATnoob+JQxEeHcOUNauYHQNtjVN0KFw8sgEppFyJQLmQTDWRmgP3LXQDPU+ejpK+/lNeTm5R9jlZEpEgQRbJse5z//ZxvdDFw7mHM5eHCe2wxtnqglfHfMEOZcJt7eVOyjzA4hGP0TaD8EZ7TWeKC5w+WfsBc1o7xVvpqCom+Dhi+KWCSIMbb+QLxzJnNCZ6iyo50w4l2kHRjCDYTHstw7eDi/ssxj51VGMOvnsK5b55Awc0RbwXObn6HQVW1Y2/DoMbu/PN+BBoreTm21LxJL0MtO5AiroKvMg0XY8fa/GGKIZ7AdCMzzatltEwJMkl4DsNZDJ7PoPOxYIQs/vZhuqsL9tRkbuwj5YNNTfCVlOhUJzM1hamLXbnPyW8huGWr+9uykIj2ITU8nD9NoJxmaSlVtYmWyeY71GpDk3mJtLGMN5kTM6ZJ5Rgan3gCwYZGYrB2m6eYxPQfP474++f072BjI5p/8EPK6qzZ78hzqZER9L34ol7U6YEBRO67D6W7djGDyo/9lXfE33sX/S8d50IVr7IoyrN5RSZ/rZLD2jbsiQlUP/wwAnX1OQs2+vpfMPbWmxTKD5MMrHrwodyJkUrj8pHDWuzk4ABKP/NZVD3wQF41dowL2/PcIQRqi9eGNxy1Ou/F9lFtk/396xIqk0jQa1nalgiL13Mk+nq9kJQpkeVnJHA17+ZOzIDi2Gu0BBKq5KdwlxocRCaVdGPNUAhWRcU6ihhpjnNF2x+HbDc5hq+gIOdx7PExpMfHKYihF91fVT0TVNjxONI0AbKQ8h4rEtELm7tXltGh8mJxz+3fh7fvuAOnb7sVnU8+uT52RaN454t348wX9uDUJ3dg+JVX1jVOz+HDOLnjFpzZvRv/2bsX9uTkzL0rf3wZJ7d/Au/cfRfO7Pk8ndfFnB2LDttsnZ7lx12Jatr0pPI/k0yuz2hnHGSmp8nglDthe30qLqYhMzkFm2NJmxvkZsT5UU5p2oQ4OWZLKpvKubltXmpNvqIi7eoFPF9haJ0xlqEdioCXIXi5q5M3TCCgZZGxfMXF85RLO62yMn1PwBMvmzt6oraGtnf5CZQcZ07L0zj4iMbJ0zuMDZdtCROyplht5FRQ6eh4w5SIxcMm6TREbRP0umK3Nmbdb149b4OsJW1cQVubdjhmRaW2WxuYefnztvk4AnV12Pn6X+es8Mau4Bj5CpI/kvragixnxfNVMqS8jLPYOq++vI4X8ftr61bIVdzUy6G9MoLzQxUjGOT15IciUqC+fsZDarWWKs1yYZOEIZRDZJay2twsxSoLu3GoNydhe/bwM9tw1hSjuhMx4RXWV07iTJjFJeh9/oiehBQJsgNI+qMsE+mxMZjhsBZoqqsLvUd/rstPkqolenrgr65m2hRb8T2Sr/a+cJTBcQYq4PdsswODk0+PjupSlDAl2FCvs4/U8NA8Wdx4sxDjb/2DC12r3y0LeumZpxn7BaH8FibOnkWQgIl9NcidS88+C7OyQsd7kx0d8NdUr4GNTtbmqZULV1IUMH0wykrR98tjFDYOwzTnsVLqdKGtrTr4FCESPd3ofOop9vNpMIPNzbAqIwR4fOX8mAB1HTigmSHsUToY5d+0rQPfUOtWLbevphZjb/wdg384MU+WbHYhwAkI8pycdx88qOWQUpq/shKBhgaXYYbCZRIi42UhIn9wSzMXZWIVAGeZt+wGUCbBRH1oElZVmGy4CrOonG3phN+OJXTLGvpATZM3G7LICripltQBVzCxFoUvaG/XoYrJxbLH4pxYcuaZ1FDMW3epcgdm37EoV3QY8swWSq2K2nkBciI6JJGGC0BpBCidZVQyOoJ03A+fOe3VLdVypFKzm96LeQCjKIRASx38RYNAMAJd2HcyOdt8J5FhTikfzJCJUk2WVRcm2PbMhBSZJQVPPb7YowQBLAkC4dA13KwTKNLwDfXAKG+nxgX1+fIlKdub4jwBZVIZVH55B8q3n2KHTwF1j7J7LDfwZD/AF8EHj/+MNuo0gi31SA0M6IqGZuHc9Mhwd6mUPwAf29T5KNqf/x4iX7mTxj96bcIWzXA6m77fQzlnYNSUecDEMb/a7ngZhiPfWBhz0CPSRg3/jUJ1H4WvSr5X2SfFHLaCHCchfUvd2hnBEhsorEsP9Hs2Zc5Y+hs4n1ZXfZ2q5yukkTfl+YlrFPM5btv6feL1O+B/zwHNj9DV1y7e/NYZhiXbZxmvWCDANQOxd4Gel4EIQSu+nzc6PPoa61CDKUxdGMDEQI/+YkvbkRWMcbJ/Qv+Pj/QgOTCqnweuFXhZAOnYCr8GlNO+d9L7N95DQt7GW5e8OSlPbXUZ3puQ2gIMvwZET/OBH5M0O9jnPS/9XW8KPIb6b9+J8J52WOFi1zuvpTI9EkPJ7S3Q38LhWsbx2Z1Ezrvyc7T1jUD3AaCK2hK5l7f6kN2OVE7nPf9F67fa9Nnl41xk2rUtBC4gmyBdEjp+SGGEsYydUAj3O8C1MkhqeYMuC/Bx7RdLwEzwJMC/8BMgRNmbvi4hO6E51mXCX8Yr9CqdT7sdt/3UEzgfwGWjoSGvrYcFH+dGu8z/snzEDbQ/QwAP0g4eAm76ERUxrJRz/t4hFLVUwLqVYcF+dpbv0CaxeC93qUTdWUD1jVQ2Ujn0XS2/TrtsA4P0UWpn4qR8rzhuYvRfHUhNtaG8OoZLjyk3FDFmH1ZzQpn5HwWpZZP42WtqWaHUjOxqaQeiVkv21Rr7L86gFoumlh5j7nnGRSLY5iDeX4jRf3b9X4ABADgctRMdEJWtAAAAAElFTkSuQmCC);
background-repeat: no-repeat;
background-position: center center;
.npm-logo {
width: 79px;
height: @mainHeaderHeight;
background-image: url( -/logo-sm );
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE8AAAAoCAYAAAC/xadkAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMDA4QUE4NzQ1QkMxMUU0QTVCMUYwQjdEREYxNENGMiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozMDA4QUE4ODQ1QkMxMUU0QTVCMUYwQjdEREYxNENGMiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjMwMDhBQTg1NDVCQzExRTRBNUIxRjBCN0RERjE0Q0YyIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjMwMDhBQTg2NDVCQzExRTRBNUIxRjBCN0RERjE0Q0YyIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+ypkYZgAACelJREFUeNrsWmtsHFcZPXd2Zh9+r+31+5W4tSPSoKZUKgpIgdAWqorSULVFqJREKpVAAqRKhfIPKfCDtP2TKilpRRACUpBC+UlbQRG0RUmo0gBtWoyT2HHstR0/Yq/X9j5mh/PdmfX7te42tZyMdO2dmTt3vnvu+Z53lHOm9VcwQntRuvMq0qMKmTSgDNw4Fh4OG3Gxyh3E/l2GZOxVE0UtDShqLUbCLkb9o0D6KmAnbgA4D7cMcbMIXAUw+BoQ2QXEu5pMAjqB2q8Cl34BXDwC1D3IzkkgNUEAfTeAE+B8IWJhAl0vAKECoOa7wIVDEwbsaQUMA03fAMJbgJ5fAxlS1Crjg/Z1DhznbxbzB1l3+SWguBpo2cdzamd6wjBcdqXYeoHa+8m8T/PncSBJ5gUiHCB9nQLHefvLCRK1sPe3QNUtQAO1ElE2MWumcJHEg881iE43ULGbQIdJ0RNA5V1EuxWY7r++VFiAC5JlcRLqyp+olV8CSm938RGn4foDwyR2xowzcQy3Q8l24KYi6vVviHyc6ryTAPa5OLt/Nq9HddiCddTM94GxvwFbHwIKb6YpE+CEa9n5K2HewoOXMj1AAQfY9h2g8xi1mjpevYdATnoob+JQxEeHcOUNauYHQNtjVN0KFw8sgEppFyJQLmQTDWRmgP3LXQDPU+ejpK+/lNeTm5R9jlZEpEgQRbJse5z//ZxvdDFw7mHM5eHCe2wxtnqglfHfMEOZcJt7eVOyjzA4hGP0TaD8EZ7TWeKC5w+WfsBc1o7xVvpqCom+Dhi+KWCSIMbb+QLxzJnNCZ6iyo50w4l2kHRjCDYTHstw7eDi/ssxj51VGMOvnsK5b55Awc0RbwXObn6HQVW1Y2/DoMbu/PN+BBoreTm21LxJL0MtO5AiroKvMg0XY8fa/GGKIZ7AdCMzzatltEwJMkl4DsNZDJ7PoPOxYIQs/vZhuqsL9tRkbuwj5YNNTfCVlOhUJzM1hamLXbnPyW8huGWr+9uykIj2ITU8nD9NoJxmaSlVtYmWyeY71GpDk3mJtLGMN5kTM6ZJ5Rgan3gCwYZGYrB2m6eYxPQfP474++f072BjI5p/8EPK6qzZ78hzqZER9L34ol7U6YEBRO67D6W7djGDyo/9lXfE33sX/S8d50IVr7IoyrN5RSZ/rZLD2jbsiQlUP/wwAnX1OQs2+vpfMPbWmxTKD5MMrHrwodyJkUrj8pHDWuzk4ABKP/NZVD3wQF41dowL2/PcIQRqi9eGNxy1Ou/F9lFtk/396xIqk0jQa1nalgiL13Mk+nq9kJQpkeVnJHA17+ZOzIDi2Gu0BBKq5KdwlxocRCaVdGPNUAhWRcU6ihhpjnNF2x+HbDc5hq+gIOdx7PExpMfHKYihF91fVT0TVNjxONI0AbKQ8h4rEtELm7tXltGh8mJxz+3fh7fvuAOnb7sVnU8+uT52RaN454t348wX9uDUJ3dg+JVX1jVOz+HDOLnjFpzZvRv/2bsX9uTkzL0rf3wZJ7d/Au/cfRfO7Pk8ndfFnB2LDttsnZ7lx12Jatr0pPI/k0yuz2hnHGSmp8nglDthe30qLqYhMzkFm2NJmxvkZsT5UU5p2oQ4OWZLKpvKubltXmpNvqIi7eoFPF9haJ0xlqEdioCXIXi5q5M3TCCgZZGxfMXF85RLO62yMn1PwBMvmzt6oraGtnf5CZQcZ07L0zj4iMbJ0zuMDZdtCROyplht5FRQ6eh4w5SIxcMm6TREbRP0umK3Nmbdb149b4OsJW1cQVubdjhmRaW2WxuYefnztvk4AnV12Pn6X+es8Mau4Bj5CpI/kvragixnxfNVMqS8jLPYOq++vI4X8ftr61bIVdzUy6G9MoLzQxUjGOT15IciUqC+fsZDarWWKs1yYZOEIZRDZJay2twsxSoLu3GoNydhe/bwM9tw1hSjuhMx4RXWV07iTJjFJeh9/oiehBQJsgNI+qMsE+mxMZjhsBZoqqsLvUd/rstPkqolenrgr65m2hRb8T2Sr/a+cJTBcQYq4PdsswODk0+PjupSlDAl2FCvs4/U8NA8Wdx4sxDjb/2DC12r3y0LeumZpxn7BaH8FibOnkWQgIl9NcidS88+C7OyQsd7kx0d8NdUr4GNTtbmqZULV1IUMH0wykrR98tjFDYOwzTnsVLqdKGtrTr4FCESPd3ofOop9vNpMIPNzbAqIwR4fOX8mAB1HTigmSHsUToY5d+0rQPfUOtWLbevphZjb/wdg384MU+WbHYhwAkI8pycdx88qOWQUpq/shKBhgaXYYbCZRIi42UhIn9wSzMXZWIVAGeZt+wGUCbBRH1oElZVmGy4CrOonG3phN+OJXTLGvpATZM3G7LICripltQBVzCxFoUvaG/XoYrJxbLH4pxYcuaZ1FDMW3epcgdm37EoV3QY8swWSq2K2nkBciI6JJGGC0BpBCidZVQyOoJ03A+fOe3VLdVypFKzm96LeQCjKIRASx38RYNAMAJd2HcyOdt8J5FhTikfzJCJUk2WVRcm2PbMhBSZJQVPPb7YowQBLAkC4dA13KwTKNLwDfXAKG+nxgX1+fIlKdub4jwBZVIZVH55B8q3n2KHTwF1j7J7LDfwZD/AF8EHj/+MNuo0gi31SA0M6IqGZuHc9Mhwd6mUPwAf29T5KNqf/x4iX7mTxj96bcIWzXA6m77fQzlnYNSUecDEMb/a7ngZhiPfWBhz0CPSRg3/jUJ1H4WvSr5X2SfFHLaCHCchfUvd2hnBEhsorEsP9Hs2Zc5Y+hs4n1ZXfZ2q5yukkTfl+YlrFPM5btv6feL1O+B/zwHNj9DV1y7e/NYZhiXbZxmvWCDANQOxd4Gel4EIQSu+nzc6PPoa61CDKUxdGMDEQI/+YkvbkRWMcbJ/Qv+Pj/QgOTCqnweuFXhZAOnYCr8GlNO+d9L7N95DQt7GW5e8OSlPbXUZ3puQ2gIMvwZET/OBH5M0O9jnPS/9XW8KPIb6b9+J8J52WOFi1zuvpTI9EkPJ7S3Q38LhWsbx2Z1Ezrvyc7T1jUD3AaCK2hK5l7f6kN2OVE7nPf9F67fa9Nnl41xk2rUtBC4gmyBdEjp+SGGEsYydUAj3O8C1MkhqeYMuC/Bx7RdLwEzwJMC/8BMgRNmbvi4hO6E51mXCX8Yr9CqdT7sdt/3UEzgfwGWjoSGvrYcFH+dGu8z/snzEDbQ/QwAP0g4eAm76ERUxrJRz/t4hFLVUwLqVYcF+dpbv0CaxeC93qUTdWUD1jVQ2Ujn0XS2/TrtsA4P0UWpn4qR8rzhuYvRfHUhNtaG8OoZLjyk3FDFmH1ZzQpn5HwWpZZP42WtqWaHUjOxqaQeiVkv21Rr7L86gFoumlh5j7nnGRSLY5iDeX4jRf3b9X4ABADgctRMdEJWtAAAAAElFTkSuQmCC);
background-repeat: no-repeat;
background-position: center center;
>a {
display: block;
width: 100%;
height: 100%;
}
}
>a {
display: block;
width: 100%;
height: 100%;
}
}
.setup {
line-height: 1.3em;
padding-top: 5px;
}
.setup {
line-height: 1.3em;
padding-top: 5px;
}
.packages-header {
border-bottom: @headerBorderWidth solid #e6e6e6;
.packages-header {
border-bottom: @headerBorderWidth solid #e6e6e6;
.search-container {
top: 9px;
.search-container {
top: 9px;
.search-icon {
background: #e6e6e6;
}
}
}
.search-icon {
background: #e6e6e6;
}
}
}
.sm-registry-info {
height: @smRegistryInfoHeight;
line-height: 1.7em;
}
}
.sm-registry-info {
height: @smRegistryInfoHeight;
line-height: 1.7em;
}
}
.content {
padding-top: @mainHeaderHeight + @packagesHeaderHeight + 10;
.content {
padding-top: @mainHeaderHeight + @packagesHeaderHeight + 10;
.entry {
.transition(height .3s);
padding: 9px 10px;
overflow: hidden;
border-bottom: 1px solid #E7E7E7;
.entry {
.transition(height .3s);
padding: 9px 10px;
overflow: hidden;
border-bottom: 1px solid #E7E7E7;
&:last-child {
border-bottom: none;
}
&:last-child {
border-bottom: none;
}
&:nth-child( even ) {
background: @entryBg;
}
&:nth-child( even ) {
background: @entryBg;
}
.title {
margin: 0 0 5px 10px;
}
.title {
margin: 0 0 5px 10px;
}
.description {
margin: 0 0 0 18px;
font-size: 13px;
}
.description {
margin: 0 0 0 18px;
font-size: 13px;
}
.name:hover {
text-decoration: none;
}
.name:hover {
text-decoration: none;
}
.name:before {
margin: 0;
margin-left: -10px;
.transformTransition(.2s);
}
.name:before {
margin: 0;
margin-left: -10px;
.transformTransition(.2s);
}
&.open .name:before {
.rotate(90deg);
}
&.open .name:before {
.rotate(90deg);
}
.version {
color: #666;
}
.version {
color: #666;
}
.author {
color: #666;
}
.author {
color: #666;
}
.readme {
font-size: 14px;
margin-top: 10px;
background: @white;
padding: 10px 12px;
.border-radius(3px);
border: 1px solid darken( @entryBg, 10% );
}
}
}
.readme {
font-size: 14px;
margin-top: 10px;
background: @white;
padding: 10px 12px;
.border-radius(3px);
border: 1px solid darken( @entryBg, 10% );
}
}
}
}
.pkg-search-container {
display: none;
display: none;
}
.packages-container {
.search-ajax {
display: block;
margin: 50px auto;
}
.search-ajax {
display: block;
margin: 50px auto;
}
}
.no-results {
text-align: center;
margin: 50px 0;
color: #888;
text-align: center;
margin: 50px 0;
color: #888;
big {
font-size: 38px;
margin-bottom: 8px;
}
big {
font-size: 38px;
margin-bottom: 8px;
}
code {
font-size: 1.2em;
}
code {
font-size: 1.2em;
}
}
.red {
color: @npmRed;
color: @npmRed;
}
.light-red {
color: lighten( @npmRed, 10% );
color: lighten( @npmRed, 10% );
}
.white {
color: @white !important;
color: @white !important;
}
.red-bg {
background: @npmRed;
background: @npmRed;
}
.light-red-bg {
background: lighten( @npmRed, 10% );
background: lighten( @npmRed, 10% );
}
.no-bg {
background: none !important;
background: none !important;
}
.no-border {
border: none !important;
border: none !important;
}
.no-rnd-cnr {
.border-radius( 0 );
.border-radius( 0 );
}
.center {
text-align: center;
text-align: center;
}
.login-btn {
margin-left: 10px;
margin-top: 5px;
margin-left: 10px;
margin-top: 5px;
}
.pad-right-10 {
padding-right: 10px;
padding-right: 10px;
}
.inline-block {
display: inline-block;
display: inline-block;
}

View file

@ -1,20 +1,20 @@
<div class="entry" data-name="{{ name }}" data-version="{{ version }}">
<div class="row">
<div class="col-md-8 col-sm-8">
<h4 class="title">
<a class='name icon-angle-right red' href='javascript:void(0)'>{{ name }}</a>
<small class='version'>v{{ version }}</small>
</h4>
</div>
<div class="col-md-4 col-sm-4">
<div class="author pull-right">
<small>By: {{ _npmUser.name }}</small>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<p class="description">{{ description }}</p>
</div>
</div>
<div class="row">
<div class="col-md-8 col-sm-8">
<h4 class="title">
<a class='name icon-angle-right red' href='javascript:void(0)'>{{ name }}</a>
<small class='version'>v{{ version }}</small>
</h4>
</div>
<div class="col-md-4 col-sm-4">
<div class="author pull-right">
<small>By: {{ _npmUser.name }}</small>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<p class="description">{{ description }}</p>
</div>
</div>
</div>

View file

@ -1,127 +1,127 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<title>{{ name }}</title>
<head>
<meta charset="utf-8">
<title>{{ name }}</title>
<link rel="icon" type="image/ico" href="{{ baseUrl }}/-/static/favicon.png"/>
<link rel="stylesheet" type="text/css" href="{{ baseUrl }}/-/static/main.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body class="body">
<header class="main-header">
<nav class="navbar navbar-default red-bg white no-border no-rnd-cnr" role="navigation">
<div class="container">
<div class="navbar-header clearfix">
<div class="npm-logo brand">
<a href="{{ baseUrl }}"></a>
</div>
<link rel="icon" type="image/ico" href="{{ baseUrl }}/-/static/favicon.png"/>
<link rel="stylesheet" type="text/css" href="{{ baseUrl }}/-/static/main.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body class="body">
<header class="main-header">
<nav class="navbar navbar-default red-bg white no-border no-rnd-cnr" role="navigation">
<div class="container">
<div class="navbar-header clearfix">
<div class="npm-logo brand">
<a href="{{ baseUrl }}"></a>
</div>
<!-- login/logout for small devices -->
<div class="pull-right visible-xs pad-right-10">
<div>
{{#if username}}
<p class="white no-bg navbar-text pad-right-10 inline-block">Hi {{username}}</p>
<button type="submit" class="btn btn-danger inline-block js-userLogoutBtn">Logout</button>
{{else}}
<p class="white no-bg navbar-text pad-right-10 inline-block">&nbsp;</p>
<button type="submit" class="btn btn-danger inline-block" data-toggle="modal" data-target="#login-form" onclick="return false">Login</button>
{{/if}}
</div>
</div>
</div>
<!-- login/logout for small devices -->
<div class="pull-right visible-xs pad-right-10">
<div>
{{#if username}}
<p class="white no-bg navbar-text pad-right-10 inline-block">Hi {{username}}</p>
<button type="submit" class="btn btn-danger inline-block js-userLogoutBtn">Logout</button>
{{else}}
<p class="white no-bg navbar-text pad-right-10 inline-block">&nbsp;</p>
<button type="submit" class="btn btn-danger inline-block" data-toggle="modal" data-target="#login-form" onclick="return false">Login</button>
{{/if}}
</div>
</div>
</div>
<div class="navbar-left hidden-xs">&nbsp;&nbsp;</div>
<div class="navbar-left hidden-xs">&nbsp;&nbsp;</div>
<div class="navbar-left setup hidden-xs">
<code class="white no-bg">npm set registry {{ baseUrl }}</code><br>
<code class="white no-bg">npm adduser --registry {{ baseUrl }}</code>
</div>
<div class="navbar-left setup hidden-xs">
<code class="white no-bg">npm set registry {{ baseUrl }}</code><br>
<code class="white no-bg">npm adduser --registry {{ baseUrl }}</code>
</div>
<!-- login/logout for large devices -->
<div class="navbar-collapse collapse">
<div class="navbar-right">
<form class="navbar-form navbar-right">
{{#if username}}
<p class="white no-bg pad-right-10 inline-block">Hi {{username}}</p>
<button type="submit" class="btn btn-danger inline-block js-userLogoutBtn">Logout</button>
{{else}}
<button type="submit" class="btn btn-danger inline-block" data-toggle="modal" data-target="#login-form" onclick="return false">Login</button>
{{/if}}
</form>
</div>
</div>
<!-- login/logout for large devices -->
<div class="navbar-collapse collapse">
<div class="navbar-right">
<form class="navbar-form navbar-right">
{{#if username}}
<p class="white no-bg pad-right-10 inline-block">Hi {{username}}</p>
<button type="submit" class="btn btn-danger inline-block js-userLogoutBtn">Logout</button>
{{else}}
<button type="submit" class="btn btn-danger inline-block" data-toggle="modal" data-target="#login-form" onclick="return false">Login</button>
{{/if}}
</form>
</div>
</div>
</div>
</nav>
<header class="sm-registry-info light-red-bg center hidden-sm hidden-lg hidden-md">
<code class="white no-bg">{{ baseUrl }}</code><br>
</header>
<header class="packages-header container">
<div class="row">
<div class="col-md-5 hidden-xs hidden-sm">
<h2 class="title">Available Packages</h2>
</div>
<div class="col-md-4 col-md-offset-3 col-sm-12">
<form id='search-form'>
<div class="input-group input-group-lg search-container">
<input type="text" class="form-control" placeholder="Search for packages">
<span class="input-group-btn">
<button class="btn btn-default search-icon js-search-btn"><i class="icon-search"></i></button>
</span>
</div>
</form>
</div>
</div>
</header>
</header>
</div>
</nav>
<header class="sm-registry-info light-red-bg center hidden-sm hidden-lg hidden-md">
<code class="white no-bg">{{ baseUrl }}</code><br>
</header>
<header class="packages-header container">
<div class="row">
<div class="col-md-5 hidden-xs hidden-sm">
<h2 class="title">Available Packages</h2>
</div>
<div class="col-md-4 col-md-offset-3 col-sm-12">
<form id='search-form'>
<div class="input-group input-group-lg search-container">
<input type="text" class="form-control" placeholder="Search for packages">
<span class="input-group-btn">
<button class="btn btn-default search-icon js-search-btn"><i class="icon-search"></i></button>
</span>
</div>
</form>
</div>
</div>
</header>
</header>
<section class="content container packages-container" id="all-packages">
{{#each packages}}
{{> entry}}
{{/each}}
<section class="content container packages-container" id="all-packages">
{{#each packages}}
{{> entry}}
{{/each}}
{{#unless packages.length}}
<div class='no-results'>
<big>No Packages</big><br>
Use <code>npm publish</code>
</div>
{{/unless}}
</section>
{{#unless packages.length}}
<div class='no-results'>
<big>No Packages</big><br>
Use <code>npm publish</code>
</div>
{{/unless}}
</section>
<section class="content container pkg-search-container" id="search-results"></section>
<section class="content container pkg-search-container" id="search-results"></section>
<div class="modal fade" id="login-form" tabindex="-1" role="dialog" aria-labelledby="login-form-label" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
<h5 class="modal-title" id="login-form-label">Welcome back</h5>
</div>
<form role="form" action="{{ baseUrl }}/-/login" method="post" id="login-form" autocomplete="off">
<div class="modal-body">
<div class="form-group">
<label for="user" class="sr-only">Email</label>
<input name="user" id="user" class="form-control" placeholder="Username" type="text">
</div>
<div class="form-group">
<label for="pass" class="sr-only">Password</label>
<input name="pass" id="pass" class="form-control" placeholder="Password" type="password">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Log in</button>
</div>
</form>
</div>
</div>
</div>
<div class="modal fade" id="login-form" tabindex="-1" role="dialog" aria-labelledby="login-form-label" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
<h5 class="modal-title" id="login-form-label">Welcome back</h5>
</div>
<form role="form" action="{{ baseUrl }}/-/login" method="post" id="login-form" autocomplete="off">
<div class="modal-body">
<div class="form-group">
<label for="user" class="sr-only">Email</label>
<input name="user" id="user" class="form-control" placeholder="Username" type="text">
</div>
<div class="form-group">
<label for="pass" class="sr-only">Password</label>
<input name="pass" id="pass" class="form-control" placeholder="Password" type="password">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Log in</button>
</div>
</form>
</div>
</div>
</div>
<form action="{{ baseUrl }}/-/logout" method="post" class="hide" id="userLogoutForm"></form>
<form action="{{ baseUrl }}/-/logout" method="post" class="hide" id="userLogoutForm"></form>
<script src="{{ baseUrl }}/-/static/jquery.min.js"></script>
<script type='text/javascript' src='{{ baseUrl }}/-/static/main.js'></script>
</body>
<script src="{{ baseUrl }}/-/static/jquery.min.js"></script>
<script type='text/javascript' src='{{ baseUrl }}/-/static/main.js'></script>
</body>
</html>

View file

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

View file

@ -6,7 +6,8 @@ require('./bootstrap-modal')
require('./search')
require('./entry')
var $ = require('unopinionate').selector
$(document).on('click', '.js-userLogoutBtn', function() {
$('#userLogoutForm').submit()
return false
$('#userLogoutForm').submit()
return false
})

View file

@ -1,81 +1,79 @@
var $ = require('unopinionate').selector,
template = require('../entry.hbs'),
onScroll = require('onscroll');
var $ = require('unopinionate').selector
var template = require('../entry.hbs')
var onScroll = require('onscroll')
$(function() {
'use strict';
;(function(window, document) {
var $form = $('#search-form')
var $input = $form.find('input')
var $body = $('body')
var $clear = $form.find('.clear')
var $searchResults = $('#search-results')
var $pkgListing = $('#all-packages')
var $searchBtn = $('.js-search-btn')
var request
var currentResults
(function( window, document ) {
var toggle = function(validQuery) {
$searchResults.toggleClass('show', validQuery)
$pkgListing.toggleClass('hide', validQuery)
var $form = $('#search-form');
var $input = $form.find('input');
var $body = $('body');
var $clear = $form.find('.clear');
var $searchResults = $("#search-results");
var $pkgListing = $("#all-packages");
var $searchBtn = $('.js-search-btn');
var request;
var currentResults;
$searchBtn.find('i').toggleClass('icon-cancel', validQuery)
$searchBtn.find('i').toggleClass('icon-search', !validQuery)
}
var toggle = function( validQuery ) {
$searchResults.toggleClass( 'show', validQuery );
$pkgListing.toggleClass( 'hide', validQuery );
$form.bind('submit keyup', function(e) {
var q, qBool
e.preventDefault()
$searchBtn.find('i').toggleClass( 'icon-cancel', validQuery );
$searchBtn.find('i').toggleClass( 'icon-search', !validQuery );
};
q = $input.val()
qBool = (q !== '')
$form.bind('submit keyup', function(e) {
var q, qBool;
toggle(qBool)
e.preventDefault();
if (!qBool) {
if (request && typeof request.abort === 'function') {
request.abort()
}
q = $input.val();
qBool = q !== '';
currentResults = null
$searchResults.html('')
return
}
toggle( qBool );
if (request && typeof request.abort === 'function') {
request.abort()
}
if( !qBool ) {
if( request && typeof request.abort === 'function' ) {
request.abort();
}
if (!currentResults) {
$searchResults.html(
"<img class='search-ajax' src='-/static/ajax.gif' alt='Spinner'/>")
}
currentResults = null;
$searchResults.html('');
return;
}
request = $.getJSON('-/search/' + q, function( results ) {
currentResults = results
if( request && typeof request.abort === 'function' ) {
request.abort();
}
if (results.length > 0) {
var html = ''
if( !currentResults ) {
$searchResults.html( "<img class='search-ajax' src='-/static/ajax.gif' alt='Spinner'/>" );
}
$.each(results, function(i, entry) {
html += template(entry)
})
request = $.getJSON('-/search/' + q, function( results ) {
currentResults = results;
$searchResults.html(html)
} else {
$searchResults.html(
"<div class='no-results'><big>No Results</big></div>")
}
})
})
if( results.length > 0 ) {
var html = '';
$(document).on('click', '.icon-cancel', function(e) {
e.preventDefault()
$input.val('')
$form.keyup()
})
$.each(results, function( i, entry ) {
html += template( entry );
});
$searchResults.html(html);
} else {
$searchResults.html("<div class='no-results'><big>No Results</big></div>");
}
});
});
$( document ).on( 'click', '.icon-cancel', function( e ) {
e.preventDefault();
$input.val('');
$form.keyup();
});
})( window, window.document );
});
})(window, window.document)
})

View file

@ -1,218 +1,221 @@
var Path = require('path')
, crypto = require('crypto')
, Error = require('http-errors')
, Logger = require('./logger')
, assert = require('assert')
var assert = require('assert')
var Crypto = require('crypto')
var Error = require('http-errors')
var Path = require('path')
var Logger = require('./logger')
module.exports = Auth
function Auth(config) {
if (!(this instanceof Auth)) return new Auth(config)
this.config = config
this.logger = Logger.logger.child({sub: 'auth'})
var stuff = {
config: config,
logger: this.logger,
}
var self = Object.create(Auth.prototype)
self.config = config
self.logger = Logger.logger.child({ sub: 'auth' })
if (config.users_file) {
if (!config.auth || !config.auth.htpasswd) {
// b/w compat
config.auth = config.auth || {}
config.auth.htpasswd = {file: config.users_file}
}
}
var stuff = {
config: config,
logger: self.logger,
}
this.plugins = Object.keys(config.auth || {}).map(function(p) {
var plugin, name
try {
name = 'sinopia-' + p
plugin = require(name)
} catch(x) {
try {
name = p
plugin = require(name)
} catch(x) {}
}
if (config.users_file) {
if (!config.auth || !config.auth.htpasswd) {
// b/w compat
config.auth = config.auth || {}
config.auth.htpasswd = { file: config.users_file }
}
}
if (plugin == null) {
throw Error('"' + p + '" auth plugin not found\n'
+ 'try "npm install sinopia-' + p + '"')
}
self.plugins = Object.keys(config.auth || {}).map(function(p) {
var plugin, name
try {
name = 'sinopia-' + p
plugin = require(name)
} catch(x) {
try {
name = p
plugin = require(name)
} catch(x) {}
}
if (typeof(plugin) !== 'function')
throw Error('"' + name + '" doesn\'t look like a valid auth plugin')
if (plugin == null) {
throw Error('"' + p + '" auth plugin not found\n'
+ 'try "npm install sinopia-' + p + '"')
}
plugin = plugin(config.auth[p], stuff)
if (typeof(plugin) !== 'function')
throw Error('"' + name + '" doesn\'t look like a valid auth plugin')
if (plugin == null || typeof(plugin.authenticate) !== 'function')
throw Error('"' + name + '" doesn\'t look like a valid auth plugin')
plugin = plugin(config.auth[p], stuff)
return plugin
})
if (plugin == null || typeof(plugin.authenticate) !== 'function')
throw Error('"' + name + '" doesn\'t look like a valid auth plugin')
this.plugins.unshift({
authenticate: function(user, password, cb) {
if (config.users != null
&& config.users[user] != null
&& (crypto.createHash('sha1').update(password).digest('hex')
=== config.users[user].password)
) {
return cb(null, [ user ])
}
return plugin
})
return cb()
},
self.plugins.unshift({
authenticate: function(user, password, cb) {
if (config.users != null
&& config.users[user] != null
&& (Crypto.createHash('sha1').update(password).digest('hex')
=== config.users[user].password)
) {
return cb(null, [ user ])
}
adduser: function(user, password, cb) {
if (config.users && config.users[user])
return cb(Error[403]('this user already exists'))
return cb()
},
return cb()
},
})
adduser: function(user, password, cb) {
if (config.users && config.users[user])
return cb( Error[403]('self user already exists') )
this.plugins.push({
authenticate: function(user, password, cb) {
return cb(Error[403]('bad username/password, access denied'))
},
return cb()
},
})
adduser: function(user, password, cb) {
return cb(Error[409]('registration is disabled'))
},
})
self.plugins.push({
authenticate: function(user, password, cb) {
return cb( Error[403]('bad username/password, access denied') )
},
adduser: function(user, password, cb) {
return cb( Error[409]('registration is disabled') )
},
})
return self
}
Auth.prototype.authenticate = function(user, password, cb) {
var plugins = this.plugins.slice(0)
var plugins = this.plugins.slice(0)
!function next() {
var p = plugins.shift()
p.authenticate(user, password, function(err, groups) {
if (err || groups) return cb(err, groups)
next()
})
}()
;(function next() {
var p = plugins.shift()
p.authenticate(user, password, function(err, groups) {
if (err || groups) return cb(err, groups)
next()
})
})()
}
Auth.prototype.add_user = function(user, password, cb) {
var plugins = this.plugins.slice(0)
var plugins = this.plugins.slice(0)
!function next() {
var p = plugins.shift()
var n = 'adduser'
if (typeof(p[n]) !== 'function') {
n = 'add_user'
}
if (typeof(p[n]) !== 'function') {
next()
} else {
p[n](user, password, function(err, ok) {
if (err || ok) return cb(err, ok)
next()
})
}
}()
;(function next() {
var p = plugins.shift()
var n = 'adduser'
if (typeof(p[n]) !== 'function') {
n = 'add_user'
}
if (typeof(p[n]) !== 'function') {
next()
} else {
p[n](user, password, function(err, ok) {
if (err || ok) return cb(err, ok)
next()
})
}
})()
}
Auth.prototype.auth_middleware = function() {
var self = this
return function(req, res, _next) {
req.pause()
function next(err) {
req.resume()
// uncomment this to reject users with bad auth headers
//return _next.apply(null, arguments)
var self = this
return function(req, res, _next) {
req.pause()
function next(err) {
req.resume()
// uncomment this to reject users with bad auth headers
//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()
}
// 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 (req.remote_user != null && req.remote_user.name !== undefined) return next()
req.remote_user = AnonymousUser()
if (req.remote_user != null && req.remote_user.name !== undefined)
return next()
req.remote_user = AnonymousUser()
var authorization = req.headers.authorization
if (authorization == null) return next()
var authorization = req.headers.authorization
if (authorization == null) return next()
var parts = authorization.split(' ')
var parts = authorization.split(' ')
if (parts.length !== 2) return next({
status: 400,
message: 'bad authorization header',
})
if (parts.length !== 2)
return next( Error[400]('bad authorization header') )
var scheme = parts[0]
, credentials = new Buffer(parts[1], 'base64').toString()
, index = credentials.indexOf(':')
var scheme = parts[0]
var credentials = Buffer(parts[1], 'base64').toString()
var index = credentials.indexOf(':')
if (scheme !== 'Basic' || index < 0) return next({
status: 400,
message: 'bad authorization header',
})
if (scheme !== 'Basic' || index < 0)
return next( Error[400]('bad authorization header') )
var user = credentials.slice(0, index)
, pass = credentials.slice(index + 1)
var user = credentials.slice(0, index)
var pass = credentials.slice(index + 1)
self.authenticate(user, pass, function(err, groups) {
if (!err && groups != null && groups != false) {
req.remote_user = AuthenticatedUser(user, groups)
next()
} else {
req.remote_user = AnonymousUser()
next(err)
}
})
}
self.authenticate(user, pass, function(err, groups) {
if (!err && groups != null && groups != false) {
req.remote_user = AuthenticatedUser(user, groups)
next()
} else {
req.remote_user = AnonymousUser()
next(err)
}
})
}
}
Auth.prototype.cookie_middleware = function() {
var self = this
return function(req, res, _next) {
req.pause()
function next(err) {
req.resume()
return _next()
}
var self = this
if (req.remote_user != null && req.remote_user.name !== undefined) return next()
req.remote_user = AnonymousUser()
return function(req, res, _next) {
req.pause()
function next(err) {
req.resume()
return _next()
}
var cookie = req.cookies.get('token')
if (cookie == null) return next()
if (req.remote_user != null && req.remote_user.name !== undefined)
return next()
var credentials = new Buffer(cookie, 'base64').toString()
var index = credentials.indexOf(':')
req.remote_user = AnonymousUser()
var user = credentials.slice(0, index)
var pass = credentials.slice(index + 1)
var cookie = req.cookies.get('token')
if (cookie == null) return next()
self.authenticate(user, pass, function(err, groups) {
if (!err && groups != null && groups != false) {
req.remote_user = AuthenticatedUser(user, groups)
next()
} else {
req.remote_user = AnonymousUser()
next(err)
}
})
}
var credentials = Buffer(cookie, 'base64').toString()
var index = credentials.indexOf(':')
var user = credentials.slice(0, index)
var pass = credentials.slice(index + 1)
self.authenticate(user, pass, function(err, groups) {
if (!err && groups != null && groups != false) {
req.remote_user = AuthenticatedUser(user, groups)
next()
} else {
req.remote_user = AnonymousUser()
next(err)
}
})
}
}
function AnonymousUser() {
return {
name: undefined,
// groups without '@' are going to be deprecated eventually
groups: ['$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous'],
}
return {
name: undefined,
// groups without '@' are going to be deprecated eventually
groups: [ '$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous' ],
}
}
function AuthenticatedUser(name, groups) {
groups = groups.concat(['$all', '$authenticated', '@all', '@authenticated', 'all'])
return {
name: name,
groups: groups,
}
groups = groups.concat([ '$all', '$authenticated', '@all', '@authenticated', 'all' ])
return {
name: name,
groups: groups,
}
}

View file

@ -3,147 +3,147 @@
/*eslint no-sync:0*/
if (process.getuid && process.getuid() === 0) {
global.console.error("Sinopia doesn't need superuser privileges. Don't run it under root.")
global.console.error("Sinopia doesn't need superuser privileges. Don't run it under root.")
}
process.title = 'sinopia'
try {
// for debugging memory leaks
// totally optional
require('heapdump')
} catch(err){}
// for debugging memory leaks
// totally optional
require('heapdump')
} catch(err) {}
var logger = require('./logger')
logger.setup() // default setup
var pkg_file = '../package.yaml'
, fs = require('fs')
, yaml = require('js-yaml')
, commander = require('commander')
, server = require('./index')
, crypto = require('crypto')
, Path = require('path')
, pkg = yaml.safeLoad(fs.readFileSync(__dirname + '/' + pkg_file, 'utf8'))
var commander = require('commander')
var fs = require('fs')
var YAML = require('js-yaml')
var Path = require('path')
var server = require('./index')
var pkg_file = '../package.yaml'
var pkg = YAML.safeLoad(fs.readFileSync(__dirname+'/'+ pkg_file, 'utf8'))
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(pkg.version)
.parse(process.argv)
.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(pkg.version)
.parse(process.argv)
if (commander.args.length == 1 && !commander.config) {
// handling "sinopia [config]" case if "-c" is missing in commandline
commander.config = commander.args.pop()
// handling "sinopia [config]" case if "-c" is missing in commandline
commander.config = commander.args.pop()
}
if (commander.args.length != 0) {
commander.help()
commander.help()
}
var config, config_path, have_question
try {
if (commander.config) {
config_path = commander.config
config = yaml.safeLoad(fs.readFileSync(config_path, 'utf8'))
} else {
config_path = './config.yaml'
try {
config = yaml.safeLoad(fs.readFileSync(config_path, 'utf8'))
} catch(err) {
var readline = require('readline')
var rl = readline.createInterface(process.stdin, process.stdout)
var timeout = setTimeout(function() {
global.console.log('I got tired waiting for an answer. Exitting...')
process.exit(1)
}, 20000)
if (commander.config) {
config_path = commander.config
config = YAML.safeLoad(fs.readFileSync(config_path, 'utf8'))
} else {
config_path = './config.yaml'
try {
config = YAML.safeLoad(fs.readFileSync(config_path, 'utf8'))
} catch(err) {
var readline = require('readline')
var rl = readline.createInterface(process.stdin, process.stdout)
var timeout = setTimeout(function() {
global.console.log('I got tired waiting for an answer. Exitting...')
process.exit(1)
}, 20000)
;(function askUser() {
have_question = true
rl.question('Config file doesn\'t exist, create a new one? (Y/n) ', function(x) {
clearTimeout(timeout)
if (x[0] == 'Y' || x[0] == 'y' || x === '') {
rl.close()
;(function askUser() {
have_question = true
rl.question('Config file doesn\'t exist, create a new one? (Y/n) ', function(x) {
clearTimeout(timeout)
if (x[0] == 'Y' || x[0] == 'y' || x === '') {
rl.close()
var created_config = require('../lib/config_gen')()
config = yaml.safeLoad(created_config.yaml)
write_config_banner(created_config, config)
fs.writeFileSync(config_path, created_config.yaml)
afterConfigLoad()
} else if (x[0] == 'N' || x[0] == 'n') {
rl.close()
global.console.log('So, you just accidentally run me in a wrong folder. Exitting...')
process.exit(1)
} else {
askUser()
}
})
})()
}
}
} catch(err) {
logger.logger.fatal({file: config_path, err: err}, 'cannot open config file @{file}: @{!err.message}')
process.exit(1)
var created_config = require('../lib/config_gen')()
config = YAML.safeLoad(created_config.yaml)
write_config_banner(created_config, config)
fs.writeFileSync(config_path, created_config.yaml)
afterConfigLoad()
} else if (x[0] == 'N' || x[0] == 'n') {
rl.close()
global.console.log('So, you just accidentally run me in a wrong folder. Exitting...')
process.exit(1)
} else {
askUser()
}
})
})()
}
}
} catch (err) {
logger.logger.fatal({ file: config_path, err: err }, 'cannot open config file @{file}: @{!err.message}')
process.exit(1)
}
if (!have_question) afterConfigLoad()
function get_hostport() {
// command line || config file || default
var hostport = commander.listen || String(config.listen || '') || '4873'
// command line || config file || default
var hostport = commander.listen || String(config.listen || '') || '4873'
hostport = hostport.split(':')
if (hostport.length < 2) {
hostport = [undefined, hostport[0]]
}
if (hostport[0] == null) {
hostport[0] = 'localhost'
}
return hostport
hostport = hostport.split(':')
if (hostport.length < 2) {
hostport = [ undefined, hostport[0] ]
}
if (hostport[0] == null) {
hostport[0] = 'localhost'
}
return hostport
}
function afterConfigLoad() {
if (!config.user_agent) config.user_agent = 'Sinopia/'+pkg.version
if (!config.self_path) config.self_path = Path.resolve(config_path)
if (!config.user_agent) config.user_agent = 'Sinopia/'+pkg.version
if (!config.self_path) config.self_path = Path.resolve(config_path)
logger.setup(config.logs)
logger.setup(config.logs)
var hostport = get_hostport()
server(config)
.listen(hostport[1], hostport[0])
.on('error', function(err) {
logger.logger.fatal({err: err}, 'cannot create server: @{err.message}')
process.exit(2)
})
var hostport = get_hostport()
server(config)
.listen(hostport[1], hostport[0])
.on('error', function(err) {
logger.logger.fatal({ err: err }, 'cannot create server: @{err.message}')
process.exit(2)
})
logger.logger.warn({addr: 'http://'+hostport[0]+':'+hostport[1]+'/', version: 'Sinopia/'+pkg.version}, 'Server is listening on @{addr}')
logger.logger.warn({ addr: 'http://'+hostport[0]+':'+hostport[1]+'/', version: 'Sinopia/'+pkg.version }, 'Server is listening on @{addr}')
// undocumented stuff for tests
if (typeof(process.send) === 'function') {
process.send({sinopia_started: hostport})
}
// undocumented stuff for tests
if (typeof(process.send) === 'function') {
process.send({ sinopia_started: hostport })
}
}
function write_config_banner(def, config) {
var hostport = get_hostport()
var log = global.console.log
var hostport = get_hostport()
var log = global.console.log
log('===========================================================')
log(' Creating a new configuration file: "%s"', config_path)
log(' ')
log(' If you want to setup npm to work with this registry,')
log(' run following commands:')
log(' ')
log(' $ npm set registry http://%s:%s/', hostport[0], hostport[1])
log(' $ npm set always-auth true')
log(' $ npm adduser')
log(' Username: %s', def.user)
log(' Password: %s', def.pass)
log('===========================================================')
log('===========================================================')
log(' Creating a new configuration file: "%s"', config_path)
log(' ')
log(' If you want to setup npm to work with this registry,')
log(' run following commands:')
log(' ')
log(' $ npm set registry http://%s:%s/', hostport[0], hostport[1])
log(' $ npm set always-auth true')
log(' $ npm adduser')
log(' Username: %s', def.user)
log(' Password: %s', def.pass)
log('===========================================================')
}
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,187 +1,189 @@
var assert = require('assert')
, crypto = require('crypto')
, Path = require('path')
, minimatch = require('minimatch')
, Error = require('http-errors')
, LocalList = require('./local-list')
, utils = require('./utils')
var assert = require('assert')
var Crypto = require('crypto')
var Error = require('http-errors')
var minimatch = require('minimatch')
var Path = require('path')
var LocalList = require('./local-list')
var Utils = require('./utils')
// [[a, [b, c]], d] -> [a, b, c, d]
function flatten(array) {
var result = []
for (var i=0; i<array.length; i++) {
if (Array.isArray(array[i])) {
result.push.apply(result, flatten(array[i]))
} else {
result.push(array[i])
}
}
return result
var result = []
for (var i=0; i<array.length; i++) {
if (Array.isArray(array[i])) {
result.push.apply(result, flatten(array[i]))
} else {
result.push(array[i])
}
}
return result
}
function Config(config) {
if (!(this instanceof Config)) return new Config(config)
for (var i in config) {
if (this[i] == null) this[i] = config[i]
}
var self = Object.create(Config.prototype)
for (var i in config) {
if (self[i] == null) self[i] = config[i]
}
// some weird shell scripts are valid yaml files parsed as string
assert.equal(typeof(config), 'object', 'CONFIG: this doesn\'t look like a valid config file')
// some weird shell scripts are valid yaml files parsed as string
assert.equal(typeof(config), 'object', 'CONFIG: self doesn\'t look like a valid config file')
assert(this.storage, 'CONFIG: storage path not defined')
this.localList = LocalList(
Path.join(
Path.resolve(Path.dirname(this.self_path), this.storage),
'.sinopia-db.json'
)
)
assert(self.storage, 'CONFIG: storage path not defined')
self.localList = LocalList(
Path.join(
Path.resolve(Path.dirname(self.self_path), self.storage),
'.sinopia-db.json'
)
)
var users = {all:true, anonymous:true, 'undefined':true, owner:true, none:true}
var users = {all:true, anonymous:true, 'undefined':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
}
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
}
;['users', 'uplinks', 'packages'].forEach(function(x) {
if (this[x] == null) this[x] = {}
assert(utils.is_object(this[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 this.users) check_user_or_uplink(i)
for (var i in this.uplinks) check_user_or_uplink(i)
for (var i in self.users) check_user_or_uplink(i)
for (var i in self.uplinks) check_user_or_uplink(i)
for (var i in this.users) {
assert(this.users[i].password, 'CONFIG: no password for user: ' + i)
assert(
typeof(this.users[i].password) === 'string' &&
this.users[i].password.match(/^[a-f0-9]{40}$/)
, 'CONFIG: wrong password format for user: ' + i + ', sha1 expected')
}
for (var i in self.users) {
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')
}
for (var i in this.uplinks) {
assert(this.uplinks[i].url, 'CONFIG: no url for uplink: ' + i)
assert(
typeof(this.uplinks[i].url) === 'string'
, 'CONFIG: wrong url format for uplink: ' + i)
this.uplinks[i].url = this.uplinks[i].url.replace(/\/$/, '')
}
for (var i in self.uplinks) {
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(/\/$/, '')
}
function check_userlist(i, hash, action) {
if (hash[action] == null) hash[action] = []
function check_userlist(i, hash, action) {
if (hash[action] == null) hash[action] = []
// if it's a string, split it to array
if (typeof(hash[action]) === 'string') {
hash[action] = hash[action].split(/\s+/)
}
// if it's a string, split it to array
if (typeof(hash[action]) === 'string') {
hash[action] = hash[action].split(/\s+/)
}
assert(
typeof(hash[action]) === 'object' &&
Array.isArray(hash[action])
, 'CONFIG: bad "'+i+'" package '+action+' description (array or string expected)')
hash[action] = flatten(hash[action])
}
assert(
typeof(hash[action]) === 'object' &&
Array.isArray(hash[action])
, 'CONFIG: bad "'+i+'" package '+action+' description (array or string expected)')
hash[action] = flatten(hash[action])
}
for (var i in this.packages) {
assert(
typeof(this.packages[i]) === 'object' &&
!Array.isArray(this.packages[i])
, 'CONFIG: bad "'+i+'" package description (object expected)')
for (var i in self.packages) {
assert(
typeof(self.packages[i]) === 'object' &&
!Array.isArray(self.packages[i])
, 'CONFIG: bad "'+i+'" package description (object expected)')
check_userlist(i, this.packages[i], 'allow_access')
check_userlist(i, this.packages[i], 'allow_publish')
check_userlist(i, this.packages[i], 'proxy_access')
check_userlist(i, this.packages[i], 'proxy_publish')
check_userlist(i, self.packages[i], 'allow_access')
check_userlist(i, self.packages[i], 'allow_publish')
check_userlist(i, self.packages[i], 'proxy_access')
check_userlist(i, self.packages[i], 'proxy_publish')
// deprecated
check_userlist(i, this.packages[i], 'access')
check_userlist(i, this.packages[i], 'proxy')
check_userlist(i, this.packages[i], 'publish')
}
// deprecated
check_userlist(i, self.packages[i], 'access')
check_userlist(i, self.packages[i], 'proxy')
check_userlist(i, self.packages[i], 'publish')
}
// loading these from ENV if aren't in config
;['http_proxy', 'https_proxy', 'no_proxy'].forEach((function(v) {
if (!(v in this)) {
this[v] = process.env[v] || process.env[v.toUpperCase()]
}
}).bind(this))
// loading these from ENV if aren't in config
;[ 'http_proxy', 'https_proxy', 'no_proxy' ].forEach((function(v) {
if (!(v in self)) {
self[v] = process.env[v] || process.env[v.toUpperCase()]
}
}).bind(self))
// unique identifier of this server (or a cluster), used to avoid loops
if (!this.server_id) {
this.server_id = crypto.pseudoRandomBytes(6).toString('hex')
}
// unique identifier of self server (or a cluster), used to avoid loops
if (!self.server_id) {
self.server_id = Crypto.pseudoRandomBytes(6).toString('hex')
}
if (this.ignore_latest_tag == null) this.ignore_latest_tag = false
if (self.ignore_latest_tag == null) self.ignore_latest_tag = false
return this
return self
}
function allow_action(package, who, action) {
return (this.get_package_setting(package, action) || []).reduce(function(prev, curr) {
if (typeof(who) === 'string' && curr === who) return true
if (Array.isArray(who) && who.indexOf(curr) !== -1) return true
return prev
}, false)
return (this.get_package_setting(package, action) || []).reduce(function(prev, curr) {
if (typeof(who) === 'string' && curr === who) return true
if (Array.isArray(who) && who.indexOf(curr) !== -1) return true
return prev
}, false)
}
Config.prototype.allow_access = function(package, user) {
return allow_action.call(this, package, user.groups, 'allow_access') || allow_action.call(this, package, user.groups, 'access')
return allow_action.call(this, package, user.groups, 'allow_access')
|| allow_action.call(this, package, user.groups, 'access')
}
Config.prototype.allow_publish = function(package, user) {
return allow_action.call(this, package, user.groups, 'allow_publish') || allow_action.call(this, package, user.groups, 'publish')
return allow_action.call(this, package, user.groups, 'allow_publish')
|| allow_action.call(this, package, user.groups, 'publish')
}
Config.prototype.proxy_access = function(package, uplink) {
return allow_action.call(this, package, uplink, 'proxy_access') || allow_action.call(this, package, uplink, 'proxy')
return allow_action.call(this, package, uplink, 'proxy_access')
|| allow_action.call(this, package, uplink, 'proxy')
}
Config.prototype.proxy_publish = function(package, uplink) {
throw new Error('deprecated')
//return allow_action.call(this, package, uplink, 'proxy_publish')
throw Error('deprecated')
//return allow_action.call(this, package, uplink, 'proxy_publish')
}
Config.prototype.get_package_setting = function(package, setting) {
for (var i in this.packages) {
if (minimatch.makeRe(i).exec(package)) {
return this.packages[i][setting]
}
}
return undefined
for (var i in this.packages) {
if (minimatch.makeRe(i).exec(package)) {
return this.packages[i][setting]
}
}
return undefined
}
module.exports = Config
var 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,
'': 1000,
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
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 (!m
|| parse_interval_table[m[4]] >= last_suffix
|| (m[4] === '' && last_suffix !== Infinity)) {
throw new Error('invalid interval: ' + interval)
}
last_suffix = parse_interval_table[m[4]]
result += Number(m[1]) * parse_interval_table[m[4]]
})
return result
var result = 0
var 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 (!m
|| 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
}

View file

@ -1,19 +1,19 @@
var fs = require('fs')
, crypto = require('crypto')
var Crypto = require('crypto')
var fs = require('fs')
module.exports = function create_config() {
var pass = crypto.randomBytes(8).toString('base64').replace(/[=+\/]/g, '')
, pass_digest = crypto.createHash('sha1').update(pass).digest('hex')
var pass = Crypto.randomBytes(8).toString('base64').replace(/[=+\/]/g, '')
var pass_digest = Crypto.createHash('sha1').update(pass).digest('hex')
/*eslint no-sync:0*/
var config = fs.readFileSync(require.resolve('./config_def.yaml'), 'utf8')
/*eslint no-sync:0*/
var config = fs.readFileSync(require.resolve('./config_def.yaml'), 'utf8')
config = config.replace('__PASSWORD__', pass_digest)
config = config.replace('__PASSWORD__', pass_digest)
return {
yaml: config,
user: 'admin',
pass: pass,
}
return {
yaml: config,
user: 'admin',
pass: pass,
}
}

View file

@ -1,112 +1,113 @@
var fs = require('fs')
var marked = require('marked')
var search = require('./search')
var Cookies = require('cookies')
var express = require('express')
var fs = require('fs')
var marked = require('marked')
var Handlebars = require('handlebars')
var Error = require('http-errors')
var express = require('express')
var Cookies = require('cookies')
var Error = require('http-errors')
var Search = require('./search')
module.exports = function(config, auth, storage) {
var app = express()
app.use(Cookies.express())
app.use(express.urlencoded())
app.use(auth.cookie_middleware())
app.use(function(req, res, next) {
// disable loading in frames (clickjacking, etc.)
res.header('X-Frame-Options', 'deny')
next()
})
var app = express()
app.use(Cookies.express())
app.use(express.urlencoded())
app.use(auth.cookie_middleware())
app.use(function(req, res, next) {
// disable loading in frames (clickjacking, etc.)
res.header('X-Frame-Options', 'deny')
next()
})
search.configureStorage(storage)
Search.configureStorage(storage)
Handlebars.registerPartial('entry', fs.readFileSync(require.resolve('./GUI/entry.hbs'), 'utf8'));
var template = Handlebars.compile(fs.readFileSync(require.resolve('./GUI/index.hbs'), 'utf8'));
Handlebars.registerPartial('entry', fs.readFileSync(require.resolve('./GUI/entry.hbs'), 'utf8'))
var template = Handlebars.compile(fs.readFileSync(require.resolve('./GUI/index.hbs'), 'utf8'))
app.get('/', function(req, res, next) {
var base = config.url_prefix || req.protocol + '://' + req.get('host')
res.setHeader('Content-Type', 'text/html');
app.get('/', function(req, res, next) {
var base = config.url_prefix || req.protocol + '://' + req.get('host')
res.setHeader('Content-Type', 'text/html')
storage.get_local(function(err, packages) {
res.send(template({
name: config.web.title || "Sinopia",
packages: packages,
baseUrl: base,
username: req.remote_user.name,
}));
});
});
storage.get_local(function(err, packages) {
if (err) throw err // that function shouldn't produce any
res.send(template({
name: config.web.title || 'Sinopia',
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
fs.exists(file, function(exists) {
if (exists) {
res.sendfile(file)
} else {
res.status(404);
res.send("File Not Found")
}
})
})
// Static
app.get('/-/static/:filename', function(req, res, next) {
var file = __dirname + '/static/' + req.params.filename
fs.exists(file, function(exists) {
if (exists) {
res.sendfile(file)
} else {
next( Error[404]('File Not Found') )
}
})
})
app.get('/-/logo', function(req, res, next) {
res.sendfile(config.web.logo ? config.web.logo : __dirname + "/static/logo.png")
})
app.get('/-/logo', function(req, res, next) {
res.sendfile(config.web.logo ? config.web.logo : __dirname + '/static/logo.png')
})
app.get('/-/logo-sm', function(req, res, next) {
res.sendfile(config.web.logosm ? config.web.logosm : __dirname + "/static/logo-sm.png")
})
app.get('/-/logo-sm', function(req, res, next) {
res.sendfile(config.web.logosm ? config.web.logosm : __dirname + '/static/logo-sm.png')
})
app.post('/-/login', function(req, res, next) {
var base = config.url_prefix || req.protocol + '://' + req.get('host')
res.cookies.set('token', Buffer(req.body.user + ':' + req.body.pass).toString('base64'))
res.redirect(base)
})
app.post('/-/login', function(req, res, next) {
var base = config.url_prefix || req.protocol + '://' + req.get('host')
res.cookies.set('token', Buffer(req.body.user + ':' + req.body.pass).toString('base64'))
res.redirect(base)
})
app.post('/-/logout', function(req, res, next) {
var base = config.url_prefix || req.protocol + '://' + req.get('host')
res.cookies.set('token', '')
res.redirect(base)
})
app.post('/-/logout', function(req, res, next) {
var base = config.url_prefix || 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),
packages = []
// Search
app.get('/-/search/:anything', function(req, res, next) {
var results = Search.query(req.params.anything),
packages = []
var getData = function(i) {
storage.get_package(results[i].ref, function(err, entry) {
if (entry) {
packages.push(entry.versions[entry['dist-tags'].latest])
}
var getData = function(i) {
storage.get_package(results[i].ref, function(err, entry) {
if (!err && entry) {
packages.push(entry.versions[entry['dist-tags'].latest])
}
if (i >= results.length - 1) {
res.send(packages)
} else {
getData(i + 1)
}
})
}
if (i >= results.length - 1) {
res.send(packages)
} else {
getData(i + 1)
}
})
}
if (results.length) {
getData(0);
} else {
res.send([])
}
})
if (results.length) {
getData(0)
} else {
res.send([])
}
})
// Readme
marked.setOptions({
highlight: function (code) {
return require('highlight.js').highlightAuto(code).value
}
})
// Readme
marked.setOptions({
highlight: function (code) {
return require('highlight.js').highlightAuto(code).value
}
})
app.get('/-/readme/:package/:version?', function(req, res, next) {
storage.get_package(req.params.package, {req: req}, function(err, info) {
if (err) return next(err)
res.send(marked(info.readme || 'ERROR: No README data found!'))
})
})
return app
app.get('/-/readme/:package/:version?', function(req, res, next) {
storage.get_package(req.params.package, {req: req}, function(err, info) {
if (err) return next(err)
res.send( marked(info.readme || 'ERROR: No README data found!') )
})
})
return app
}

View file

@ -1,433 +1,416 @@
var express = require('express')
, cookies = require('cookies')
, utils = require('./utils')
, Storage = require('./storage')
, Config = require('./config')
, Error = require('http-errors')
, Middleware = require('./middleware')
, Logger = require('./logger')
, Cats = require('./status-cats')
, validate_name = Middleware.validate_name
, media = Middleware.media
, expect_json = Middleware.expect_json
, fs = require('fs')
, Auth = require('./auth')
var Cookies = require('cookies')
var express = require('express')
var fs = require('fs')
var Error = require('http-errors')
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')
var Utils = require('./utils')
var expect_json = Middleware.expect_json
var media = Middleware.media
var validate_name = Middleware.validate_name
function match(regexp) {
return function(req, res, next, value, name) {
if (regexp.exec(value)) {
next()
} else {
next('route')
}
}
return function(req, res, next, value, name) {
if (regexp.exec(value)) {
next()
} else {
next('route')
}
}
}
module.exports = function(config_hash) {
var config = new Config(config_hash)
, storage = new Storage(config)
, auth = new Auth(config)
var config = Config(config_hash)
var storage = Storage(config)
var auth = Auth(config)
var can = function(action) {
return function(req, res, next) {
if (config['allow_'+action](req.params.package, req.remote_user)) {
next()
} else {
if (!req.remote_user.name) {
if (req.remote_user.error) {
var message = "can't "+action+' restricted package, ' + req.remote_user.error
} else {
var message = "can't "+action+" restricted package without auth, did you forget 'npm set always-auth true'?"
}
next(Error[403](message))
} else {
next(Error[403]('user ' + req.remote_user.name
+ ' not allowed to ' + action + ' it'))
}
}
}
}
var can = function(action) {
return function(req, res, next) {
if (config['allow_'+action](req.params.package, req.remote_user)) {
next()
} else {
if (!req.remote_user.name) {
if (req.remote_user.error) {
var message = "can't "+action+' restricted package, ' + req.remote_user.error
} else {
var message = "can't "+action+" restricted package without auth, did you forget 'npm set always-auth true'?"
}
next( Error[403](message) )
} else {
next( Error[403]('user ' + req.remote_user.name
+ ' not allowed to ' + action + ' it') )
}
}
}
}
var app = express()
var 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')
// run in production mode by default, just in case
// it shouldn't make any difference anyway
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)
res.send({error: err.message || 'unknown error'})
}
} else {
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()
} else if (!res.headersSent) {
res.status(500)
res.send({error: 'internal server error'})
} else {
// socket should be already closed
}
}
}
next()
}
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)
res.send({ error: err.message || 'unknown error' })
}
} else {
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()
} else if (!res.headersSent) {
res.status(500)
res.send({ error: 'internal server error' })
} else {
// socket should be already closed
}
}
}
next()
}
app.use(error_reporting_middleware)
app.use(Middleware.log_and_etagify)
app.use(function(req, res, next) {
res.setHeader('X-Powered-By', config.user_agent)
next()
})
app.use(Cats.middleware)
app.use(auth.auth_middleware())
app.use(express.json({strict: false, limit: config.max_body_size || '10mb'}))
app.use(express.compress())
app.use(Middleware.anti_loop(config))
app.use(error_reporting_middleware)
app.use(Middleware.log_and_etagify)
app.use(function(req, res, next) {
res.setHeader('X-Powered-By', config.user_agent)
next()
})
app.use(Cats.middleware)
app.use(auth.auth_middleware())
app.use(express.json({ strict: false, limit: config.max_body_size || '10mb' }))
app.use(express.compress())
app.use(Middleware.anti_loop(config))
// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble
app.param('package', validate_name)
app.param('filename', validate_name)
app.param('tag', validate_name)
app.param('version', validate_name)
app.param('revision', validate_name)
// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble
app.param('package', validate_name)
app.param('filename', validate_name)
app.param('tag', validate_name)
app.param('version', validate_name)
app.param('revision', 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(/.*/))
// 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.get('/-/all', function(req, res) {
var https = require('https')
var JSONStream = require('JSONStream')
var request = require('request')({
url: 'https://registry.npmjs.org/-/all',
})
.pipe(JSONStream.parse('*'))
.on('data', function(d) {
console.log(d)
})
})*/
var https = require('https')
var JSONStream = require('JSONStream')
var request = require('request')({
url: 'https://registry.npmjs.org/-/all',
})
.pipe(JSONStream.parse('*'))
.on('data', function(d) {
console.log(d)
})
})*/
// 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)
// 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)
var version = req.params.version
, t
if (!version) {
return res.send(info)
}
var version = req.params.version
if (!version) return res.send(info)
if ((t = utils.get_version(info, version)) != null) {
return res.send(t)
}
var t = Utils.get_version(info, version)
if (t != null) return res.send(t)
if (info['dist-tags'] != null) {
if (info['dist-tags'][version] != null) {
version = info['dist-tags'][version]
if ((t = utils.get_version(info, version)) != null) {
return res.send(t)
}
}
}
if (info['dist-tags'] != null) {
if (info['dist-tags'][version] != null) {
version = info['dist-tags'][version]
if ((t = Utils.get_version(info, version)) != null) {
return res.send(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)
stream.on('content-length', function(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)
})
app.get('/:package/-/:filename', can('access'), function(req, res, next) {
var stream = storage.get_tarball(req.params.package, req.params.filename)
stream.on('content-length', function(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)
})
// searching packages
app.get('/-/all/:anything?', function(req, res, next) {
storage.search(req.param.startkey || 0, {req: req}, function(err, result) {
if (err) return next(err)
for (var pkg in result) {
if (!config.allow_access(pkg, req.remote_user)) {
delete result[pkg]
}
}
return res.send(result)
})
})
// searching packages
app.get('/-/all/:anything?', function(req, res, next) {
storage.search(req.param.startkey || 0, {req: req}, function(err, result) {
if (err) return next(err)
for (var pkg in result) {
if (!config.allow_access(pkg, req.remote_user)) {
delete result[pkg]
}
}
return res.send(result)
})
})
//app.get('/*', function(req, res) {
// proxy.request(req, res)
//})
//app.get('/*', function(req, res) {
// proxy.request(req, res)
//})
// 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) {
res.cookies.set('AuthSession', String(Math.random()), {
// npmjs.org sets 10h expire
expires: new Date(Date.now() + 10*60*60*1000)
})
res.send({'ok':true,'name':'somebody','roles':[]})
})
// 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) {
res.cookies.set('AuthSession', String(Math.random()), {
// npmjs.org sets 10h expire
expires: new Date(Date.now() + 10*60*60*1000)
})
res.send({ ok: true, name: 'somebody', roles: [] })
})
app.get('/-/user/:org_couchdb_user', function(req, res, next) {
res.status(200)
return res.send({
ok: 'you are authenticated as "' + req.remote_user.name + '"',
})
})
app.get('/-/user/:org_couchdb_user', function(req, res, next) {
res.status(200)
return res.send({
ok: 'you are authenticated as "' + req.remote_user.name + '"',
})
})
app.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req, res, next) {
if (req.remote_user.name != null) {
res.status(201)
return res.send({
ok: 'you are authenticated as "' + req.remote_user.name + '"',
})
} else {
if (typeof(req.body.name) !== 'string' || typeof(req.body.password) !== 'string') {
return next(Error[400]('user/password is not found in request (npm issue?)'))
}
auth.add_user(req.body.name, req.body.password, function(err) {
if (err) {
if (err.status < 500 && err.message === 'this user already exists') {
// with npm registering is the same as logging in
// so we replace message in case of conflict
return next(Error[409]('bad username/password, access denied'))
}
return next(err)
}
app.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req, res, next) {
if (req.remote_user.name != null) {
res.status(201)
return res.send({
ok: 'you are authenticated as "' + req.remote_user.name + '"',
})
} else {
if (typeof(req.body.name) !== 'string' || typeof(req.body.password) !== 'string') {
return next( Error[400]('user/password is not found in request (npm issue?)') )
}
auth.add_user(req.body.name, req.body.password, function(err) {
if (err) {
if (err.status < 500 && err.message === 'this user already exists') {
// with npm registering is the same as logging in
// so we replace message in case of conflict
return next( Error[409]('bad username/password, access denied') )
}
return next(err)
}
res.status(201)
return res.send({
ok: 'user "' + req.body.name + '" created',
})
})
}
})
res.status(201)
return res.send({ ok: 'user "' + req.body.name + '" created' })
})
}
})
// tagging a package
app.put('/:package/:tag', can('publish'), media('application/json'), function(req, res, next) {
if (typeof(req.body) !== 'string') return next('route')
// tagging a package
app.put('/:package/:tag', can('publish'), media('application/json'), function(req, res, next) {
if (typeof(req.body) !== 'string') return next('route')
var tags = {}
tags[req.params.tag] = req.body
storage.add_tags(req.params.package, tags, function(err) {
if (err) return next(err)
res.status(201)
return res.send({
ok: 'package tagged'
})
})
})
var tags = {}
tags[req.params.tag] = req.body
storage.add_tags(req.params.package, tags, function(err) {
if (err) return next(err)
res.status(201)
return res.send({ ok: 'package tagged' })
})
})
// publishing a package
app.put('/:package/:_rev?/:revision?', can('publish'), media('application/json'), expect_json, function(req, res, next) {
var name = req.params.package
// publishing a package
app.put('/:package/:_rev?/:revision?', can('publish'), media('application/json'), expect_json, function(req, res, next) {
var 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'))
}
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') )
}
try {
var metadata = utils.validate_metadata(req.body, name)
} catch(err) {
return next(Error[422]('bad incoming package data'))
}
try {
var metadata = Utils.validate_metadata(req.body, name)
} catch(err) {
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')
})
} else {
storage.add_package(name, metadata, function(err) {
after_change(err, 'created new package')
})
}
if (req.params._rev) {
storage.change_package(name, metadata, req.params.revision, function(err) {
after_change(err, 'package changed')
})
} else {
storage.add_package(name, metadata, function(err) {
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 res.send({
ok: ok_message
})
}
function after_change(err, ok_message) {
// old npm behaviour
if (metadata._attachments == null) {
if (err) return next(err)
res.status(201)
return res.send({ ok: ok_message })
}
// npm-registry-client 0.3+ embeds tarball into the json upload
// https://github.com/isaacs/npm-registry-client/commit/e9fbeb8b67f249394f735c74ef11fe4720d46ca0
// issue #31, dealing with it here:
// npm-registry-client 0.3+ embeds tarball into the json upload
// https://github.com/isaacs/npm-registry-client/commit/e9fbeb8b67f249394f735c74ef11fe4720d46ca0
// 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) {
if (typeof(metadata._attachments) !== 'object'
|| 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'))
}
// npm is doing something strange again
// if this happens in normal circumstances, report it as a bug
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]
create_tarball(t1, metadata._attachments[t1], function(err) {
if (err) return next(err)
// at this point document is either created or existed before
var t1 = Object.keys(metadata._attachments)[0]
create_tarball(t1, metadata._attachments[t1], function(err) {
if (err) return next(err)
var 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)
var 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)
add_tags(metadata['dist-tags'], function(err) {
if (err) return next(err)
add_tags(metadata['dist-tags'], function(err) {
if (err) return next(err)
res.status(201)
return res.send({
ok: ok_message
})
})
})
})
}
res.status(201)
return res.send({ ok: ok_message })
})
})
})
}
function create_tarball(filename, data, cb) {
var stream = storage.add_tarball(name, filename)
stream.on('error', function(err) {
cb(err)
})
stream.on('success', function() {
cb()
})
function create_tarball(filename, data, cb) {
var stream = storage.add_tarball(name, filename)
stream.on('error', function(err) {
cb(err)
})
stream.on('success', function() {
cb()
})
// this is dumb and memory-consuming, but what choices do we have?
stream.end(new Buffer(data.data, 'base64'))
stream.done()
}
// this is dumb and memory-consuming, but what choices do we have?
stream.end(Buffer(data.data, 'base64'))
stream.done()
}
function create_version(version, data, cb) {
storage.add_version(name, version, data, null, cb)
}
function create_version(version, data, cb) {
storage.add_version(name, version, data, null, cb)
}
function add_tags(tags, cb) {
storage.add_tags(name, tags, cb)
}
})
function add_tags(tags, cb) {
storage.add_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 res.send({
ok: 'package removed'
})
})
})
// 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 res.send({ 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 res.send({
ok: 'tarball 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 res.send({ 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
// uploading package tarball
app.put('/:package/-/:filename/*', can('publish'), media('application/octet-stream'), function(req, res, next) {
var name = req.params.package
var stream = storage.add_tarball(name, req.params.filename)
req.pipe(stream)
var stream = storage.add_tarball(name, req.params.filename)
req.pipe(stream)
// checking if end event came before closing
var complete = false
req.on('end', function() {
complete = true
stream.done()
})
req.on('close', function() {
if (!complete) {
stream.abort()
}
})
// checking if end event came before closing
var complete = false
req.on('end', function() {
complete = true
stream.done()
})
req.on('close', function() {
if (!complete) {
stream.abort()
}
})
stream.on('error', function(err) {
return res.report_error(err)
})
stream.on('success', function() {
res.status(201)
return res.send({
ok: 'tarball uploaded successfully'
})
})
})
stream.on('error', function(err) {
return res.report_error(err)
})
stream.on('success', function() {
res.status(201)
return res.send({
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
, version = req.params.version
, tag = req.params.tag
// 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
storage.add_version(name, version, req.body, tag, function(err) {
if (err) return next(err)
res.status(201)
return res.send({
ok: 'package published'
})
})
})
storage.add_version(name, version, req.body, tag, function(err) {
if (err) return next(err)
res.status(201)
return res.send({ ok: 'package published' })
})
})
// hook for tests only
if (config._debug) {
app.get('/-/_debug', function(req, res) {
var do_gc = typeof(global.gc) !== 'undefined'
if (do_gc) global.gc()
res.send({
pid: process.pid,
main: process.mainModule.filename,
conf: config.self_path,
mem: process.memoryUsage(),
gc: do_gc,
})
})
}
// hook for tests only
if (config._debug) {
app.get('/-/_debug', function(req, res) {
var do_gc = typeof(global.gc) !== 'undefined'
if (do_gc) global.gc()
res.send({
pid : process.pid,
main : process.mainModule.filename,
conf : config.self_path,
mem : process.memoryUsage(),
gc : do_gc,
})
})
}
app.use(app.router)
app.use(function(err, req, res, 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(){})
}
res.report_error(err)
})
app.use(app.router)
app.use(function(err, req, res, 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(){})
}
res.report_error(err)
})
if (config.web && config.web.enable) {
app.use(require('./index-web')(config, auth, storage))
} else {
app.get('/', function(req, res) {
res.send('Web interface is disabled in the config file')
})
}
if (config.web && config.web.enable) {
app.use(require('./index-web')(config, auth, storage))
} else {
app.get('/', function(req, res) {
res.send('Web interface is disabled in the config file')
})
}
return app
return app
}

View file

@ -1,270 +1,270 @@
var fs = require('fs')
, Path = require('path')
, mkdirp = require('mkdirp')
, mystreams = require('./streams')
, Error = require('http-errors')
var fs = require('fs')
var Error = require('http-errors')
var mkdirp = require('mkdirp')
var Path = require('path')
var MyStreams = require('./streams')
function FSError(code) {
var err = Error(code)
err.code = code
return err
var err = Error(code)
err.code = code
return err
}
try {
var fsExt = require('fs-ext')
} catch(e) {
fsExt = {
flock: function() {
arguments[arguments.length-1]()
}
}
var fsExt = require('fs-ext')
} catch (e) {
fsExt = {
flock: function() {
arguments[arguments.length-1]()
}
}
}
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)
_cb(err)
}
function cb(err) {
if (err) fs.unlink(src)
_cb(err)
}
if (process.platform !== 'win32') {
return fs.rename(src, dst, cb)
}
if (process.platform !== 'win32') {
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)
fs.rename(dst, tmp, function(err) {
fs.rename(src, dst, cb)
if (!err) fs.unlink(tmp)
})
// windows can't remove opened file,
// but it seem to be able to rename it
var tmp = tempFile(dst)
fs.rename(dst, tmp, function(err) {
fs.rename(src, dst, cb)
if (!err) fs.unlink(tmp)
})
}
function write(dest, data, cb) {
var safe_write = function(cb) {
var tmpname = tempFile(dest)
fs.writeFile(tmpname, data, function(err) {
if (err) return cb(err)
renameTmp(tmpname, dest, cb)
})
}
var safe_write = function(cb) {
var tmpname = tempFile(dest)
fs.writeFile(tmpname, data, function(err) {
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)
})
} else {
cb(err)
}
})
safe_write(function(err) {
if (err && err.code === 'ENOENT') {
mkdirp(Path.dirname(dest), function(err) {
if (err) return cb(err)
safe_write(cb)
})
} else {
cb(err)
}
})
}
function write_stream(name) {
var stream = new mystreams.UploadTarballStream()
var stream = MyStreams.UploadTarballStream()
var _ended = 0
stream.on('end', function() {
_ended = 1
})
var _ended = 0
stream.on('end', function() {
_ended = 1
})
fs.exists(name, function(exists) {
if (exists) return stream.emit('error', FSError('EEXISTS'))
fs.exists(name, function(exists) {
if (exists) return stream.emit('error', FSError('EEXISTS'))
var tmpname = name + '.tmp-'+String(Math.random()).replace(/^0\./, '')
, file = fs.createWriteStream(tmpname)
, opened = false
stream.pipe(file)
var tmpname = name + '.tmp-'+String(Math.random()).replace(/^0\./, '')
var file = fs.createWriteStream(tmpname)
var 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)
} else {
stream.emit('success')
}
})
})
file.destroySoon()
}
if (_ended) {
onend()
} else {
stream.on('end', onend)
}
}
stream.abort = function() {
if (opened) {
opened = false
file.on('close', function() {
fs.unlink(tmpname, function(){})
})
}
file.destroySoon()
}
file.on('open', function() {
opened = true
// re-emitting open because it's handled in storage.js
stream.emit('open')
})
file.on('error', function(err) {
stream.emit('error', err)
})
})
return stream
stream.done = function() {
function onend() {
file.on('close', function() {
renameTmp(tmpname, name, function(err) {
if (err) {
stream.emit('error', err)
} else {
stream.emit('success')
}
})
})
file.destroySoon()
}
if (_ended) {
onend()
} else {
stream.on('end', onend)
}
}
stream.abort = function() {
if (opened) {
opened = false
file.on('close', function() {
fs.unlink(tmpname, function(){})
})
}
file.destroySoon()
}
file.on('open', function() {
opened = true
// re-emitting open because it's handled in storage.js
stream.emit('open')
})
file.on('error', function(err) {
stream.emit('error', err)
})
})
return stream
}
function read_stream(name, stream, callback) {
var rstream = fs.createReadStream(name)
rstream.on('error', function(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)
})
})
var rstream = fs.createReadStream(name)
rstream.on('error', function(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)
})
})
var stream = new mystreams.ReadTarballStream()
stream.abort = function() {
rstream.close()
}
return stream
var stream = MyStreams.ReadTarballStream()
stream.abort = function() {
rstream.close()
}
return stream
}
function create(name, contents, callback) {
fs.exists(name, function(exists) {
if (exists) return callback(FSError('EEXISTS'))
write(name, contents, callback)
})
fs.exists(name, function(exists) {
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)
})
fs.exists(name, function(exists) {
if (!exists) return callback( FSError('ENOENT') )
write(name, contents, callback)
})
}
function read(name, callback) {
fs.readFile(name, callback)
fs.readFile(name, callback)
}
// open and flock with exponential backoff
function open_flock(name, opmod, flmod, tries, backoff, cb) {
fs.open(name, opmod, function(err, fd) {
if (err) return cb(err, fd)
fs.open(name, opmod, function(err, fd) {
if (err) return cb(err, fd)
fsExt.flock(fd, flmod, function(err) {
if (err) {
if (!tries) {
fs.close(fd, function() {
cb(err)
})
} else {
fs.close(fd, function() {
setTimeout(function() {
open_flock(name, opmod, flmod, tries-1, backoff*2, cb)
}, backoff)
})
}
} else {
cb(null, fd)
}
})
})
fsExt.flock(fd, flmod, function(err) {
if (err) {
if (!tries) {
fs.close(fd, function() {
cb(err)
})
} else {
fs.close(fd, function() {
setTimeout(function() {
open_flock(name, opmod, flmod, tries-1, backoff*2, cb)
}, backoff)
})
}
} else {
cb(null, fd)
}
})
})
}
// this function neither unlocks file nor closes it
// it'll have to be done manually later
function lock_and_read(name, _callback) {
open_flock(name, 'r', 'exnb', 4, 10, function(err, fd) {
function callback(err) {
if (err && fd) {
fs.close(fd, function(err2) {
_callback(err)
})
} else {
_callback.apply(null, arguments)
}
}
open_flock(name, 'r', 'exnb', 4, 10, function(err, fd) {
function callback(err) {
if (err && fd) {
fs.close(fd, function(err2) {
_callback(err)
})
} else {
_callback.apply(null, arguments)
}
}
if (err) return callback(err, fd)
if (err) return callback(err, fd)
fs.fstat(fd, function(err, st) {
if (err) return callback(err, fd)
fs.fstat(fd, function(err, st) {
if (err) return callback(err, fd)
var buffer = new Buffer(st.size)
if (st.size === 0) return onRead(null, 0, buffer)
fs.read(fd, buffer, 0, st.size, null, onRead)
var buffer = Buffer(st.size)
if (st.size === 0) return onRead(null, 0, buffer)
fs.read(fd, buffer, 0, st.size, null, onRead)
function onRead(err, bytesRead, buffer) {
if (err) return callback(err, fd)
if (bytesRead != st.size) return callback(new Error('st.size != bytesRead'), fd)
function onRead(err, bytesRead, buffer) {
if (err) return callback(err, fd)
if (bytesRead != st.size) return callback(Error('st.size != bytesRead'), fd)
callback(null, fd, buffer)
}
})
})
callback(null, fd, buffer)
}
})
})
}
module.exports.read = read
module.exports.read_json = function(name, cb) {
read(name, function(err, res) {
if (err) return cb(err)
read(name, function(err, res) {
if (err) return cb(err)
var args = []
try {
args = [null, JSON.parse(res.toString('utf8'))]
} catch(err) {
args = [err]
}
cb.apply(null, args)
})
var args = []
try {
args = [ null, JSON.parse(res.toString('utf8')) ]
} catch(err) {
args = [ err ]
}
cb.apply(null, args)
})
}
module.exports.lock_and_read = lock_and_read
module.exports.lock_and_read_json = function(name, cb) {
lock_and_read(name, function(err, fd, res) {
if (err) return cb(err, fd)
lock_and_read(name, function(err, fd, res) {
if (err) return cb(err, fd)
var args = []
try {
args = [null, fd, JSON.parse(res.toString('utf8'))]
} catch(err) {
args = [err, fd]
}
cb.apply(null, args)
})
var args = []
try {
args = [ null, fd, JSON.parse(res.toString('utf8')) ]
} catch(err) {
args = [ err, fd ]
}
cb.apply(null, args)
})
}
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_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_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

View file

@ -1,38 +1,40 @@
var fs = require('fs')
function LocalList(path) {
var self = Object.create(LocalList.prototype)
self.path = path
try {
self.list = JSON.parse(fs.readFileSync(self.path, 'utf8')).list
} catch(_) {
self.list = []
}
return self
}
LocalList.prototype = {
add: function(name) {
if (this.list.indexOf(name) === -1) {
this.list.push(name)
this.sync()
}
},
remove: function(name) {
var i = this.list.indexOf(name)
if (i !== -1) {
this.list.splice(i, 1)
}
this.sync()
},
get: function() {
return this.list
},
sync: function() {
fs.writeFileSync(this.path, JSON.stringify({list: this.list})); //Uses sync to prevent ugly race condition
},
}
module.exports = LocalList
function LocalList(path) {
var self = Object.create(LocalList.prototype)
self.path = path
try {
self.list = JSON.parse(fs.readFileSync(self.path, 'utf8')).list
} catch(_) {
self.list = []
}
return self
}
LocalList.prototype.add = function(name) {
if (this.list.indexOf(name) === -1) {
this.list.push(name)
this.sync()
}
}
LocalList.prototype.remove = function(name) {
var i = this.list.indexOf(name)
if (i !== -1) {
this.list.splice(i, 1)
}
this.sync()
}
LocalList.prototype.get = function() {
return this.list
}
LocalList.prototype.sync = function() {
// Uses sync to prevent ugly race condition
fs.writeFileSync(this.path, JSON.stringify({ list: this.list }))
}

File diff suppressed because it is too large Load diff

View file

@ -1,77 +1,77 @@
var Logger = require('bunyan')
, Error = require('http-errors')
, Stream = require('stream')
, utils = require('./utils')
var Error = require('http-errors')
var Stream = require('stream')
var Utils = require('./utils')
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' }]
var 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) {
var 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
var 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 {
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 {
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.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: 'sinopia',
streams: streams,
serializers: {
err: Logger.stdSerializers.err,
req: Logger.stdSerializers.req,
res: Logger.stdSerializers.res,
},
})
var logger = new Logger({
name: 'sinopia',
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
@ -80,75 +80,74 @@ module.exports.setup = function(logs) {
// level to color
var levels = {
fatal: 31,
error: 31,
warn: 33,
http: 35,
info: 36,
debug: 90,
trace: 90,
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)
max = Math.max(max, l.length)
}
function pad(str) {
if (str.length < max) return str + new Array(max - str.length + 1).join(' ')
return str
if (str.length < max) return str + Array(max - str.length + 1).join(' ')
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 : '\033[32m<--\033[39m',
out : '\033[33m-->\033[39m',
fs : '\033[90m-=-\033[39m',
default : '\033[34m---\033[39m',
}, {
in: '<--',
out: '-->',
fs: '-=-',
default: '---',
in : '<--',
out : '-->',
fs : '-=-',
default : '---',
}]
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)
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
}
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
}
}
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
}
}
if (typeof(str) === 'string') {
if (!colors || ~str.indexOf('\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, void 0, void 0, colors)
}
})
var sub = subsystems[+!colors][obj.sub] || subsystems[+!colors].default
// ^^--- black magic... kidding, just "colors ? 0 : 1"
if (typeof(str) === 'string') {
if (!colors || ~str.indexOf('\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 (colors) {
return ' \033[' + levels[type] + 'm' + (pad(type)) + '\033[39m ' + sub + ' ' + finalmsg
} else {
return ' ' + (pad(type)) + ' ' + sub + ' ' + finalmsg
}
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
}
}

View file

@ -1,151 +1,150 @@
var crypto = require('crypto')
, Error = require('http-errors')
, utils = require('./utils')
, Logger = require('./logger')
var Error = require('http-errors')
var utils = require('./utils')
var Logger = require('./logger')
module.exports.validate_name = function validate_name(req, res, next, value, name) {
if (value.charAt(0) === '-') {
// special case in couchdb usually
next('route')
} else if (utils.validate_name(value)) {
next()
} else {
next(Error[403]('invalid ' + name))
}
if (value.charAt(0) === '-') {
// special case in couchdb usually
next('route')
} else if (utils.validate_name(value)) {
next()
} else {
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']))
} else {
next()
}
}
return function(req, res, next) {
if (req.headers['content-type'] !== expect) {
next( Error[415]('wrong content-type, expect: ' + expect
+ ', got: '+req.headers['content-type']) )
} else {
next()
}
}
}
module.exports.expect_json = function expect_json(req, res, next) {
if (!utils.is_object(req.body)) {
return next({
status: 400,
message: 'can\'t parse incoming json',
})
}
next()
if (!utils.is_object(req.body)) {
return next( Error[400]("can't parse incoming json") )
}
next()
}
module.exports.anti_loop = function(config) {
return function(req, res, next) {
if (req.headers.via != null) {
var arr = req.headers.via.split(',')
for (var i=0; i<arr.length; i++) {
var m = arr[i].match(/\s*(\S+)\s+(\S+)/)
if (m && m[2] === config.server_id) {
return next(Error[508]('loop detected'))
}
}
}
next()
}
return function(req, res, next) {
if (req.headers.via != null) {
var arr = req.headers.via.split(',')
for (var i=0; i<arr.length; i++) {
var m = arr[i].match(/\s*(\S+)\s+(\S+)/)
if (m && m[2] === config.server_id) {
return next( Error[508]('loop detected') )
}
}
}
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.log_and_etagify = function(req, res, next) {
// logger
req.log = Logger.logger.child({sub: 'in'})
// logger
req.log = Logger.logger.child({ sub: 'in' })
var _auth = req.headers.authorization
if (_auth) req.headers.authorization = '<Classified>'
req.log.info({req: req, ip: req.ip}, '@{ip} requested \'@{req.method} @{req.url}\'')
if (_auth) req.headers.authorization = _auth
var _auth = req.headers.authorization
if (_auth) req.headers.authorization = '<Classified>'
req.log.info( { req: req, ip: req.ip }
, '@{ip} requested \'@{req.method} @{req.url}\'' )
if (_auth) req.headers.authorization = _auth
var bytesin = 0
req.on('data', function(chunk) {
bytesin += chunk.length
})
var bytesin = 0
req.on('data', function(chunk) {
bytesin += chunk.length
})
var _send = res.send
res.send = function(body) {
try {
if (typeof(body) === 'string' || typeof(body) === 'object') {
if (!res.getHeader('Content-type')) {
res.header('Content-type', 'application/json')
}
var _send = res.send
res.send = function(body) {
try {
if (typeof(body) === 'string' || typeof(body) === 'object') {
if (!res.getHeader('Content-type')) {
res.header('Content-type', 'application/json')
}
if (typeof(body) === 'object' && body != null) {
if (typeof(body.error) === 'string') {
res._sinopia_error = body.error
}
body = JSON.stringify(body, undefined, '\t') + '\n'
}
if (typeof(body) === 'object' && body != null) {
if (typeof(body.error) === 'string') {
res._sinopia_error = body.error
}
body = JSON.stringify(body, undefined, '\t') + '\n'
}
// don't send etags with errors
if (!res.statusCode || (res.statusCode >= 200 && res.statusCode < 300)) {
res.header('ETag', '"' + md5sum(body) + '"')
}
} else {
// send(null), send(204), etc.
}
} catch(err) {
// if sinopia sends headers first, and then calls res.send()
// 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
} else {
throw err
}
}
// don't send etags with errors
if (!res.statusCode || (res.statusCode >= 200 && res.statusCode < 300)) {
res.header('ETag', '"' + md5sum(body) + '"')
}
} else {
// send(null), send(204), etc.
}
} catch(err) {
// if sinopia sends headers first, and then calls res.send()
// 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
} else {
throw err
}
}
res.send = _send
res.send(body)
}
res.send = _send
res.send(body)
}
var bytesout = 0
, _write = res.write
res.write = function(buf) {
bytesout += buf.length
_write.apply(res, arguments)
}
var bytesout = 0
var _write = res.write
res.write = function(buf) {
bytesout += buf.length
_write.apply(res, arguments)
}
function log() {
var message = '@{status}, user: @{user}, req: \'@{request.method} @{request.url}\''
if (res._sinopia_error) {
message += ', error: @{!error}'
} else {
message += ', bytes: @{bytes.in}/@{bytes.out}'
}
req.log.warn({
request: {method: req.method, url: req.url},
level: 35, // http
user: req.remote_user.name,
status: res.statusCode,
error: res._sinopia_error,
bytes: {
in: bytesin,
out: bytesout,
}
}, message)
}
function log() {
var message = "@{status}, user: @{user}, req: '@{request.method} @{request.url}'"
if (res._sinopia_error) {
message += ', error: @{!error}'
} else {
message += ', bytes: @{bytes.in}/@{bytes.out}'
}
req.log.warn({
request : { method: req.method, url: req.url },
level : 35, // http
user : req.remote_user.name,
status : res.statusCode,
error : res._sinopia_error,
bytes : {
in : bytesin,
out : bytesout,
}
}, message)
}
req.on('close', function() {
log(true)
})
req.on('close', function() {
log(true)
})
var _end = res.end
res.end = function(buf) {
if (buf) bytesout += buf.length
_end.apply(res, arguments)
log()
}
next()
var _end = res.end
res.end = function(buf) {
if (buf) bytesout += buf.length
_end.apply(res, arguments)
log()
}
next()
}

View file

@ -1,45 +1,48 @@
var lunr = require('lunr')
var Search = function() {
this.index = lunr(function () {
this.field('name', {boost: 10});
this.field('description', {boost: 4});
this.field('author', {boost: 6});
this.field('readme');
});
};
function Search() {
var self = Object.create(Search.prototype)
self.index = lunr(function() {
this.field('name' , { boost: 10 })
this.field('description' , { boost: 4 })
this.field('author' , { boost: 6 })
this.field('readme')
})
return self
}
Search.prototype = {
query: function(q) {
return this.index.search(q);
},
add: function(package) {
this.index.add({
id: package.name,
name: package.name,
description: package.description,
author: package._npmUser ? package._npmUser.name : '???'
});
},
remove: function(name) {
this.index.remove({
id: name
});
},
reindex: function() {
var self = this;
this.storage.get_local(function(err, packages) {
var i = packages.length;
Search.prototype.query = function(q) {
return this.index.search(q)
}
while(i--) {
self.add(packages[i]);
}
});
},
configureStorage: function(storage) {
this.storage = storage;
this.reindex();
}
};
Search.prototype.add = function(package) {
this.index.add({
id: package.name,
name: package.name,
description: package.description,
author: package._npmUser ? package._npmUser.name : '???',
})
},
Search.prototype.remove = function(name) {
this.index.remove({ id: name })
}
Search.prototype.reindex = function() {
var self = this
this.storage.get_local(function(err, packages) {
if (err) throw err // that function shouldn't produce any
var i = packages.length
while (i--) {
self.add(packages[i])
}
})
}
Search.prototype.configureStorage = function(storage) {
this.storage = storage
this.reindex()
}
module.exports = Search()
module.exports = new Search();

View file

@ -2,73 +2,73 @@
// see https://secure.flickr.com/photos/girliemac/sets/72157628409467125
var images = {
100: 'aVvDhR', // '6512768893', // 100 - Continue
101: 'aXXExP', // '6540479029', // 101 - Switching Protocols
200: 'aVuVsF', // '6512628175', // 200 - OK
201: 'aXWm1Z', // '6540221577', // 201 - Created
202: 'aXXEyF', // '6540479079', // 202 - Accepted
204: 'aYyJ7B', // '6547319943', // 204 - No Content
206: 'aVEnUP', // '6514473163', // 206 - Partial Content
207: 'aVEnRD', // '6514472979', // 207 - Multi-Status
300: 'aW7mac', // '6519540181', // 300 - Multiple Choices
301: 'aW7mb4', // '6519540231', // 301 - Moved Permanently
302: 'aV6jKp', // '6508023829', // 302 - Found
303: 'aVxtaK', // '6513125065', // 303 - See Other
304: 'aXY3dH', // '6540551929', // 304 - Not Modified
305: 'aXX5LK', // '6540365403', // 305 - Use Proxy
307: 'aVwQnk', // '6513001269', // 307 - Temporary Redirect
400: 'aXYDeT', // '6540669737', // 400 - Bad Request
401: 'aV6jwe', // '6508023065', // 401 - Unauthorized
402: 'aVwQoe', // '6513001321', // 402 - Payment Required
403: 'aV6jFK', // '6508023617', // 403 - Forbidden
404: 'aV6juR', // '6508022985', // 404 - Not Found
405: 'aV6jE8', // '6508023523', // 405 - Method Not Allowed
406: 'aV6jxa', // '6508023119', // 406 - Not Acceptable
408: 'aV6jyc', // '6508023179', // 408 - Request Timeout
409: 'aV6jzz', // '6508023259', // 409 - Conflict
410: 'aVES2H', // '6514567755', // 410 - Gone
411: 'aXYVpT', // '6540724141', // 411 - Length Required
413: 'aV6jHZ', // '6508023747', // 413 - Request Entity Too Large
414: 'aV6jBa', // '6508023351', // 414 - Request-URI Too Long
416: 'aVxQvr', // '6513196851', // 416 - Requested Range Not Satisfiable
417: 'aV6jGP', // '6508023679', // 417 - Expectation Failed
418: 'aV6J7c', // '6508102407', // 418 - I'm a teapot
422: 'aVEnTt', // '6514473085', // 422 - Unprocessable Entity
423: 'aVEyVZ', // '6514510235', // 423 - Locked
424: 'aVEWZ6', // '6514584423', // 424 - Failed Dependency
425: 'aXYdzH', // '6540586787', // 425 - Unordered Collection
426: 'aVdo4M', // '6509400771', // 426 - Upgrade Required
429: 'aVdo8F', // '6509400997', // 429 - Too Many Requests
431: 'aVdo3n', // '6509400689', // 431 - Request Header Fields Too Large
444: 'aVdo1P', // '6509400599', // 444 - No Response
450: 'aVxtbK', // '6513125123', // 450 - Blocked by Windows Parental Controls
451: 'eTiGQd', // '9113233540', // 451 - Unavailable for Legal Reasons
500: 'aVdo6e', // '6509400855', // 500 - Internal Server Error
502: 'aV6jCv', // '6508023429', // 502 - Bad Gateway
503: 'aXYvop', // '6540643319', // 503 - Service Unavailable
506: 'aXYvnH', // '6540643279', // 506 - Variant Also Negotiates
507: 'aVdnZa', // '6509400503', // 507 - Insufficient Storage
508: 'aVdnYa', // '6509400445', // 508 - Loop Detected
509: 'aXXg1V', // '6540399865', // 509 - Bandwidth Limit Exceeded
599: 'aVdo7v', // '6509400929', // 599 - Network connect timeout error
100: 'aVvDhR', // '6512768893', // 100 - Continue
101: 'aXXExP', // '6540479029', // 101 - Switching Protocols
200: 'aVuVsF', // '6512628175', // 200 - OK
201: 'aXWm1Z', // '6540221577', // 201 - Created
202: 'aXXEyF', // '6540479079', // 202 - Accepted
204: 'aYyJ7B', // '6547319943', // 204 - No Content
206: 'aVEnUP', // '6514473163', // 206 - Partial Content
207: 'aVEnRD', // '6514472979', // 207 - Multi-Status
300: 'aW7mac', // '6519540181', // 300 - Multiple Choices
301: 'aW7mb4', // '6519540231', // 301 - Moved Permanently
302: 'aV6jKp', // '6508023829', // 302 - Found
303: 'aVxtaK', // '6513125065', // 303 - See Other
304: 'aXY3dH', // '6540551929', // 304 - Not Modified
305: 'aXX5LK', // '6540365403', // 305 - Use Proxy
307: 'aVwQnk', // '6513001269', // 307 - Temporary Redirect
400: 'aXYDeT', // '6540669737', // 400 - Bad Request
401: 'aV6jwe', // '6508023065', // 401 - Unauthorized
402: 'aVwQoe', // '6513001321', // 402 - Payment Required
403: 'aV6jFK', // '6508023617', // 403 - Forbidden
404: 'aV6juR', // '6508022985', // 404 - Not Found
405: 'aV6jE8', // '6508023523', // 405 - Method Not Allowed
406: 'aV6jxa', // '6508023119', // 406 - Not Acceptable
408: 'aV6jyc', // '6508023179', // 408 - Request Timeout
409: 'aV6jzz', // '6508023259', // 409 - Conflict
410: 'aVES2H', // '6514567755', // 410 - Gone
411: 'aXYVpT', // '6540724141', // 411 - Length Required
413: 'aV6jHZ', // '6508023747', // 413 - Request Entity Too Large
414: 'aV6jBa', // '6508023351', // 414 - Request-URI Too Long
416: 'aVxQvr', // '6513196851', // 416 - Requested Range Not Satisfiable
417: 'aV6jGP', // '6508023679', // 417 - Expectation Failed
418: 'aV6J7c', // '6508102407', // 418 - I'm a teapot
422: 'aVEnTt', // '6514473085', // 422 - Unprocessable Entity
423: 'aVEyVZ', // '6514510235', // 423 - Locked
424: 'aVEWZ6', // '6514584423', // 424 - Failed Dependency
425: 'aXYdzH', // '6540586787', // 425 - Unordered Collection
426: 'aVdo4M', // '6509400771', // 426 - Upgrade Required
429: 'aVdo8F', // '6509400997', // 429 - Too Many Requests
431: 'aVdo3n', // '6509400689', // 431 - Request Header Fields Too Large
444: 'aVdo1P', // '6509400599', // 444 - No Response
450: 'aVxtbK', // '6513125123', // 450 - Blocked by Windows Parental Controls
451: 'eTiGQd', // '9113233540', // 451 - Unavailable for Legal Reasons
500: 'aVdo6e', // '6509400855', // 500 - Internal Server Error
502: 'aV6jCv', // '6508023429', // 502 - Bad Gateway
503: 'aXYvop', // '6540643319', // 503 - Service Unavailable
506: 'aXYvnH', // '6540643279', // 506 - Variant Also Negotiates
507: 'aVdnZa', // '6509400503', // 507 - Insufficient Storage
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/'
}
if (status in images) {
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
res.writeHead = function(status) {
if (status in images) {
res.setHeader('X-Status-Cat', module.exports.get_image(status))
}
_writeHead.apply(res, arguments)
}
var _writeHead = res.writeHead
res.writeHead = function(status) {
if (status in images) {
res.setHeader('X-Status-Cat', module.exports.get_image(status))
}
_writeHead.apply(res, arguments)
}
next()
next()
}

View file

@ -1,32 +1,33 @@
var async = require('async')
, assert = require('assert')
, Error = require('http-errors')
, Local = require('./local-storage')
, Proxy = require('./up-storage')
, mystreams = require('./streams')
, utils = require('./utils')
, Logger = require('./logger')
var assert = require('assert')
var async = require('async')
var Error = require('http-errors')
var Local = require('./local-storage')
var Logger = require('./logger')
var MyStreams = require('./streams')
var Proxy = require('./up-storage')
var Utils = require('./utils')
module.exports = Storage
//
// Implements Storage interface
// (same for storage.js, local-storage.js, up-storage.js)
//
function Storage(config) {
if (!(this instanceof Storage)) return new Storage(config)
var self = Object.create(Storage.prototype)
self.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
self.uplinks = {}
for (var p in config.uplinks) {
self.uplinks[p] = Proxy(config.uplinks[p], config)
self.uplinks[p].upname = p
}
self.local = Local(config)
self.logger = Logger.logger.child()
// we support a number of uplinks, but only one local storage
// Proxy and Local classes should have similar API interfaces
this.uplinks = {}
for (var p in config.uplinks) {
this.uplinks[p] = new Proxy(config.uplinks[p], config)
this.uplinks[p].upname = p
}
this.local = new Local(config)
this.logger = Logger.logger.child()
return this
return self
}
//
@ -38,65 +39,61 @@ function Storage(config) {
// Used storages: local (write) && uplinks
//
Storage.prototype.add_package = function(name, metadata, callback) {
var self = this
var self = this
// NOTE:
// - when we checking package for existance, we ask ALL uplinks
// - when we publishing package, we only publish it to some of them
// so all requests are necessary
// NOTE:
// - when we checking package for existance, we ask ALL uplinks
// - when we publishing package, we only publish it to some of them
// so all requests are necessary
check_package_local(function(err) {
if (err) return callback(err)
check_package_local(function(err) {
if (err) return callback(err)
check_package_remote(function(err) {
if (err) return callback(err)
check_package_remote(function(err) {
if (err) return callback(err)
publish_package(function(err) {
if (err) return callback(err)
callback()
})
})
})
publish_package(function(err) {
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)
function check_package_local(cb) {
self.local.get_package(name, {}, function(err, results) {
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)
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)
// checking package
if (results) {
return cb(Error[409]('this package is already present'))
}
// checking package
if (results) return cb( Error[409]('this package is already present') )
for (var 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'))
}
}
}
for (var 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()
})
}
return cb()
})
}
function publish_package(cb) {
self.local.add_package(name, metadata, callback)
}
function publish_package(cb) {
self.local.add_package(name, metadata, callback)
}
}
//
@ -105,7 +102,7 @@ Storage.prototype.add_package = function(name, metadata, callback) {
// Used storages: local (write)
//
Storage.prototype.add_version = function(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)
}
//
@ -114,7 +111,7 @@ Storage.prototype.add_version = function(name, version, metadata, tag, callback)
// Used storages: local (write)
//
Storage.prototype.add_tags = function(name, tag_hash, callback) {
return this.local.add_tags(name, tag_hash, callback)
return this.local.add_tags(name, tag_hash, callback)
}
//
@ -126,7 +123,7 @@ Storage.prototype.add_tags = function(name, tag_hash, callback) {
// Used storages: local (write)
//
Storage.prototype.change_package = function(name, metadata, revision, callback) {
return this.local.change_package(name, metadata, revision, callback)
return this.local.change_package(name, metadata, revision, callback)
}
//
@ -137,7 +134,7 @@ Storage.prototype.change_package = function(name, metadata, revision, callback)
// Used storages: local (write)
//
Storage.prototype.remove_package = function(name, callback) {
return this.local.remove_package(name, callback)
return this.local.remove_package(name, callback)
}
//
@ -150,7 +147,7 @@ Storage.prototype.remove_package = function(name, callback) {
// Used storages: local (write)
//
Storage.prototype.remove_tarball = function(name, filename, revision, callback) {
return this.local.remove_tarball(name, filename, revision, callback)
return this.local.remove_tarball(name, filename, revision, callback)
}
//
@ -161,7 +158,7 @@ Storage.prototype.remove_tarball = function(name, filename, revision, callback)
// Used storages: local (write)
//
Storage.prototype.add_tarball = function(name, filename) {
return this.local.add_tarball(name, filename)
return this.local.add_tarball(name, filename)
}
//
@ -175,102 +172,103 @@ Storage.prototype.add_tarball = function(name, filename) {
// Used storages: local || uplink (just one)
//
Storage.prototype.get_tarball = function(name, filename) {
var stream = new mystreams.ReadTarballStream()
stream.abort = function() {}
var stream = MyStreams.ReadTarballStream()
stream.abort = function() {}
var self = this
var self = this
// if someone requesting tarball, it means that we should already have some
// information about it, so fetching package info is unnecessary
// 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
rstream.on('error', function(err) {
if (is_open || err.status !== 404) {
return stream.emit('error', err)
}
// trying local first
var rstream = self.local.get_tarball(name, filename)
var is_open = false
rstream.on('error', function(err) {
if (is_open || err.status !== 404) {
return stream.emit('error', err)
}
// local reported 404
var err404 = err
var uplink = null
rstream.abort()
rstream = null // gc
// local reported 404
var err404 = err
var uplink = null
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])
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])
} else {
// we know nothing about this file, trying to get information elsewhere
} 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)
self._sync_package_with_uplinks(name, info, {}, function(err, info) {
if (err) return stream.emit('error', err)
if (!info._distfiles || info._distfiles[filename] == null) {
return stream.emit('error', err404)
}
if (!info._distfiles || info._distfiles[filename] == null) {
return stream.emit('error', err404)
}
serve_file(info._distfiles[filename])
})
}
})
})
rstream.on('content-length', function(v) {
stream.emit('content-length', v)
})
rstream.on('open', function() {
is_open = true
rstream.pipe(stream)
})
return stream
serve_file(info._distfiles[filename])
})
}
})
})
rstream.on('content-length', function(v) {
stream.emit('content-length', v)
})
rstream.on('open', function() {
is_open = true
rstream.pipe(stream)
})
return stream
function serve_file(file) {
var uplink = null
for (var p in self.uplinks) {
if (self.uplinks[p].can_fetch_url(file.url)) {
uplink = self.uplinks[p]
}
}
if (uplink == null) {
uplink = new Proxy({
url: file.url,
_autogenerated: true,
}, self.config)
}
function serve_file(file) {
var uplink = null
for (var p in self.uplinks) {
if (self.uplinks[p].can_fetch_url(file.url)) {
uplink = self.uplinks[p]
}
}
if (uplink == null) {
uplink = Proxy({
url: file.url,
_autogenerated: true,
}, self.config)
}
var 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)
rstream2.on('error', function(err) {
if (savestream) savestream.abort()
savestream = null
stream.emit('error', err)
})
rstream2.on('end', function() {
if (savestream) savestream.done()
})
var 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)
rstream2.on('error', function(err) {
if (savestream) savestream.abort()
savestream = null
stream.emit('error', err)
})
rstream2.on('end', function() {
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)
}
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)
}
savestream.on('open', function() {
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()
})
}
savestream.on('open', function() {
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()
})
}
}
//
@ -283,40 +281,40 @@ Storage.prototype.get_tarball = function(name, filename) {
// Used storages: local && uplink (proxy_access)
//
Storage.prototype.get_package = function(name, options, callback) {
if (typeof(options) === 'function') callback = options, options = {}
if (typeof(options) === 'function') callback = options, options = {}
var self = this
var self = this
self.local.get_package(name, options, function(err, data) {
if (err && (!err.status || err.status >= 500)) {
// report internal errors right away
return callback(err)
}
self.local.get_package(name, options, function(err, data) {
if (err && (!err.status || err.status >= 500)) {
// report internal errors right away
return callback(err)
}
self._sync_package_with_uplinks(name, data, options, function(err, result, uplink_errors) {
if (err) return callback(err)
var whitelist = ['_rev', 'name', 'versions', 'dist-tags', 'readme']
for (var i in result) {
if (whitelist.indexOf(i) === -1) delete result[i]
}
self._sync_package_with_uplinks(name, data, options, function(err, result, uplink_errors) {
if (err) return callback(err)
var whitelist = [ '_rev', 'name', 'versions', 'dist-tags', 'readme' ]
for (var i in result) {
if (whitelist.indexOf(i) === -1) delete result[i]
}
if (self.config.ignore_latest_tag || !result['dist-tags'].latest) {
result['dist-tags'].latest = utils.semver_sort(Object.keys(result.versions))
}
if (self.config.ignore_latest_tag || !result['dist-tags'].latest) {
result['dist-tags'].latest = Utils.semver_sort(Object.keys(result.versions))
}
for (var i in result['dist-tags']) {
if (Array.isArray(result['dist-tags'][i])) {
result['dist-tags'][i] = result['dist-tags'][i][result['dist-tags'][i].length-1]
if (result['dist-tags'][i] == null) delete result['dist-tags'][i]
}
}
for (var i in result['dist-tags']) {
if (Array.isArray(result['dist-tags'][i])) {
result['dist-tags'][i] = result['dist-tags'][i][result['dist-tags'][i].length-1]
if (result['dist-tags'][i] == null) delete result['dist-tags'][i]
}
}
// npm can throw if this field doesn't exist
result._attachments = {}
// npm can throw if this field doesn't exist
result._attachments = {}
callback(null, result, uplink_errors)
})
})
callback(null, result, uplink_errors)
})
})
}
//
@ -328,225 +326,222 @@ Storage.prototype.get_package = function(name, options, callback) {
// Used storages: local && uplink (proxy_access)
//
Storage.prototype.search = function(startkey, options, callback) {
var self = this
var uplinks = []
var i = 0
var self = this
var uplinks = []
var i = 0
var uplinks
for (var p in self.uplinks) {
uplinks.push(p)
}
var uplinks
for (var p in self.uplinks) {
uplinks.push(p)
}
function merge_with_local_packages(err, res, body) {
if (err) return callback(err)
var j = 0
function merge_with_local_packages(err, res, body) {
if (err) return callback(err)
var j = 0
self.local.get_recent_packages(startkey, function(err, list) {
if (err) return callback(err)
self.local.get_recent_packages(startkey, function(err, list) {
if (err) return callback(err)
var listL = list.length
var listL = list.length
if (!listL) return callback(null, body)
if (!listL) return callback(null, body)
list.forEach(function(item) {
self.local.get_package(item.name, options, function(err, data) {
if (err) return callback(err)
list.forEach(function(item) {
self.local.get_package(item.name, options, function(err, data) {
if (err) return callback(err)
var versions = utils.semver_sort(Object.keys(data.versions))
var latest = versions[versions.length - 1]
var versions = Utils.semver_sort(Object.keys(data.versions))
var latest = versions[versions.length - 1]
if (data.versions[latest]) {
body[item.name] = {
name : data.versions[latest].name,
description : data.versions[latest].description,
'dist-tags' : {
latest: latest
},
maintainers : data.versions[latest].maintainers || [data.versions[latest]._npmUser].filter(Boolean),
readmeFilename: data.versions[latest].readmeFilename || '',
time : {
modified: new Date(item.time).toISOString()
},
versions : {},
repository : data.versions[latest].repository,
keywords : data.versions[latest].keywords
}
body[item.name].versions[latest] = 'latest'
}
if (data.versions[latest]) {
body[item.name] = {
name : data.versions[latest].name,
description : data.versions[latest].description,
'dist-tags' : { latest: latest },
maintainers : data.versions[latest].maintainers || [data.versions[latest]._npmUser].filter(Boolean),
readmeFilename: data.versions[latest].readmeFilename || '',
time : {
modified: new Date(item.time).toISOString()
},
versions : {},
repository : data.versions[latest].repository,
keywords : data.versions[latest].keywords
}
body[item.name].versions[latest] = 'latest'
}
if (++j !== listL) {
return false
}
if (++j !== listL) {
return false
}
return callback(null, body)
})
})
})
}
return callback(null, body)
})
})
})
}
function remote_search() {
var uplink = self.uplinks[uplinks[i]]
if (!uplink) {
return merge_with_local_packages(null, null, {})
}
self.uplinks[uplinks[i]].request({
uri: options.req.url,
timeout: self.uplinks[p].timeout,
json: true
}, function(err, res, body) {
if (err || Math.floor(res.statusCode / 100) > 3) {
i++
return remote_search()
}
return merge_with_local_packages(err, res, body)
})
}
function remote_search() {
var uplink = self.uplinks[uplinks[i]]
if (!uplink) {
return merge_with_local_packages(null, null, {})
}
self.uplinks[uplinks[i]].request({
uri: options.req.url,
timeout: self.uplinks[p].timeout,
json: true,
}, function(err, res, body) {
if (err || Math.floor(res.statusCode / 100) > 3) {
i++
return remote_search()
}
return merge_with_local_packages(err, res, body)
})
}
remote_search()
remote_search()
}
Storage.prototype.get_local = function(callback) {
var self = this
, locals = this.config.localList.get()
, packages = [];
var self = this
var locals = this.config.localList.get()
var packages = []
var getPackage = function(i) {
self.local.get_package(locals[i], function(err, info) {
if (!err) {
var latest = Array.isArray(info['dist-tags'].latest)
? utils.semver_sort(info['dist-tags'].latest).pop()
: info['dist-tags'].latest
if (info.versions[latest]) {
packages.push(info.versions[latest])
} else {
self.logger.warn({package: locals[i]}, 'package @{package} does not have a "latest" tag?')
}
}
var getPackage = function(i) {
self.local.get_package(locals[i], function(err, info) {
if (!err) {
var latest = Array.isArray(info['dist-tags'].latest)
? Utils.semver_sort(info['dist-tags'].latest).pop()
: info['dist-tags'].latest
if (info.versions[latest]) {
packages.push(info.versions[latest])
} else {
self.logger.warn( { package: locals[i] }
, 'package @{package} does not have a "latest" tag?' )
}
}
if (i >= locals.length - 1) {
callback(null, packages);
} else {
getPackage(i + 1);
}
});
};
if (i >= locals.length - 1) {
callback(null, packages)
} else {
getPackage(i + 1)
}
})
}
if(locals.length) {
getPackage(0);
}
else {
callback(null, []);
}
};
if (locals.length) {
getPackage(0)
} else {
callback(null, [])
}
}
// function fetches package information from uplinks and synchronizes it with local data
// if package is available locally, it MUST be provided in pkginfo
// returns callback(err, result, uplink_errors)
Storage.prototype._sync_package_with_uplinks = function(name, pkginfo, options, callback) {
var self = this
var self = this
if (!pkginfo) {
var exists = false
if (!pkginfo) {
var exists = false
pkginfo = {
name: name,
versions: {},
'dist-tags': {},
_uplinks: {},
}
} else {
var exists = true
}
pkginfo = {
name : name,
versions : {},
'dist-tags' : {},
_uplinks : {},
}
} else {
var exists = true
}
var uplinks = []
for (var i in self.uplinks) {
if (self.config.proxy_access(name, i)) {
uplinks.push(self.uplinks[i])
}
}
var uplinks = []
for (var i in self.uplinks) {
if (self.config.proxy_access(name, i)) {
uplinks.push(self.uplinks[i])
}
}
async.map(uplinks, function(up, cb) {
var _options = Object.create(options)
if (utils.is_object(pkginfo._uplinks[up.upname])) {
var fetched = pkginfo._uplinks[up.upname].fetched
if (fetched && fetched > (Date.now() - up.maxage)) {
return cb()
}
async.map(uplinks, function(up, cb) {
var _options = Object.create(options)
if (Utils.is_object(pkginfo._uplinks[up.upname])) {
var fetched = pkginfo._uplinks[up.upname].fetched
if (fetched && fetched > (Date.now() - up.maxage)) {
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()
up.get_package(name, _options, function(err, up_res, etag) {
if (err && err.status === 304)
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)
} catch(err) {
self.logger.error({
sub: 'out',
err: err,
}, 'package.json validating error @{!err.message}\n@{err.stack}')
return cb(null, [err])
}
try {
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 ])
}
pkginfo._uplinks[up.upname] = {
etag: etag,
fetched: Date.now()
}
pkginfo._uplinks[up.upname] = {
etag: etag,
fetched: Date.now()
}
try {
Storage._merge_versions(pkginfo, up_res, self.config)
} catch(err) {
self.logger.error({
sub: 'out',
err: err,
}, 'package.json parsing error @{!err.message}\n@{err.stack}')
return cb(null, [err])
}
try {
Storage._merge_versions(pkginfo, up_res, self.config)
} catch(err) {
self.logger.error({
sub: 'out',
err: 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()
})
}, function(err, uplink_errors) {
assert(!err && Array.isArray(uplink_errors))
// if we got to this point, assume that the correct package exists
// on the uplink
exists = true
cb()
})
}, function(err, uplink_errors) {
assert(!err && Array.isArray(uplink_errors))
if (!exists) {
return callback( Error[404]('no such package available')
, null
, uplink_errors )
}
if (!exists) {
return callback( Error[404]('no such package available')
, null
, uplink_errors )
}
self.local.update_versions(name, pkginfo, function(err, pkginfo) {
if (err) return callback(err)
return callback(null, pkginfo, uplink_errors)
})
})
self.local.update_versions(name, pkginfo, function(err, pkginfo) {
if (err) return callback(err)
return callback(null, pkginfo, uplink_errors)
})
})
}
// function gets a local info and an info from uplinks and tries to merge it
// exported for unit tests only
Storage._merge_versions = function(local, up, config) {
// copy new versions to a cache
// 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]
}
}
// copy new versions to a cache
// 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]
}
}
// refresh dist-tags
for (var i in up['dist-tags']) {
var added = utils.tag_version(local, up['dist-tags'][i], i, config || {})
if (i === 'latest' && added) {
// if remote has more fresh package, we should borrow its readme
local.readme = up.readme
}
}
// refresh dist-tags
for (var i in up['dist-tags']) {
var added = Utils.tag_version(local, up['dist-tags'][i], i, config || {})
if (i === 'latest' && added) {
// if remote has more fresh package, we should borrow its readme
local.readme = up.readme
}
}
}
module.exports = Storage

View file

@ -1,53 +1,60 @@
var stream = require('stream')
, util = require('util')
var Stream = require('stream')
var Util = require('util')
module.exports.ReadTarballStream = ReadTarball
module.exports.UploadTarballStream = UploadTarball
//
// This stream is used to read tarballs from repository
//
function ReadTarball(options) {
stream.PassThrough.call(this, options)
var self = new Stream.PassThrough(options)
self.__proto__ = ReadTarball.prototype
// called when data is not needed anymore
add_abstract_method(this, 'abort')
// called when data is not needed anymore
add_abstract_method(self, 'abort')
return self
}
util.inherits(ReadTarball, stream.PassThrough)
module.exports.ReadTarballStream = ReadTarball
Util.inherits(ReadTarball, Stream.PassThrough)
//
// This stream is used to upload tarballs to a repository
//
function UploadTarball(options) {
stream.PassThrough.call(this, options)
var self = new Stream.PassThrough(options)
self.__proto__ = UploadTarball.prototype
// called when user closes connection before upload finishes
add_abstract_method(this, 'abort')
// called when user closes connection before upload finishes
add_abstract_method(self, 'abort')
// called when upload finishes successfully
add_abstract_method(this, 'done')
// called when upload finishes successfully
add_abstract_method(self, 'done')
return self
}
util.inherits(UploadTarball, stream.PassThrough)
module.exports.UploadTarballStream = UploadTarball
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.__defineGetter__(name, function() {
return function() {
self._called_methods[name] = true
}
})
self.__defineSetter__(name, function(fn) {
delete self[name]
self[name] = fn
if (self._called_methods && self._called_methods[name]) {
delete self._called_methods[name]
self[name]()
}
})
self._called_methods = self._called_methods || {}
self.__defineGetter__(name, function() {
return function() {
self._called_methods[name] = true
}
})
self.__defineSetter__(name, function(fn) {
delete self[name]
self[name] = fn
if (self._called_methods && self._called_methods[name]) {
delete self._called_methods[name]
self[name]()
}
})
}

View file

@ -1,345 +1,346 @@
var URL = require('url')
, request = require('request')
, Stream = require('stream')
, zlib = require('zlib')
, Error = require('http-errors')
, mystreams = require('./streams')
, Logger = require('./logger')
, utils = require('./utils')
, parse_interval = require('./config').parse_interval
, encode = encodeURIComponent
var Error = require('http-errors')
var request = require('request')
var Stream = require('stream')
var URL = require('url')
var zlib = require('zlib')
var parse_interval = require('./config').parse_interval
var Logger = require('./logger')
var MyStreams = require('./streams')
var Utils = require('./utils')
var encode = encodeURIComponent
module.exports = Storage
//
// Implements Storage interface
// (same for storage.js, local-storage.js, up-storage.js)
//
function Storage(config, mainconfig) {
if (!(this instanceof Storage)) return new Storage(config)
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
var self = Object.create(Storage.prototype)
self.config = config
self.failed_requests = 0
self.userAgent = mainconfig.user_agent
self.ca = config.ca
self.logger = Logger.logger.child({sub: 'out'})
self.server_id = mainconfig.server_id
this.url = URL.parse(this.config.url)
if (this.url.hostname === 'registry.npmjs.org') {
// npm registry is too slow working with ssl :(
/*if (this.config._autogenerated) {
// encrypt all the things!
this.url.protocol = 'https'
this.config.url = URL.format(this.url)
}*/
}
self.url = URL.parse(self.config.url)
if (self.url.hostname === 'registry.npmjs.org') {
// npm registry is too slow working with ssl :(
/*if (self.config._autogenerated) {
// encrypt all the things!
self.url.protocol = 'https'
self.config.url = URL.format(self.url)
}*/
}
_setupProxy.call(this, this.url.hostname, config, mainconfig, this.url.protocol === 'https:')
_setupProxy.call(self, self.url.hostname, config, mainconfig, self.url.protocol === 'https:')
this.config.url = this.config.url.replace(/\/$/, '')
if (Number(this.config.timeout) >= 1000) {
this.logger.warn('Too big timeout value: ' + this.config.timeout + '\nWe changed time format to nginx-like one\n(see http://wiki.nginx.org/ConfigNotation)\nso please update your config accordingly')
}
self.config.url = self.config.url.replace(/\/$/, '')
if (Number(self.config.timeout) >= 1000) {
self.logger.warn([ 'Too big timeout value: ' + self.config.timeout,
'We changed time format to nginx-like one',
'(see http://wiki.nginx.org/ConfigNotation)',
'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
// a bunch of different configurable timers
self.maxage = parse_interval(config_get('maxage' , '2m' ))
self.timeout = parse_interval(config_get('timeout' , '30s'))
self.max_fails = Number(config_get('max_fails' , 2 ))
self.fail_timeout = parse_interval(config_get('fail_timeout', '5m' ))
return self
// just a helper (`config[key] || default` doesn't work because of zeroes)
function config_get(key, def) {
return config[key] != null ? config[key] : def
}
// just a helper (`config[key] || default` doesn't work because of zeroes)
function config_get(key, def) {
return config[key] != null ? config[key] : def
}
}
function _setupProxy(hostname, config, mainconfig, isHTTPS) {
var no_proxy
var proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy'
var no_proxy
var proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy'
// get http_proxy and no_proxy configs
if (proxy_key in config) {
this.proxy = config[proxy_key]
} else if (proxy_key in mainconfig) {
this.proxy = mainconfig[proxy_key]
}
if ('no_proxy' in config) {
no_proxy = config.no_proxy
} else if ('no_proxy' in mainconfig) {
no_proxy = mainconfig.no_proxy
}
// get http_proxy and no_proxy configs
if (proxy_key in config) {
this.proxy = config[proxy_key]
} else if (proxy_key in mainconfig) {
this.proxy = mainconfig[proxy_key]
}
if ('no_proxy' in config) {
no_proxy = config.no_proxy
} else if ('no_proxy' in mainconfig) {
no_proxy = mainconfig.no_proxy
}
// use wget-like algorithm to determine if proxy shouldn't be used
if (hostname[0] !== '.') hostname = '.' + hostname
if (typeof(no_proxy) === 'string' && no_proxy.length) {
no_proxy = no_proxy.split(',')
}
if (Array.isArray(no_proxy)) {
for (var 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
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
}
break
}
}
}
// use wget-like algorithm to determine if proxy shouldn't be used
if (hostname[0] !== '.') hostname = '.' + hostname
if (typeof(no_proxy) === 'string' && no_proxy.length) {
no_proxy = no_proxy.split(',')
}
if (Array.isArray(no_proxy)) {
for (var 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
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
}
break
}
}
}
// if it's non-string (i.e. "false"), don't use it
if (typeof(this.proxy) !== 'string') {
delete this.proxy
} else {
this.logger.debug({url: this.url.href, proxy: this.proxy},
'using proxy @{proxy} for @{url}')
}
// if it's non-string (i.e. "false"), don't use it
if (typeof(this.proxy) !== 'string') {
delete this.proxy
} else {
this.logger.debug( { url: this.url.href, proxy: this.proxy }
, 'using proxy @{proxy} for @{url}' )
}
}
Storage.prototype.request = function(options, cb) {
if (!this.status_check()) {
var req = new Stream.Readable()
process.nextTick(function() {
if (typeof(cb) === 'function') cb(Error('uplink is offline'))
req.emit('error', Error('uplink is offline'))
})
// preventing 'Uncaught, unspecified "error" event'
req.on('error', function(){})
return req
}
if (!this.status_check()) {
var req = new Stream.Readable()
process.nextTick(function() {
if (typeof(cb) === 'function') cb(Error('uplink is offline'))
req.emit('error', Error('uplink is offline'))
})
// preventing 'Uncaught, unspecified "error" event'
req.on('error', function(){})
return req
}
var self = this
, headers = options.headers || {}
headers['Accept'] = headers['Accept'] || 'application/json'
headers['Accept-Encoding'] = headers['Accept-Encoding'] || 'gzip'
headers['User-Agent'] = headers['User-Agent'] || this.userAgent
var self = this
var headers = options.headers || {}
headers['Accept'] = headers['Accept'] || 'application/json'
headers['Accept-Encoding'] = headers['Accept-Encoding'] || 'gzip'
headers['User-Agent'] = headers['User-Agent'] || this.userAgent
var method = options.method || 'GET'
, uri = options.uri_full || (this.config.url + options.uri)
self.logger.info({
method: method,
headers: headers,
uri: uri,
}, "making request: '@{method} @{uri}'")
var method = options.method || 'GET'
var uri = options.uri_full || (this.config.url + options.uri)
if (utils.is_object(options.json)) {
var json = JSON.stringify(options.json)
headers['Content-Type'] = headers['Content-Type'] || 'application/json'
}
self.logger.info({
method : method,
headers : headers,
uri : uri,
}, "making request: '@{method} @{uri}'")
var req = request({
url: uri,
method: method,
headers: headers,
body: json,
ca: this.ca,
proxy: this.proxy,
encoding: null,
timeout: this.timeout,
}, function(err, res, body) {
var error
var res_length = err ? 0 : body.length
if (Utils.is_object(options.json)) {
var json = JSON.stringify(options.json)
headers['Content-Type'] = headers['Content-Type'] || 'application/json'
}
do_gunzip(function() {
do_decode()
do_log()
if (cb) cb(err, res, body)
})
var req = request({
url : uri,
method : method,
headers : headers,
body : json,
ca : this.ca,
proxy : this.proxy,
encoding : null,
timeout : this.timeout,
}, function(err, res, body) {
var error
var res_length = err ? 0 : body.length
function do_gunzip(cb) {
if (err) return cb()
if (res.headers['content-encoding'] !== 'gzip') return cb()
zlib.gunzip(body, function(er, buf) {
if (er) err = er
body = buf
return cb()
})
}
do_gunzip(function() {
do_decode()
do_log()
if (cb) cb(err, res, body)
})
function do_decode() {
if (err) {
error = err.message
return
}
function do_gunzip(cb) {
if (err) return cb()
if (res.headers['content-encoding'] !== 'gzip') return cb()
zlib.gunzip(body, function(er, buf) {
if (er) err = er
body = buf
return cb()
})
}
if (options.json && res.statusCode < 300) {
try {
body = JSON.parse(body.toString('utf8'))
} catch(_err) {
body = {}
err = _err
error = err.message
}
}
function do_decode() {
if (err) {
error = err.message
return
}
if (!err && utils.is_object(body)) {
if (typeof(body.error) === 'string') {
error = body.error
}
}
}
if (options.json && res.statusCode < 300) {
try {
body = JSON.parse(body.toString('utf8'))
} catch(_err) {
body = {}
err = _err
error = err.message
}
}
function do_log() {
var message = '@{!status}, req: \'@{request.method} @{request.url}\''
if (error) {
message += ', error: @{!error}'
} else {
message += ', 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)
}
})
if (!err && Utils.is_object(body)) {
if (typeof(body.error) === 'string') {
error = body.error
}
}
}
var status_called = false
req.on('response', function(res) {
if (!req._sinopia_aborted && !status_called) {
status_called = true
self.status_check(true)
}
})
req.on('error', function(_err) {
if (!req._sinopia_aborted && !status_called) {
status_called = true
self.status_check(false)
}
})
return req
function do_log() {
var message = '@{!status}, req: \'@{request.method} @{request.url}\''
message += error
? ', error: @{!error}'
: ', 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)
}
})
var status_called = false
req.on('response', function(res) {
if (!req._sinopia_aborted && !status_called) {
status_called = true
self.status_check(true)
}
})
req.on('error', function(_err) {
if (!req._sinopia_aborted && !status_called) {
status_called = true
self.status_check(false)
}
})
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
} else {
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.failed_requests = 0
} else {
this.failed_requests++
if (this.failed_requests === this.max_fails) {
this.logger.warn({host: this.url.host}, 'host @{host} is now offline')
}
}
this.last_request_time = Date.now()
}
if (arguments.length === 0) {
if (this.failed_requests >= this.max_fails
&& Math.abs(Date.now() - this.last_request_time) < this.fail_timeout) {
return false
} else {
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.failed_requests = 0
} else {
this.failed_requests++
if (this.failed_requests === this.max_fails) {
this.logger.warn({ host: this.url.host }, 'host @{host} is now offline')
}
}
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
return url.protocol === this.url.protocol
&& url.host === this.url.host
&& 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 = {}
if (options.etag) {
headers['If-None-Match'] = options.etag
headers['Accept'] = 'application/octet-stream'
}
this._add_proxy_headers(options.req, headers)
var headers = {}
if (options.etag) {
headers['If-None-Match'] = options.etag
headers['Accept'] = 'application/octet-stream'
}
this._add_proxy_headers(options.req, headers)
this.request({
uri: '/' + encode(name),
json: true,
headers: headers,
}, function(err, res, body) {
if (err) return callback(err)
if (res.statusCode === 404) {
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)
}
callback(null, body, res.headers.etag)
})
this.request({
uri : '/' + encode(name),
json : true,
headers : headers,
}, function(err, res, body) {
if (err) return callback(err)
if (res.statusCode === 404) {
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)
}
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 = new mystreams.ReadTarballStream()
stream.abort = function() {}
var current_length = 0, expected_length
var stream = MyStreams.ReadTarballStream()
stream.abort = function() {}
var current_length = 0, expected_length
var rstream = this.request({
uri_full: url,
encoding: null,
headers: {
Accept: 'application/octet-stream',
},
})
var rstream = this.request({
uri_full: url,
encoding: null,
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"))
}
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
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'])
}
rstream.on('response', function(res) {
if (res.statusCode === 404) {
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))
}
if (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)
})
rstream.on('data', function(d) {
current_length += d.length
})
rstream.on('end', function(d) {
if (d) current_length += d.length
if (expected_length && current_length != expected_length)
stream.emit('error', Error('content length mismatch'))
})
return stream
rstream.on('error', function(err) {
stream.emit('error', err)
})
rstream.on('data', function(d) {
current_length += d.length
})
rstream.on('end', function(d) {
if (d) current_length += d.length
if (expected_length && current_length != expected_length)
stream.emit('error', Error('content length mismatch'))
})
return stream
}
Storage.prototype._add_proxy_headers = function(req, headers) {
if (req) {
headers['X-Forwarded-For'] = (
(req && req.headers['x-forwarded-for']) ?
req.headers['x-forwarded-for'] + ', ' :
''
) + req.connection.remoteAddress
}
if (req) {
headers['X-Forwarded-For'] = (
req && req.headers['x-forwarded-for']
? req.headers['x-forwarded-for'] + ', '
: ''
) + req.connection.remoteAddress
}
// always attach Via header to avoid loops, even if we're not proxying
headers['Via'] =
(req && req.headers['via']) ?
req.headers['via'] + ', ' :
''
// always attach Via header to avoid loops, even if we're not proxying
headers['Via'] =
req && req.headers['via']
? req.headers['via'] + ', '
: ''
headers['Via'] += '1.1 ' + this.server_id + ' (Sinopia)'
headers['Via'] += '1.1 ' + this.server_id + ' (Sinopia)'
}
module.exports = Storage

View file

@ -1,149 +1,149 @@
var assert = require('assert')
, semver = require('semver')
, Logger = require('./logger')
, URL = require('url')
var Semver = require('semver')
var URL = require('url')
var Logger = require('./logger')
// from normalize-package-data/lib/fixer.js
module.exports.validate_name = function(name) {
if (typeof(name) !== 'string') return false
name = name.toLowerCase()
if (
// all URL-safe characters and "@" for issue #75
!name.match(/^[-a-zA-Z0-9_.!~*'()@]+$/) ||
name.charAt(0) === '.' || // ".bin", etc.
name.charAt(0) === '-' || // "-" is reserved by couchdb
name === 'node_modules' ||
name === '__proto__' ||
name === 'package.json' ||
name === 'favicon.ico'
) {
return false
} else {
return true
}
if (typeof(name) !== 'string') return false
name = name.toLowerCase()
// all URL-safe characters and "@" for issue #75
if (!name.match(/^[-a-zA-Z0-9_.!~*'()@]+$/)
|| name.charAt(0) === '.' // ".bin", etc.
|| name.charAt(0) === '-' // "-" is reserved by couchdb
|| name === 'node_modules'
|| name === '__proto__'
|| name === 'package.json'
|| name === 'favicon.ico'
) {
return false
} else {
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'] = {}
}
if (!module.exports.is_object(object['dist-tags'])) {
object['dist-tags'] = {}
}
if (!module.exports.is_object(object['versions'])) {
object['versions'] = {}
}
if (!module.exports.is_object(object['versions'])) {
object['versions'] = {}
}
return object
return object
}
module.exports.parse_tarball_url = function(_url) {
var url = URL.parse(_url)
var url = URL.parse(_url)
var path = url.path.replace(/^\//, '').split('/')
if (path.length >= 3 && path[path.length-2] === '-') {
var filename = path.pop()
, pkgpath = '/' + filename // tarball name
pkgpath = '/' + path.pop() + pkgpath // "-"
pkgpath = '/' + path.pop() + pkgpath // package.name
} else {
return null
}
var path = url.path.replace(/^\//, '').split('/')
if (path.length >= 3 && path[path.length-2] === '-') {
var filename = path.pop()
var pkgpath = '/' + filename // tarball name
pkgpath = '/' + path.pop() + pkgpath // "-"
pkgpath = '/' + path.pop() + pkgpath // package.name
} else {
return null
}
return {
protocol: url.protocol,
host: url.host,
prepath: '/' + path.join('/'),
pkgpath: pkgpath,
filename: filename,
}
return {
protocol : url.protocol,
host : url.host,
prepath : '/' + path.join('/'),
pkgpath : pkgpath,
filename : filename,
}
}
module.exports.filter_tarball_urls = function(pkg, req, config) {
function filter(_url) {
if (!req.headers.host) return _url
function filter(_url) {
if (!req.headers.host) return _url
var url = module.exports.parse_tarball_url(_url)
// weird url, just return it
if (url == null) return _url
var url = module.exports.parse_tarball_url(_url)
// weird url, just return it
if (url == null) return _url
if (config.url_prefix != null) {
var result = config.url_prefix.replace(/\/$/, '')
} else {
var result = req.protocol + '://' + req.headers.host
}
if (config.url_prefix != null) {
var result = config.url_prefix.replace(/\/$/, '')
} else {
var result = req.protocol + '://' + req.headers.host
}
return result + url.pkgpath
}
return result + url.pkgpath
}
for (var ver in pkg.versions) {
var dist = pkg.versions[ver].dist
if (dist != null && dist.tarball != null) {
//dist.__sinopia_orig_tarball = dist.tarball
dist.tarball = filter(dist.tarball)
}
}
return pkg
for (var ver in pkg.versions) {
var dist = pkg.versions[ver].dist
if (dist != null && dist.tarball != null) {
//dist.__sinopia_orig_tarball = dist.tarball
dist.tarball = filter(dist.tarball)
}
}
return pkg
}
function can_add_tag(tag, config) {
if (!tag) return false
if (tag === 'latest' && config.ignore_latest_tag) return false
return true
if (!tag) return false
if (tag === 'latest' && config.ignore_latest_tag) return false
return true
}
module.exports.tag_version = function(data, version, tag, config) {
if (!can_add_tag(tag, config)) return false
if (!can_add_tag(tag, config)) return false
switch(typeof(data['dist-tags'][tag])) {
case 'string':
data['dist-tags'][tag] = [data['dist-tags'][tag]]
break
case 'object': // array
break
default:
data['dist-tags'][tag] = []
}
if (data['dist-tags'][tag].indexOf(version) === -1) {
data['dist-tags'][tag].push(version)
data['dist-tags'][tag] = module.exports.semver_sort(data['dist-tags'][tag])
return data['dist-tags'][tag][data['dist-tags'][tag].length - 1] === version
}
return false
switch (typeof(data['dist-tags'][tag])) {
case 'string':
data['dist-tags'][tag] = [ data['dist-tags'][tag] ]
break
case 'object': // array
break
default:
data['dist-tags'][tag] = []
}
if (data['dist-tags'][tag].indexOf(version) === -1) {
data['dist-tags'][tag].push(version)
data['dist-tags'][tag] = module.exports.semver_sort(data['dist-tags'][tag])
return data['dist-tags'][tag][data['dist-tags'][tag].length - 1] === version
}
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) {
if (version.compare(semver.parse(k, true)) === 0) {
return object.versions[k]
}
}
} catch(err) {
return undefined
}
try {
version = Semver.parse(version, true)
for (var k in object.versions) {
if (version.compare(Semver.parse(k, true)) === 0) {
return object.versions[k]
}
}
} catch (err) {
return undefined
}
}
// 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
}
return true
})
.sort(semver.compareLoose)
.map(String)
return array
.filter(function(x) {
if (!Semver.parse(x, true)) {
Logger.logger.warn( {ver: x}, 'ignoring bad version @{ver}' )
return false
}
return true
})
.sort(Semver.compareLoose)
.map(String)
}

View file

@ -1,58 +1,58 @@
var assert = require('assert')
function readfile(x) {
return require('fs').readFileSync(__dirname + '/' + x)
return require('fs').readFileSync(__dirname + '/' + x)
}
module.exports = function() {
var server = process.server
var server = process.server
it('add tag - 404', function(cb) {
server.add_tag('testpkg-tag', 'tagtagtag', '0.0.1', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
it('add tag - 404', function(cb) {
server.add_tag('testpkg-tag', 'tagtagtag', '0.0.1', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
describe('addtag', function() {
before(function(cb) {
server.put_package('testpkg-tag', eval(
'(' + readfile('fixtures/publish.json5')
.toString('utf8')
.replace(/__NAME__/g, 'testpkg-tag')
.replace(/__VERSION__/g, '0.0.1')
+ ')'
), function(res, body) {
assert.equal(res.statusCode, 201)
cb()
})
})
describe('addtag', function() {
before(function(cb) {
server.put_package('testpkg-tag', eval(
'(' + readfile('fixtures/publish.json5')
.toString('utf8')
.replace(/__NAME__/g, 'testpkg-tag')
.replace(/__VERSION__/g, '0.0.1')
+ ')'
), function(res, body) {
assert.equal(res.statusCode, 201)
cb()
})
})
it('add testpkg-tag', function(){})
it('add testpkg-tag', function(){})
it('add tag - bad ver', function(cb) {
server.add_tag('testpkg-tag', 'tagtagtag', '0.0.1-x', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('version doesn\'t exist'))
cb()
})
})
it('add tag - bad ver', function(cb) {
server.add_tag('testpkg-tag', 'tagtagtag', '0.0.1-x', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('version doesn\'t exist'))
cb()
})
})
it('add tag - bad tag', function(cb) {
server.add_tag('testpkg-tag', 'tag/tag/tag', '0.0.1-x', function(res, body) {
assert.equal(res.statusCode, 403)
assert(~body.error.indexOf('invalid tag'))
cb()
})
})
it('add tag - bad tag', function(cb) {
server.add_tag('testpkg-tag', 'tag/tag/tag', '0.0.1-x', function(res, body) {
assert.equal(res.statusCode, 403)
assert(~body.error.indexOf('invalid tag'))
cb()
})
})
it('add tag - good', function(cb) {
server.add_tag('testpkg-tag', 'tagtagtag', '0.0.1', function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('tagged'))
cb()
})
})
})
it('add tag - good', function(cb) {
server.add_tag('testpkg-tag', 'tagtagtag', '0.0.1', function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('tagged'))
cb()
})
})
})
}

View file

@ -2,35 +2,35 @@ var assert = require('assert')
var Server = require('./lib/server')
module.exports = function() {
var server = new Server('http://localhost:55551/')
var server = new Server('http://localhost:55551/')
describe('adduser', function() {
var user = String(Math.random())
var pass = String(Math.random())
before(function(cb) {
server.auth(user, pass, function(res, body) {
assert.equal(res.statusCode, 201)
assert(body.ok.match(/user .* created/))
cb()
})
})
describe('adduser', function() {
var user = String(Math.random())
var pass = String(Math.random())
before(function(cb) {
server.auth(user, pass, function(res, body) {
assert.equal(res.statusCode, 201)
assert(body.ok.match(/user .* created/))
cb()
})
})
it('creating new user', function(){})
it('creating new user', function(){})
it('should log in', function(cb) {
server.auth(user, pass, function(res, body) {
assert.equal(res.statusCode, 201)
assert(body.ok.match(/you are authenticated as/))
cb()
})
})
it('should log in', function(cb) {
server.auth(user, pass, function(res, body) {
assert.equal(res.statusCode, 201)
assert(body.ok.match(/you are authenticated as/))
cb()
})
})
it('should not register more users', function(cb) {
server.auth(String(Math.random()), String(Math.random()), function(res, body) {
assert.equal(res.statusCode, 403)
assert(body.error.match(/maximum amount of users reached/))
cb()
})
})
})
it('should not register more users', function(cb) {
server.auth(String(Math.random()), String(Math.random()), function(res, body) {
assert.equal(res.statusCode, 403)
assert(body.error.match(/maximum amount of users reached/))
cb()
})
})
})
}

View file

@ -1,133 +1,133 @@
require('./lib/startup')
var assert = require('assert')
, async = require('async')
, crypto = require('crypto')
var async = require('async')
var crypto = require('crypto')
function readfile(x) {
return require('fs').readFileSync(__dirname + '/' + x)
return require('fs').readFileSync(__dirname + '/' + x)
}
module.exports = function() {
var server = process.server
var server2 = process.server2
var server = process.server
var server2 = process.server2
it('trying to fetch non-existent package', function(cb) {
server.get_package('testpkg', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
it('trying to fetch non-existent package', function(cb) {
server.get_package('testpkg', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
describe('testpkg', function() {
before(server.add_package.bind(server, 'testpkg'))
describe('testpkg', function() {
before(server.add_package.bind(server, 'testpkg'))
it('creating new package', function(){/* test for before() */})
it('creating new package', function(){/* test for before() */})
it('downloading non-existent tarball', function(cb) {
server.get_tarball('testpkg', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such file'))
cb()
})
})
it('downloading non-existent tarball', function(cb) {
server.get_tarball('testpkg', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such file'))
cb()
})
})
it('uploading incomplete tarball', function(cb) {
server.put_tarball_incomplete('testpkg', 'blahblah1', readfile('fixtures/binary'), 3000, function(res, body) {
cb()
})
})
it('uploading incomplete tarball', function(cb) {
server.put_tarball_incomplete('testpkg', 'blahblah1', readfile('fixtures/binary'), 3000, function(res, body) {
cb()
})
})
describe('tarball', function() {
before(function(cb) {
server.put_tarball('testpkg', 'blahblah', readfile('fixtures/binary'), function(res, body) {
assert.equal(res.statusCode, 201)
assert(body.ok)
cb()
})
})
describe('tarball', function() {
before(function(cb) {
server.put_tarball('testpkg', 'blahblah', readfile('fixtures/binary'), function(res, body) {
assert.equal(res.statusCode, 201)
assert(body.ok)
cb()
})
})
it('uploading new tarball', function(){/* test for before() */})
it('uploading new tarball', function(){/* test for before() */})
it('downloading newly created tarball', function(cb) {
server.get_tarball('testpkg', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 200)
assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
cb()
})
})
it('downloading newly created tarball', function(cb) {
server.get_tarball('testpkg', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 200)
assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
cb()
})
})
it('uploading new package version (bad sha)', function(cb) {
var pkg = require('./lib/package')('testpkg')
pkg.dist.shasum = crypto.createHash('sha1').update('fake').digest('hex')
server.put_version('testpkg', '0.0.1', pkg, function(res, body) {
assert.equal(res.statusCode, 400)
assert(~body.error.indexOf('shasum error'))
cb()
})
})
it('uploading new package version (bad sha)', function(cb) {
var pkg = require('./lib/package')('testpkg')
pkg.dist.shasum = crypto.createHash('sha1').update('fake').digest('hex')
server.put_version('testpkg', '0.0.1', pkg, function(res, body) {
assert.equal(res.statusCode, 400)
assert(~body.error.indexOf('shasum error'))
cb()
})
})
describe('version', function() {
before(function(cb) {
var pkg = require('./lib/package')('testpkg')
pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex')
server.put_version('testpkg', '0.0.1', pkg, function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('published'))
cb()
})
})
describe('version', function() {
before(function(cb) {
var pkg = require('./lib/package')('testpkg')
pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex')
server.put_version('testpkg', '0.0.1', pkg, function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('published'))
cb()
})
})
it('uploading new package version', function(){/* test for before() */})
it('uploading new package version', function(){/* test for before() */})
it('downloading newly created package', function(cb) {
server.get_package('testpkg', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body.name, 'testpkg')
assert.equal(body.versions['0.0.1'].name, 'testpkg')
assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55551/testpkg/-/blahblah')
assert.deepEqual(body['dist-tags'], {latest: '0.0.1'})
cb()
})
})
it('downloading newly created package', function(cb) {
server.get_package('testpkg', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body.name, 'testpkg')
assert.equal(body.versions['0.0.1'].name, 'testpkg')
assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55551/testpkg/-/blahblah')
assert.deepEqual(body['dist-tags'], {latest: '0.0.1'})
cb()
})
})
it('downloading package via server2', function(cb) {
server2.get_package('testpkg', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body.name, 'testpkg')
assert.equal(body.versions['0.0.1'].name, 'testpkg')
assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55552/testpkg/-/blahblah')
assert.deepEqual(body['dist-tags'], {latest: '0.0.1'})
cb()
})
})
})
})
})
it('downloading package via server2', function(cb) {
server2.get_package('testpkg', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body.name, 'testpkg')
assert.equal(body.versions['0.0.1'].name, 'testpkg')
assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55552/testpkg/-/blahblah')
assert.deepEqual(body['dist-tags'], {latest: '0.0.1'})
cb()
})
})
})
})
})
it('uploading new package version for bad pkg', function(cb) {
server.put_version('testpxg', '0.0.1', require('./lib/package')('testpxg'), function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
it('uploading new package version for bad pkg', function(cb) {
server.put_version('testpxg', '0.0.1', require('./lib/package')('testpxg'), function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
it('doubleerr test', function(cb) {
server.put_tarball('testfwd2', 'blahblah', readfile('fixtures/binary'), function(res, body) {
assert.equal(res.statusCode, 404)
assert(body.error)
cb()
})
})
it('doubleerr test', function(cb) {
server.put_tarball('testfwd2', 'blahblah', readfile('fixtures/binary'), function(res, body) {
assert.equal(res.statusCode, 404)
assert(body.error)
cb()
})
})
it('publishing package / bad ro uplink', function(cb) {
server.put_package('baduplink', require('./lib/package')('baduplink'), function(res, body) {
assert.equal(res.statusCode, 503)
assert(~body.error.indexOf('one of the uplinks is down, refuse to publish'))
cb()
})
})
it('publishing package / bad ro uplink', function(cb) {
server.put_package('baduplink', require('./lib/package')('baduplink'), function(res, body) {
assert.equal(res.statusCode, 503)
assert(~body.error.indexOf('one of the uplinks is down, refuse to publish'))
cb()
})
})
}

View file

@ -1,75 +1,74 @@
var assert = require('assert')
, crypto = require('crypto')
, ex = module.exports
var crypto = require('crypto')
function readfile(x) {
return require('fs').readFileSync(__dirname + '/' + x)
return require('fs').readFileSync(__dirname + '/' + x)
}
module.exports = function() {
var server = process.server
var server2 = process.server2
var server = process.server
var server2 = process.server2
it('downloading non-existent tarball #1 / srv2', function(cb) {
server2.get_tarball('testpkg-gh29', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
it('downloading non-existent tarball #1 / srv2', function(cb) {
server2.get_tarball('testpkg-gh29', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
describe('pkg-gh29', function() {
before(function(cb) {
server.put_package('testpkg-gh29', require('./lib/package')('testpkg-gh29'), function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('created new package'))
cb()
})
})
describe('pkg-gh29', function() {
before(function(cb) {
server.put_package('testpkg-gh29', require('./lib/package')('testpkg-gh29'), function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('created new package'))
cb()
})
})
it('creating new package / srv1', function(){})
it('creating new package / srv1', function(){})
it('downloading non-existent tarball #2 / srv2', function(cb) {
server2.get_tarball('testpkg-gh29', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such file'))
cb()
})
})
it('downloading non-existent tarball #2 / srv2', function(cb) {
server2.get_tarball('testpkg-gh29', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such file'))
cb()
})
})
describe('tarball', function() {
before(function(cb) {
server.put_tarball('testpkg-gh29', 'blahblah', readfile('fixtures/binary'), function(res, body) {
assert.equal(res.statusCode, 201)
assert(body.ok)
cb()
})
})
describe('tarball', function() {
before(function(cb) {
server.put_tarball('testpkg-gh29', 'blahblah', readfile('fixtures/binary'), function(res, body) {
assert.equal(res.statusCode, 201)
assert(body.ok)
cb()
})
})
it('uploading new tarball / srv1', function(){})
it('uploading new tarball / srv1', function(){})
describe('pkg version', function() {
before(function(cb) {
var pkg = require('./lib/package')('testpkg-gh29')
pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex')
server.put_version('testpkg-gh29', '0.0.1', pkg, function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('published'))
cb()
})
})
describe('pkg version', function() {
before(function(cb) {
var pkg = require('./lib/package')('testpkg-gh29')
pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex')
server.put_version('testpkg-gh29', '0.0.1', pkg, function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('published'))
cb()
})
})
it('uploading new package version / srv1', function(){})
it('uploading new package version / srv1', function(){})
it('downloading newly created tarball / srv2', function(cb) {
server2.get_tarball('testpkg-gh29', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 200)
assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
cb()
})
})
})
})
})
it('downloading newly created tarball / srv2', function(cb) {
server2.get_tarball('testpkg-gh29', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 200)
assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
cb()
})
})
})
})
})
}

View file

@ -1,93 +1,93 @@
require('./lib/startup')
var assert = require('assert')
, async = require('async')
, crypto = require('crypto')
var async = require('async')
var crypto = require('crypto')
function readfile(x) {
return require('fs').readFileSync(__dirname + '/' + x)
return require('fs').readFileSync(__dirname + '/' + x)
}
module.exports = function() {
var server = process.server
var express = process.express
var server = process.server
var express = process.express
describe('testexp_gzip', function() {
before(function() {
express.get('/testexp_gzip', function(req, res) {
var x = eval(
'(' + readfile('fixtures/publish.json5')
.toString('utf8')
.replace(/__NAME__/g, 'testexp_gzip')
.replace(/__VERSION__/g, '0.0.1')
+ ')'
)
describe('testexp_gzip', function() {
before(function() {
express.get('/testexp_gzip', function(req, res) {
var x = eval(
'(' + readfile('fixtures/publish.json5')
.toString('utf8')
.replace(/__NAME__/g, 'testexp_gzip')
.replace(/__VERSION__/g, '0.0.1')
+ ')'
)
// overcoming compress threshold
x.versions['0.0.2'] = x.versions['0.0.1']
x.versions['0.0.3'] = x.versions['0.0.1']
x.versions['0.0.4'] = x.versions['0.0.1']
x.versions['0.0.5'] = x.versions['0.0.1']
x.versions['0.0.6'] = x.versions['0.0.1']
x.versions['0.0.7'] = x.versions['0.0.1']
x.versions['0.0.8'] = x.versions['0.0.1']
x.versions['0.0.9'] = x.versions['0.0.1']
// overcoming compress threshold
x.versions['0.0.2'] = x.versions['0.0.1']
x.versions['0.0.3'] = x.versions['0.0.1']
x.versions['0.0.4'] = x.versions['0.0.1']
x.versions['0.0.5'] = x.versions['0.0.1']
x.versions['0.0.6'] = x.versions['0.0.1']
x.versions['0.0.7'] = x.versions['0.0.1']
x.versions['0.0.8'] = x.versions['0.0.1']
x.versions['0.0.9'] = x.versions['0.0.1']
require('zlib').gzip(JSON.stringify(x), function(err, buf) {
assert(!err)
assert.equal(req.headers['accept-encoding'], 'gzip')
res.header('content-encoding', 'gzip')
res.send(buf)
})
})
require('zlib').gzip(JSON.stringify(x), function(err, buf) {
assert(!err)
assert.equal(req.headers['accept-encoding'], 'gzip')
res.header('content-encoding', 'gzip')
res.send(buf)
})
})
express.get('/testexp_baddata', function(req, res) {
assert.equal(req.headers['accept-encoding'], 'gzip')
res.header('content-encoding', 'gzip')
res.send(new Buffer([1,2,3,4,5,6,7,7,6,5,4,3,2,1]))
})
})
express.get('/testexp_baddata', function(req, res) {
assert.equal(req.headers['accept-encoding'], 'gzip')
res.header('content-encoding', 'gzip')
res.send(new Buffer([1,2,3,4,5,6,7,7,6,5,4,3,2,1]))
})
})
it('should not fail on bad gzip', function(cb) {
server.get_package('testexp_baddata', function(res, body) {
assert.equal(res.statusCode, 404)
cb()
})
})
it('should not fail on bad gzip', function(cb) {
server.get_package('testexp_baddata', function(res, body) {
assert.equal(res.statusCode, 404)
cb()
})
})
it('should understand gzipped data from uplink', function(cb) {
server.get_package('testexp_gzip', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(res.headers['content-encoding'], undefined)
assert.equal(body.name, 'testexp_gzip')
assert.equal(Object.keys(body.versions).length, 9)
cb()
})
})
it('should understand gzipped data from uplink', function(cb) {
server.get_package('testexp_gzip', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(res.headers['content-encoding'], undefined)
assert.equal(body.name, 'testexp_gzip')
assert.equal(Object.keys(body.versions).length, 9)
cb()
})
})
it('should serve gzipped data', function(cb) {
server.request({
uri: '/testexp_gzip',
encoding: null,
headers: {
'Accept-encoding': 'gzip',
},
json: false,
}, function(err, res, body) {
assert.equal(res.statusCode, 200)
assert.equal(res.headers['content-encoding'], 'gzip')
assert.throws(function() {
JSON.parse(body.toString('utf8'))
})
require('zlib').gunzip(body, function(err, buf) {
assert(!err)
body = JSON.parse(buf)
assert.equal(body.name, 'testexp_gzip')
assert.equal(Object.keys(body.versions).length, 9)
cb()
})
})
})
})
it('should serve gzipped data', function(cb) {
server.request({
uri: '/testexp_gzip',
encoding: null,
headers: {
'Accept-encoding': 'gzip',
},
json: false,
}, function(err, res, body) {
assert.equal(res.statusCode, 200)
assert.equal(res.headers['content-encoding'], 'gzip')
assert.throws(function() {
JSON.parse(body.toString('utf8'))
})
require('zlib').gunzip(body, function(err, buf) {
assert(!err)
body = JSON.parse(buf)
assert.equal(body.name, 'testexp_gzip')
assert.equal(Object.keys(body.versions).length, 9)
cb()
})
})
})
})
}

View file

@ -1,67 +1,66 @@
var assert = require('assert')
, ex = module.exports
module.exports = function() {
var server = process.server
var express = process.express
var server = process.server
var express = process.express
describe('Incomplete', function() {
var on_tarball
describe('Incomplete', function() {
var on_tarball
before(function() {
express.get('/testexp-incomplete', function(_, res) {
res.send({
"name": "testexp-incomplete",
"versions": {
"0.1.0": {
"name": "testexp_tags",
"version": "0.1.0",
"dist": {
"shasum": "fake",
"tarball": "http://localhost:55550/testexp-incomplete/-/content-length.tar.gz"
}
},
"0.1.1": {
"name": "testexp_tags",
"version": "0.1.1",
"dist": {
"shasum": "fake",
"tarball": "http://localhost:55550/testexp-incomplete/-/chunked.tar.gz"
}
}
}
})
})
})
before(function() {
express.get('/testexp-incomplete', function(_, res) {
res.send({
"name": "testexp-incomplete",
"versions": {
"0.1.0": {
"name": "testexp_tags",
"version": "0.1.0",
"dist": {
"shasum": "fake",
"tarball": "http://localhost:55550/testexp-incomplete/-/content-length.tar.gz"
}
},
"0.1.1": {
"name": "testexp_tags",
"version": "0.1.1",
"dist": {
"shasum": "fake",
"tarball": "http://localhost:55550/testexp-incomplete/-/chunked.tar.gz"
}
}
}
})
})
})
;['content-length', 'chunked'].forEach(function(type) {
it('should not store tarballs / ' + type, function(_cb) {
var called
express.get('/testexp-incomplete/-/'+type+'.tar.gz', function(_, res) {
if (called) return res.socket.destroy()
called = true
if (type !== 'chunked') res.header('content-length', 1e6)
res.write('test test test\n')
setTimeout(function() {
res.socket.write('200\nsss\n')
res.socket.destroy()
cb()
}, 10)
})
;['content-length', 'chunked'].forEach(function(type) {
it('should not store tarballs / ' + type, function(_cb) {
var called
express.get('/testexp-incomplete/-/'+type+'.tar.gz', function(_, res) {
if (called) return res.socket.destroy()
called = true
if (type !== 'chunked') res.header('content-length', 1e6)
res.write('test test test\n')
setTimeout(function() {
res.socket.write('200\nsss\n')
res.socket.destroy()
cb()
}, 10)
})
server.request({uri:'/testexp-incomplete/-/'+type+'.tar.gz'}, function(err, res, body) {
if (type !== 'chunked') assert.equal(res.headers['content-length'], 1e6)
assert(body.match(/test test test/))
})
server.request({uri:'/testexp-incomplete/-/'+type+'.tar.gz'}, function(err, res, body) {
if (type !== 'chunked') assert.equal(res.headers['content-length'], 1e6)
assert(body.match(/test test test/))
})
function cb() {
server.request({uri:'/testexp-incomplete/-/'+type+'.tar.gz'}, function(err, res, body) {
assert.equal(body.error, 'internal server error')
_cb()
})
}
})
})
})
function cb() {
server.request({uri:'/testexp-incomplete/-/'+type+'.tar.gz'}, function(err, res, body) {
assert.equal(body.error, 'internal server error')
_cb()
})
}
})
})
})
}

View file

@ -1,80 +1,79 @@
require('./lib/startup')
var assert = require('assert')
, async = require('async')
, crypto = require('crypto')
, exec = require('child_process').exec
, ex = module.exports
var async = require('async')
var exec = require('child_process').exec
var crypto = require('crypto')
function readfile(x) {
return require('fs').readFileSync(__dirname + '/' + x)
return require('fs').readFileSync(__dirname + '/' + x)
}
describe('Func', function() {
var server = process.server
var server2 = process.server2
var server = process.server
var server2 = process.server2
before(function(cb) {
async.parallel([
function(cb) {
require('./lib/startup').start('./test-storage', './config-1.yaml', cb)
},
function(cb) {
require('./lib/startup').start('./test-storage2', './config-2.yaml', cb)
},
], cb)
})
before(function(cb) {
async.parallel([
function(cb) {
require('./lib/startup').start('./test-storage', './config-1.yaml', cb)
},
function(cb) {
require('./lib/startup').start('./test-storage2', './config-2.yaml', cb)
},
], cb)
})
before(function(cb) {
async.map([server, server2], function(server, cb) {
server.debug(function(res, body) {
server.pid = body.pid
exec('lsof -p ' + Number(server.pid), function(err, result) {
server.fdlist = result
cb()
})
})
}, cb)
})
before(function(cb) {
async.map([server, server2], function(server, cb) {
server.debug(function(res, body) {
server.pid = body.pid
exec('lsof -p ' + Number(server.pid), function(err, result) {
server.fdlist = result
cb()
})
})
}, cb)
})
before(function auth(cb) {
async.map([server, server2], function(server, cb) {
server.auth('test', 'test', function(res, body) {
assert.equal(res.statusCode, 201)
assert.notEqual(body.ok.indexOf('"test"'), -1)
cb()
})
}, cb)
})
before(function auth(cb) {
async.map([server, server2], function(server, cb) {
server.auth('test', 'test', function(res, body) {
assert.equal(res.statusCode, 201)
assert.notEqual(body.ok.indexOf('"test"'), -1)
cb()
})
}, cb)
})
it('authenticate', function(){/* test for before() */})
it('authenticate', function(){/* test for before() */})
require('./basic')()
require('./gh29')()
require('./tags')()
require('./gzip')()
require('./incomplete')()
require('./mirror')()
require('./newnpmreg')()
require('./nullstorage')()
require('./race')()
require('./racycrash')()
require('./security')()
require('./adduser')()
require('./addtag')()
require('./basic')()
require('./gh29')()
require('./tags')()
require('./gzip')()
require('./incomplete')()
require('./mirror')()
require('./newnpmreg')()
require('./nullstorage')()
require('./race')()
require('./racycrash')()
require('./security')()
require('./adduser')()
require('./addtag')()
after(function(cb) {
async.map([server, server2], function(server, cb) {
exec('lsof -p ' + Number(server.pid), function(err, result) {
assert.equal(server.fdlist, result.split('\n').filter(function(q) {
if (q.match(/TCP .*->.* \(ESTABLISHED\)/)) return false;
if (q.match(/\/libcrypt-[^\/]+\.so/)) return false;
if (q.match(/\/node_modules\/crypt3\/build\/Release/)) return false;
return true;
}).join('\n'))
cb()
})
}, cb)
})
after(function(cb) {
async.map([server, server2], function(server, cb) {
exec('lsof -p ' + Number(server.pid), function(err, result) {
assert.equal(server.fdlist, result.split('\n').filter(function(q) {
if (q.match(/TCP .*->.* \(ESTABLISHED\)/)) return false
if (q.match(/\/libcrypt-[^\/]+\.so/)) return false
if (q.match(/\/node_modules\/crypt3\/build\/Release/)) return false
return true
}).join('\n'))
cb()
})
}, cb)
})
})

View file

@ -1,12 +1,12 @@
module.exports = function(name, version) {
return {
"name": name,
"version": version || "0.0.0",
"dist": {
"shasum": "fake",
"tarball": "http://localhost:55551/"+escape(name)+"/-/blahblah"
}
}
return {
"name": name,
"version": version || "0.0.0",
"dist": {
"shasum": "fake",
"tarball": "http://localhost:55551/"+escape(name)+"/-/blahblah"
}
}
}

View file

@ -1,143 +1,144 @@
var assert = require('assert')
var request = require('request')
, assert = require('assert')
function Server(url) {
if (!(this instanceof Server)) return new Server(url)
this.url = url.replace(/\/$/, '')
this.userAgent = 'node/v0.10.8 linux x64'
this.authstr = 'Basic '+(new Buffer('test:test')).toString('base64')
var self = Object.create(Server.prototype)
self.url = url.replace(/\/$/, '')
self.userAgent = 'node/v0.10.8 linux x64'
self.authstr = 'Basic '+(new Buffer('test:test')).toString('base64')
return self
}
function prep(cb) {
return function(err, res, body) {
if (err) throw err
cb(res, body)
}
return function(err, res, body) {
if (err) throw err
cb(res, body)
}
}
Server.prototype.request = function(options, cb) {
assert(options.uri)
var headers = options.headers || {}
headers.accept = headers.accept || 'application/json'
headers['user-agent'] = headers['user-agent'] || this.userAgent
headers.authorization = headers.authorization || this.authstr
return request({
url: this.url + options.uri,
method: options.method || 'GET',
headers: headers,
encoding: options.encoding,
json: options.json != null ? options.json : true,
}, cb)
assert(options.uri)
var headers = options.headers || {}
headers.accept = headers.accept || 'application/json'
headers['user-agent'] = headers['user-agent'] || this.userAgent
headers.authorization = headers.authorization || this.authstr
return request({
url: this.url + options.uri,
method: options.method || 'GET',
headers: headers,
encoding: options.encoding,
json: options.json != null ? options.json : true,
}, cb)
}
Server.prototype.auth = function(user, pass, cb) {
this.authstr = 'Basic '+(new Buffer(user+':'+pass)).toString('base64')
this.request({
uri: '/-/user/org.couchdb.user:'+encodeURIComponent(user)+'/-rev/undefined',
method: 'PUT',
json: {
name: user,
password: pass,
email: 'test@example.com',
_id: 'org.couchdb.user:' + user,
type: 'user',
roles: [],
date: new Date(),
}
}, prep(cb))
this.authstr = 'Basic '+(Buffer(user+':'+pass)).toString('base64')
this.request({
uri: '/-/user/org.couchdb.user:'+encodeURIComponent(user)+'/-rev/undefined',
method: 'PUT',
json: {
name: user,
password: pass,
email: 'test@example.com',
_id: 'org.couchdb.user:' + user,
type: 'user',
roles: [],
date: new Date(),
}
}, prep(cb))
}
Server.prototype.get_package = function(name, cb) {
this.request({
uri: '/'+name,
method: 'GET',
}, prep(cb))
this.request({
uri: '/'+name,
method: 'GET',
}, prep(cb))
}
Server.prototype.put_package = function(name, data, cb) {
if (typeof(data) === 'object' && !Buffer.isBuffer(data)) data = JSON.stringify(data)
this.request({
uri: '/'+encodeURIComponent(name),
method: 'PUT',
headers: {
'content-type': 'application/json'
},
}, prep(cb)).end(data)
if (typeof(data) === 'object' && !Buffer.isBuffer(data)) data = JSON.stringify(data)
this.request({
uri: '/'+encodeURIComponent(name),
method: 'PUT',
headers: {
'content-type': 'application/json'
},
}, prep(cb)).end(data)
}
Server.prototype.put_version = function(name, version, data, cb) {
if (typeof(data) === 'object' && !Buffer.isBuffer(data)) data = JSON.stringify(data)
this.request({
uri: '/'+encodeURIComponent(name)+'/'+encodeURIComponent(version)+'/-tag/latest',
method: 'PUT',
headers: {
'content-type': 'application/json'
},
}, prep(cb)).end(data)
if (typeof(data) === 'object' && !Buffer.isBuffer(data)) data = JSON.stringify(data)
this.request({
uri: '/'+encodeURIComponent(name)+'/'+encodeURIComponent(version)+'/-tag/latest',
method: 'PUT',
headers: {
'content-type': 'application/json'
},
}, prep(cb)).end(data)
}
Server.prototype.get_tarball = function(name, filename, cb) {
this.request({
uri: '/'+encodeURIComponent(name)+'/-/'+encodeURIComponent(filename),
method: 'GET',
}, prep(cb))
this.request({
uri: '/'+encodeURIComponent(name)+'/-/'+encodeURIComponent(filename),
method: 'GET',
}, prep(cb))
}
Server.prototype.put_tarball = function(name, filename, data, cb) {
this.request({
uri: '/'+encodeURIComponent(name)+'/-/'+encodeURIComponent(filename)+'/whatever',
method: 'PUT',
headers: {
'content-type': 'application/octet-stream'
},
}, prep(cb)).end(data)
this.request({
uri: '/'+encodeURIComponent(name)+'/-/'+encodeURIComponent(filename)+'/whatever',
method: 'PUT',
headers: {
'content-type': 'application/octet-stream'
},
}, prep(cb)).end(data)
}
Server.prototype.add_tag = function(name, tag, version, cb) {
this.request({
uri: '/'+encodeURIComponent(name)+'/'+encodeURIComponent(tag),
method: 'PUT',
headers: {
'content-type': 'application/json'
},
}, prep(cb)).end(JSON.stringify(version))
this.request({
uri: '/'+encodeURIComponent(name)+'/'+encodeURIComponent(tag),
method: 'PUT',
headers: {
'content-type': 'application/json'
},
}, prep(cb)).end(JSON.stringify(version))
}
Server.prototype.put_tarball_incomplete = function(name, filename, data, size, cb) {
var req = this.request({
uri: '/'+encodeURIComponent(name)+'/-/'+encodeURIComponent(filename)+'/whatever',
method: 'PUT',
headers: {
'content-type': 'application/octet-stream',
'content-length': size,
},
timeout: 1000,
}, function(err) {
assert(err)
cb()
})
req.write(data)
setTimeout(function() {
req.req.abort()
}, 20)
var req = this.request({
uri: '/'+encodeURIComponent(name)+'/-/'+encodeURIComponent(filename)+'/whatever',
method: 'PUT',
headers: {
'content-type': 'application/octet-stream',
'content-length': size,
},
timeout: 1000,
}, function(err) {
assert(err)
cb()
})
req.write(data)
setTimeout(function() {
req.req.abort()
}, 20)
}
Server.prototype.add_package = function(name, cb) {
this.put_package(name, require('./package')(name), function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('created new package'))
cb()
})
this.put_package(name, require('./package')(name), function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('created new package'))
cb()
})
}
Server.prototype.debug = function(cb) {
this.request({
uri: '/-/_debug',
method: 'GET',
headers: {
'content-type': 'application/json'
},
}, prep(cb))
this.request({
uri: '/-/_debug',
method: 'GET',
headers: {
'content-type': 'application/json'
},
}, prep(cb))
}
module.exports = Server

View file

@ -1,44 +1,43 @@
var rimraf = require('rimraf')
, fork = require('child_process').fork
, assert = require('assert')
, express = require('express')
, readfile = require('fs').readFileSync
, Server = require('./server')
var assert = require('assert')
var fork = require('child_process').fork
var express = require('express')
var readfile = require('fs').readFileSync
var rimraf = require('rimraf')
var Server = require('./server')
var forks = process.forks = []
process.server = new Server('http://localhost:55551/')
process.server2 = new Server('http://localhost:55552/')
process.server = Server('http://localhost:55551/')
process.server2 = Server('http://localhost:55552/')
process.express = express()
process.express.listen(55550)
module.exports.start = function start(dir, conf, cb) {
rimraf(__dirname + '/../' + dir, function() {
// filter out --debug-brk
var oldArgv = process.execArgv
process.execArgv = process.execArgv.filter(function(x) {
return x !== '--debug-brk'
})
rimraf(__dirname + '/../' + dir, function() {
// filter out --debug-brk
var oldArgv = process.execArgv
process.execArgv = process.execArgv.filter(function(x) {
return x !== '--debug-brk'
})
var f = fork(__dirname + '/../../../bin/sinopia'
, ['-c', __dirname + '/../' + conf]
, {silent: !process.env.TRAVIS}
)
forks.push(f)
f.on('message', function(msg) {
if ('sinopia_started' in msg) {
cb()
cb = function(){}
}
})
f.on('error', function(err) {
throw err
})
process.execArgv = oldArgv
})
var f = fork(__dirname + '/../../../bin/sinopia'
, ['-c', __dirname + '/../' + conf]
, {silent: !process.env.TRAVIS}
)
forks.push(f)
f.on('message', function(msg) {
if ('sinopia_started' in msg) {
cb(), cb = function(){}
}
})
f.on('error', function(err) {
throw err
})
process.execArgv = oldArgv
})
}
process.on('exit', function() {
if (forks[0]) forks[0].kill()
if (forks[1]) forks[1].kill()
if (forks[0]) forks[0].kill()
if (forks[1]) forks[1].kill()
})

View file

@ -1,75 +1,74 @@
var assert = require('assert')
, ex = module.exports
function readfile(x) {
return require('fs').readFileSync(__dirname + '/' + x)
return require('fs').readFileSync(__dirname + '/' + x)
}
module.exports = function() {
var server = process.server
var server2 = process.server2
var server = process.server
var server2 = process.server2
it('testing anti-loop', function(cb) {
server2.get_package('testloop', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
it('testing anti-loop', function(cb) {
server2.get_package('testloop', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
;['fwd', /*'loop'*/].forEach(function(pkg) {
var prefix = pkg + ': '
pkg = 'test' + pkg
;['fwd', /*'loop'*/].forEach(function(pkg) {
var prefix = pkg + ': '
pkg = 'test' + pkg
describe(pkg, function() {
before(function(cb) {
server.put_package(pkg, require('./lib/package')(pkg), function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('created new package'))
cb()
})
})
describe(pkg, function() {
before(function(cb) {
server.put_package(pkg, require('./lib/package')(pkg), function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('created new package'))
cb()
})
})
it(prefix+'creating new package', function(){})
it(prefix+'creating new package', function(){})
describe(pkg, function() {
before(function(cb) {
server.put_version(pkg, '0.1.1', require('./lib/package')(pkg), function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('published'))
cb()
})
})
describe(pkg, function() {
before(function(cb) {
server.put_version(pkg, '0.1.1', require('./lib/package')(pkg), function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('published'))
cb()
})
})
it(prefix+'uploading new package version', function(){})
it(prefix+'uploading new package version', function(){})
it(prefix+'uploading incomplete tarball', function(cb) {
server.put_tarball_incomplete(pkg, pkg+'.bad', readfile('fixtures/binary'), 3000, function(res, body) {
cb()
})
})
it(prefix+'uploading incomplete tarball', function(cb) {
server.put_tarball_incomplete(pkg, pkg+'.bad', readfile('fixtures/binary'), 3000, function(res, body) {
cb()
})
})
describe('tarball', function() {
before(function(cb) {
server.put_tarball(pkg, pkg+'.file', readfile('fixtures/binary'), function(res, body) {
assert.equal(res.statusCode, 201)
assert(body.ok)
cb()
})
})
describe('tarball', function() {
before(function(cb) {
server.put_tarball(pkg, pkg+'.file', readfile('fixtures/binary'), function(res, body) {
assert.equal(res.statusCode, 201)
assert(body.ok)
cb()
})
})
it(prefix+'uploading new tarball', function(){})
it(prefix+'uploading new tarball', function(){})
it(prefix+'downloading tarball from server1', function(cb) {
server.get_tarball(pkg, pkg+'.file', function(res, body) {
assert.equal(res.statusCode, 200)
assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
cb()
})
})
})
})
})
})
it(prefix+'downloading tarball from server1', function(cb) {
server.get_tarball(pkg, pkg+'.file', function(res, body) {
assert.equal(res.statusCode, 200)
assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
cb()
})
})
})
})
})
})
}

View file

@ -1,132 +1,132 @@
var assert = require('assert')
function readfile(x) {
return require('fs').readFileSync(__dirname + '/' + x)
return require('fs').readFileSync(__dirname + '/' + x)
}
function sha(x) {
return require('crypto').createHash('sha1', 'binary').update(x).digest('hex')
return require('crypto').createHash('sha1', 'binary').update(x).digest('hex')
}
module.exports = function() {
var server = process.server
var server2 = process.server2
var express = process.express
var server = process.server
var server2 = process.server2
var express = process.express
describe('newnpmreg', function() {
before(function(cb) {
server.request({
uri: '/testpkg-newnpmreg',
headers: {
'content-type': 'application/json',
},
method: 'PUT',
json: JSON.parse(readfile('fixtures/newnpmreg.json')),
}, function(err, res, body) {
assert.equal(res.statusCode, 201)
cb()
})
})
describe('newnpmreg', function() {
before(function(cb) {
server.request({
uri: '/testpkg-newnpmreg',
headers: {
'content-type': 'application/json',
},
method: 'PUT',
json: JSON.parse(readfile('fixtures/newnpmreg.json')),
}, function(err, res, body) {
assert.equal(res.statusCode, 201)
cb()
})
})
it('add pkg', function(){})
it('add pkg', function(){})
it('server1 - tarball', function(cb) {
server.get_tarball('testpkg-newnpmreg', 'testpkg-newnpmreg-0.0.0.tgz', function(res, body) {
assert.equal(res.statusCode, 200)
// not real sha due to utf8 conversion
assert.strictEqual(sha(body), '789ca61e3426ce55c4983451b58e62b04abceaf6')
cb()
})
})
it('server1 - tarball', function(cb) {
server.get_tarball('testpkg-newnpmreg', 'testpkg-newnpmreg-0.0.0.tgz', function(res, body) {
assert.equal(res.statusCode, 200)
// not real sha due to utf8 conversion
assert.strictEqual(sha(body), '789ca61e3426ce55c4983451b58e62b04abceaf6')
cb()
})
})
it('server2 - tarball', function(cb) {
server2.get_tarball('testpkg-newnpmreg', 'testpkg-newnpmreg-0.0.0.tgz', function(res, body) {
assert.equal(res.statusCode, 200)
// not real sha due to utf8 conversion
assert.strictEqual(sha(body), '789ca61e3426ce55c4983451b58e62b04abceaf6')
cb()
})
})
it('server2 - tarball', function(cb) {
server2.get_tarball('testpkg-newnpmreg', 'testpkg-newnpmreg-0.0.0.tgz', function(res, body) {
assert.equal(res.statusCode, 200)
// not real sha due to utf8 conversion
assert.strictEqual(sha(body), '789ca61e3426ce55c4983451b58e62b04abceaf6')
cb()
})
})
it('server1 - package', function(cb) {
server.get_package('testpkg-newnpmreg', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body.name, 'testpkg-newnpmreg')
assert.equal(body.versions['0.0.0'].name, 'testpkg-newnpmreg')
assert.equal(body.versions['0.0.0'].dist.tarball, 'http://localhost:55551/testpkg-newnpmreg/-/testpkg-newnpmreg-0.0.0.tgz')
assert.deepEqual(body['dist-tags'], {foo: '0.0.0', latest: '0.0.0'})
cb()
})
})
it('server1 - package', function(cb) {
server.get_package('testpkg-newnpmreg', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body.name, 'testpkg-newnpmreg')
assert.equal(body.versions['0.0.0'].name, 'testpkg-newnpmreg')
assert.equal(body.versions['0.0.0'].dist.tarball, 'http://localhost:55551/testpkg-newnpmreg/-/testpkg-newnpmreg-0.0.0.tgz')
assert.deepEqual(body['dist-tags'], {foo: '0.0.0', latest: '0.0.0'})
cb()
})
})
it('server2 - package', function(cb) {
server2.get_package('testpkg-newnpmreg', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body.name, 'testpkg-newnpmreg')
assert.equal(body.versions['0.0.0'].name, 'testpkg-newnpmreg')
assert.equal(body.versions['0.0.0'].dist.tarball, 'http://localhost:55552/testpkg-newnpmreg/-/testpkg-newnpmreg-0.0.0.tgz')
assert.deepEqual(body['dist-tags'], {foo: '0.0.0', latest: '0.0.0'})
cb()
})
})
it('server2 - package', function(cb) {
server2.get_package('testpkg-newnpmreg', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body.name, 'testpkg-newnpmreg')
assert.equal(body.versions['0.0.0'].name, 'testpkg-newnpmreg')
assert.equal(body.versions['0.0.0'].dist.tarball, 'http://localhost:55552/testpkg-newnpmreg/-/testpkg-newnpmreg-0.0.0.tgz')
assert.deepEqual(body['dist-tags'], {foo: '0.0.0', latest: '0.0.0'})
cb()
})
})
it('server1 - readme', function(cb) {
server.request({uri:'/-/readme/testpkg-newnpmreg'}, function(err, res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body, '<p>blah blah blah</p>\n')
cb()
})
})
it('server1 - readme', function(cb) {
server.request({uri:'/-/readme/testpkg-newnpmreg'}, function(err, res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body, '<p>blah blah blah</p>\n')
cb()
})
})
it('server2 - readme', function(cb) {
server2.request({uri:'/-/readme/testpkg-newnpmreg'}, function(err, res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body, '<p>blah blah blah</p>\n')
cb()
})
})
it('server2 - readme', function(cb) {
server2.request({uri:'/-/readme/testpkg-newnpmreg'}, function(err, res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body, '<p>blah blah blah</p>\n')
cb()
})
})
describe('search', function() {
function check(obj) {
obj.testpkg.time.modified = '2014-10-02T07:07:51.000Z'
assert.deepEqual(obj.testpkg, {
"name": "testpkg",
"dist-tags": {
"latest": "0.0.1"
},
"maintainers": [],
"readmeFilename": "",
"time": {
"modified": "2014-10-02T07:07:51.000Z"
},
"versions": {
"0.0.1": "latest"
}
})
}
describe('search', function() {
function check(obj) {
obj.testpkg.time.modified = '2014-10-02T07:07:51.000Z'
assert.deepEqual(obj.testpkg, {
"name": "testpkg",
"dist-tags": {
"latest": "0.0.1"
},
"maintainers": [],
"readmeFilename": "",
"time": {
"modified": "2014-10-02T07:07:51.000Z"
},
"versions": {
"0.0.1": "latest"
}
})
}
before(function(cb) {
express.get('/-/all', function(req, res) {
res.send({})
})
cb()
})
before(function(cb) {
express.get('/-/all', function(req, res) {
res.send({})
})
cb()
})
it('server1 - search', function(cb) {
server.request({uri:'/-/all'}, function(err, res, body) {
assert.equal(res.statusCode, 200)
check(body)
cb()
})
})
it('server1 - search', function(cb) {
server.request({uri:'/-/all'}, function(err, res, body) {
assert.equal(res.statusCode, 200)
check(body)
cb()
})
})
it('server2 - search', function(cb) {
server2.request({uri:'/-/all'}, function(err, res, body) {
assert.equal(res.statusCode, 200)
check(body)
cb()
})
})
})
})
it('server2 - search', function(cb) {
server2.request({uri:'/-/all'}, function(err, res, body) {
assert.equal(res.statusCode, 200)
check(body)
cb()
})
})
})
})
}

View file

@ -1,78 +1,78 @@
require('./lib/startup')
var assert = require('assert')
, async = require('async')
, crypto = require('crypto')
var async = require('async')
var crypto = require('crypto')
function readfile(x) {
return require('fs').readFileSync(__dirname + '/' + x)
return require('fs').readFileSync(__dirname + '/' + x)
}
module.exports = function() {
var server = process.server
var server2 = process.server2
var server = process.server
var server2 = process.server2
it('trying to fetch non-existent package / null storage', function(cb) {
server.get_package('test-nullstorage-nonexist', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
it('trying to fetch non-existent package / null storage', function(cb) {
server.get_package('test-nullstorage-nonexist', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
describe('test-nullstorage on server2', function() {
before(server2.add_package.bind(server2, 'test-nullstorage2'))
describe('test-nullstorage on server2', function() {
before(server2.add_package.bind(server2, 'test-nullstorage2'))
it('creating new package - server2', function(){/* test for before() */})
it('creating new package - server2', function(){/* test for before() */})
it('downloading non-existent tarball', function(cb) {
server.get_tarball('test-nullstorage2', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such file'))
cb()
})
})
it('downloading non-existent tarball', function(cb) {
server.get_tarball('test-nullstorage2', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such file'))
cb()
})
})
describe('tarball', function() {
before(function(cb) {
server2.put_tarball('test-nullstorage2', 'blahblah', readfile('fixtures/binary'), function(res, body) {
assert.equal(res.statusCode, 201)
assert(body.ok)
cb()
})
})
describe('tarball', function() {
before(function(cb) {
server2.put_tarball('test-nullstorage2', 'blahblah', readfile('fixtures/binary'), function(res, body) {
assert.equal(res.statusCode, 201)
assert(body.ok)
cb()
})
})
before(function(cb) {
var pkg = require('./lib/package')('test-nullstorage2')
pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex')
server2.put_version('test-nullstorage2', '0.0.1', pkg, function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('published'))
cb()
})
})
before(function(cb) {
var pkg = require('./lib/package')('test-nullstorage2')
pkg.dist.shasum = crypto.createHash('sha1').update(readfile('fixtures/binary')).digest('hex')
server2.put_version('test-nullstorage2', '0.0.1', pkg, function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('published'))
cb()
})
})
it('uploading new tarball', function(){/* test for before() */})
it('uploading new tarball', function(){/* test for before() */})
it('downloading newly created tarball', function(cb) {
server.get_tarball('test-nullstorage2', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 200)
assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
cb()
})
})
it('downloading newly created tarball', function(cb) {
server.get_tarball('test-nullstorage2', 'blahblah', function(res, body) {
assert.equal(res.statusCode, 200)
assert.deepEqual(body, readfile('fixtures/binary').toString('utf8'))
cb()
})
})
it('downloading newly created package', function(cb) {
server.get_package('test-nullstorage2', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body.name, 'test-nullstorage2')
assert.equal(body.versions['0.0.1'].name, 'test-nullstorage2')
assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55551/test-nullstorage2/-/blahblah')
assert.deepEqual(body['dist-tags'], {latest: '0.0.1'})
cb()
})
})
})
})
it('downloading newly created package', function(cb) {
server.get_package('test-nullstorage2', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body.name, 'test-nullstorage2')
assert.equal(body.versions['0.0.1'].name, 'test-nullstorage2')
assert.equal(body.versions['0.0.1'].dist.tarball, 'http://localhost:55551/test-nullstorage2/-/blahblah')
assert.deepEqual(body['dist-tags'], {latest: '0.0.1'})
cb()
})
})
})
})
}

View file

@ -1,94 +1,94 @@
var assert = require('assert')
, readfile = require('fs').readFileSync
, ex = module.exports
, async = require('async')
, _oksum = 0
var assert = require('assert')
var async = require('async')
var readfile = require('fs').readFileSync
var ex = module.exports
var _oksum = 0
module.exports = function() {
var server = process.server
var server2 = process.server2
var server = process.server
var server2 = process.server2
describe('race', function() {
before(function(cb) {
server.put_package('race', require('./lib/package')('race'), function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('created new package'))
cb()
})
})
describe('race', function() {
before(function(cb) {
server.put_package('race', require('./lib/package')('race'), function(res, body) {
assert.equal(res.statusCode, 201)
assert(~body.ok.indexOf('created new package'))
cb()
})
})
it('creating new package', function(){})
it('creating new package', function(){})
it('uploading 10 same versions', function(cb) {
var fns = []
for (var i=0; i<10; i++) {
fns.push(function(cb_) {
var data = require('./lib/package')('race')
data.rand = Math.random()
server.put_version('race', '0.0.1', data, function(res, body) {
cb_(null, res, body)
})
})
}
it('uploading 10 same versions', function(cb) {
var fns = []
for (var i=0; i<10; i++) {
fns.push(function(cb_) {
var data = require('./lib/package')('race')
data.rand = Math.random()
server.put_version('race', '0.0.1', data, function(res, body) {
cb_(null, res, body)
})
})
}
async.parallel(fns, function(err, res) {
var okcount = 0
, failcount = 0
async.parallel(fns, function(err, res) {
var okcount = 0
, failcount = 0
res.forEach(function(arr) {
var resp = arr[0]
, body = arr[1]
res.forEach(function(arr) {
var resp = arr[0]
, body = arr[1]
if (resp.statusCode === 201 && ~body.ok.indexOf('published')) okcount++
if (resp.statusCode === 409 && ~body.error.indexOf('already present')) failcount++
if (resp.statusCode === 503 && ~body.error.indexOf('unavailable')) failcount++
})
assert.equal(okcount + failcount, 10)
assert.equal(okcount, 1)
_oksum += okcount
if (resp.statusCode === 201 && ~body.ok.indexOf('published')) okcount++
if (resp.statusCode === 409 && ~body.error.indexOf('already present')) failcount++
if (resp.statusCode === 503 && ~body.error.indexOf('unavailable')) failcount++
})
assert.equal(okcount + failcount, 10)
assert.equal(okcount, 1)
_oksum += okcount
cb()
})
})
cb()
})
})
it('uploading 10 diff versions', function(cb) {
var fns = []
for (var i=0; i<10; i++) {
;(function(i) {
fns.push(function(cb_) {
server.put_version('race', '0.1.'+String(i), require('./lib/package')('race'), function(res, body) {
cb_(null, res, body)
})
})
})(i)
}
it('uploading 10 diff versions', function(cb) {
var fns = []
for (var i=0; i<10; i++) {
;(function(i) {
fns.push(function(cb_) {
server.put_version('race', '0.1.'+String(i), require('./lib/package')('race'), function(res, body) {
cb_(null, res, body)
})
})
})(i)
}
async.parallel(fns, function(err, res) {
var okcount = 0
, failcount = 0
async.parallel(fns, function(err, res) {
var okcount = 0
, failcount = 0
res.forEach(function(arr) {
var resp = arr[0]
, body = arr[1]
if (resp.statusCode === 201 && ~body.ok.indexOf('published')) okcount++
if (resp.statusCode === 409 && ~body.error.indexOf('already present')) failcount++
if (resp.statusCode === 503 && ~body.error.indexOf('unavailable')) failcount++
})
assert.equal(okcount + failcount, 10)
_oksum += okcount
res.forEach(function(arr) {
var resp = arr[0]
, body = arr[1]
if (resp.statusCode === 201 && ~body.ok.indexOf('published')) okcount++
if (resp.statusCode === 409 && ~body.error.indexOf('already present')) failcount++
if (resp.statusCode === 503 && ~body.error.indexOf('unavailable')) failcount++
})
assert.equal(okcount + failcount, 10)
_oksum += okcount
cb()
})
})
cb()
})
})
// XXX: this should be after anything else, but we can't really ensure that with mocha
it('downloading package', function(cb) {
server.get_package('race', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(Object.keys(body.versions).length, _oksum)
cb()
})
})
})
// XXX: this should be after anything else, but we can't really ensure that with mocha
it('downloading package', function(cb) {
server.get_package('race', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(Object.keys(body.versions).length, _oksum)
cb()
})
})
})
}

View file

@ -1,69 +1,68 @@
var assert = require('assert')
, ex = module.exports
module.exports = function() {
var server = process.server
var express = process.express
var server = process.server
var express = process.express
describe('Racy', function() {
var on_tarball
describe('Racy', function() {
var on_tarball
before(function() {
express.get('/testexp-racycrash', function(_, res) {
res.send({
"name": "testexp-racycrash",
"versions": {
"0.1.0": {
"name": "testexp_tags",
"version": "0.1.0",
"dist": {
"shasum": "fake",
"tarball": "http://localhost:55550/testexp-racycrash/-/test.tar.gz"
}
}
}
})
})
before(function() {
express.get('/testexp-racycrash', function(_, res) {
res.send({
"name": "testexp-racycrash",
"versions": {
"0.1.0": {
"name": "testexp_tags",
"version": "0.1.0",
"dist": {
"shasum": "fake",
"tarball": "http://localhost:55550/testexp-racycrash/-/test.tar.gz"
}
}
}
})
})
express.get('/testexp-racycrash/-/test.tar.gz', function(_, res) {
on_tarball(res)
})
})
express.get('/testexp-racycrash/-/test.tar.gz', function(_, res) {
on_tarball(res)
})
})
it('should not crash on error if client disconnects', function(_cb) {
on_tarball = function(res) {
res.header('content-length', 1e6)
res.write('test test test\n')
setTimeout(function() {
res.write('test test test\n')
res.socket.destroy()
cb()
}, 200)
}
it('should not crash on error if client disconnects', function(_cb) {
on_tarball = function(res) {
res.header('content-length', 1e6)
res.write('test test test\n')
setTimeout(function() {
res.write('test test test\n')
res.socket.destroy()
cb()
}, 200)
}
server.request({uri:'/testexp-racycrash/-/test.tar.gz'}, function(err, res, body) {
assert.equal(body, 'test test test\n')
})
server.request({uri:'/testexp-racycrash/-/test.tar.gz'}, function(err, res, body) {
assert.equal(body, 'test test test\n')
})
function cb() {
// test for NOT crashing
server.request({uri:'/testexp-racycrash'}, function(err, res, body) {
assert.equal(res.statusCode, 200)
_cb()
})
}
})
function cb() {
// test for NOT crashing
server.request({uri:'/testexp-racycrash'}, function(err, res, body) {
assert.equal(res.statusCode, 200)
_cb()
})
}
})
it('should not store tarball', function(cb) {
on_tarball = function(res) {
res.socket.destroy()
}
it('should not store tarball', function(cb) {
on_tarball = function(res) {
res.socket.destroy()
}
server.request({uri:'/testexp-racycrash/-/test.tar.gz'}, function(err, res, body) {
assert.equal(body.error, 'internal server error')
cb()
})
})
})
server.request({uri:'/testexp-racycrash/-/test.tar.gz'}, function(err, res, body) {
assert.equal(body.error, 'internal server error')
cb()
})
})
})
}

View file

@ -1,88 +1,87 @@
var assert = require('assert')
, ex = module.exports
module.exports = function() {
var server = process.server
var server2 = process.server2
var server = process.server
var server2 = process.server2
describe('Security', function() {
before(server.add_package.bind(server, 'testpkg-sec'))
describe('Security', function() {
before(server.add_package.bind(server, 'testpkg-sec'))
it('bad pkg #1', function(cb) {
server.get_package('package.json', function(res, body) {
assert.equal(res.statusCode, 403)
assert(~body.error.indexOf('invalid package'))
cb()
})
})
it('bad pkg #1', function(cb) {
server.get_package('package.json', function(res, body) {
assert.equal(res.statusCode, 403)
assert(~body.error.indexOf('invalid package'))
cb()
})
})
it('bad pkg #2', function(cb) {
server.get_package('__proto__', function(res, body) {
assert.equal(res.statusCode, 403)
assert(~body.error.indexOf('invalid package'))
cb()
})
})
it('bad pkg #2', function(cb) {
server.get_package('__proto__', function(res, body) {
assert.equal(res.statusCode, 403)
assert(~body.error.indexOf('invalid package'))
cb()
})
})
it('__proto__, connect stuff', function(cb) {
server.request({uri:'/testpkg-sec?__proto__=1'}, function(err, res, body) {
// test for NOT outputting stack trace
assert(!body || typeof(body) === 'object' || body.indexOf('node_modules') === -1)
it('__proto__, connect stuff', function(cb) {
server.request({uri:'/testpkg-sec?__proto__=1'}, function(err, res, body) {
// test for NOT outputting stack trace
assert(!body || typeof(body) === 'object' || body.indexOf('node_modules') === -1)
// test for NOT crashing
server.request({uri:'/testpkg-sec'}, function(err, res, body) {
assert.equal(res.statusCode, 200)
cb()
})
})
})
// test for NOT crashing
server.request({uri:'/testpkg-sec'}, function(err, res, body) {
assert.equal(res.statusCode, 200)
cb()
})
})
})
it('do not return package.json as an attachment', function(cb) {
server.request({uri:'/testpkg-sec/-/package.json'}, function(err, res, body) {
assert.equal(res.statusCode, 403)
assert(body.error.match(/invalid filename/))
cb()
})
})
it('do not return package.json as an attachment', function(cb) {
server.request({uri:'/testpkg-sec/-/package.json'}, function(err, res, body) {
assert.equal(res.statusCode, 403)
assert(body.error.match(/invalid filename/))
cb()
})
})
it('silly things - reading #1', function(cb) {
server.request({uri:'/testpkg-sec/-/../../../../../../../../etc/passwd'}, function(err, res, body) {
assert.equal(res.statusCode, 404)
cb()
})
})
it('silly things - reading #1', function(cb) {
server.request({uri:'/testpkg-sec/-/../../../../../../../../etc/passwd'}, function(err, res, body) {
assert.equal(res.statusCode, 404)
cb()
})
})
it('silly things - reading #2', function(cb) {
server.request({uri:'/testpkg-sec/-/%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'}, function(err, res, body) {
assert.equal(res.statusCode, 403)
assert(body.error.match(/invalid filename/))
cb()
})
})
it('silly things - reading #2', function(cb) {
server.request({uri:'/testpkg-sec/-/%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd'}, function(err, res, body) {
assert.equal(res.statusCode, 403)
assert(body.error.match(/invalid filename/))
cb()
})
})
it('silly things - writing #1', function(cb) {
server.put_tarball('testpkg-sec', 'package.json', '{}', function(res, body) {
assert.equal(res.statusCode, 403)
assert(body.error.match(/invalid filename/))
cb()
})
})
it('silly things - writing #1', function(cb) {
server.put_tarball('testpkg-sec', 'package.json', '{}', function(res, body) {
assert.equal(res.statusCode, 403)
assert(body.error.match(/invalid filename/))
cb()
})
})
it('silly things - writing #3', function(cb) {
server.put_tarball('testpkg-sec', 'node_modules', '{}', function(res, body) {
assert.equal(res.statusCode, 403)
assert(body.error.match(/invalid filename/))
cb()
})
})
it('silly things - writing #3', function(cb) {
server.put_tarball('testpkg-sec', 'node_modules', '{}', function(res, body) {
assert.equal(res.statusCode, 403)
assert(body.error.match(/invalid filename/))
cb()
})
})
it('silly things - writing #4', function(cb) {
server.put_tarball('testpkg-sec', '../testpkg.tgz', '{}', function(res, body) {
assert.equal(res.statusCode, 403)
assert(body.error.match(/invalid filename/))
cb()
})
})
})
it('silly things - writing #4', function(cb) {
server.put_tarball('testpkg-sec', '../testpkg.tgz', '{}', function(res, body) {
assert.equal(res.statusCode, 403)
assert(body.error.match(/invalid filename/))
cb()
})
})
})
}

View file

@ -1,51 +1,50 @@
var assert = require('assert')
, ex = module.exports
function readfile(x) {
return require('fs').readFileSync(__dirname + '/' + x)
return require('fs').readFileSync(__dirname + '/' + x)
}
module.exports = function() {
var server = process.server
var express = process.express
var server = process.server
var express = process.express
it('tags - testing for 404', function(cb) {
server.get_package('testexp_tags', function(res, body) {
// shouldn't exist yet
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
it('tags - testing for 404', function(cb) {
server.get_package('testexp_tags', function(res, body) {
// shouldn't exist yet
assert.equal(res.statusCode, 404)
assert(~body.error.indexOf('no such package'))
cb()
})
})
describe('tags', function() {
before(function(cb) {
express.get('/testexp_tags', function(req, res) {
res.send(JSON.parse(readfile('fixtures/tags.json')))
})
cb()
})
describe('tags', function() {
before(function(cb) {
express.get('/testexp_tags', function(req, res) {
res.send(JSON.parse(readfile('fixtures/tags.json')))
})
cb()
})
it('fetching package again', function(cb) {
server.get_package('testexp_tags', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(typeof(body.versions['1.1']), 'object')
assert.equal(body['dist-tags'].something, '0.1.1alpha')
// note: 5.4.3 is invalid tag, 0.1.3alpha is highest semver
assert.equal(body['dist-tags'].latest, '0.1.3alpha')
assert.equal(body['dist-tags'].bad, null)
cb()
})
})
it('fetching package again', function(cb) {
server.get_package('testexp_tags', function(res, body) {
assert.equal(res.statusCode, 200)
assert.equal(typeof(body.versions['1.1']), 'object')
assert.equal(body['dist-tags'].something, '0.1.1alpha')
// note: 5.4.3 is invalid tag, 0.1.3alpha is highest semver
assert.equal(body['dist-tags'].latest, '0.1.3alpha')
assert.equal(body['dist-tags'].bad, null)
cb()
})
})
;['0.1.1alpha', '0.1.1-alpha', '0000.00001.001-alpha'].forEach(function(ver) {
it('fetching '+ver, function(cb) {
server.request({uri:'/testexp_tags/'+ver}, function(err, res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body.version, '0.1.1alpha')
cb()
})
})
})
})
;['0.1.1alpha', '0.1.1-alpha', '0000.00001.001-alpha'].forEach(function(ver) {
it('fetching '+ver, function(cb) {
server.request({uri:'/testexp_tags/'+ver}, function(err, res, body) {
assert.equal(res.statusCode, 200)
assert.equal(body.version, '0.1.1alpha')
cb()
})
})
})
})
}

View file

@ -1,8 +1,8 @@
describe('config.yaml', function() {
it('should be parseable', function() {
var source = require('fs').readFileSync(__dirname + '/../../lib/config_def.yaml', 'utf8')
require('js-yaml').safeLoad(source)
})
it('should be parseable', function() {
var source = require('fs').readFileSync(__dirname + '/../../lib/config_def.yaml', 'utf8')
require('js-yaml').safeLoad(source)
})
})

View file

@ -1,17 +1,17 @@
var ReadTarball = require('../../lib/streams').ReadTarballStream
describe('mystreams', function() {
it('should delay events', function(cb) {
var test = new ReadTarball()
test.abort()
setTimeout(function() {
test.abort = function() {
cb()
}
test.abort = function() {
throw new Error('fail')
}
}, 10)
})
it('should delay events', function(cb) {
var test = new ReadTarball()
test.abort()
setTimeout(function() {
test.abort = function() {
cb()
}
test.abort = function() {
throw Error('fail')
}
}, 10)
})
})

View file

@ -1,88 +1,88 @@
var assert = require('assert')
, Storage = require('../../lib/up-storage')
var assert = require('assert')
var Storage = require('../../lib/up-storage')
require('../../lib/logger').setup([])
function setup(host, config, mainconfig) {
config.url = host
return new Storage(config, mainconfig)
config.url = host
return Storage(config, mainconfig)
}
describe('Use proxy', function() {
it('should work fine without proxy', function() {
var x = setup('http://x/x', {}, {})
assert.equal(x.proxy, null)
})
it('should work fine without proxy', function() {
var x = setup('http://x/x', {}, {})
assert.equal(x.proxy, null)
})
it('local config should take priority', function() {
var x = setup('http://x/x', {http_proxy: '123'}, {http_proxy: '456'})
assert.equal(x.proxy, '123')
})
it('local config should take priority', function() {
var x = setup('http://x/x', {http_proxy: '123'}, {http_proxy: '456'})
assert.equal(x.proxy, '123')
})
it('no_proxy is invalid', function() {
var 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}, {})
assert.equal(x.proxy, '123')
var 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: ''}, {})
assert.equal(x.proxy, '123')
})
it('no_proxy is invalid', function() {
var 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}, {})
assert.equal(x.proxy, '123')
var 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: ''}, {})
assert.equal(x.proxy, '123')
})
it('no_proxy - simple/include', function() {
var x = setup('http://localhost', {http_proxy: '123'}, {no_proxy: 'localhost'})
assert.equal(x.proxy, undefined)
})
it('no_proxy - simple/include', function() {
var x = setup('http://localhost', {http_proxy: '123'}, {no_proxy: 'localhost'})
assert.equal(x.proxy, undefined)
})
it('no_proxy - simple/not', function() {
var x = setup('http://localhost', {http_proxy: '123'}, {no_proxy: 'blah'})
assert.equal(x.proxy, '123')
})
it('no_proxy - simple/not', function() {
var x = setup('http://localhost', {http_proxy: '123'}, {no_proxy: 'blah'})
assert.equal(x.proxy, '123')
})
it('no_proxy - various, single string', function() {
var 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'})
assert.equal(x.proxy, null)
var 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'}, {})
assert.equal(x.proxy, null)
var 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'}, {})
assert.equal(x.proxy, '123')
})
it('no_proxy - various, single string', function() {
var 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'})
assert.equal(x.proxy, null)
var 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'}, {})
assert.equal(x.proxy, null)
var 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'}, {})
assert.equal(x.proxy, '123')
})
it('no_proxy - various, array', function() {
var 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'})
assert.equal(x.proxy, null)
var 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'})
assert.equal(x.proxy, '123')
var 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']})
assert.equal(x.proxy, null)
})
it('no_proxy - various, array', function() {
var 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'})
assert.equal(x.proxy, null)
var 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'})
assert.equal(x.proxy, '123')
var 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']})
assert.equal(x.proxy, null)
})
it('no_proxy - hostport', function() {
var 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'})
assert.equal(x.proxy, null)
})
it('no_proxy - hostport', function() {
var 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'})
assert.equal(x.proxy, null)
})
it('no_proxy - secure', function() {
var x = setup('https://something', {http_proxy: '123'}, {})
assert.equal(x.proxy, null)
var x = setup('https://something', {https_proxy: '123'}, {})
assert.equal(x.proxy, '123')
var x = setup('https://something', {http_proxy: '456', https_proxy: '123'}, {})
assert.equal(x.proxy, '123')
})
it('no_proxy - secure', function() {
var x = setup('https://something', {http_proxy: '123'}, {})
assert.equal(x.proxy, null)
var x = setup('https://something', {https_proxy: '123'}, {})
assert.equal(x.proxy, '123')
var x = setup('https://something', {http_proxy: '456', https_proxy: '123'}, {})
assert.equal(x.proxy, '123')
})
})

View file

@ -1,34 +1,34 @@
var assert = require('assert')
, parse_interval = require('../../lib/config').parse_interval
var assert = require('assert')
var parse_interval = require('../../lib/config').parse_interval
describe('Parse interval', function() {
function add_test(str, res) {
it('parse ' + str, function() {
if (res === null) {
assert.throws(function() {
console.log(parse_interval(str))
})
} else {
assert.strictEqual(parse_interval(str), res)
}
})
}
function add_test(str, res) {
it('parse ' + str, function() {
if (res === null) {
assert.throws(function() {
console.log(parse_interval(str))
})
} else {
assert.strictEqual(parse_interval(str), res)
}
})
}
add_test(12345, 12345000)
add_test('1000', 1000000)
add_test('1.5s', 1500)
add_test('25ms', 25)
add_test('2m', 2*1000*60)
add_test('3h', 3*1000*60*60)
add_test('0.5d', 0.5*1000*60*60*24)
add_test('0.5w', 0.5*1000*60*60*24*7)
add_test('1M', 1000*60*60*24*30)
add_test('5s 20ms', 5020)
add_test('1y', 1000*60*60*24*365)
add_test('1y 5', null)
add_test('1m 1m', null)
add_test('1m 1y', null)
add_test('1y 1M 1w 1d 1h 1m 1s 1ms', 34822861001)
add_test(' 5s 25ms ', 5025)
add_test(12345, 12345000)
add_test('1000', 1000000)
add_test('1.5s', 1500)
add_test('25ms', 25)
add_test('2m', 2*1000*60)
add_test('3h', 3*1000*60*60)
add_test('0.5d', 0.5*1000*60*60*24)
add_test('0.5w', 0.5*1000*60*60*24*7)
add_test('1M', 1000*60*60*24*30)
add_test('5s 20ms', 5020)
add_test('1y', 1000*60*60*24*365)
add_test('1y 5', null)
add_test('1m 1m', null)
add_test('1m 1y', null)
add_test('1y 1M 1w 1d 1h 1m 1s 1ms', 34822861001)
add_test(' 5s 25ms ', 5025)
})

View file

@ -1,53 +1,53 @@
var assert = require('assert')
, semver_sort = require('../../lib/utils').semver_sort
, merge = require('../../lib/storage')._merge_versions
var assert = require('assert')
var semver_sort = require('../../lib/utils').semver_sort
var merge = require('../../lib/storage')._merge_versions
require('../../lib/logger').setup([])
describe('Merge', function() {
it('simple', function() {
var x = {
versions: {a:1,b:1,c:1},
'dist-tags': {},
}
merge(x, {versions: {a:2,q:2}})
assert.deepEqual(x, {
versions: {a:1,b:1,c:1,q:2},
'dist-tags': {},
})
})
it('simple', function() {
var x = {
versions: {a:1,b:1,c:1},
'dist-tags': {},
}
merge(x, {versions: {a:2,q:2}})
assert.deepEqual(x, {
versions: {a:1,b:1,c:1,q:2},
'dist-tags': {},
})
})
it('dist-tags - compat', function() {
var x = {
versions: {},
'dist-tags': {q:'1.1.1',w:['2.2.2']},
}
merge(x, {'dist-tags':{q:'2.2.2',w:'3.3.3',t:'4.4.4'}})
assert.deepEqual(x, {
versions: {},
'dist-tags': {q:['1.1.1','2.2.2'],w:['2.2.2','3.3.3'],t:['4.4.4']},
})
})
it('dist-tags - compat', function() {
var x = {
versions: {},
'dist-tags': {q:'1.1.1',w:['2.2.2']},
}
merge(x, {'dist-tags':{q:'2.2.2',w:'3.3.3',t:'4.4.4'}})
assert.deepEqual(x, {
versions: {},
'dist-tags': {q:['1.1.1','2.2.2'],w:['2.2.2','3.3.3'],t:['4.4.4']},
})
})
it('dist-tags - sort', function() {
var x = {
versions: {},
'dist-tags': {w:['2.2.2','1.1.1','12.2.2','2.2.2-rc2']},
}
merge(x, {'dist-tags':{w:'3.3.3'}})
assert.deepEqual(x, {
versions: {},
'dist-tags': {w:["1.1.1","2.2.2-rc2","2.2.2","3.3.3","12.2.2"]},
})
})
it('dist-tags - sort', function() {
var x = {
versions: {},
'dist-tags': {w:['2.2.2','1.1.1','12.2.2','2.2.2-rc2']},
}
merge(x, {'dist-tags':{w:'3.3.3'}})
assert.deepEqual(x, {
versions: {},
'dist-tags': {w:["1.1.1","2.2.2-rc2","2.2.2","3.3.3","12.2.2"]},
})
})
it('semver_sort', function() {
assert.deepEqual(semver_sort(['1.2.3','1.2','1.2.3a','1.2.3c','1.2.3-b']),
[ '1.2.3a',
'1.2.3-b',
'1.2.3c',
'1.2.3' ]
)
})
it('semver_sort', function() {
assert.deepEqual(semver_sort(['1.2.3','1.2','1.2.3a','1.2.3c','1.2.3-b']),
[ '1.2.3a',
'1.2.3-b',
'1.2.3c',
'1.2.3' ]
)
})
})

View file

@ -1,55 +1,55 @@
var assert = require('assert')
, tag_version = require('../../lib/utils').tag_version
var assert = require('assert')
var tag_version = require('../../lib/utils').tag_version
require('../../lib/logger').setup([])
describe('tag_version', function() {
it('add new one', function() {
var x = {
versions: {},
'dist-tags': {},
}
assert(tag_version(x, '1.1.1', 'foo', {}))
assert.deepEqual(x, {
versions: {},
'dist-tags': {foo: ['1.1.1']},
})
})
it('add new one', function() {
var x = {
versions: {},
'dist-tags': {},
}
assert(tag_version(x, '1.1.1', 'foo', {}))
assert.deepEqual(x, {
versions: {},
'dist-tags': {foo: ['1.1.1']},
})
})
it('add (compat)', function() {
var x = {
versions: {},
'dist-tags': {foo: '1.1.0'},
}
assert(tag_version(x, '1.1.1', 'foo', {}))
assert.deepEqual(x, {
versions: {},
'dist-tags': {foo: ['1.1.0', '1.1.1']},
})
})
it('add (compat)', function() {
var x = {
versions: {},
'dist-tags': {foo: '1.1.0'},
}
assert(tag_version(x, '1.1.1', 'foo', {}))
assert.deepEqual(x, {
versions: {},
'dist-tags': {foo: ['1.1.0', '1.1.1']},
})
})
it('add fresh tag', function() {
var x = {
versions: {},
'dist-tags': {foo: ['1.1.0']},
}
assert(tag_version(x, '1.1.1', 'foo', {}))
assert.deepEqual(x, {
versions: {},
'dist-tags': {foo: ['1.1.0', '1.1.1']},
})
})
it('add fresh tag', function() {
var x = {
versions: {},
'dist-tags': {foo: ['1.1.0']},
}
assert(tag_version(x, '1.1.1', 'foo', {}))
assert.deepEqual(x, {
versions: {},
'dist-tags': {foo: ['1.1.0', '1.1.1']},
})
})
it('add stale tag', function() {
var x = {
versions: {},
'dist-tags': {foo: ['1.1.2']},
}
assert(!tag_version(x, '1.1.1', 'foo', {}))
assert.deepEqual(x, {
versions: {},
'dist-tags': {foo: ['1.1.1', '1.1.2']},
})
})
it('add stale tag', function() {
var x = {
versions: {},
'dist-tags': {foo: ['1.1.2']},
}
assert(!tag_version(x, '1.1.1', 'foo', {}))
assert.deepEqual(x, {
versions: {},
'dist-tags': {foo: ['1.1.1', '1.1.2']},
})
})
})

View file

@ -1,42 +1,42 @@
var assert = require('assert')
, validate = require('../../lib/utils').validate_name
var assert = require('assert')
var validate = require('../../lib/utils').validate_name
describe('Validate', function() {
it('good ones', function() {
assert(validate('sinopia'))
assert(validate('some.weird.package-zzz'))
assert(validate('old-package@0.1.2.tgz'))
})
it('good ones', function() {
assert( validate('sinopia') )
assert( validate('some.weird.package-zzz') )
assert( validate('old-package@0.1.2.tgz') )
})
it('uppercase', function() {
assert(validate('EVE'))
assert(validate('JSONStream'))
})
it('uppercase', function() {
assert( validate('EVE') )
assert( validate('JSONStream') )
})
it('no package.json', function() {
assert(!validate('package.json'))
})
it('no package.json', function() {
assert( !validate('package.json') )
})
it('no path seps', function() {
assert(!validate('some/thing'))
assert(!validate('some\\thing'))
})
it('no hidden', function() {
assert(!validate('.bin'))
})
it('no reserved', function() {
assert(!validate('favicon.ico'))
assert(!validate('node_modules'))
assert(!validate('__proto__'))
})
it('no path seps', function() {
assert( !validate('some/thing') )
assert( !validate('some\\thing') )
})
it('no hidden', function() {
assert( !validate('.bin') )
})
it('no reserved', function() {
assert( !validate('favicon.ico') )
assert( !validate('node_modules') )
assert( !validate('__proto__') )
})
it('other', function() {
assert(!validate('pk g'))
assert(!validate('pk\tg'))
assert(!validate('pk%20g'))
assert(!validate('pk+g'))
assert(!validate('pk:g'))
})
it('other', function() {
assert( !validate('pk g') )
assert( !validate('pk\tg') )
assert( !validate('pk%20g') )
assert( !validate('pk+g') )
assert( !validate('pk:g') )
})
})

View file

@ -3,31 +3,31 @@
var assert = require('assert')
describe('index.js app', function() {
var source = require('fs').readFileSync(__dirname + '/../../lib/index.js', 'utf8')
var source = require('fs').readFileSync(__dirname + '/../../lib/index.js', 'utf8')
var very_scary_regexp = /\n\s*app\.(\w+)\s*\(\s*(("[^"]*")|('[^']*'))\s*,/g
var m
var params = {}
var very_scary_regexp = /\n\s*app\.(\w+)\s*\(\s*(("[^"]*")|('[^']*'))\s*,/g
var m
var params = {}
while ((m = very_scary_regexp.exec(source)) != null) {
if (m[1] === 'set') continue
while ((m = very_scary_regexp.exec(source)) != null) {
if (m[1] === 'set') continue
var inner = m[2].slice(1, m[2].length-1)
var t
var inner = m[2].slice(1, m[2].length-1)
var t
inner.split('/').forEach(function(x) {
if (m[1] === 'param') {
params[x] = 'ok'
} else if (t = x.match(/^:([^?:]*)\??$/)) {
params[t[1]] = params[t[1]] || m[0].trim()
}
})
}
inner.split('/').forEach(function(x) {
if (m[1] === 'param') {
params[x] = 'ok'
} else if (t = x.match(/^:([^?:]*)\??$/)) {
params[t[1]] = params[t[1]] || m[0].trim()
}
})
}
Object.keys(params).forEach(function(param) {
it('should validate ":'+param+'"', function() {
assert.equal(params[param], 'ok')
})
})
Object.keys(params).forEach(function(param) {
it('should validate ":'+param+'"', function() {
assert.equal(params[param], 'ok')
})
})
})