Mercurial > eldonilo > avatar
view avatar.js @ 18:92d6e1b40df5
Move available extensions into configuration.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Sun, 10 Jul 2011 15:48:20 +0200 |
parents | 32f7526da5fe |
children | 9ff8f951da99 |
line wrap: on
line source
#!/usr/bin/env node /* * Copyright (C) 2011 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> * * This file is the source code of an XMPP avatar retriever. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ 'use strict'; var config = require('./configuration'); var util = require('util'); var fs = require('fs'); var http = require('http'); var xmpp = require('node-xmpp'); var conn = new xmpp.Client(config); var Element = require('ltx').Element; Element.prototype.getAttribute = function(name) { return this.attrs[name]; }; process.addListener('uncaughtException', function (err) { console.log('[41;1mUncaught exception (' + err + '), this should never happen:[0m\n' + err.stack); }); (function() { var send = conn.send; conn.send = function(s) { util.log('Sent: [1;32m' + s + '[0m'); send.call(conn, s); }; })(); conn.on('online', function () { console.log('Connected'); }); conn.on('stanza', function (stanza) { util.log('Recv: [1;34m' + stanza + '[0m'); if (stanza.is('iq')) onIq(stanza); else onError(stanza); }); var getUniqueId = (function() { var id = 0; return function() { return ++id; }; })(); var jids = {}; var sent = {}; var svgError = function(res, message) { res.writeHead(500, {'Content-Type': 'image/svg+xml'}); res.write('<?xml version="1.0" encoding="UTF-8"?>\n'); res.write('<svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 -36 64 64">\n'); res.write('\t<title>'+message+'</title>\n'); res.write('\t<rect x="-32" y="-36" width="64" height="64" fill="white"/>\n'); res.write('\t<text font-family="sans-serif" font-weight="bold" text-anchor="middle">Error</text>\n'); res.end('</svg>\n'); } var makeError = function(response) { response.attrs.type = 'error'; response.c('error', {type: 'cancel'}) .c('feature-not-implemented', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}); return response; } function onIq(stanza) { var type = stanza.getAttribute('type'); var from = stanza.getAttribute('to'); var to = stanza.getAttribute('from'); var id = stanza.getAttribute('id'); var response = new Element('iq', {to: to, from: from, type: 'result'}); if (id) response.attrs.id = id; if (!sent[id]) return conn.send(makeError(response)); var res = sent[id]; delete sent[id]; if (type == 'error') { try { var err = stanza.getChild('error').getChild().name; } catch (e) { var err = 'none'; } return svgError(res, 'Error during query of this user’s vCard: “'+err+'”.'); } var vCard = stanza.getChild('vCard', 'vcard-temp'); if (!vCard) return svgError(res, 'Error: this user doesn’t have a vCard.'); try { var photo = vCard.getChild('PHOTO', 'vcard-temp'); var base64 = photo.getChild('BINVAL', 'vcard-temp').getText(); try { var type = photo.getChild('TYPE', 'vcard-temp').getText(); } catch (e) { if (config.guessType) type = 'image/png'; // FIXME: use magic. else return svgError(res, 'Error: this user’s vCard doesn’t specify the MIME type of its avatar.'); } var ext; for (var i in config.extensions) if (type == config.extensions[i]) ext = i; // Here we don’t try to guess the extension even if the option is set. if (ext === undefined) { console.log('Unknown MIME type: '+type); return svgError(res, 'Error: this user’s avatar is in an unknown format.'); } var binval = new Buffer(base64.replace(/\n/g, ''), 'base64'); fs.writeFile(config.directory+'/'+to+'.'+ext, binval, function() { jids[to] = ext; showImage(to, res); }); } catch (e) { svgError(res, 'Error: this user doesn’t have an avatar in his/her vCard.'); } } function onError(stanza) { if (stanza.getAttribute('type') == 'error') return; var from = stanza.getAttribute('to'); var to = stanza.getAttribute('from'); var id = stanza.getAttribute('id'); var response = new Element(stanza.name, {to: to, from: from}); if (id) response.attrs.id = id; conn.send(makeError(response)); } var getVCard = function(jid, res) { var id = getUniqueId(); var toSend = new Element('iq', {to: jid, from: config.jid, type: 'get', id: id}) .c('vCard', {xmlns: 'vcard-temp'}) .up(); conn.send(toSend); sent[id] = res; } var showImage = function(jid, res) { var extension = jids[jid]; var file = config.directory+'/'+jid+'.'+extension; var now = new Date; res.writeHead(200, {'Content-Type': config.extensions[extension], Expires: new Date(config.expire * 1000 + now.getTime())}); fs.readFile(file, function(err, data) { res.end(data); }); fs.stat(file, function(err, stats) { if (err) { console.log('Error when stat on “'+file+'”.'); return; } var last = new Date(stats.mtime); if (now - last > config.expire * 1000) getVCard(jid, res); }); return; } fs.readdir(config.directory, function(err, files) { if (err) return fs.mkdir(config.directory, 488); // Actually 0750 for (var i in files) { var tab = /(.*)\.([a-z]{3})/.exec(files[i]); jids[tab[1]] = tab[2]; } }); http.createServer(function (req, res) { console.log('Connection from ' + (req.headers['x-forwarded-for'] || req.client.remoteAddress) + ' (' + req.headers['user-agent'] + ') to ' + req.method.toLocaleLowerCase() + ' “' + req.url + '”.'); var easterEggs = { source: { re: new RegExp(config.webRoot + 'source/code$'), file: process.argv[1], mime: 'application/ecmascript', error: 'source code unavailable! oO' }, README: {}, COPYING: {}, 'index.xhtml': { re: /\/(index.xhtml)?$/, mime: 'application/xhtml+xml', error: '<h1 xmlns="http://www.w3.org/1999/xhtml">Index file not available.</h1>' } }; req.setEncoding('utf-8'); for (var i in easterEggs) { var ee = easterEggs[i]; var file = ee.file || i; var re = ee.re || new RegExp(config.webRoot + file + '$'); if (re.test(req.url)) { fs.readFile(file, function(err, content) { if (err) return svgError(res, 'Error: ' + (ee.error || file + ' unavailable.')); res.writeHead(200, {'Content-Type': ee.mime || 'text/plain'}); res.end(content); }); return; } } var jid = unescape(req.url.replace(new RegExp(config.webRoot), '')); if (jid === 'redirect') { if (req.method !== 'POST') { res.writeHead(404, {'Content-Type': 'text/plain'}); res.end('Error: redirect unavailable.'); return; } req.on('data', function(content) { var jid = unescape(content.toString()).replace(/^jid=/, ''); res.writeHead(301, {'Location': jid}); res.end(); }); return; } if (jid in jids) { showImage(jid, res); return; } getVCard(jid, res); }).listen(config.webPort, config.webHost);