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