diff --git a/config.default.json b/config.default.json index 761bfe95..314f7c9a 100644 --- a/config.default.json +++ b/config.default.json @@ -7,6 +7,7 @@ "ssl": false, "static": false }, + "max-request-size": "1MB", "store": { "adapter": "sqlite", "sqlite": { diff --git a/lib/app.js b/lib/app.js index f9c00e79..f55eda40 100644 --- a/lib/app.js +++ b/lib/app.js @@ -133,6 +133,7 @@ app.connect = function (callback) { } app.use(mount, express.static(path.join(app.set('root'), 'public'))); + app.use(middleware.limitContentLength({limit: app.set('max-request-size')})); app.use(express.cookieParser(app.set('session secret'))); app.use(express.cookieSession({key: 'jsbin', cookie: {maxAge: 365 * 24 * 60 * 60 * 1000}})); app.use(express.urlencoded()); diff --git a/lib/errors.js b/lib/errors.js index 8c385a9e..48e7ac42 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -76,6 +76,12 @@ exports.BadRequest = HTTPError.extend({ } }); +exports.RequestEntityTooLarge = HTTPError.extend({ + constructor: function RequestEntityTooLarge(message) { + HTTPError.call(this, 413, message); + } +}); + exports.MailerError = HTTPError.extend({ constructor: function MailerError(message) { HTTPError.call(this, 500, message); diff --git a/lib/middleware.js b/lib/middleware.js index 66b7ce88..d91cd3b6 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -1,6 +1,7 @@ var utils = require('./utils'), helpers = require('./helpers'), custom = require('./custom'), + errors = require('./errors'), connect = require('express/node_modules/connect'); // Custom middleware used by the application. @@ -118,5 +119,45 @@ module.exports = { } next(); }; + }, + + // Limit the file size that can be uploaded. + limitContentLength: function (options) { + // Parse a string representing a file size and convert it into bytes. + // A number on it's own will be assumed to be bytes. A multiple such as + // "k" or "m" can be appended to the string to handle larger numbers. This + // is case insensitive and uses powers of 1024 rather than (1000). + // So both 1kB and 1kb == 1024. + function parseLimit(string) { + var matches = ('' + string).toLowerCase().match(regexp), + bytes = null, power; + + if (matches) { + bytes = parseFloat(matches[1]); + power = powers[matches[2]]; + + if (bytes && power) { + bytes = Math.pow(bytes * 1024, power); + } + } + + return bytes || null; + } + + var powers = { k: 1, m: 2, g: 3, t: 4 }, + regexp = /^(\d+(?:.\d+)?)\s*([kmgt]?)b?$/, + limit = options && parseLimit(options.limit); + + return function (req, res, next) { + if (limit) { + var contentLength = parseInt(req.header('Content-Length', 0), 10), + message = 'Sorry, the content you have uploaded is larger than JS Bin can handle. Max size is ' + options.limit; + + if (limit && contentLength > limit) { + return next(new errors.RequestEntityTooLarge(message)); + } + } + next(); + }; } }; diff --git a/public/css/style.css b/public/css/style.css index f77a49d2..86a70b3d 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -2020,12 +2020,13 @@ a.active:hover { text-shadow: none; /*-webkit-transition-delay: 100ms;*/ font-size: 13px; - padding: 40px 0 10px 0; + padding: 0 0 10px 0; position: absolute; /*height: 100%;*/ - top: 0; + top: 36px; left: 0; right: 0; + right: 30%; bottom: 0; overflow: auto; background: #fff; @@ -2064,7 +2065,7 @@ a.active:hover { #history table { border-collapse: collapse; table-layout: fixed; - width: 70%; + width: 100%; position: relative; } diff --git a/public/js/chrome/save.js b/public/js/chrome/save.js index cad9192d..08e4357a 100644 --- a/public/js/chrome/save.js +++ b/public/js/chrome/save.js @@ -14,6 +14,16 @@ $document.one('saved', function () { $shareLinks.removeClass('disabled').unbind('click mousedown mouseup'); }); +function onSaveError(jqXHR) { + if (jqXHR.status === 413) { + // Hijack the tip label to show an error message. + $('#tip p').html('Sorry this bin is too large for us to save'); + $(document.documentElement).addClass('showtip'); + } else { + window._console.error({message: 'Warning: Something went wrong while saving. Your most recent work is not saved.'});; + } +} + function updateSavedState() { $shareLinks.each(function () { var url = jsbin.getURL() + this.getAttribute('data-path'), @@ -149,9 +159,7 @@ if (!jsbin.saveDisabled) { }); } }, - error: function () { - window._console.error({message: 'Warning: Something went wrong while saving. Your most recent work is not saved.'}); - } + error: onSaveError }); } }, 250)); @@ -249,9 +257,7 @@ function saveCode(method, ajax, ajaxCallback) { window.location.hash = data.edit; } }, - error: function () { - window._console.error({message: 'Warning: Something went wrong while saving. Your most recent work is not saved.'}); - } + error: onSaveError }); } else { $form.submit();