_getContentType: function() { var res = this.get('res'), type = res.get('Content-Type'); if (!type) { type = mime.lookup(res.mimeType); type = (~type.indexOf('/')) ? type : mime.lookup(type); type += '; charset=' + res.charset; } return type; },
Controller.prototype.respondWith = function(obj) { var keys = Object.keys(obj) , req = this.__req , res = this.__res , format = this.__req.params.format , format = format ? mime.lookup(format) : undefined; var op = obj.default; if (op) delete obj.default; // prefer format param, fallback to accept header var key = format ? utils.accepts(keys, format) : req.accepts(keys); if (key) { op = obj[key]; } if (op === true) op = {}; res.setHeader('Vary', 'Accept'); if (typeof op == 'object') { op.format = op.format || key; this.render(op.template, op); } else if (typeof op == 'function') { op(); } else { var err = new Error('Not Acceptable'); err.status = 406; err.types = utils.normalizeTypes(keys); this.error(err); } }
it('should be set for .' + e + ' with query string', function(done) { helper.request() .get('/test.' + e + '?' + Math.random()) .expect('Content-Type', new RegExp( express.mime.lookup(e).replace(/([\/\+])/g, '\\$1') + '(?:charset=UTF-8)?') ) .expect(200, done); });
Controller.prototype.respondWith = function(obj) { var req = this.__req , res = this.__res , format = this.__req.params.format; format = format ? mime.lookup(format) : undefined; if (Array.isArray(obj) || typeof obj == 'string') { var arr = flatten([].slice.call(arguments)) , o = {}, i, len; for (i = 0, len = arr.length; i < len; ++i) { o[arr[i]] = true; } obj = o; } var op = obj.default; if (op) { delete obj.default; } var keys = Object.keys(obj); // prefer format param, fallback to accept header var key = format ? utils.accepts(keys, format) : req.accepts(keys); var via = format ? undefined : utils.acceptedVia(keys, req.headers.accept); if (op && via && via.type == '*' && via.subtype == '*') { // use default format, if available and negotiated via */* key = undefined; } else if (via && via.type == '*' && via.subtype == '*') { // use first format, if no default and accepts anything key = keys[0]; } if (key) { op = obj[key]; } if (op === true) { op = {}; } else if (typeof op === 'string') { op = { format: op }; } var vary = this.__res.getHeader('Vary'); if (!vary) { res.setHeader('Vary', 'Accept'); } else { vary = vary.split(/ *, */); if (vary.indexOf('Accept') == -1) { vary.push('Accept'); } res.setHeader('Vary', vary.join(', ')); } if (typeof op == 'object') { var template = op.template; delete op.template; op.format = op.format || key; if (key && key.indexOf('/') != -1) { op.mime = key; } this.render(template, op); } else if (typeof op == 'function') { op(this.localProperties()); } else { var err = new Error('Not Acceptable'); err.status = 406; err.types = utils.normalizeTypes(keys).map(function(o) { return o.value; }); this.error(err); } };
exports.normalizeTypes = function(types){ var ret = []; for (var i = 0; i < types.length; ++i) { ret.push(~types[i].indexOf('/') ? types[i] : mime.lookup(types[i])); } return ret; };
return function(req, res, next) { var url = req.url, host = req.headers.host, ua = req.headers['user-agent'], cc = '', type; /** * private function to respond with the appropriate http status code. * @param {Number} code a valid http status code. */ function respond(code) { var err = new Error(http.STATUS_CODES[code]); err.status = code; // early return for: // - redirects // - forbidden, prioritize 403 over 404 if (/301|302|403/.test(err.status)) { res.statusCode = err.status; res.end(err.message); } else { next(err); } } // Block access to "hidden" directories or files whose names begin with a // period. This includes directories used by version control systems such as // Subversion or Git. if (RE_HIDDEN.test(url)) { return respond(403); } // Block access to backup and source files. These files may be left by some // text/html editors and pose a great security danger, when anyone can access // them. if (RE_SRCBAK.test(url)) { return respond(403); } /** * Suppress or force the "www." at the beginning of URLs */ // The same content should never be available under two different URLs - // especially not with and without "www." at the beginning, since this can cause // SEO problems (duplicate content). That's why you should choose one of the // alternatives and redirect the other one. // By default option 1 (no "www.") is activated. // no-www.org/faq.php?q=class_b // If you'd prefer to use option 2, just comment out all option 1 lines // and uncomment option 2. // IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! // ---------------------------------------------------------------------- // Option 1: // Rewrite "www.example.com -> example.com". if (false === options.www && RE_WWW.test(host)) { res.setHeader('location', '//' + host.replace(RE_WWW, '') + url); return respond(301); } // ---------------------------------------------------------------------- // Option 2: // Rewrite "example.com -> www.example.com". // Be aware that the following rule might not be a good idea if you use "real" // subdomains for certain parts of your website. if (true === options.www && !RE_WWW.test(host)) { res.setHeader('location', '//www.' + host.replace(RE_WWW, '') + url); return respond(301); } /** * Proper MIME type for all files */ // early mime type sniffing type = express.mime.lookup(req.url); res.setHeader('Content-Type', type); /** * Better website experience for IE users */ // Force the latest IE version, in various cases when it may fall back to IE7 mode // github.com/rails/rails/commit/123eb25#commitcomment-118920 // Use ChromeFrame if it's installed for a better experience for the poor IE folk if (RE_MSIE.test(ua) && 'text/html' == type) { res.setHeader('X-UA-Compatible', 'IE=Edge,chrome=1'); } /** * Cross-domain AJAX requests */ // Serve cross-domain Ajax requests, disabled by default. // enable-cors.org // code.google.com/p/html5security/wiki/CrossOriginRequestSecurity if (options.cors) { res.setHeader('Access-Control-Allow-Origin', '*'); } /** * CORS-enabled images (@crossorigin) */ // Send CORS headers if browsers request them; enabled by default for images. // developer.mozilla.org/en/CORS_Enabled_Image // blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html // hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ // wiki.mozilla.org/Security/Reviews/crossoriginAttribute if (RE_MIME_IMAGE.test(type)) { res.setHeader('Access-Control-Allow-Origin', '*'); } /** * Webfont access */ // Allow access from all domains for webfonts. // Alternatively you could only whitelist your // subdomains like "subdomain.example.com". if (RE_MIME_FONT.test(url)) { res.setHeader('Access-Control-Allow-Origin', '*'); } /** * Allow concatenation from within specific js and css files */ // e.g. Inside of script.combined.js you could have // <!--#include file="libs/jquery-1.5.0.min.js" --> // <!--#include file="plugins/jquery.idletimer.js" --> // and they would be included into this single file. // TODO /** * Expires headers (for better cache control) */ // These are pretty far-future expires headers. // They assume you control versioning with filename-based cache busting // Additionally, consider that outdated proxies may miscache // www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/ // If you don't use filenames to version, lower the CSS and JS to something like // "access plus 1 week". // note: we don't use express.static maxAge feature because it does not allow fine tune // Perhaps better to whitelist expires rules? Perhaps. // cache.appcache needs re-requests in FF 3.6 (thanks Remy ~Introducing HTML5) // Your document html // Data if (RE_MIME_DATA.test(type)) { cc = 'public,max-age=0'; } // Feed else if (RE_MIME_FEED.test(type)) { cc = 'public,max-age=' + ONE_HOUR; } // Favicon (cannot be renamed) else if (RE_MIME_FAVICON.test(type)) { cc = 'public,max-age=' + ONE_WEEK; } // Media: images, video, audio // HTC files (css3pie) // Webfonts else if (RE_MIME_MEDIA.test(type)) { cc = 'public,max-age=' + ONE_MONTH; } // CSS and JavaScript else if (RE_MIME_CSSJS.test(type)) { cc = 'public,max-age=' + ONE_YEAR; } // Misc else { cc = 'public,max-age=' + ONE_MONTH; } /** * Prevent mobile network providers from modifying your site */ // The following header prevents modification of your code over 3G on some // European providers. // This is the official 'bypass' suggested by O2 in the UK. cc += (cc ? ',' : '') + 'no-transform'; // hack: send does not compute ETag if header is already set, this save us ETag generation res.setHeader('cache-control', ''); /** * ETag removal */ // Since we're sending far-future expires, we don't need ETags for // static content. // developer.yahoo.com/performance/rules.html#etags // hack: send does not compute ETag if header is already set, this save us ETag generation res.setHeader('etag', ''); // handle headers correctly after express.static res.on('header', function() { res.setHeader('cache-control', cc); // remote empty etag header res.removeHeader('etag'); }); /** * Stop screen flicker in IE on CSS rollovers */ // The following directives stop screen flicker in IE on CSS rollovers - in // combination with the "ExpiresByType" rules for images (see above). // TODO /** * Set Keep-Alive Header */ // Keep-Alive allows the server to send multiple requests through one // TCP-expression. Be aware of possible disadvantages of this setting. Turn on // if you serve a lot of static content. res.setHeader('connection', 'keep-alive'); /** * Cookie setting from iframes */ // Allow cookies to be set from iframes (for IE only) // If needed, specify a path or regex in the Location directive. // TODO /** * Built-in filename-based cache busting */ // If you're not using the build script to manage your filename version revving, // you might want to consider enabling this, which will route requests for // /css/style.20110203.css to /css/style.css // To understand why this is important and a better idea than all.css?v1231, // read: github.com/h5bp/html5-boilerplate/wiki/cachebusting req.url = req.url.replace(/^(.+)\.(\d+)\.(js|css|png|jpg|gif)$/, '$1.$3'); /** * A little more security */ // do we want to advertise what kind of server we're running? if ('express' == options.server) { res.removeHeader('X-Powered-By'); } next(); };
it('should be set for .' + e, function(done) { helper.request() .get('/test.' + e) .expect('Content-Type', express.mime.lookup(e)) .expect(200, done); });
const HTML = 'html htm'.split(' '); const IMAGE = 'bmp gif jpeg jpg jpe png svg svgz tiff tif ico'.split(' '); const ICON = 'ico'.split(' '); const VIDEO = 'ogv mp4 m4v f4v f4p webm flv'.split(' '); const AUDIO = 'oga ogg m4a f4a f4b'.split(' '); const FONT = 'ttf ttc otf eot woff'.split(' '); const RSS = 'rss atom'.split(' '); const MISC = 'txt crx oex xpi safariextz webapp vcf swf vtt'.split(' '); const FEED = RSS; const MEDIA = IMAGE.concat(VIDEO.concat(AUDIO)); const DATA = 'appcache manifest html htm xml rdf json'; const ALL = [].concat(HTML, IMAGE, ICON, VIDEO, AUDIO, FONT, RSS, 'js jsonp css'.split(' ')); express.mime.load(path.join(__dirname, '..', 'lib', 'h5bp.types')); describe('h5bp', function() { describe('with express/connect', function() { before(function() { helper.stop(); helper.create(); helper.start(); }); describe('proper MIME type for all files', function() { ALL.forEach(function(e) { it('should be set for .' + e, function(done) { helper.request() .get('/test.' + e) .expect('Content-Type', express.mime.lookup(e))
exports.normalizeType = function(type){ return ~type.indexOf('/') ? type : mime.lookup(type); };
exports.extensionizeType = function(type){ return type.indexOf('/') != -1 ? mime.extension(type) : type; };