diff --git a/lib/defaults.js b/lib/defaults.js
index 635e155be..c4cb507cb 100644
--- a/lib/defaults.js
+++ b/lib/defaults.js
@@ -1,6 +1,6 @@
var Default = module.exports;
-Default.commonCSP = function (domain) {
+Default.commonCSP = function (domain, sandbox) {
domain = ' ' + domain;
// Content-Security-Policy
@@ -23,7 +23,7 @@ Default.commonCSP = function (domain) {
if you are deploying to production, you'll probably want to remove
the ws://* directive, and change '*' to your domain
*/
- "connect-src 'self' ws: wss: blob:" + domain,
+ "connect-src 'self' ws: wss: blob: " + domain + (sandbox && sandbox !== domain? ' ' + sandbox: ''),
// data: is used by codemirror
"img-src 'self' data: blob:" + domain,
@@ -35,12 +35,12 @@ Default.commonCSP = function (domain) {
];
};
-Default.contentSecurity = function (domain) {
- return (Default.commonCSP(domain).join('; ') + "script-src 'self' resource: " + domain).replace(/\s+/g, ' ');
+Default.contentSecurity = function (domain, sandbox) {
+ return (Default.commonCSP(domain, sandbox).join('; ') + "script-src 'self' resource: " + domain).replace(/\s+/g, ' ');
};
-Default.padContentSecurity = function (domain) {
- return (Default.commonCSP(domain).join('; ') + "script-src 'self' 'unsafe-eval' 'unsafe-inline' resource: " + domain).replace(/\s+/g, ' ');
+Default.padContentSecurity = function (domain, sandbox) {
+ return (Default.commonCSP(domain, sandbox).join('; ') + "script-src 'self' 'unsafe-eval' 'unsafe-inline' resource: " + domain).replace(/\s+/g, ' ');
};
Default.httpHeaders = function () {
diff --git a/lib/env.js b/lib/env.js
index 5ad0bbdac..9970bc4f9 100644
--- a/lib/env.js
+++ b/lib/env.js
@@ -17,6 +17,10 @@ var canonicalizeOrigin = function (s) {
return (s || '').trim().replace(/\/+$/, '');
};
+var isValidPort = function (p) {
+ return typeof(p) === 'number' && p < 65535;
+};
+
module.exports.create = function (config) {
const Env = {
version: Package.version,
@@ -25,6 +29,9 @@ module.exports.create = function (config) {
httpUnsafeOrigin: canonicalizeOrigin(config.httpUnsafeOrigin),
httpSafeOrigin: canonicalizeOrigin(config.httpSafeOrigin),
removeDonateButton: config.removeDonateButton,
+ httpPort: isValidPort(config.httpPort)? config.httpPort: 3000,
+ httpAddress: typeof(config.httpAddress) === 'string'? config.httpAddress: '127.0.0.1',
+ websocketPath: config.externalWebsocketURL,
OFFLINE_MODE: false,
FRESH_KEY: '',
diff --git a/server.js b/server.js
index 07cae4b23..0b95119f9 100644
--- a/server.js
+++ b/server.js
@@ -23,28 +23,24 @@ var fancyURL = function (domain, path) {
return false;
};
+var deriveSandboxOrigin = function (unsafe, port) {
+ var url = new URL(unsafe);
+ url.port = port;
+ return url.origin;
+};
+
(function () {
// you absolutely must provide an 'httpUnsafeOrigin' (a truthy string)
if (!Env.httpUnsafeOrigin || typeof(Env.httpUnsafeOrigin) !== 'string') {
throw new Error("No 'httpUnsafeOrigin' provided");
}
- // fall back to listening on a local address
- // if httpAddress is not a string
- if (typeof(config.httpAddress) !== 'string') {
- config.httpAddress = '127.0.0.1';
- }
-
- // listen on port 3000 if a valid port number was not provided
- if (typeof(config.httpPort) !== 'number' || config.httpPort > 65535) {
- config.httpPort = 3000;
- }
-
if (typeof(Env.httpSafeOrigin) !== 'string') {
Env.NO_SANDBOX = true;
- if (typeof(config.httpSafePort) !== 'number') {
- config.httpSafePort = config.httpPort + 1;
+ if (typeof(Env.httpSafePort) !== 'number') {
+ Env.httpSafePort = Env.httpPort + 1;
}
+ Env.httpSafeOrigin = deriveSandboxOrigin(Env.httpUnsafeOrigin, Env.httpSafePort);
}
}());
@@ -77,7 +73,7 @@ var setHeaders = (function () {
}
} else {
// use the default CSP headers constructed with your domain
- headers['Content-Security-Policy'] = Default.contentSecurity(Env.httpUnsafeOrigin);
+ headers['Content-Security-Policy'] = Default.contentSecurity(Env.httpUnsafeOrigin, Env.httpSafeOrigin);
}
const padHeaders = Util.clone(headers);
@@ -239,14 +235,14 @@ var makeRouteCache = function (template, cacheName) {
var serveConfig = makeRouteCache(function (host) {
return [
'define(function(){',
- 'var obj = ' + JSON.stringify({
+ 'return ' + JSON.stringify({
requireConf: {
waitSeconds: 600,
urlArgs: 'ver=' + Env.version + cacheString(),
},
removeDonateButton: (Env.removeDonateButton === true),
allowSubscriptions: (Env.allowSubscriptions === true),
- websocketPath: config.externalWebsocketURL,
+ websocketPath: Env.websocketPath,
httpUnsafeOrigin: Env.httpUnsafeOrigin,
adminEmail: Env.adminEmail,
adminKeys: Env.admins,
@@ -256,16 +252,8 @@ var serveConfig = makeRouteCache(function (host) {
maxUploadSize: Env.maxUploadSize,
premiumUploadSize: Env.premiumUploadSize,
restrictRegistration: Env.restrictRegistration,
+ httpSafeOrigin: Env.httpSafeOrigin,
}, null, '\t'),
- 'obj.httpSafeOrigin = ' + (function () {
- if (Env.httpSafeOrigin) { return '"' + Env.httpSafeOrigin + '"'; }
- if (config.httpSafePort) {
- return "(function () { return window.location.origin.replace(/\:[0-9]+$/, ':" +
- config.httpSafePort + "'); }())";
- }
- return 'window.location.origin';
- }()),
- 'return obj',
'});'
].join(';\n')
}, 'configCache');
@@ -314,11 +302,11 @@ nThen(function (w) {
console.log("CryptPad is customizable, see customize.dist/readme.md for details");
}));
}).nThen(function (w) {
- httpServer.listen(config.httpPort,config.httpAddress,function(){
- var host = config.httpAddress;
+ httpServer.listen(Env.httpPort, Env.httpAddress, function(){
+ var host = Env.httpAddress;
var hostName = !host.indexOf(':') ? '[' + host + ']' : host;
- var port = config.httpPort;
+ var port = Env.httpPort;
var ps = port === 80? '': ':' + port;
var roughAddress = 'http://' + hostName + ps;
@@ -336,8 +324,8 @@ nThen(function (w) {
}
});
- if (config.httpSafePort) {
- Http.createServer(app).listen(config.httpSafePort, config.httpAddress, w());
+ if (Env.httpSafePort) {
+ Http.createServer(app).listen(Env.httpSafePort, Env.httpAddress, w());
}
}).nThen(function () {
var wsConfig = { server: httpServer };
@@ -348,7 +336,7 @@ nThen(function (w) {
config.log = _log;
if (Env.OFFLINE_MODE) { return; }
- if (config.externalWebsocketURL) { return; }
+ if (Env.websocketPath) { return; }
require("./lib/api").create(Env);
});
diff --git a/www/checkup/main.js b/www/checkup/main.js
index 3118be749..d3841b8bd 100644
--- a/www/checkup/main.js
+++ b/www/checkup/main.js
@@ -830,6 +830,33 @@ define([
});
});
+ assert(function (cb, msg) { // XXX
+ // check that the sandbox domain is included in connect-src
+ msg.appendChild(h('span', [
+ "This instance's ",
+ code("Content-Security-Policy"),
+ " headers do not include the sandboxed domain (",
+ code(trimmedSafe),
+ ") in ",
+ code("connect-src"),
+ ". This can cause problems with fonts when printing office documents.",
+ " This is probably due to an incorrectly configured reverse proxy.",
+ " See the provided NGINX configuration file for an example of how to set this header correctly.",
+ ]));
+
+ $.ajax(cacheBuster('/'), {
+ dataType: 'text',
+ complete: function (xhr) {
+ var CSP = parseCSP(xhr.getResponseHeader('content-security-policy'));
+ var connect = (CSP && CSP['connect-src']) || "";
+ if (connect.includes(trimmedSafe)) {
+ return void cb(true);
+ }
+ cb(CSP);
+ },
+ });
+ });
+
/*
assert(function (cb, msg) {
setWarningClass(msg);
diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js
index 2709e87d3..b56dee169 100644
--- a/www/common/drive-ui.js
+++ b/www/common/drive-ui.js
@@ -1303,6 +1303,14 @@ define([
if (!metadata || !Util.isPlainTextFile(metadata.fileType, metadata.title)) {
hide.push('openincode');
}
+
+ if (metadata && /\/(doc|presentation|sheet)\//.test(metadata.href)) {
+ console.error(metadata); // XXX
+ hide.push('openinsheet');
+ hide.push('openindoc');
+ hide.push('openinpresentation');
+ }
+
if (!metadata || !Util.isSpreadsheet(metadata.fileType, metadata.title)
|| !priv.supportsWasm) {
hide.push('openinsheet');
diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json
index 30bd5a217..317f9dad2 100644
--- a/www/common/translations/messages.de.json
+++ b/www/common/translations/messages.de.json
@@ -5,7 +5,7 @@
"code": "Code",
"poll": "Umfrage",
"kanban": "Kanban",
- "slide": "Präsentation",
+ "slide": "Markdown-Folien",
"drive": "CryptDrive",
"whiteboard": "Whiteboard",
"file": "Datei",
@@ -383,7 +383,7 @@
"settings_importConfirm": "Bist du sicher, dass du die kürzlich besuchten Dokumente in das CryptDrive deines Accounts importieren möchtest?",
"settings_importDone": "Import abgeschlossen",
"settings_autostoreTitle": "Speichern von Pads im CryptDrive",
- "settings_autostoreHint": "Automatisch: Alle Pads werden in deinem CryptDrive gespeichert.
Manuell (immer nachfragen): Wenn du ein Pad noch nicht gespeichert hast, wirst du gefragt, ob du es im CryptDrive speichern willst.
Manuell (nie nachfragen): Pads werden nicht automatisch im CryptDrive gespeichert. Die Option zum Speichern wird versteckt.",
+ "settings_autostoreHint": "Automatisch: Alle Dokumente werden in deinem CryptDrive gespeichert.
Manuell (immer nachfragen): Wenn du ein Dokument noch nicht gespeichert hast, wirst du gefragt, ob du es im CryptDrive speichern willst.
Manuell (nie nachfragen): Dokumente werden nicht automatisch im CryptDrive gespeichert. Die Option zum Speichern wird nicht mehr angezeigt.",
"settings_autostoreYes": "Automatisch",
"settings_autostoreNo": "Manuell (nie nachfragen)",
"settings_autostoreMaybe": "Manuell (immer nachfragen)",
@@ -567,11 +567,11 @@
"password_submit": "Abschicken",
"properties_addPassword": "Passwort hinzufügen",
"properties_changePassword": "Passwort ändern",
- "properties_confirmNew": "Bist du sicher? Das Hinzufügen eines Passworts wird die URL dieses Pads ändern und den Verlauf löschen. Benutzer ohne das Passwort werden den Zugang zu diesem Pad verlieren",
- "properties_confirmChange": "Bist du sicher? Das Ändern des Passworts wird den Verlauf löschen. Benutzer ohne das neue Passwort werden den Zugang zu diesem Pad verlieren",
+ "properties_confirmNew": "Bist du sicher? Das Hinzufügen eines Passworts wird die URL dieses Dokuments ändern und den Verlauf löschen. Benutzer ohne das Passwort werden den Zugang zu diesem Dokument verlieren",
+ "properties_confirmChange": "Bist du sicher? Das Ändern des Passworts wird den Verlauf löschen. Benutzer ohne das neue Passwort werden den Zugang zu diesem Dokument verlieren",
"properties_passwordSame": "Das neue Passwort muss sich von dem alten Passwort unterscheiden.",
"properties_passwordError": "Beim Versuch das Passwort zu ändern ist ein Fehler aufgetreten. Bitte versuche es nochmal.",
- "properties_passwordWarning": "Das Passwort wurde erfolgreich geändert, aber dein CryptDrive konnte nicht aktualisiert werden. Du musst möglicherweise die alte Version des Pads manuell entfernen.
Klicke auf OK, um die Seite neu zu laden und die Zugriffsrechte zu aktualisieren.",
+ "properties_passwordWarning": "Das Passwort wurde erfolgreich geändert, aber dein CryptDrive konnte nicht aktualisiert werden. Du musst möglicherweise die alte Version des Dokuments manuell entfernen.
Klicke auf OK, um die Seite neu zu laden und die Zugriffsrechte zu aktualisieren.",
"properties_passwordSuccess": "Das Passwort wurde erfolgreich geändert.
Klicke auf OK, um die Seite neu zu laden und die Zugriffsrechte zu aktualisieren.",
"properties_changePasswordButton": "Absenden",
"share_linkCategory": "Link",
@@ -584,8 +584,8 @@
"share_linkCopy": "Link kopieren",
"share_embedCategory": "Einbetten",
"share_mediatagCopy": "Media-Tag in die Zwischenablage kopieren",
- "sharedFolders_forget": "Dieses Pad ist nur in einem geteilten Ordner gespeichert. Du kannst es nicht in den Papierkorb verschieben. Aber du kannst es in deinem CryptDrive löschen.",
- "sharedFolders_duplicate": "Einige zu verschiebende Pads waren schon im Zielordner geteilt.",
+ "sharedFolders_forget": "Dieses Dokument ist nur in einem geteilten Ordner gespeichert. Du kannst es nicht in den Papierkorb verschieben. Aber du kannst es in deinem CryptDrive löschen.",
+ "sharedFolders_duplicate": "Einige zu verschiebende Dokumente waren schon im Zielordner geteilt.",
"sharedFolders_create": "Erstelle einen geteilten Ordner",
"sharedFolders_create_name": "Neuer Ordner",
"sharedFolders_create_owned": "Eigener Ordner",
@@ -595,13 +595,13 @@
"autostore_sf": "Dieser Ordner",
"autostore_pad": "Dieses Dokument",
"autostore_notstored": "{0} ist nicht in deinem CryptDrive. Möchtest du es jetzt dort speichern?",
- "autostore_settings": "Du kannst die automatische Speicherung in deinen Einstellungen aktivieren!",
+ "autostore_settings": "Du kannst die automatische Speicherung von Dokumenten in deinen Einstellungen aktivieren.",
"autostore_store": "Speichern",
"autostore_hide": "Nicht speichern",
- "autostore_error": "Unerwarteter Fehler: Wir konnten das Pad nicht speichern, bitte versuche es nochmal.",
- "autostore_saved": "Das Pad wurde erfolgreich in deinem CryptDrive gespeichert!",
+ "autostore_error": "Unerwarteter Fehler: Wir konnten das Dokument nicht speichern, bitte versuche es nochmal.",
+ "autostore_saved": "Das Dokument wurde erfolgreich in deinem CryptDrive gespeichert!",
"autostore_forceSave": "Speichere die Datei in deinem CryptDrive",
- "autostore_notAvailable": "Du musst dieses Pad in deinem CryptDrive speichern, bevor du diese Funktion benutzen kannst.",
+ "autostore_notAvailable": "Du musst dieses Dokument in deinem CryptDrive speichern, bevor du diese Funktion benutzen kannst.",
"crowdfunding_button": "Unterstütze CryptPad",
"crowdfunding_popup_text": "