Initial commit. Version 0.5.0
[minimalist.git] / components / minimalist.js
blob7e449c9463ffcccad1033e9693f0519f3be724fe
1 function NSGetModule(compMgr, fileSpec)
3 // Constants //
4 const settings = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefBranch)
5 const urlParser = Components.classes['@mozilla.org/network/standard-url;1'].getService(Components.interfaces.nsIURI)
6 const whiteList = {'chrome': true, 'resource': true}
7 const hostRE = /^(?:\d{1,3}\.){3}\d{1,3}|[\w\-]{3,}(?=(?:\.[a-z]{2})*\.\w{2,}$)/
8 const contentRE = /^(?:text\/html|application\/xhtml\+xml)/
9 const cacheRE = /^(?:text|image|multipart)\/|^application\/(?:x-)?(?:javascript|json)/
10 const ACCEPT = Components.interfaces.nsIContentPolicy.ACCEPT
11 const REJECT = Components.interfaces.nsIContentPolicy.REJECT_SERVER
12 var exceptions = { }
13 var filterLevel = 3
15 // Implementation: content-policy //
16 function getDomainName(URL)
18 hostName = hostRE.exec(URL.host)
19 return hostName ? hostName[0].toLowerCase() : URL.host
22 function isImageLink(node)
24 return node instanceof Components.interfaces.nsIDOMHTMLImageElement &&
25 node.parentNode instanceof Components.interfaces.nsIDOMHTMLAnchorElement
28 function lastMeasure(node, targetURL)
30 if (filterLevel > 2 && isImageLink(node)) {
31 return ACCEPT
32 } else
33 return ACCEPT
36 function shouldLoad(contentType, contentLocation, requestOrigin, context, mimeTypeGuess, extra)
38 if (filterLevel < 1 || context instanceof Components.interfaces.nsIDOMXULElement || requestOrigin == null ||
39 requestOrigin.scheme in whiteList || contentLocation.scheme in whiteList)
40 return ACCEPT
42 sourceHost = getDomainName(requestOrigin)
43 destHost = getDomainName(contentLocation)
45 if (filterLevel > 1 && isImageLink(context)) {
46 urlParser.spec = context.parentNode.href
47 if (getDomainName(urlParser) != sourceHost)
48 return REJECT
51 if (sourceHost == destHost)
52 return lastMeasure(context, contentLocation)
54 exceptHosts = exceptions[sourceHost]
55 if (exceptHosts && destHost in exceptHosts)
56 return lastMeasure(context, contentLocation)
57 else
58 return REJECT
61 // Implementation: http-on-modify-request //
62 function getRequestHeader(channel, tag)
64 try { return channel.getRequestHeader(tag) }
65 catch (e) { return false }
68 function getResponseHeader(channel, tag)
70 try { return channel.getResponseHeader(tag) }
71 catch (e) { return false }
74 function observe(subject, topic, data)
76 switch (topic)
78 case 'http-on-modify-request':
79 if (subject) {
80 httpChannel = subject.QueryInterface(Components.interfaces.nsIHttpChannel)
81 httpChannel.setRequestHeader('If-Modified-Since', '', false)
82 httpChannel.setRequestHeader('User-Agent', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.6) '
83 + 'Gecko/2009011913 Firefox/3.0.6 ', false)
85 if (httpReferer = getRequestHeader(httpChannel, 'Referer')) {
86 urlParser.spec = httpReferer
87 httpReferer = getDomainName(urlParser)
88 urlParser.spec = 'http://' + getRequestHeader(httpChannel, 'Host')
90 if (httpReferer != getDomainName(urlParser))
91 httpChannel.setRequestHeader('Referer', urlParser.spec, false)
94 break
96 case 'http-on-examine-response':
97 if (subject) {
98 httpChannel = subject.QueryInterface(Components.interfaces.nsIHttpChannel)
99 httpChannel.setResponseHeader('Age', '', false)
100 httpChannel.setResponseHeader('Cache-Control', '', false)
101 httpChannel.setResponseHeader('Date', '', false)
102 httpChannel.setResponseHeader('ETag', '', false)
103 httpChannel.setResponseHeader('Last-Modified', '', false)
104 httpChannel.setResponseHeader('Pragma', '', false)
105 httpChannel.setResponseHeader('Vary', '', false)
107 if (contentType = getResponseHeader(httpChannel, 'Content-Type')) {
108 if (contentType.match(contentRE))
109 httpChannel.setResponseHeader('Expires', new Date( Date.now() + 120000 ).toGMTString(), false)
110 else {
111 contentLength = getResponseHeader(httpChannel, 'Content-Length')
112 if (!contentLength || parseInt(contentLength) > 2097152 || !contentType.match(cacheRE))
113 httpChannel.setResponseHeader('Expires', 'Thu, 01 Jan 1970 00:00:00 GMT', false)
114 else
115 httpChannel.setResponseHeader('Expires', 'Sun, 07 Feb 2106 06:28:15 GMT', false)
119 break
121 case 'nsPref:changed':
122 path = data.split('.')
123 switch (path[2])
125 case 'whitelist':
126 srcHost = path[3]
127 dstHost = settings.getCharPref(data).split(',')
129 if (dstHost.length > 0) {
130 exceptions[srcHost] = { }
131 for (i in dstHost)
132 exceptions[srcHost][dstHost[i]] = true
133 } else
134 delete exceptions[srcHost]
135 break
137 case 'level':
138 filterLevel = settings.getIntPref(data)
139 break
141 break
145 // XPCOM registration //
146 Components.utils.import('resource://gre/modules/XPCOMUtils.jsm')
148 function Minimalist() {
149 this.wrappedJSObject = this
150 observer = Components.classes['@mozilla.org/observer-service;1'].getService(Components.interfaces.nsIObserverService)
151 observer.addObserver(this, 'http-on-modify-request', false)
152 observer.addObserver(this, 'http-on-examine-response', false)
153 settings.QueryInterface(Components.interfaces.nsIPrefBranch2)
154 settings.addObserver('extensions.minimalist.', this, false)
156 entries = settings.getChildList('extensions.minimalist.whitelist', {})
157 for (i in entries) {
158 entry = entries[i].split('.')[3]
159 exceptions[entry] = {}
161 values = settings.getCharPref(entries[i]).split(',')
162 for (j in values)
163 exceptions[entry][values[j]] = true
166 Minimalist.prototype = {
167 classDescription: 'Minimalist',
168 classID: Components.ID('{124b1de7-bd8a-4a41-a04a-9d40070a0b3a}'),
169 contractID: '@zelgadis.jp/minimalist;1',
170 _xpcom_categories: [{category: 'content-policy'}],
171 QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIContentPolicy, Components.interfaces.nsIObserver]),
172 shouldLoad: shouldLoad,
173 shouldProcess: function() { return ACCEPT },
174 observe: observe
177 return XPCOMUtils.generateModule([Minimalist])