summaryrefslogtreecommitdiff
path: root/web-ui/app/js/helpers/sanitizer.js
blob: 443e8602fbcc2175efdc072750194d12d750cf71 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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 <http://www.gnu.org/licenses/>.
 */

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: '&#x3C;&#x65;&#x6D;&#x20;&#x63;&#x6C;&#x61;&#x73;&#x73;&#x3D;&#x22;&#x73;&#x65;&#x61;&#x72;&#x63;&#x68;&#x2D;&#x68;&#x69;&#x67;&#x68;&#x6C;&#x69;&#x67;&#x68;&#x74;&#x22;&#x3E;',
    post: '<em class="search-highlight">'
  }, {
    // highlight tag close
    pre: '&#x3C;&#x2F;&#x65;&#x6D;&#x3E;',
    post: '</em>'
  }];

  /**
   * 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 (<br/>)
   */
  sanitizer.addLineBreaks = function (textPlainBody) {
    return textPlainBody.replace(/(\r)?\n/g, '<br/>').replace(/(&#xD;)?&#xA;/g, '<br/>');
  };

  /**
   * 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;
});