Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/html/safehtmlformatter.js
2868 views
1
// Copyright 2016 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
16
goog.provide('goog.html.SafeHtmlFormatter');
17
18
goog.require('goog.asserts');
19
goog.require('goog.dom.tags');
20
goog.require('goog.html.SafeHtml');
21
goog.require('goog.string');
22
23
24
25
/**
26
* Formatter producing SafeHtml from a plain text format and HTML fragments.
27
*
28
* Example usage:
29
*
30
* var formatter = new goog.html.SafeHtmlFormatter();
31
* var safeHtml = formatter.format(
32
* formatter.startTag('b') +
33
* 'User input:' +
34
* formatter.endTag('b') +
35
* ' ' +
36
* formatter.text(userInput));
37
*
38
* The most common usage is with goog.getMsg:
39
*
40
* var MSG_USER_INPUT = goog.getMsg(
41
* '{$startLink}Learn more{$endLink} about {$userInput}', {
42
* 'startLink': formatter.startTag('a', {'href': url}),
43
* 'endLink': formatter.endTag('a'),
44
* 'userInput': formatter.text(userInput)
45
* });
46
* var safeHtml = formatter.format(MSG_USER_INPUT);
47
*
48
* The formatting string should be constant with all variables processed by
49
* formatter.text().
50
*
51
* @constructor
52
* @struct
53
* @final
54
*/
55
goog.html.SafeHtmlFormatter = function() {
56
/**
57
* Mapping from a marker to a replacement.
58
* @private {!Object<string, !goog.html.SafeHtmlFormatter.Replacement>}
59
*/
60
this.replacements_ = {};
61
62
/** @private {number} Number of stored replacements. */
63
this.replacementsCount_ = 0;
64
};
65
66
67
/**
68
* @typedef {?{
69
* startTag: (string|undefined),
70
* attributes: (string|undefined),
71
* endTag: (string|undefined),
72
* html: (string|undefined)
73
* }}
74
*/
75
goog.html.SafeHtmlFormatter.Replacement;
76
77
78
/**
79
* Formats a plain text string with markers holding HTML fragments to SafeHtml.
80
* @param {string} format Plain text format, will be HTML-escaped.
81
* @return {!goog.html.SafeHtml}
82
*/
83
goog.html.SafeHtmlFormatter.prototype.format = function(format) {
84
var openedTags = [];
85
var html = goog.string.htmlEscape(format).replace(
86
/\{SafeHtmlFormatter:\w+\}/g,
87
goog.bind(this.replaceFormattingString_, this, openedTags));
88
goog.asserts.assert(openedTags.length == 0,
89
'Expected no unclosed tags, got <' + openedTags.join('>, <') + '>.');
90
return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
91
html, null);
92
};
93
94
95
/**
96
* Replaces found formatting strings with saved tags.
97
* @param {!Array<string>} openedTags The tags opened so far, modified by this
98
* function.
99
* @param {string} match
100
* @return {string}
101
* @private
102
*/
103
goog.html.SafeHtmlFormatter.prototype.replaceFormattingString_ =
104
function(openedTags, match) {
105
var replacement = this.replacements_[match];
106
if (!replacement) {
107
// Someone included a string looking like our internal marker in the format.
108
return match;
109
}
110
var result = '';
111
if (replacement.startTag) {
112
result += '<' + replacement.startTag + replacement.attributes + '>';
113
if (goog.asserts.ENABLE_ASSERTS) {
114
if (!goog.dom.tags.isVoidTag(replacement.startTag.toLowerCase())) {
115
openedTags.push(replacement.startTag.toLowerCase());
116
}
117
}
118
}
119
if (replacement.html) {
120
result += replacement.html;
121
}
122
if (replacement.endTag) {
123
result += '</' + replacement.endTag + '>';
124
if (goog.asserts.ENABLE_ASSERTS) {
125
var lastTag = openedTags.pop();
126
goog.asserts.assert(lastTag == replacement.endTag.toLowerCase(),
127
'Expected </' + lastTag + '>, got </' + replacement.endTag + '>.');
128
}
129
}
130
return result;
131
};
132
133
134
/**
135
* Saves a start tag and returns its marker.
136
* @param {string} tagName
137
* @param {?Object<string, ?goog.html.SafeHtml.AttributeValue>=} opt_attributes
138
* Mapping from attribute names to their values. Only attribute names
139
* consisting of [a-zA-Z0-9-] are allowed. Value of null or undefined causes
140
* the attribute to be omitted.
141
* @return {string} Marker.
142
* @throws {Error} If invalid tag name, attribute name, or attribute value is
143
* provided. This function accepts the same tags and attributes as
144
* {@link goog.html.SafeHtml.create}.
145
*/
146
goog.html.SafeHtmlFormatter.prototype.startTag = function(
147
tagName, opt_attributes) {
148
goog.html.SafeHtml.verifyTagName(tagName);
149
return this.storeReplacement_({
150
startTag: tagName,
151
attributes: goog.html.SafeHtml.stringifyAttributes(tagName, opt_attributes)
152
});
153
};
154
155
156
/**
157
* Saves an end tag and returns its marker.
158
* @param {string} tagName
159
* @return {string} Marker.
160
* @throws {Error} If invalid tag name, attribute name, or attribute value is
161
* provided. This function accepts the same tags and attributes as
162
* {@link goog.html.SafeHtml.create}.
163
*/
164
goog.html.SafeHtmlFormatter.prototype.endTag = function(tagName) {
165
goog.html.SafeHtml.verifyTagName(tagName);
166
return this.storeReplacement_({endTag: tagName});
167
};
168
169
170
/**
171
* Escapes a text, saves it and returns its marker.
172
*
173
* Wrapping any user input to .text() prevents the attacker with access to
174
* the random number generator to duplicate tags used elsewhere in the format.
175
*
176
* @param {string} text
177
* @return {string} Marker.
178
*/
179
goog.html.SafeHtmlFormatter.prototype.text = function(text) {
180
return this.storeReplacement_({html: goog.string.htmlEscape(text)});
181
};
182
183
184
/**
185
* Saves SafeHtml and returns its marker.
186
* @param {!goog.html.SafeHtml} safeHtml
187
* @return {string} Marker.
188
*/
189
goog.html.SafeHtmlFormatter.prototype.safeHtml = function(safeHtml) {
190
return this.storeReplacement_({
191
html: goog.html.SafeHtml.unwrap(safeHtml)
192
});
193
};
194
195
196
/**
197
* Stores a replacement and returns its marker.
198
* @param {!goog.html.SafeHtmlFormatter.Replacement} replacement
199
* @return {string} Marker.
200
* @private
201
*/
202
goog.html.SafeHtmlFormatter.prototype.storeReplacement_ = function(
203
replacement) {
204
this.replacementsCount_++;
205
var marker = '{SafeHtmlFormatter:' + this.replacementsCount_ + '_' +
206
goog.string.getRandomString() + '}';
207
this.replacements_[marker] = replacement;
208
return marker;
209
};
210
211