From fafac3b4128a0993b0de1c6e8ca3062bf1ccc14e Mon Sep 17 00:00:00 2001 From: Roald de Vries Date: Thu, 8 Dec 2016 16:59:09 +0100 Subject: Revert "[#801] Merge branch 'signup'" This reverts commit d10f607a4d40587510b0dc31b31fe4750bf4a3a3, reversing changes made to c28abba2f5b1186c671ebef508d40ffaae6d5bc5. --- web-ui/app/js/helpers/browser.js | 36 +++++++ web-ui/app/js/helpers/contenttype.js | 184 ++++++++++++++++++++++++++++++++ web-ui/app/js/helpers/iterator.js | 60 +++++++++++ web-ui/app/js/helpers/monitored_ajax.js | 67 ++++++++++++ web-ui/app/js/helpers/sanitizer.js | 126 ++++++++++++++++++++++ web-ui/app/js/helpers/triggering.js | 29 +++++ web-ui/app/js/helpers/view_helper.js | 163 ++++++++++++++++++++++++++++ 7 files changed, 665 insertions(+) create mode 100644 web-ui/app/js/helpers/browser.js create mode 100644 web-ui/app/js/helpers/contenttype.js create mode 100644 web-ui/app/js/helpers/iterator.js create mode 100644 web-ui/app/js/helpers/monitored_ajax.js create mode 100644 web-ui/app/js/helpers/sanitizer.js create mode 100644 web-ui/app/js/helpers/triggering.js create mode 100644 web-ui/app/js/helpers/view_helper.js (limited to 'web-ui/app/js/helpers') diff --git a/web-ui/app/js/helpers/browser.js b/web-ui/app/js/helpers/browser.js new file mode 100644 index 00000000..dacf2263 --- /dev/null +++ b/web-ui/app/js/helpers/browser.js @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2014 ThoughtWorks, Inc. + * + * Pixelated 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, either version 3 of the License, or + * (at your option) any later version. + * + * Pixelated 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 Pixelated. If not, see . + */ + +define([], function () { + + 'use strict'; + + function redirect(url) { + window.location.replace(url); + } + + function getCookie(name) { + var value = '; ' + document.cookie; + var parts = value.split('; ' + name + '='); + if (parts.length === 2) { return parts.pop().split(';').shift(); } + } + + return { + redirect: redirect, + getCookie: getCookie + }; +}); diff --git a/web-ui/app/js/helpers/contenttype.js b/web-ui/app/js/helpers/contenttype.js new file mode 100644 index 00000000..a1e5361a --- /dev/null +++ b/web-ui/app/js/helpers/contenttype.js @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2014 ThoughtWorks, Inc. + * + * Pixelated 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, either version 3 of the License, or + * (at your option) any later version. + * + * Pixelated 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 Pixelated. If not, see . + */ + +/* jshint curly: false */ +define([], function () { + 'use strict'; + var exports = {}; + + // Licence: PUBLIC DOMAIN + // Author: Austin Wright + + function MediaType(s, p){ + this.type = ''; + this.params = {}; + var c, i, n; + if(typeof s==='string'){ + c = splitQuotedString(s); + this.type = c.shift(); + for(i=0; i=0){ + offset = [delim,quote].reduce(findNextChar, 1/0); + if(offset===1/0) break; + switch(str[offset]){ + case quote: + // Skip to end of quoted string + while(1){ + offset=str.indexOf(quote, offset+1); + if(offset<0) break; + if(str[offset-1]==='\\') continue; + break; + } + continue; + case delim: + res.push(str.substr(start, offset-start).trim()); + start = ++offset; + break; + } + } + res.push(str.substr(start).trim()); + return res; + } + exports.splitQuotedString = splitQuotedString; + + // Split a list of content types found in an Accept header + // Maybe use it like: splitContentTypes(request.headers.accept).map(parseMedia) + function splitContentTypes(str){ + return splitQuotedString(str, ','); + } + exports.splitContentTypes = splitContentTypes; + + function parseMedia(str){ + var o = new MediaType(str); + if(o.q===undefined) o.q=1; + return o; + } + exports.parseMedia = parseMedia; + + // Pick an ideal representation to send given a list of representations to choose from and the client-preferred list + function select(reps, accept){ + var cr = {q:0}; + var ca = {q:0}; + var cq = 0; + for(var i=0; i=0){ + if(aq*rq>cq){ + ca = a; + cr = r; + cq = ca.q*cr.q; + if(cq===1 && cr.type) return cr; + } + } + } + } + return cr.type&&cr; + } + exports.select = select; + + // Determine if one media type is a subset of another + // If a is a superset of b (b is smaller than a), return 1 + // If b is a superset of a, return -1 + // If they are the exact same, return 0 + // If they are disjoint, return null + function mediaCmp(a, b){ + if(a.type==='*/*' && b.type!=='*/*') return 1; + else if(a.type!=='*/*' && b.type==='*/*') return -1; + var ac = (a.type||'').split('/'); + var bc = (b.type||'').split('/'); + if(ac[0]==='*' && bc[0]!=='*') return 1; + if(ac[0]!=='*' && bc[0]==='*') return -1; + if(a.type!==b.type) return null; + var ap = a.params || {}; + var bp = b.params || {}; + var ak = Object.keys(ap); + var bk = Object.keys(bp); + if(ak.length < bk.length) return 1; + if(ak.length > bk.length) return -1; + var k = ak.concat(bk).sort(); + var dir = 0; + for(var n in ap){ + if(ap[n] && !bp[n]){ if(dir<0) return null; else dir=1; } + if(!ap[n] && bp[n]){ if(dir>0) return null; else dir=-1; } + } + return dir; + } + exports.mediaCmp = mediaCmp; + + return exports; +}); diff --git a/web-ui/app/js/helpers/iterator.js b/web-ui/app/js/helpers/iterator.js new file mode 100644 index 00000000..236c7a40 --- /dev/null +++ b/web-ui/app/js/helpers/iterator.js @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014 ThoughtWorks, Inc. + * + * Pixelated 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, either version 3 of the License, or + * (at your option) any later version. + * + * Pixelated 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 Pixelated. If not, see . + */ +define(function () { + 'use strict'; + + return Iterator; + + function Iterator(elems, startingIndex) { + + this.index = startingIndex || 0; + this.elems = elems; + + this.hasPrevious = function () { + return this.index !== 0; + }; + + this.hasNext = function () { + return this.index < this.elems.length - 1; + }; + + this.previous = function () { + return this.elems[--this.index]; + }; + + this.next = function () { + return this.elems[++this.index]; + }; + + this.current = function () { + return this.elems[this.index]; + }; + + this.hasElements = function () { + return this.elems.length > 0; + }; + + this.removeCurrent = function () { + var removed = this.current(), + toRemove = this.index; + + if(!this.hasNext()) { this.index--; } + this.elems.remove(toRemove); + return removed; + }; + } +}); diff --git a/web-ui/app/js/helpers/monitored_ajax.js b/web-ui/app/js/helpers/monitored_ajax.js new file mode 100644 index 00000000..bbf85c45 --- /dev/null +++ b/web-ui/app/js/helpers/monitored_ajax.js @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2014 ThoughtWorks, Inc. + * + * Pixelated 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, either version 3 of the License, or + * (at your option) any later version. + * + * Pixelated 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 Pixelated. If not, see . + */ + +define(['page/events', 'views/i18n', 'helpers/browser'], function (events, i18n, browser) { + + 'use strict'; + + var messages = { + timeout: 'error.timeout', + error: 'error.general', + parseerror: 'error.parse' + }; + + function monitoredAjax(on, url, config) { + config = config || {}; + config.timeout = 60 * 1000; + + var originalBeforeSend = config.beforeSend; + config.beforeSend = function () { + if (originalBeforeSend) { + originalBeforeSend(); + } + }; + + config.headers = {'X-XSRF-TOKEN': browser.getCookie('XSRF-TOKEN')}; + + var originalComplete = config.complete; + config.complete = function () { + if (originalComplete) { + originalComplete(); + } + }; + + return $.ajax(url, config).fail(function (xmlhttprequest, textstatus, message) { + if (!config.skipErrorMessage) { + var msg = (xmlhttprequest.responseJSON && xmlhttprequest.responseJSON.message) || + messages[textstatus] || messages.error; + on.trigger(document, events.ui.userAlerts.displayMessage, {message: i18n.t(msg), class: 'error'}); + } + + if (xmlhttprequest.status === 302) { + var redirectUrl = xmlhttprequest.getResponseHeader('Location'); + browser.redirect(redirectUrl); + } else if (xmlhttprequest.status === 401) { + browser.redirect('/'); + } + + }.bind(this)); + } + + return monitoredAjax; + +}); diff --git a/web-ui/app/js/helpers/sanitizer.js b/web-ui/app/js/helpers/sanitizer.js new file mode 100644 index 00000000..443e8602 --- /dev/null +++ b/web-ui/app/js/helpers/sanitizer.js @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2016 ThoughtWorks, Inc. + * + * Pixelated 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, either version 3 of the License, or + * (at your option) any later version. + * + * Pixelated 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 Pixelated. If not, see . + */ + +define(['DOMPurify', 'he'], function (DOMPurify, he) { + 'use strict'; + + /** + * Sanitizes a mail body to safe-to-display HTML + */ + var sanitizer = {}; + + sanitizer.whitelist = [{ + // highlight tag open + pre: '<em class="search-highlight">', + post: '' + }, { + // highlight tag close + pre: '</em>', + post: '' + }]; + + /** + * Adds html line breaks to a plaintext with line breaks (incl carriage return) + * + * @param {string} textPlainBody Plaintext input + * @returns {string} Plaintext with HTML line breals (
) + */ + sanitizer.addLineBreaks = function (textPlainBody) { + return textPlainBody.replace(/(\r)?\n/g, '
').replace(/( )? /g, '
'); + }; + + /** + * Runs a given dirty body through DOMPurify, thereby removing + * potentially hazardous XSS attacks. Please be advised that this + * will not act as a privacy leak prevention. Contained contents + * will still point to remote sources. + * + * For future reference: Running DOMPurify with these parameters + * can help mitigate some of the most widely used privacy leaks. + * FORBID_TAGS: ['style', 'svg', 'audio', 'video', 'math'], + * FORBID_ATTR: ['src'] + * + * @param {string} dirtyBody The unsanitized string + * @return {string} Safe-to-display HTML string + */ + sanitizer.purifyHtml = function (dirtyBody) { + return DOMPurify.sanitize(dirtyBody, { + SAFE_FOR_JQUERY: true, + SAFE_FOR_TEMPLATES: true + }); + }; + + /** + * Runs a given dirty body through he, thereby encoding everything + * as HTML entities. + * + * @param {string} dirtyBody The unsanitized string + * @return {string} Safe-to-display HTML string + */ + sanitizer.purifyText = function (dirtyBody) { + var escapedBody = he.encode(dirtyBody, { + encodeEverything: true + }); + + this.whitelist.forEach(function(entry) { + while (escapedBody.indexOf(entry.pre) > -1) { + escapedBody = escapedBody.replace(entry.pre, entry.post); + } + }); + + return escapedBody; + }; + + /** + * Calls #purify and #addLineBreaks to turn untrusted mail body content + * into safe-to-display HTML. + * + * NB: HTML content is preferred to plaintext content. + * + * @param {object} mail Pixelated Mail Object + * @return {string} Safe-to-display HTML string + */ + sanitizer.sanitize = function (mail) { + var body; + + if (mail.htmlBody) { + body = this.purifyHtml(mail.htmlBody); + } else { + body = this.purifyText(mail.textPlainBody); + body = this.addLineBreaks(body); + } + + return body; + }; + + /** + * Add hooks to DOMPurify for opening links in new windows + */ + DOMPurify.addHook('afterSanitizeAttributes', function (node) { + // set all elements owning target to target=_blank + if ('target' in node) { + node.setAttribute('target', '_blank'); + } + + // set non-HTML/MathML links to xlink:show=new + if (!node.hasAttribute('target') && (node.hasAttribute('xlink:href') || node.hasAttribute('href'))) { + node.setAttribute('xlink:show', 'new'); + } + }); + + return sanitizer; +}); diff --git a/web-ui/app/js/helpers/triggering.js b/web-ui/app/js/helpers/triggering.js new file mode 100644 index 00000000..d26d9fc6 --- /dev/null +++ b/web-ui/app/js/helpers/triggering.js @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2014 ThoughtWorks, Inc. + * + * Pixelated 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, either version 3 of the License, or + * (at your option) any later version. + * + * Pixelated 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 Pixelated. If not, see . + */ +define([], function() { + 'use strict'; + + return function(that, event, data, on) { + return function() { + if(on) { + that.trigger(on, event, data || {}); + } else { + that.trigger(event, data || {}); + } + }; + }; +}); diff --git a/web-ui/app/js/helpers/view_helper.js b/web-ui/app/js/helpers/view_helper.js new file mode 100644 index 00000000..ed9e0559 --- /dev/null +++ b/web-ui/app/js/helpers/view_helper.js @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2014 ThoughtWorks, Inc. + * + * Pixelated 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, either version 3 of the License, or + * (at your option) any later version. + * + * Pixelated 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 Pixelated. If not, see . + */ +define( + [ + 'helpers/contenttype', + 'views/i18n', + 'quoted-printable/quoted-printable', + 'utf8/utf8', + 'helpers/sanitizer' + ], + function(contentType, i18n, quotedPrintable, utf8, sanitizer) { + 'use strict'; + + function formatStatusClasses(ss) { + return _.map(ss, function(s) { + return 'status-' + s; + }).join(' '); + } + + function formatMailBody(mail) { + return sanitizer.sanitize(mail); + } + + function moveCaretToEnd(el) { + if (typeof el.selectionStart === 'number') { + el.selectionStart = el.selectionEnd = el.value.length; + } else if (typeof el.createTextRange !== 'undefined') { + el.focus(); + var range = el.createTextRange(); + range.collapse(false); + range.select(); + } + } + + function fixedSizeNumber(num, size) { + var res = num.toString(); + while(res.length < size) { + res = '0' + res; + } + return res; + } + + function createTodayDate() { + var today = new Date(); + today.setHours(0); + today.setMinutes(0); + today.setSeconds(0); + return today; + } + + function moveCaretToEndOfText() { + var self = this; + + moveCaretToEnd(self); + window.setTimeout(function() { + moveCaretToEnd(self); + }, 1); + } + + function prependFrom(mail) { + return i18n.t( + 'reply-author-line', {'date': new Date(mail.header.date).toString(), 'from': mail.header.from} + ); + } + + function quoteMail(mail) { + return '\n\n' + prependFrom(mail) + mail.textPlainBody.replace(/^/mg, '> '); + } + + function formatDate(dateString) { + var date = new Date(dateString); + var today = createTodayDate(); + if (date.getTime() > today.getTime()) { + return fixedSizeNumber(date.getHours(), 2) + ':' + fixedSizeNumber(date.getMinutes(), 2); + } else { + return '' + date.getFullYear() + '-' + fixedSizeNumber(date.getMonth() + 1, 2) + '-' + fixedSizeNumber(date.getDate(), 2); + } + } + + function formatSize(bytes) { + var e = Math.floor(Math.log(bytes) / Math.log(1024)); + return (bytes / Math.pow(1024, e)).toFixed(2) + ' ' + ' KMGTP'.charAt(e) + 'b'; + } + + + function formatFingerPrint(fingerprint) { + fingerprint = fingerprint || ''; + return fingerprint.replace(/(.{4})/g, '$1 ').trim(); + } + + function getSinceDate(sinceDate){ + var commitDate = new Date(sinceDate); + var number = Date.now(); + var millisecondsSince = number - commitDate; + + var SECONDS = 1000, + MIN = 60 * SECONDS, + HOUR = MIN * 60, + DAY = HOUR * 24, + WEEK = DAY * 7, + MONTH = WEEK * 4, + YEAR = DAY * 365; + + var years = Math.floor(millisecondsSince / YEAR); + if (years >= 1){ + return years + ' year(s)'; + } + + var months = Math.floor(millisecondsSince / MONTH); + if (months >= 1) { + return months + ' month(s)'; + } + + var weeks = Math.floor(millisecondsSince / WEEK); + if (weeks >= 1) { + return weeks + ' week(s)'; + } + + var days = Math.floor(millisecondsSince / DAY); + if (days >= 1) { + return days + ' day(s)'; + } + + var hours = Math.floor(millisecondsSince / HOUR); + if (hours >= 1) { + return hours + ' hour(s)'; + } + + var minutes = Math.floor(millisecondsSince / MIN); + return minutes + ' minute(s)'; + } + + Handlebars.registerHelper('formatDate', formatDate); + Handlebars.registerHelper('formatSize', formatSize); + Handlebars.registerHelper('formatStatusClasses', formatStatusClasses); + Handlebars.registerHelper('formatFingerPrint', formatFingerPrint); + Handlebars.registerHelper('sinceDate', getSinceDate); + + return { + formatStatusClasses: formatStatusClasses, + formatSize: formatSize, + formatMailBody: formatMailBody, + formatFingerPrint: formatFingerPrint, + moveCaretToEndOfText: moveCaretToEndOfText, + quoteMail: quoteMail, + sinceDate: getSinceDate, + i18n: i18n + }; +}); -- cgit v1.2.3