Merge branch 'development' of github.com:prawnsalad/KiwiIRC into development
[KiwiIRC.git] / server / httphandler.js
1 var url = require('url'),
2 fs = require('fs'),
3 node_static = require('node-static'),
4 Negotiator = require('negotiator'),
5 _ = require('lodash'),
6 config = require('./configuration.js'),
7 winston = require('winston'),
8 SettingsGenerator = require('./settingsgenerator.js'),
9 Stats = require('./stats.js');
10
11
12
13
14 var HttpHandler = function (config) {
15 var public_http = config.public_http || 'client/';
16 this.file_server = new node_static.Server(public_http);
17 };
18
19 module.exports.HttpHandler = HttpHandler;
20
21
22
23 HttpHandler.prototype.serve = function (request, response) {
24 // The incoming requests base path (ie. /kiwiclient)
25 var base_path, base_check,
26 whitelisted_folders = ['/assets', '/src'],
27 is_whitelisted_folder = false;
28
29 // Trim off any trailing slashes from the base_path
30 base_path = global.config.http_base_path || '';
31 if (base_path.substr(base_path.length - 1) === '/') {
32 base_path = base_path.substr(0, base_path.length - 1);
33 }
34
35 // Normalise the URL to compare against the base_path
36 base_check = request.url;
37 if (base_check.substr(base_check.length - 1) !== '/') {
38 base_check += '/';
39 }
40
41 // Normalise the URL we use by removing the base path
42 if (base_check.indexOf(base_path + '/') === 0) {
43 request.url = request.url.replace(base_path, '');
44
45 } else if (base_check !== '/') {
46 // We don't handle requests outside of the base path and not /, so just 404
47 response.writeHead(404);
48 response.write('Not Found');
49 response.end();
50 return;
51 }
52
53 // Map any whitelisted folders to the local directories
54 whitelisted_folders.forEach(function(folder) {
55 if (request.url.indexOf(folder) === 0) {
56 is_whitelisted_folder = true;
57 }
58 });
59
60 // Any requests not for whitelisted assets returns the index page
61 if (!is_whitelisted_folder) {
62 request.url = '/index.html';
63 }
64
65 if (request.url === '/index.html') {
66 Stats.incr('http.homepage');
67 }
68
69 // If the 'magic' translation is requested, figure out the best language to use from
70 // the Accept-Language HTTP header. If nothing is suitible, fallback to our en-gb default translation
71 if (request.url.substr(0, 16) === '/assets/locales/') {
72 if (request.url === '/assets/locales/magic.json') {
73 return serveMagicLocale.call(this, request, response);
74 } else {
75 response.setHeader('Content-Language', request.url.substr(16, request.url.indexOf('.') - 16));
76 }
77 } else if (request.url.substr(0, 21) === '/assets/settings.json') {
78 return serveSettings.call(this, request, response);
79 }
80
81 this.file_server.serve(request, response, function (err) {
82 if (err) {
83 response.writeHead(err.status, err.headers);
84 response.end();
85 }
86 });
87 };
88
89
90 // Cached list of available translations
91 var cached_available_locales = [];
92
93 // Get a list of the available translations we have
94 fs.readdir('client/assets/locales', function (err, files) {
95 if (err) {
96 if (err.code === 'ENOENT') {
97 winston.error('No locale files could be found at ' + err.path);
98 } else {
99 winston.error('Error reading locales.', err);
100 }
101 }
102
103 (files || []).forEach(function (file) {
104 if (file.substr(-5) === '.json') {
105 cached_available_locales.push(file.slice(0, -5));
106 }
107 });
108 });
109
110
111
112 /**
113 * Handle the /assets/locales/magic.json request
114 * Find the closest translation we have for the language
115 * set in the browser.
116 **/
117 var serveMagicLocale = function (request, response) {
118 var default_locale_id = 'en-gb',
119 found_locale, negotiator;
120
121 if (!request.headers['accept-language']) {
122 // No accept-language specified in the request so send the default
123 found_locale = default_locale_id;
124
125 } else {
126 negotiator = new Negotiator(request);
127 found_locale = negotiator.language(cached_available_locales);
128
129 // If a locale couldn't be negotiated, use the default
130 found_locale = found_locale || default_locale_id;
131 }
132
133 // Send a locale to the browser
134 this.file_server.serveFile('/assets/locales/' + found_locale + '.json', 200, {
135 Vary: 'Accept-Language',
136 'Content-Language': found_locale
137 }, request, response);
138 };
139
140
141
142 /**
143 * Handle the settings.json request
144 */
145 var serveSettings = function(request, response) {
146 var referrer_url,
147 debug = false,
148 settings;
149
150 // Check the referrer for a debug option
151 if (request.headers['referer']) {
152 referrer_url = url.parse(request.headers['referer'], true);
153 if (referrer_url.query && referrer_url.query.debug) {
154 debug = true;
155 }
156 }
157
158 SettingsGenerator.get(debug, function(settings) {
159 if (request.headers['if-none-match'] && request.headers['if-none-match'] === settings.hash) {
160 response.writeHead(304, 'Not Modified');
161 return response.end();
162 }
163
164 response.writeHead(200, {
165 'ETag': settings.hash,
166 'Content-Type': 'application/json'
167 });
168 response.end(settings.settings);
169 });
170 };