2016-09-11 22:04:37 +03:00
const gen = require ( 'gen-thread' ) ;
const MailParser = require ( 'mailparser' ) . MailParser ;
const htmlawed = require ( 'htmlawed' ) ;
const express = require ( 'express' ) ;
const express _session = require ( 'express-session' ) ;
const bodyparser = require ( 'body-parser' ) ;
const multer = require ( 'multer' ) ;
2016-10-03 13:00:56 +03:00
const css = require ( 'css' ) ;
2016-09-11 22:04:37 +03:00
module . exports = SyncerWeb ;
function SyncerWeb ( syncer , pg , cfg )
{
this . syncer = syncer ;
this . pg = pg ;
this . cfg = cfg ;
this . app = express ( ) ;
this . app . use ( bodyparser . urlencoded ( { extended : false } ) ) ;
this . app . use ( express _session ( {
secret : this . cfg . sessionSecret || '1083581xm1l3s1l39k' ,
resave : false ,
saveUninitialized : false
} ) ) ;
this . app . get ( '/auth' , this . get _auth ) ;
this . app . post ( '/auth' , this . post _auth ) ;
this . app . get ( '/folders' , genRequest ( this . get _folders . bind ( this ) ) ) ;
this . app . get ( '/messages' , genRequest ( this . get _messages . bind ( this ) ) ) ;
this . app . get ( '/message' , genRequest ( this . get _message . bind ( this ) ) ) ;
this . app . post ( '/sync' , genRequest ( this . post _sync . bind ( this ) ) ) ;
}
SyncerWeb . prototype . get _auth = function ( req , res )
{
2016-10-02 21:57:43 +03:00
return res . type ( 'html' ) . send (
'<form action="/auth" method="post"><input name="login" />' +
' <input name="password" type="password" /> <input type="submit" /></form>'
) ;
2016-09-11 22:04:37 +03:00
}
SyncerWeb . prototype . post _auth = function ( req , res )
{
if ( ! req . body )
return res . sendStatus ( 400 ) ;
if ( req . body . login == this . cfg . login && req . body . password == this . cfg . password )
{
req . session . auth = true ;
return res . send ( { ok : true } ) ;
}
return res . send ( { ok : false } ) ;
}
SyncerWeb . prototype . get _folders = function * ( req , res )
{
2016-10-02 21:57:43 +03:00
if ( this . cfg . login && ( ! req . session || ! req . session . auth ) )
{
2016-09-11 22:04:37 +03:00
return res . sendStatus ( 401 ) ;
2016-10-02 21:57:43 +03:00
}
var [ accounts ] = yield this . pg . select (
'id, name, email, settings->\'folders\' folderMap,' +
' (select count(*) from messages m, folders f where m.folder_id=f.id and f.account_id=a.id and (flags @> array[\'pinned\',\'unread\']::varchar(255)[])) pinned_unread_count'
) . from ( 'accounts a' ) . rows ( gen . ef ( ) ) ;
2016-09-11 22:04:37 +03:00
var [ folders ] = yield this . pg . select (
'id, account_id, name,' +
' (select count(*) from messages m where m.folder_id=f.id) total_count,' +
' (select count(*) from messages m where m.folder_id=f.id and (flags @> array[\'unread\']::varchar(255)[])) unread_count'
) . from ( 'folders f' ) . orderBy ( 'account_id, name' ) . rows ( gen . ef ( ) ) ;
var fh = { } ;
for ( let i = 0 ; i < folders . length ; i ++ )
{
fh [ folders [ i ] . account _id ] = fh [ folders [ i ] . account _id ] || [ ] ;
fh [ folders [ i ] . account _id ] . push ( folders [ i ] ) ;
}
for ( let i = 0 ; i < accounts . length ; i ++ )
{
accounts [ i ] . folders = fh [ accounts [ i ] . id ] || [ ] ;
}
return res . send ( { accounts : accounts } ) ;
}
SyncerWeb . prototype . get _messages = function * ( req , res )
{
2016-10-02 21:57:43 +03:00
if ( this . cfg . login && ( ! req . session || ! req . session . auth ) )
2016-09-11 22:04:37 +03:00
return res . sendStatus ( 401 ) ;
var folderId = req . query . folderId ;
if ( ! folderId )
return res . status ( 500 ) . send ( 'Need `folderId` query parameter' ) ;
var limit = req . query . limit || 50 ;
var offset = req . query . offset || 0 ;
var [ msgs ] = yield this . pg . select ( '*' ) . from ( 'messages' ) . where ( { folder _id : folderId } )
. orderBy ( 'time desc' ) . limit ( limit ) . offset ( offset ) . rows ( gen . ef ( ) ) ;
2016-10-02 21:57:43 +03:00
for ( var i = 0 ; i < msgs . length ; i ++ )
{
delete msgs [ i ] . text _index ;
}
2016-09-11 22:04:37 +03:00
return res . send ( { messages : msgs } ) ;
}
SyncerWeb . prototype . get _message = function * ( req , res )
{
2016-10-02 21:57:43 +03:00
if ( this . cfg . login && ( ! req . session || ! req . session . auth ) )
2016-09-11 22:04:37 +03:00
return res . sendStatus ( 401 ) ;
var msgId = req . query . msgId ;
var [ msg ] = yield this . pg . select ( 'm.*, f.name folder_name, f.account_id' )
. from ( 'messages m' ) . join ( 'folders f' , this . pg . sql ( 'f.id=m.folder_id' ) )
2016-10-02 21:57:43 +03:00
. where ( { 'm.id' : msgId } ) . rows ( gen . ef ( ) ) ;
if ( ! msg . length )
2016-09-11 22:04:37 +03:00
return res . send ( { error : 'not-found' } ) ;
2016-10-02 21:57:43 +03:00
msg = msg [ 0 ] ;
2016-09-11 22:04:37 +03:00
delete msg . text _index ;
if ( ! msg . body _html && ! msg . body _text )
{
var srv = yield * this . syncer . imap . getConnection ( msg . account _id , msg . folder _name ) ;
var [ upd ] = yield * this . syncer . imap . runFetch (
srv , msg . uid , { bodies : '' } ,
( messages , state ) => getBody ( this . pg , messages , msg . folder _id )
) ;
this . syncer . imap . releaseConnection ( msg . account _id ) ;
return res . send ( { msg : { ... msg , ... upd } } ) ;
}
return res . send ( { msg : msg } ) ;
}
SyncerWeb . prototype . post _sync = function * ( req , res )
{
2016-10-02 21:57:43 +03:00
if ( this . cfg . login && ( ! req . session || ! req . session . auth ) )
2016-09-11 22:04:37 +03:00
return res . sendStatus ( 401 ) ;
if ( self . syncer . syncInProgress )
return res . send ( { error : 'already-running' } ) ;
gen . run ( self . syncer . syncAll ( ) ) ;
return res . send ( { status : 'started' } ) ;
}
2016-10-03 13:00:56 +03:00
function rewriteCss ( ast )
{
var rules = ast . rules || ast . stylesheet && ast . stylesheet . rules ;
if ( rules )
{
for ( var i = 0 ; i < rules . length ; i ++ )
{
if ( rules [ i ] . type == 'document' )
{
// prune @document instructions (may spy on current URL)
rules . splice ( i -- , 1 ) ;
}
else
rewriteCss ( rules [ i ] ) ;
}
}
else if ( ast . type == 'rule' )
{
for ( var i = 0 ; i < ast . selectors . length ; i ++ )
{
// FIXME: Do not hardcode css selector for frontend here
// This will require generating unique substitution string,
// so we may also generate 'blocked images' stubs when we do it.
ast . selectors [ i ] = '.message-view .text ' + ast . selectors [ i ] ;
}
}
}
2016-09-11 22:04:37 +03:00
function * getBody ( pg , messages , boxId )
{
var p = new MailParser ( { streamAttachments : false , defaultCharset : 'windows-1251' } ) ;
for ( var i = 0 ; i < messages . length ; i ++ )
{
let msg = messages [ i ] ;
p . on ( 'end' , gen . cb ( ) ) ;
p . write ( msg [ 0 ] . headers ) ;
let [ obj ] = yield p . end ( ) ;
2016-10-03 01:33:47 +03:00
let styles = '' ;
obj . html = ( obj . html || '' ) . replace ( /<style[^<>]*>([\s\S]*?)<\/style\s*>/ig , function ( m , m1 )
{
styles += m1 + '\n' ;
return '' ;
} ) ;
obj . html = obj . html . replace ( /^[\s\S]*?<body[^<>]*>([\s\S]*)<\/body>[\s\S]*$/i , '$1' ) ;
obj . html = obj . html . replace ( /^[\s\S]*?<html[^<>]*>([\s\S]*)<\/html>[\s\S]*$/i , '$1' ) ;
if ( styles )
{
obj . html = '<style>\n' + styles + '</style>\n' + obj . html ;
2016-10-03 13:00:56 +03:00
styles = '' ;
2016-10-03 01:33:47 +03:00
}
2016-10-03 13:00:56 +03:00
obj . html = htmlawed . sanitize ( obj . html || '' , { safe : 1 , elements : '* +style' , keep _bad : 0 , comment : 1 } ) ;
obj . html = obj . html . replace ( /<style[^>]*>([\s\S]*)<\/style\s*>/ig , function ( m , m1 )
{
var ast = css . parse ( m1 , { silent : true } ) ;
rewriteCss ( ast ) ;
return '<style>' + css . stringify ( ast ) + '</style>' ;
} ) ;
2016-09-11 22:04:37 +03:00
let upd = { body _text : obj . text || '' , body _html : obj . html } ;
upd . body _html _text = obj . html . replace ( /<style[^>]*>.*<\/style\s*>|<\/?[^>]*>/g , '' ) ;
yield pg . update ( 'messages m' , upd ) . where ( { folder _id : boxId , uid : msg [ 0 ] . uid } ) . run ( gen . ef ( ) ) ;
if ( messages . length == 1 )
return [ upd ] ;
}
}
function genRequest ( fn )
{
return ( req , res ) => gen . run ( fn ( req , res ) , null , e => res . status ( 500 ) . send ( 'Internal Error: ' + e . stack ) ) ;
}