X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=server%2Fhttphandler.js;h=c55dc570e8ff7403935cd28d0b8b92e862cda18a;hb=24dd1faa442897b5b915977f2dfb4880dc33c807;hp=2cddc37c226b501fcf976ddb128ecbeae3d97c3d;hpb=093c5b379858cbdff788fe2b3d162303297319d2;p=KiwiIRC.git diff --git a/server/httphandler.js b/server/httphandler.js index 2cddc37..c55dc57 100644 --- a/server/httphandler.js +++ b/server/httphandler.js @@ -1,35 +1,19 @@ var url = require('url'), fs = require('fs'), - crypto = require('crypto'), node_static = require('node-static'), + Negotiator = require('negotiator'), _ = require('lodash'), - config = require('./configuration.js'); - - -// Cache for settings.json -var cached_settings = { - debug: { - hash: '', - settings: '' - }, - production: { - hash: '', - settings: '' - } -}; - -// Clear the settings cache when the settings change -config.on('loaded', function () { - cached_settings.debug.settings = cached_settings.production.settings = ''; - cached_settings.debug.hash = cached_settings.production.hash = ''; -}); + config = require('./configuration.js'), + winston = require('winston'), + SettingsGenerator = require('./settingsgenerator.js'), + Stats = require('./stats.js'); var HttpHandler = function (config) { - var public_html = config.public_html || 'client/'; - this.file_server = new node_static.Server(public_html); + var public_http = config.public_http || 'client/'; + this.file_server = new node_static.Server(public_http); }; module.exports.HttpHandler = HttpHandler; @@ -38,23 +22,48 @@ module.exports.HttpHandler = HttpHandler; HttpHandler.prototype.serve = function (request, response) { // The incoming requests base path (ie. /kiwiclient) - var base_path = global.config.http_base_path || '/kiwi', - base_path_regex; + var base_path, base_check, + whitelisted_folders = ['/assets', '/src'], + is_whitelisted_folder = false; - // Trim of any trailing slashes + // Trim off any trailing slashes from the base_path + base_path = global.config.http_base_path || ''; if (base_path.substr(base_path.length - 1) === '/') { base_path = base_path.substr(0, base_path.length - 1); } - // Build the regex to match the base_path - base_path_regex = base_path.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + // Normalise the URL to compare against the base_path + base_check = request.url; + if (base_check.substr(base_check.length - 1) !== '/') { + base_check += '/'; + } - // Any asset request to head into the asset dir - request.url = request.url.replace(base_path + '/assets/', '/assets/'); + // Normalise the URL we use by removing the base path + if (base_check.indexOf(base_path + '/') === 0) { + request.url = request.url.replace(base_path, ''); - // Any requests for /client to load the index file - if (request.url.match(new RegExp('^' + base_path_regex + '([/$]|$)', 'i'))) { - request.url = '/'; + } else if (base_check !== '/') { + // We don't handle requests outside of the base path and not /, so just 404 + response.writeHead(404); + response.write('Not Found'); + response.end(); + return; + } + + // Map any whitelisted folders to the local directories + whitelisted_folders.forEach(function(folder) { + if (request.url.indexOf(folder) === 0) { + is_whitelisted_folder = true; + } + }); + + // Any requests not for whitelisted assets returns the index page + if (!is_whitelisted_folder) { + request.url = '/index.html'; + } + + if (request.url === '/index.html') { + Stats.incr('http.homepage'); } // If the 'magic' translation is requested, figure out the best language to use from @@ -78,77 +87,62 @@ HttpHandler.prototype.serve = function (request, response) { }; +// Cached list of available translations +var cached_available_locales = []; + +// Get a list of the available translations we have +fs.readdir('client/assets/locales', function (err, files) { + if (err) { + if (err.code === 'ENOENT') { + winston.error('No locale files could be found at ' + err.path); + } else { + winston.error('Error reading locales.', err); + } + } + + (files || []).forEach(function (file) { + if (file.substr(-5) === '.json') { + cached_available_locales.push(file.slice(0, -5)); + } + }); +}); + + + /** * Handle the /assets/locales/magic.json request * Find the closest translation we have for the language * set in the browser. **/ var serveMagicLocale = function (request, response) { - var that = this, - default_locale_id = 'en-gb'; - - if (request.headers['accept-language']) { - fs.readdir('client/assets/locales', function (err, files) { - var available = [], - i = 0, - langs = request.headers['accept-language'].split(','), // Example: en-gb,en;q=0.5 - found_locale = default_locale_id; - - // Get a list of the available translations we have - files.forEach(function (file) { - if (file.substr(-5) === '.json') { - available.push(file.slice(0, -5)); - } - }); - - // Sanitise the browsers accepted languages and the qualities - for (i = 0; i < langs.length; i++) { - langs[i]= langs[i].split(';q='); - langs[i][0] = langs[i][0].toLowerCase(); - langs[i][1] = (typeof langs[i][1] === 'string') ? parseFloat(langs[i][1]) : 1.0; - } - - // Sort the accepted languages by quality - langs.sort(function (a, b) { - return b[1] - a[1]; - }); - - // Serve the first language we have a translation for - for (i = 0; i < langs.length; i++) { - if (langs[i][0] === '*') { - break; - } else if (_.contains(available, langs[i][0])) { - found_locale = langs[i][0]; - break; - } - } - - return serveLocale.call(that, request, response, found_locale); - }); - } else { + var default_locale_id = 'en-gb', + found_locale, negotiator; + if (!request.headers['accept-language']) { // No accept-language specified in the request so send the default - return serveLocale.call(this, request, response, default_locale_id); - } + found_locale = default_locale_id; -}; + } else { + negotiator = new Negotiator(request); + found_locale = negotiator.language(cached_available_locales); + // If a locale couldn't be negotiated, use the default + found_locale = found_locale || default_locale_id; + } -/** - * Send a locale to the browser - */ -var serveLocale = function (request, response, locale_id) { - this.file_server.serveFile('/assets/locales/' + locale_id + '.json', 200, { + // Send a locale to the browser + this.file_server.serveFile('/assets/locales/' + found_locale + '.json', 200, { Vary: 'Accept-Language', - 'Content-Language': locale_id + 'Content-Language': found_locale }, request, response); }; + /** * Handle the settings.json request */ -function serveSettings(request, response) { +var serveSettings = function(request, response) { var referrer_url, debug = false, settings; @@ -161,180 +155,16 @@ function serveSettings(request, response) { } } - settings = cached_settings[debug ? 'debug' : 'production']; - - // Generate the settings if we don't have them cached as yet - if (settings.settings === '') { - generateSettings(request, debug, function (err, settings) { - if (err) { - response.statusCode = 500; - response.end(); - } else { - sendSettings.call(this, request, response, settings); - } - }); - - } else { - sendSettings.call(this, request, response, settings); - } -} - - -/** - * Send the the settings to the browser - */ -function sendSettings(request, response, settings) { - if (request.headers['if-none-match'] && request.headers['if-none-match'] === settings.hash) { - response.writeHead(304, 'Not Modified'); - return response.end(); - } - - response.writeHead(200, { - 'ETag': settings.hash, - 'Content-Type': 'application/json' - }); - response.end(settings.settings); -} - - -/** - * Generate a settings object for the client. - * Settings include available translations, default client config, etc - */ -function generateSettings(request, debug, callback) { - var vars = { - server_settings: {}, - client_plugins: [], - translations: [], - scripts: [ - [ - 'libs/lodash.min.js' - ], - ['libs/backbone.min.js', 'libs/jed.js'] - ] - }; - - if (debug) { - vars.scripts = vars.scripts.concat([ - [ - 'src/app.js', - 'libs/engine.io.js', - 'libs/engine.io.tools.js' - ], - [ - 'src/models/application.js', - 'src/models/gateway.js' - ], - [ - 'src/models/newconnection.js', - 'src/models/panellist.js', - 'src/models/networkpanellist.js', - 'src/models/panel.js', - 'src/models/member.js', - 'src/models/memberlist.js', - 'src/models/network.js' - ], - - [ - 'src/models/query.js', - 'src/models/channel.js', - 'src/models/server.js', - 'src/models/applet.js' - ], - - [ - 'src/applets/settings.js', - 'src/applets/chanlist.js', - 'src/applets/scripteditor.js' - ], - - [ - 'src/models/pluginmanager.js', - 'src/models/datastore.js', - 'src/helpers/utils.js' - ], - - // Some views extend these, so make sure they're loaded beforehand - [ - 'src/views/panel.js' - ], - - [ - 'src/views/channel.js', - 'src/views/applet.js', - 'src/views/application.js', - 'src/views/apptoolbar.js', - 'src/views/controlbox.js', - 'src/views/favicon.js', - 'src/views/mediamessage.js', - 'src/views/member.js', - 'src/views/memberlist.js', - 'src/views/menubox.js', - 'src/views/networktabs.js', - 'src/views/nickchangebox.js', - 'src/views/resizehandler.js', - 'src/views/serverselect.js', - 'src/views/statusmessage.js', - 'src/views/tabs.js', - 'src/views/topicbar.js', - 'src/views/userbox.js' - ] - ]); - } else { - vars.scripts.push(['kiwi.min.js', 'libs/engine.io.bundle.min.js']); - } - - // Any restricted server mode set? - if (config.get().restrict_server) { - vars.server_settings = { - connection: { - server: config.get().restrict_server, - port: config.get().restrict_server_port || 6667, - ssl: config.get().restrict_server_ssl, - channel: config.get().restrict_server_channel, - nick: config.get().restrict_server_nick, - allow_change: false - } - }; - } - - // Any client default settings? - if (config.get().client) { - vars.server_settings.client = config.get().client; - } - - // Any client plugins? - if (config.get().client_plugins && config.get().client_plugins.length > 0) { - vars.client_plugins = config.get().client_plugins; - } - - // Get a list of available translations - fs.readFile(__dirname + '/../client/assets/src/translations/translations.json', function (err, translations) { - if (err) { - return callback(err); + SettingsGenerator.get(debug, function(settings) { + if (request.headers['if-none-match'] && request.headers['if-none-match'] === settings.hash) { + response.writeHead(304, 'Not Modified'); + return response.end(); } - var translation_files; - translations = JSON.parse(translations); - fs.readdir(__dirname + '/../client/assets/src/translations/', function (err, pofiles) { - var hash, settings; - if (err) { - return callback(err); - } - - pofiles.forEach(function (file) { - var locale = file.slice(0, -3); - if ((file.slice(-3) === '.po') && (locale !== 'template')) { - vars.translations.push({tag: locale, language: translations[locale]}); - } - }); - - settings = cached_settings[debug?'debug':'production']; - settings.settings = JSON.stringify(vars); - settings.hash = crypto.createHash('md5').update(settings.settings).digest('hex'); - - return callback(null, settings); + response.writeHead(200, { + 'ETag': settings.hash, + 'Content-Type': 'application/json' }); + response.end(settings.settings); }); -} - +};