/ *
globals require console
* /
var Express = require ( 'express' ) ;
var Http = require ( 'http' ) ;
var Fs = require ( 'fs' ) ;
var Path = require ( "path" ) ;
var nThen = require ( "nthen" ) ;
var Util = require ( "./lib/common-util" ) ;
var Default = require ( "./lib/defaults" ) ;
var Keys = require ( "./lib/keys" ) ;
var config = require ( "./lib/load-config" ) ;
var Env = require ( "./lib/env" ) . create ( config ) ;
var app = Express ( ) ;
var fancyURL = function ( domain , path ) {
try {
if ( domain && path ) { return new URL ( path , domain ) . href ; }
return new URL ( domain ) ;
} catch ( err ) { }
return false ;
} ;
( function ( ) {
// you absolutely must provide an 'httpUnsafeOrigin' (a truthy string)
if ( typeof ( Env . httpUnsafeOrigin ) !== 'string' || ! Env . httpUnsafeOrigin . trim ( ) ) {
throw new Error ( "No 'httpUnsafeOrigin' provided" ) ;
}
} ( ) ) ;
var applyHeaderMap = function ( res , map ) {
for ( let header in map ) {
if ( typeof ( map [ header ] ) === 'string' ) { res . setHeader ( header , map [ header ] ) ; }
}
} ;
var EXEMPT = [
/^\/common\/onlyoffice\/.*\.html.*/ ,
/^\/(sheet|presentation|doc)\/inner\.html.*/ ,
/^\/unsafeiframe\/inner\.html.*$/ ,
] ;
var cacheHeaders = function ( Env , key , headers ) {
if ( Env . DEV _MODE ) { return ; }
Env [ key ] = headers ;
} ;
var getHeaders = function ( Env , type ) {
var key = type + 'HeadersCache' ;
if ( Env [ key ] ) { return Env [ key ] ; }
var headers = { } ;
var custom = config . httpHeaders ;
// if the admin provided valid http headers then use them
if ( custom && typeof ( custom ) === 'object' && ! Array . isArray ( custom ) ) {
headers = Util . clone ( custom ) ;
} else {
// otherwise use the default
headers = Default . httpHeaders ( Env ) ;
}
headers [ 'Content-Security-Policy' ] = type === 'office' ?
Default . padContentSecurity ( Env ) :
Default . contentSecurity ( Env ) ;
if ( Env . NO _SANDBOX ) { // handles correct configuration for local development
// https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs
headers [ "Cross-Origin-Resource-Policy" ] = 'cross-origin' ;
headers [ "Cross-Origin-Embedder-Policy" ] = 'require-corp' ;
}
// Don't set CSP headers on /api/ endpoints
// because they aren't necessary and they cause problems
// when duplicated by NGINX in production environments
if ( type === 'api' ) {
cacheHeaders ( Env , key , headers ) ;
return headers ;
}
headers [ "Cross-Origin-Resource-Policy" ] = 'cross-origin' ;
cacheHeaders ( Env , key , headers ) ;
return headers ;
} ;
var setHeaders = function ( req , res ) {
var type ;
if ( EXEMPT . some ( regex => regex . test ( req . url ) ) ) {
type = 'office' ;
} else if ( /^\/api\/(broadcast|config)/ . test ( req . url ) ) {
type = 'api' ;
} else {
type = 'standard' ;
}
var h = getHeaders ( Env , type ) ;
//console.log('PEWPEW', type, h);
applyHeaderMap ( res , h ) ;
} ;
( function ( ) {
if ( ! config . logFeedback ) { return ; }
const logFeedback = function ( url ) {
url . replace ( /\?(.*?)=/ , function ( all , fb ) {
if ( ! config . log ) { return ; }
config . log . feedback ( fb , '' ) ;
} ) ;
} ;
app . head ( /^\/common\/feedback\.html/ , function ( req , res , next ) {
logFeedback ( req . url ) ;
next ( ) ;
} ) ;
} ( ) ) ;
app . use ( '/blob' , function ( req , res , next ) {
if ( req . method === 'HEAD' ) {
Express . static ( Path . join ( _ _dirname , Env . paths . blob ) , {
setHeaders : function ( res , path , stat ) {
res . set ( 'Access-Control-Allow-Origin' , Env . disableEmbedding ? Env . permittedEmbedders : '*' ) ;
res . set ( 'Access-Control-Allow-Headers' , 'Content-Length' ) ;
res . set ( 'Access-Control-Expose-Headers' , 'Content-Length' ) ;
}
} ) ( req , res , next ) ;
return ;
}
next ( ) ;
} ) ;
app . use ( function ( req , res , next ) {
if ( req . method === 'OPTIONS' && /\/blob\// . test ( req . url ) ) {
res . setHeader ( 'Access-Control-Allow-Origin' , Env . disableEmbedding ? Env . permittedEmbedders : '*' ) ;
res . setHeader ( 'Access-Control-Allow-Methods' , 'GET, OPTIONS' ) ;
res . setHeader ( 'Access-Control-Allow-Headers' , 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range,Access-Control-Allow-Origin' ) ;
res . setHeader ( 'Access-Control-Max-Age' , 1728000 ) ;
res . setHeader ( 'Content-Type' , 'application/octet-stream; charset=utf-8' ) ;
res . setHeader ( 'Content-Length' , 0 ) ;
res . statusCode = 204 ;
return void res . end ( ) ;
}
setHeaders ( req , res ) ;
if ( /[\?\&]ver=[^\/]+$/ . test ( req . url ) ) { res . setHeader ( "Cache-Control" , "max-age=31536000" ) ; }
else { res . setHeader ( "Cache-Control" , "no-cache" ) ; }
next ( ) ;
} ) ;
app . use ( Express . static ( _ _dirname + '/www' ) ) ;
// FIXME I think this is a regression caused by a recent PR
// correct this hack without breaking the contributor's intended behaviour.
var mainPages = config . mainPages || Default . mainPages ( ) ;
var mainPagePattern = new RegExp ( '^\/(' + mainPages . join ( '|' ) + ').html$' ) ;
app . get ( mainPagePattern , Express . static ( _ _dirname + '/customize' ) ) ;
app . get ( mainPagePattern , Express . static ( _ _dirname + '/customize.dist' ) ) ;
app . use ( "/blob" , Express . static ( Path . join ( _ _dirname , Env . paths . blob ) , {
maxAge : Env . DEV _MODE ? "0d" : "365d"
} ) ) ;
app . use ( "/datastore" , Express . static ( Path . join ( _ _dirname , Env . paths . data ) , {
maxAge : "0d"
} ) ) ;
app . use ( "/block" , Express . static ( Path . join ( _ _dirname , Env . paths . block ) , {
maxAge : "0d" ,
} ) ) ;
app . use ( "/customize" , Express . static ( _ _dirname + '/customize' ) ) ;
app . use ( "/customize" , Express . static ( _ _dirname + '/customize.dist' ) ) ;
app . use ( "/customize.dist" , Express . static ( _ _dirname + '/customize.dist' ) ) ;
app . use ( /^\/[^\/]*$/ , Express . static ( 'customize' ) ) ;
app . use ( /^\/[^\/]*$/ , Express . static ( 'customize.dist' ) ) ;
// if dev mode: never cache
var cacheString = function ( ) {
return ( Env . FRESH _KEY ? '-' + Env . FRESH _KEY : '' ) + ( Env . DEV _MODE ? '-' + ( + new Date ( ) ) : '' ) ;
} ;
var makeRouteCache = function ( template , cacheName ) {
var cleanUp = { } ;
var cache = Env [ cacheName ] = Env [ cacheName ] || { } ;
return function ( req , res ) {
var host = req . headers . host . replace ( /\:[0-9]+/ , '' ) ;
res . setHeader ( 'Content-Type' , 'text/javascript' ) ;
// don't cache anything if you're in dev mode
if ( Env . DEV _MODE ) {
return void res . send ( template ( host ) ) ;
}
// generate a lookup key for the cache
var cacheKey = host + ':' + cacheString ( ) ;
// FIXME mutable
// we must be able to clear the cache when updating any mutable key
// if there's nothing cached for that key...
if ( ! cache [ cacheKey ] ) {
// generate the response and cache it in memory
cache [ cacheKey ] = template ( host ) ;
// and create a function to conditionally evict cache entries
// which have not been accessed in the last 20 seconds
cleanUp [ cacheKey ] = Util . throttle ( function ( ) {
delete cleanUp [ cacheKey ] ;
delete cache [ cacheKey ] ;
} , 20000 ) ;
}
// successive calls to this function
cleanUp [ cacheKey ] ( ) ;
return void res . send ( cache [ cacheKey ] ) ;
} ;
} ;
var serveConfig = makeRouteCache ( function ( host ) {
return [
'define(function(){' ,
'return ' + JSON . stringify ( {
requireConf : {
waitSeconds : 600 ,
urlArgs : 'ver=' + Env . version + cacheString ( ) ,
} ,
removeDonateButton : ( Env . removeDonateButton === true ) ,
allowSubscriptions : ( Env . allowSubscriptions === true ) ,
websocketPath : Env . websocketPath ,
httpUnsafeOrigin : Env . httpUnsafeOrigin ,
adminEmail : Env . adminEmail ,
adminKeys : Env . admins ,
inactiveTime : Env . inactiveTime ,
supportMailbox : Env . supportMailbox ,
defaultStorageLimit : Env . defaultStorageLimit ,
maxUploadSize : Env . maxUploadSize ,
premiumUploadSize : Env . premiumUploadSize ,
restrictRegistration : Env . restrictRegistration ,
httpSafeOrigin : Env . httpSafeOrigin ,
disableEmbedding : Env . disableEmbedding ,
fileHost : Env . fileHost ,
shouldUpdateNode : Env . shouldUpdateNode || undefined ,
} , null , '\t' ) ,
'});'
] . join ( ';\n' )
} , 'configCache' ) ;
var serveBroadcast = makeRouteCache ( function ( host ) {
var maintenance = Env . maintenance ;
if ( maintenance && maintenance . end && maintenance . end < ( + new Date ( ) ) ) {
maintenance = undefined ;
}
return [
'define(function(){' ,
'return ' + JSON . stringify ( {
lastBroadcastHash : Env . lastBroadcastHash ,
surveyURL : Env . surveyURL ,
maintenance : maintenance
} , null , '\t' ) ,
'});'
] . join ( ';\n' )
} , 'broadcastCache' ) ;
app . get ( '/api/config' , serveConfig ) ;
app . get ( '/api/broadcast' , serveBroadcast ) ;
var four04 _path = Path . resolve ( _ _dirname + '/customize.dist/404.html' ) ;
var custom _four04 _path = Path . resolve ( _ _dirname + '/customize/404.html' ) ;
var send404 = function ( res , path ) {
if ( ! path && path !== four04 _path ) { path = four04 _path ; }
Fs . exists ( path , function ( exists ) {
res . setHeader ( 'Content-Type' , 'text/html; charset=utf-8' ) ;
if ( exists ) { return Fs . createReadStream ( path ) . pipe ( res ) ; }
send404 ( res ) ;
} ) ;
} ;
app . get ( '/api/profiling' , function ( req , res , next ) {
if ( ! Env . enableProfiling ) { return void send404 ( res ) ; }
res . setHeader ( 'Content-Type' , 'text/javascript' ) ;
res . send ( JSON . stringify ( {
bytesWritten : Env . bytesWritten ,
} ) ) ;
} ) ;
app . use ( function ( req , res , next ) {
res . status ( 404 ) ;
send404 ( res , custom _four04 _path ) ;
} ) ;
var httpServer = Env . httpServer = Http . createServer ( app ) ;
nThen ( function ( w ) {
Fs . exists ( _ _dirname + "/customize" , w ( function ( e ) {
if ( e ) { return ; }
use consistent capitalization for CryptPad
run docs/ARCHITECTURE.md:[XWiki-Labs](https://labs.xwiki.com/) has published an open source suite (called [Cryptpad](https://github.com/xwiki-labs/cryptpad)) of collaborative editors which employ end to end encryption.
docs/ARCHITECTURE.md:Cryptpad is capable of using a variety of data stores.
docs/ARCHITECTURE.md:Cryptpad was initially written to use [websockets](https://en.wikipedia.org/wiki/WebSocket) for transportation of messages.
docs/ARCHITECTURE.md:The encryption scheme employed by Cryptpad is a [symmetric encryption](https://en.wikipedia.org/wiki/Symmetric-key_algorithm) which utilizes a single [pre-shared-key](https://en.wikipedia.org/wiki/Pre-shared_key) known by all participants.
readme.md:See [Cryptpad-Docker](https://github.com/xwiki-labs/cryptpad-docker) repository for details on how to get up-and-running with Cryptpad in Docker. This repository is maintained by the community and not officially supported.
readme.md:If you have any questions or comments, or if you're interested in contributing to Cryptpad, come say hi in our [Matrix channel](https://app.element.io/#/room/#cryptpad:matrix.xwiki.com).
www/common/translations/README.md:To illustrate the process of translating, this guide will make an english-pirate translation of Cryptpad.
www/common/translations/README.md:We'll assume that you have a work locally-installed, properly functioning installation of Cryptpad.
www/common/translations/README.md:If you don't have Cryptpad installed locally, start by following the steps in the main readme.
www/common/translations/README.md: out.main_title = "Cryptpad: Zero Knowledge, Collaborative Real Time Editing";
www/common/translations/README.md: out.main_title = "Cryptpad: Knowledge lost at sea while ye scribble with yer mateys";
www/common/translations/README.md:It's advisable to save your translation file frequently, and reload Cryptpad in your browser to check that there are no errors in your translation file.
www/common/translations/README.md:When you're happy with your translation file, you can visit http://localhost:3000/assert/translations/ to view Cryptpad's tests.
www/common/translations/messages.ca.json: "topbar_whatIsCryptpad": "Què és CryptPad",
www/common/translations/messages.de.json: "topbar_whatIsCryptpad": "Was ist CryptPad",
www/common/translations/messages.el.json: "topbar_whatIsCryptpad": "Τι είναι το CryptPad",
www/common/translations/messages.es.json: "main_title": "Cryptpad: Zero Knowledge, Editor Colaborativo en Tiempo Real",
www/common/translations/messages.es.json: "tos_title": "Condiciones de servicio Cryptpad",
www/common/translations/messages.es.json: "tos_e2ee": "Los documentos Cryptpad pueden ser leídos o modificados por cualquiera que pueda adivinar o que pueda tener el enlace. Recomendamos que utilices mensajes cifrados de punto a punto (e2ee) para compartir URLs, no asumimos ninguna responsabilidad en el evento de alguna fuga.",
www/common/translations/messages.es.json: "topbar_whatIsCryptpad": "Qué es CryptPad",
www/common/translations/messages.es.json: "settings_autostoreHint": "<b> Automático </b> Todos los pads que visita se almacenan en su CryptDrive. <br> <b> Manual (siempre pregunte) </b> Si aún no ha guardado un pad, se le preguntará si desea para almacenarlos en su CryptDrive. <br> <b> Manual (nunca preguntar) </b> Los Pads no se almacenan automáticamente en su Cryptpad. La opción para almacenarlos estará oculta.",
www/common/translations/messages.fi.json: "home_host": "Tämä on itsenäinen yhteisön ylläpitämä Cryptpad-instanssi.",
www/common/translations/messages.fi.json: "topbar_whatIsCryptpad": "Mikä on CryptPad",
www/common/translations/messages.fr.json: "topbar_whatIsCryptpad": "Qu'est-ce que CryptPad",
www/common/translations/messages.fr.json: "admin_updateAvailableHint": "Une nouvelle version de Cryptpad est disponible",
www/common/translations/messages.id.json: "main_title": "Cryptpad: Informasi Aman, Kolaborasi Waktu Nyata"
www/common/translations/messages.it.json: "topbar_whatIsCryptpad": "Cos'è CryptPad",
www/common/translations/messages.it.json: "settings_autostoreHint": "<b>Automatico</b> Tutti i pad che visiti sono conservati nel tuo CryptDrive.<br><b>Manuale (chiedi sempre)</b> Se non hai ancora conservato alcun pad ti verrà chiesto se vuoi conservarli nel tuo CryptDrive.<br><b>Manuale (non chiedere mai)</b> I pads non sono conservati automaticamente nel tuo Cryptpad. L'opzione di conservarli sarà nascosta.",
www/common/translations/messages.it.json: "survey": "Sondaggio Cryptpad",
www/common/translations/messages.it.json: "crowdfunding_button": "Supporta Cryptpad",
www/common/translations/messages.ja.json: "topbar_whatIsCryptpad": "CryptPadとは何か",
www/common/translations/messages.json: "settings_autostoreHint": "<b>Automatic</b> All the pads you visit are stored in your CryptDrive.<br><b>Manual (always ask)</b> If you have not stored a pad yet, you will be asked if you want to store them in your CryptDrive.<br><b>Manual (never ask)</b> Pads are not stored automatically in your Cryptpad. The option to store them will be hidden.",
www/common/translations/messages.json: "topbar_whatIsCryptpad": "What is CryptPad",
www/common/translations/messages.nb.json: "topbar_whatIsCryptpad": "Hva er CryptPad",
www/common/translations/messages.nl.json: "settings_autostoreHint": "<b>Automatisch</b> Alle geopende werkomgevingen worden automatisch opgeslagen in uw CryptDrive.<br><b>Handmatig (altijd vragen)</b> Als u een werkomgeving nog niet hebt opgeslagen, zult u gevraagd worden of u het in uw CryptDrive wilt opslaan.<br><b>Handmatig (nooit vragen)</b> Werkomgevingen worden niet automatisch opgeslagen in uw Cryptpad. The optie om op te slaan wordt verborgen.",
www/common/translations/messages.pl.json: "main_title": "Cryptpad: Wspólne edytowanie w czasie rzeczywistym, bez wiedzy specjalistycznej",
www/common/translations/messages.pl.json: "tos_title": "Warunki korzystania z usług Cryptpad",
www/common/translations/messages.pl.json: "tos_e2ee": "Dokumenty Cryptpad mogą być odczytywane i modyfikowane przez każdego kto może zgadnąć lub w inny sposób uzyskać identyfikator dokumentu. Polecamy korzystania z oprogramowania szyfrującego end-to-end (e2ee) do udostępniania linków URL. Nie będziesz rościł sobie żadnych wierzytelności w wypadku gdy taki URL dostanie się w niepowołane ręce.",
www/common/translations/messages.pt-br.json: "main_title": "Cryptpad: Zero Knowledge, Edição Colaborativa em Tempo Real",
www/common/translations/messages.pt-br.json: "tos_title": "Termos de serviço doCryptpad",
www/common/translations/messages.pt-br.json: "topbar_whatIsCryptpad": "O que é CryptPad",
www/common/translations/messages.ro.json: "settings_autostoreHint": "<b>Automat</b> Toate documentele accesate sunt stocate în CryptDrive-ul dumneavoastră.<br><b>Manual (întreabă întotdeauna)</b> Dacă nu ai stocat încă un document, vei fi întrebat dacă dorești să îl stochezi în Cryptdrive-ul tău.<br><b>Manual (nu mai întreba)</b> Documentele nu sunt stocate automat în Cryptpad-ul tău. Opțiunea de a le stoca ulterior va fi ascunsă.",
www/common/translations/messages.ru.json: "topbar_whatIsCryptpad": "Что такое CryptPad",
www/common/translations/messages.zh.json: "footer_aboutUs": "關於 Cryptpad", for many more examples
3 years ago
console . log ( "CryptPad is customizable, see customize.dist/readme.md for details" ) ;
} ) ) ;
} ) . nThen ( function ( w ) {
httpServer . listen ( Env . httpPort , Env . httpAddress , function ( ) {
var host = Env . httpAddress ;
var hostName = ! host . indexOf ( ':' ) ? '[' + host + ']' : host ;
var port = Env . httpPort ;
var ps = port === 80 ? '' : ':' + port ;
var roughAddress = 'http://' + hostName + ps ;
var betterAddress = fancyURL ( Env . httpUnsafeOrigin ) ;
if ( betterAddress ) {
console . log ( 'Serving content for %s via %s.\n' , betterAddress , roughAddress ) ;
} else {
console . log ( 'Serving content via %s.\n' , roughAddress ) ;
}
if ( ! Env . admins . length ) {
console . log ( "Your instance is not correctly configured for safe use in production.\nSee %s for more information.\n" ,
fancyURL ( Env . httpUnsafeOrigin , '/checkup/' ) || 'https://your-domain.com/checkup/'
) ;
}
} ) ;
if ( Env . httpSafePort ) {
Http . createServer ( app ) . listen ( Env . httpSafePort , Env . httpAddress , w ( ) ) ;
}
} ) . nThen ( function ( ) {
var wsConfig = { server : httpServer } ;
// Initialize logging then start the API server
require ( "./lib/log" ) . create ( config , function ( _log ) {
Env . Log = _log ;
config . log = _log ;
if ( Env . shouldUpdateNode ) {
Env . Log . warn ( "NODEJS_OLD_VERSION" , {
message : ` The CryptPad development team recommends using at least NodeJS v16.14.2 ` ,
currentVersion : process . version ,
} ) ;
}
if ( Env . NODE _ENV !== 'production' ) {
Env . Log . warn ( "NODE_ENV" , ` If this server is running in a production context then it is recommended that you set NODE_ENV=production to prevent Expressjs from responding with stack traces when it catches an error. ` ) ;
}
if ( Env . OFFLINE _MODE ) { return ; }
if ( Env . websocketPath ) { return ; }
require ( "./lib/api" ) . create ( Env ) ;
} ) ;
} ) ;