├── .gitignore
├── src
├── options.css
├── icon16.png
├── icon48.png
├── icon128.png
├── options.html
├── manifest.json
├── options.js
└── background.js
├── Makefile
├── README.md
├── LICENSE
└── icon.svg
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 |
--------------------------------------------------------------------------------
/src/options.css:
--------------------------------------------------------------------------------
1 | #config {
2 | width: 40em;
3 | height: 40em;
4 | }
5 |
--------------------------------------------------------------------------------
/src/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rufflewind/chrome_cspmod/HEAD/src/icon16.png
--------------------------------------------------------------------------------
/src/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rufflewind/chrome_cspmod/HEAD/src/icon48.png
--------------------------------------------------------------------------------
/src/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rufflewind/chrome_cspmod/HEAD/src/icon128.png
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | mkdir -p dist
3 | rm -f dist/dist.zip
4 | zip dist/dist.zip LICENSE
5 | cd src && zip -r ../dist/dist.zip *
6 |
7 | clean:
8 | rm -f dist/dist.zip
9 | rmdir 2>/dev/null dist || true
10 |
--------------------------------------------------------------------------------
/src/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Options
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "version": "1.2.0",
4 | "name": "Content Security Policy Override",
5 | "description": "Modify the Content Security Policy of web pages.",
6 | "icons": {
7 | "16": "icon16.png",
8 | "48": "icon48.png",
9 | "128": "icon128.png"
10 | },
11 | "permissions": [
12 | "storage",
13 | "webRequest",
14 | "webRequestBlocking",
15 | "*://*/"
16 | ],
17 | "options_ui": {
18 | "page": "options.html",
19 | "chrome_style": true
20 | },
21 | "background": {
22 | "scripts": ["background.js"],
23 | "persistent": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Content Security Policy Override
2 | ================================
3 |
4 | This is a simple extension that allows the user to modify the Content Security
5 | Policy (CSP) of web pages.
6 |
7 | Warning: improper use of this extension can diminish the security of your
8 | browser. Do not use unless you really know what you’re doing.
9 |
10 | Installation
11 | ---------------------
12 |
13 | You can install directly from the [Google Chrome webstore][1].
14 |
15 | Usage
16 | -----
17 |
18 | To edit the configuration, go to `chrome://extensions` and click *Options*
19 | under *Content Security Policy Override*.
20 |
21 | The text area in the *Options* automatically saves as you edit.
22 |
23 | [1]: https://chrome.google.com/webstore/detail/lhieoncdgamiiogcllfmboilhgoknmpi
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | License of this extension
2 | =========================
3 |
4 | Copyright (c) 2014-2015 Phil Ruffwind
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/src/options.js:
--------------------------------------------------------------------------------
1 | function restore() {
2 | chrome.runtime.sendMessage({
3 | action: "RESTORE_CONFIG"
4 | }, function(response) {
5 | document.getElementById("config").value = response;
6 | });
7 | }
8 |
9 | function save() {
10 | chrome.runtime.sendMessage({
11 | action: "SAVE_CONFIG",
12 | config: document.getElementById("config").value
13 | }, function(response) {
14 | var color;
15 | if (response === "SUCCESS") {
16 | color = "";
17 | } else {
18 | color = "#ffbbbb";
19 | }
20 | document.getElementById("config").style.backgroundColor = color;
21 | });
22 | }
23 |
24 | function throttle(func, delay) {
25 | var timeoutID = null;
26 | function wrappedFunc() {
27 | timeoutID = null;
28 | func();
29 | }
30 | return function() {
31 | if (timeoutID !== null) {
32 | window.clearTimeout(timeoutID);
33 | }
34 | timeoutID = window.setTimeout(wrappedFunc, delay);
35 | };
36 | }
37 |
38 | var throttledSave = throttle(save, 250);
39 | document.addEventListener("DOMContentLoaded", restore);
40 | document.getElementById("config").addEventListener("input", throttledSave);
41 |
--------------------------------------------------------------------------------
/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
123 |
--------------------------------------------------------------------------------
/src/background.js:
--------------------------------------------------------------------------------
1 | function dropCommentsAndWhitespace(s) {
2 | var r = "";
3 | var lines = s.match(/[^\r\n]+/g) || [];
4 | lines.forEach(function(line) {
5 | if (line.match(/^\s*#/) !== null ||
6 | line.match(/^\s*$/) !== null) {
7 | return;
8 | }
9 | r += line + "\n";
10 | });
11 | return r;
12 | }
13 |
14 | function parseRules(config) {
15 | config = dropCommentsAndWhitespace(config);
16 | if (config === "") {
17 | return [];
18 | }
19 | try {
20 | return JSON.parse(config);
21 | } catch (_) {
22 | return null;
23 | }
24 | }
25 |
26 | function validateRules(rules) {
27 | if (!Array.isArray(rules)) {
28 | return null;
29 | }
30 | var fail = false;
31 | rules.forEach(function(rule) {
32 | if (rule.length !== 2 ||
33 | typeof rule[0] !== "string" ||
34 | !Array.isArray(rule[1])) {
35 | fail = true;
36 | return null;
37 | }
38 | rule[1].forEach(function(subrule) {
39 | if (subrule.length !== 2 ||
40 | typeof subrule[0] !== "string" ||
41 | typeof subrule[1] !== "string") {
42 | fail = true;
43 | return null;
44 | }
45 | });
46 | if (fail) {
47 | return null;
48 | }
49 | });
50 | if (fail) {
51 | return null;
52 | }
53 | return rules;
54 | }
55 |
56 | function regexpifyRules(newRules) {
57 | if (newRules === null) {
58 | return null;
59 | }
60 | return newRules.map(function(rule) {
61 | return [
62 | new RegExp(rule[0]),
63 | rule[1].map(function(subrule) {
64 | return [
65 | new RegExp(subrule[0]),
66 | subrule[1]
67 | ];
68 | })
69 | ];
70 | });
71 | }
72 |
73 | function processConfig(config) {
74 | if (typeof config !== "string") {
75 | config = "";
76 | }
77 | return regexpifyRules(validateRules(parseRules(config)));
78 | }
79 |
80 | function messageHandler(request, sender, sendResponse) {
81 | if (request.action === "RESTORE_CONFIG") {
82 | chrome.storage.sync.get({config: defaultConfig}, function(items) {
83 | var config = items.config;
84 | if (typeof config !== "string") {
85 | config = defaultConfig;
86 | }
87 | sendResponse(config);
88 | });
89 | return true;
90 | } else if (request.action === "SAVE_CONFIG") {
91 | var config = request.config;
92 | newRules = processConfig(config);
93 | if (newRules !== null) {
94 | chrome.storage.sync.set({config: config});
95 | rules = newRules;
96 | sendResponse("SUCCESS");
97 | } else {
98 | sendResponse("FAILURE");
99 | }
100 | } else {
101 | console.error("Invalid request: ", request);
102 | }
103 | }
104 |
105 | function requestProcessor(details) {
106 | for (var i = 0, iLen = rules.length; i !== iLen; ++i) {
107 | if (!rules[i][0].test(details.url)) {
108 | continue;
109 | }
110 | var subrules = rules[i][1];
111 | var headers = details.responseHeaders;
112 | for (var j = 0, jLen = headers.length; j !== jLen; ++j) {
113 | var header = headers[j];
114 | var name = header.name.toLowerCase();
115 | if (name !== "content-security-policy" &&
116 | name !== "content-security-policy-report-only" &&
117 | name !== "x-webkit-csp") {
118 | continue;
119 | }
120 | for (var k = 0, kLen = subrules.length; k !== kLen; ++k) {
121 | header.value = header.value.replace(subrules[k][0],
122 | subrules[k][1]);
123 | }
124 | }
125 | return {responseHeaders: headers};
126 | }
127 | }
128 |
129 | var defaultConfig =
130 | "# Rules need to be in JSON syntax:\n" +
131 | "#\n" +
132 | "# [\n" +
133 | '# ["url-regexp", [\n' +
134 | '# ["pattern-regexp", "replacement-string"],\n' +
135 | "# ...\n" +
136 | "# ]],\n" +
137 | "# ...\n" +
138 | "# ]\n" +
139 | "#\n" +
140 | "# Keep in mind that JSON does not allow trailing commas.\n" +
141 | "# Lines starting with '#' are ignored. Have fun!\n" +
142 | "\n" +
143 | "[\n" +
144 | "# Example: whitelisting MathJax on GitHub:\n" +
145 | '# ["https://gist\\\\.github\\\\.com", [\n' +
146 | '# ["script-src", "script-src https://cdn.mathjax.org"],\n' +
147 | '# ["font-src", "font-src https://cdn.mathjax.org"]\n' +
148 | "# ]]\n" +
149 | "]\n";
150 |
151 | var rules = []
152 |
153 | chrome.storage.sync.get({config: ""}, function(items) {
154 | newRules = processConfig(items.config);
155 | if (newRules !== null) {
156 | rules = newRules;
157 | }
158 | });
159 | chrome.runtime.onMessage.addListener(messageHandler);
160 | chrome.webRequest.onHeadersReceived.addListener(requestProcessor, {
161 | urls: ["*://*/*"],
162 | types: ["main_frame", "sub_frame"]
163 | }, ["blocking", "responseHeaders"]);
164 |
--------------------------------------------------------------------------------