forked from InternationalColorConsortium/iccDEV
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsanitize.js
More file actions
145 lines (131 loc) · 4.4 KB
/
Copy pathsanitize.js
File metadata and controls
145 lines (131 loc) · 4.4 KB
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
* @file sanitize.js
*
* Client-side sanitization primitives for iccDEV IIS ISAPI pages.
*
* Mirrors the server-side IccIsapiSanitize API surface on the client.
* Loaded before site.js; provides the global `iccSanitize` object.
*
* Defenses:
* - HTML entity encoding (XSS via innerHTML — CWE-79)
* - URI scheme/fragment guard (DOM-XSS via location.hash — CWE-79)
* - Control-char stripping (log spoofing, header injection)
* - Safe DOM text helper (.textContent enforcement)
*
* Reference DOM-XSS vectors this prevents:
* document.location.replace(document.location.hash.split("#")[1])
* element.innerHTML = new URLSearchParams(location.search).get("q")
* ?q=<a"'\x0A`= +%20>;...hostname?url=https://attacker:8889
*
* Copyright (c) International Color Consortium. BSD 3-Clause.
*/
"use strict";
const iccSanitize = Object.freeze({
/**
* Escape the 5 HTML-sensitive characters.
* Mirrors HtmlEscape() in IccIsapiSanitize.cpp.
*/
htmlEscape: function htmlEscape(str) {
if (typeof str !== "string") {
return "";
}
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
},
/**
* Strip C0 control characters (except TAB \x09 and LF \x0A) and DEL \x7F.
* Mirrors the C0/DEL stripping in HtmlEscape() server-side.
*/
stripControl: function stripControl(str) {
if (typeof str !== "string") {
return "";
}
// eslint-disable-next-line no-control-regex
return str.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
},
/**
* Sanitize a URI: strip fragment, reject dangerous schemes.
* Mirrors SanitizeUri() in IccIsapiSanitize.cpp.
*
* Only allows relative paths (./ ../ /), http:, and https:.
* Blocks javascript:, data:, vbscript:, and any other scheme.
*
* Also strips null bytes, newlines, and tabs that confuse URL parsers.
* These characters let attackers bypass naive scheme checks:
* "java\tscript:alert(1)" → some parsers see "javascript:alert(1)"
* "java\x00script:alert(1)" → C-string truncation to "java"
*/
sanitizeUri: function sanitizeUri(uri) {
if (typeof uri !== "string" || uri.length === 0) {
return "";
}
// Strip parser-confusing characters: null, newline, carriage return, tab
var clean = uri.replace(/[\0\n\r\t]/g, "");
// Strip fragment (#...) — prevents DOM-XSS via location.hash reflection
var hashIdx = clean.indexOf("#");
if (hashIdx !== -1) {
clean = clean.substring(0, hashIdx);
}
// Check for scheme prefix (colon before first slash)
var colonIdx = clean.indexOf(":");
if (colonIdx !== -1) {
var isRelative = clean[0] === "." || clean[0] === "/";
var lower = clean.toLowerCase();
var isHttp = lower.indexOf("http://") === 0;
var isHttps = lower.indexOf("https://") === 0;
if (!isRelative && !isHttp && !isHttps) {
return "";
}
}
return clean;
},
/**
* Safely set text content of a DOM element.
* Never uses innerHTML. Strips control characters first.
* Use this instead of direct .textContent assignment when the
* source is untrusted (query params, server responses, user input).
*/
safeText: function safeText(element, text) {
if (!element) {
return;
}
element.textContent = iccSanitize.stripControl(
typeof text === "string" ? text : String(text)
);
},
/**
* Extract a query parameter from the current URL safely.
* Returns the decoded value or empty string if not found.
* Strips control characters from the result.
*/
getQueryParam: function getQueryParam(name) {
try {
var params = new URLSearchParams(window.location.search);
var value = params.get(name);
return value ? iccSanitize.stripControl(value) : "";
} catch (e) {
return "";
}
},
/**
* Build a safe relative URL for fetch/navigation.
* Rejects absolute URLs with non-http(s) schemes and strips fragments.
*/
safeRelativeUrl: function safeRelativeUrl(path, params) {
var clean = iccSanitize.sanitizeUri(path);
if (!clean) {
return "";
}
if (params && typeof params === "object") {
var search = new URLSearchParams(params).toString();
if (search) {
clean += (clean.indexOf("?") === -1 ? "?" : "&") + search;
}
}
return clean;
}
});