OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * This Source Code is subject to the terms of the Mozilla Public License |
| 3 * version 2.0 (the "License"). You can obtain a copy of the License at |
| 4 * http://mozilla.org/MPL/2.0/. |
| 5 */ |
| 6 |
| 7 function removeTrailingDots(string) |
| 8 { |
| 9 return string.replace(/\.+$/, ""); |
| 10 } |
| 11 |
| 12 /** |
| 13 * Checks whether a request is third party for the given document, uses |
| 14 * information from the public suffix list to determine the effective domain |
| 15 * name for the document. |
| 16 */ |
| 17 function isThirdParty(requestHost, documentHost) |
| 18 { |
| 19 requestHost = removeTrailingDots(requestHost); |
| 20 documentHost = removeTrailingDots(documentHost); |
| 21 |
| 22 // Extract domain name - leave IP addresses unchanged, otherwise leave only ba
se domain |
| 23 var documentDomain = getBaseDomain(documentHost); |
| 24 if (requestHost.length > documentDomain.length) |
| 25 return (requestHost.substr(requestHost.length - documentDomain.length - 1) !
= "." + documentDomain); |
| 26 else |
| 27 return (requestHost != documentDomain); |
| 28 } |
| 29 |
| 30 function reportError(exp) |
| 31 { |
| 32 Android.print("Error: " + exp); |
| 33 if (typeof exp == "string") |
| 34 { |
| 35 Android.showToast(exp); |
| 36 } |
| 37 Android.print(exp.stack); |
| 38 } |
| 39 |
| 40 function MatcherPatch() |
| 41 { |
| 42 // Very ugly - we need to rewrite _checkEntryMatch() function to make sure |
| 43 // it calls Filter.fromText() instead of assuming that the filter exists. |
| 44 var origFunction = Matcher.prototype._checkEntryMatch.toString(); |
| 45 var newFunction = origFunction.replace(/\bFilter\.knownFilters\[(.*?)\];/g, "F
ilter.fromText($1);"); |
| 46 eval("Matcher.prototype._checkEntryMatch = " + newFunction); |
| 47 } |
| 48 |
| 49 var window = this; |
| 50 |
| 51 var Components = |
| 52 { |
| 53 interfaces: |
| 54 { |
| 55 nsIFile: {DIRECTORY_TYPE: 0}, |
| 56 nsIFileURL: function() {}, |
| 57 nsIFileInputStream: null, |
| 58 nsIFileOutputStream: null, |
| 59 nsIHttpChannel: function() {}, |
| 60 nsIConverterInputStream: {DEFAULT_REPLACEMENT_CHARACTER: null}, |
| 61 nsIConverterOutputStream: null, |
| 62 nsIUnicharLineInputStream: null, |
| 63 nsISafeOutputStream: null, |
| 64 nsITimer: {TYPE_REPEATING_SLACK: 0}, |
| 65 nsIInterfaceRequestor: null, |
| 66 nsIChannelEventSink: null |
| 67 }, |
| 68 classes: |
| 69 { |
| 70 "@mozilla.org/network/file-input-stream;1": |
| 71 { |
| 72 createInstance: function() |
| 73 { |
| 74 return new FakeInputStream(); |
| 75 } |
| 76 }, |
| 77 "@mozilla.org/network/file-output-stream;1": |
| 78 { |
| 79 createInstance: function() |
| 80 { |
| 81 return new FakeOutputStream(); |
| 82 } |
| 83 }, |
| 84 "@mozilla.org/dom/json;1": |
| 85 { |
| 86 createInstance: function() { |
| 87 return { |
| 88 decodeFromStream: function(stream, encoding) |
| 89 { |
| 90 var line = {}; |
| 91 var haveMore = true; |
| 92 var s = new String(); |
| 93 while (true) |
| 94 { |
| 95 if (haveMore) |
| 96 haveMore = stream.readLine(line); |
| 97 else |
| 98 break; |
| 99 s += line.value; |
| 100 } |
| 101 return JSON.parse(s); |
| 102 }, |
| 103 encodeToStream: function(stream, encoding, something, obj) |
| 104 { |
| 105 var s = JSON.stringify(obj); |
| 106 stream.writeString(s); |
| 107 } |
| 108 } |
| 109 } |
| 110 }, |
| 111 "@mozilla.org/timer;1": |
| 112 { |
| 113 createInstance: function() |
| 114 { |
| 115 return new FakeTimer(); |
| 116 } |
| 117 } |
| 118 }, |
| 119 results: {}, |
| 120 utils: { |
| 121 reportError: reportError |
| 122 }, |
| 123 manager: null, |
| 124 ID: function() |
| 125 { |
| 126 return null; |
| 127 }, |
| 128 Constructor: function() |
| 129 { |
| 130 // This method is only used to get XMLHttpRequest constructor |
| 131 return XMLHttpRequest; |
| 132 } |
| 133 }; |
| 134 const Cc = Components.classes; |
| 135 const Ci = Components.interfaces; |
| 136 const Cr = Components.results; |
| 137 const Cu = Components.utils; |
| 138 |
| 139 Cc["@mozilla.org/intl/converter-input-stream;1"] = Cc["@mozilla.org/network/file
-input-stream;1"]; |
| 140 Cc["@mozilla.org/network/safe-file-output-stream;1"] = Cc["@mozilla.org/intl/con
verter-output-stream;1"] = Cc["@mozilla.org/network/file-output-stream;1"]; |
| 141 |
| 142 var Prefs = |
| 143 { |
| 144 patternsbackups: 5, |
| 145 patternsbackupinterval: 24, |
| 146 data_directory: _datapath, |
| 147 savestats: false, |
| 148 privateBrowsing: false, |
| 149 get subscriptions_autoupdate() { return Android.canAutoupdate() }, |
| 150 subscriptions_fallbackerrors: 5, |
| 151 subscriptions_fallbackurl: "https://adblockplus.org/getSubscription?version=%V
ERSION%&url=%SUBSCRIPTION%&downloadURL=%URL%&error=%ERROR%&channelStatus=%CHANNE
LSTATUS%&responseStatus=%RESPONSESTATUS%", |
| 152 addListener: function() {} |
| 153 }; |
| 154 |
| 155 var Utils = |
| 156 { |
| 157 systemPrincipal: null, |
| 158 getString: function(id) |
| 159 { |
| 160 return id; |
| 161 }, |
| 162 getLineBreak: function() |
| 163 { |
| 164 return "\n"; |
| 165 }, |
| 166 resolveFilePath: function(path) |
| 167 { |
| 168 return new FakeFile(path); |
| 169 }, |
| 170 ioService: |
| 171 { |
| 172 newURI: function(uri) |
| 173 { |
| 174 if (!uri.length || uri[0] == "~") |
| 175 throw new Error("Invalid URI"); |
| 176 |
| 177 /^([^:\/]*)/.test(uri); |
| 178 var scheme = RegExp.$1.toLowerCase(); |
| 179 |
| 180 return {scheme: scheme, spec: uri}; |
| 181 } |
| 182 }, |
| 183 observerService: |
| 184 { |
| 185 addObserver: function() {}, |
| 186 removeObserver: function() {} |
| 187 }, |
| 188 chromeRegistry: |
| 189 { |
| 190 convertChromeURL: function() {} |
| 191 }, |
| 192 runAsync: function(callback, thisPtr) |
| 193 { |
| 194 var params = Array.prototype.slice.call(arguments, 2); |
| 195 Android.setTimeout(function() |
| 196 { |
| 197 callback.apply(thisPtr, params); |
| 198 }, 0); |
| 199 }, |
| 200 addonVersion: _version, |
| 201 platformVersion: "10.0", |
| 202 get appLocale() |
| 203 { |
| 204 Android.getLocale(); |
| 205 }, |
| 206 generateChecksum: function(lines) |
| 207 { |
| 208 // We cannot calculate MD5 checksums yet :-( |
| 209 return null; |
| 210 }, |
| 211 makeURI: function(url) |
| 212 { |
| 213 return Utils.ioService.newURI(url); |
| 214 }, |
| 215 checkLocalePrefixMatch: function(prefixes) |
| 216 { |
| 217 if (!prefixes) |
| 218 return null; |
| 219 |
| 220 var list = prefixes.split(","); |
| 221 for (var i = 0; i < list.length; i++) |
| 222 if (new RegExp("^" + list[i] + "\\b").test(Utils.appLocale)) |
| 223 return list[i]; |
| 224 |
| 225 return null; |
| 226 }, |
| 227 versionComparator: |
| 228 { |
| 229 compare: function(v1, v2) |
| 230 { |
| 231 var parts1 = v1.split("."); |
| 232 var parts2 = v2.split("."); |
| 233 for (var i = 0; i < Math.max(parts1.length, parts2.length); i++) |
| 234 { |
| 235 // TODO: Handle non-integer version parts properly |
| 236 var part1 = parseInt(i < parts1.length ? parts1[i] : "0"); |
| 237 var part2 = parseInt(i < parts2.length ? parts2[i] : "0"); |
| 238 if (part1 != part2) |
| 239 return part1 - part2; |
| 240 } |
| 241 return 0; |
| 242 } |
| 243 } |
| 244 }; |
| 245 |
| 246 var XPCOMUtils = |
| 247 { |
| 248 generateQI: function() {} |
| 249 }; |
| 250 |
| 251 function FakeFile(path) |
| 252 { |
| 253 this.path = path; |
| 254 } |
| 255 FakeFile.prototype = |
| 256 { |
| 257 get leafName() |
| 258 { |
| 259 return this.path; |
| 260 }, |
| 261 set leafName(value) |
| 262 { |
| 263 this.path = value; |
| 264 }, |
| 265 append: function(path) |
| 266 { |
| 267 this.path += _separator + path; |
| 268 }, |
| 269 clone: function() |
| 270 { |
| 271 return new FakeFile(this.path); |
| 272 }, |
| 273 exists: function() |
| 274 { |
| 275 return Android.fileExists(this.path); |
| 276 }, |
| 277 remove: function() |
| 278 { |
| 279 Android.fileRemove(this.path); |
| 280 }, |
| 281 moveTo: function(parent, newPath) |
| 282 { |
| 283 Android.fileRename(this.path, newPath); |
| 284 }, |
| 285 get lastModifiedTime() |
| 286 { |
| 287 return Android.fileLastModified(this.path); |
| 288 }, |
| 289 get parent() |
| 290 { |
| 291 return {create: function() {}}; |
| 292 }, |
| 293 normalize: function() {} |
| 294 }; |
| 295 |
| 296 function FakeInputStream() |
| 297 { |
| 298 } |
| 299 FakeInputStream.prototype = |
| 300 { |
| 301 lines: null, |
| 302 currentIndex: 0, |
| 303 |
| 304 init: function(file) |
| 305 { |
| 306 if (file instanceof FakeInputStream) |
| 307 this.lines = file.lines; |
| 308 else |
| 309 this.lines = Android.fileRead(file.path).split(/\n/); |
| 310 }, |
| 311 readLine: function(line) |
| 312 { |
| 313 if (this.currentIndex < this.lines.length) |
| 314 line.value = this.lines[this.currentIndex]; |
| 315 this.currentIndex++; |
| 316 return (this.currentIndex < this.lines.length); |
| 317 }, |
| 318 close: function() {}, |
| 319 QueryInterface: function() |
| 320 { |
| 321 return this; |
| 322 } |
| 323 }; |
| 324 |
| 325 function FakeOutputStream() |
| 326 { |
| 327 } |
| 328 FakeOutputStream.prototype = |
| 329 { |
| 330 file: null, |
| 331 buffer: null, |
| 332 |
| 333 init: function(file) |
| 334 { |
| 335 if (file instanceof FakeOutputStream) |
| 336 { |
| 337 this.file = file.file; |
| 338 this.buffer = file.buffer; |
| 339 } |
| 340 else |
| 341 { |
| 342 this.file = file; |
| 343 this.buffer = []; |
| 344 } |
| 345 }, |
| 346 writeString: function(string) |
| 347 { |
| 348 this.buffer.push(string); |
| 349 }, |
| 350 close: function() |
| 351 { |
| 352 Android.fileWrite(this.file.path, this.buffer.join("")); |
| 353 }, |
| 354 finish: function() |
| 355 { |
| 356 this.close(); |
| 357 }, |
| 358 flush: function() {}, |
| 359 QueryInterface: function() |
| 360 { |
| 361 return this; |
| 362 } |
| 363 }; |
| 364 |
| 365 function FakeTimer() |
| 366 { |
| 367 } |
| 368 FakeTimer.prototype = |
| 369 { |
| 370 delay: 0, |
| 371 callback: null, |
| 372 initWithCallback: function(callback, delay) |
| 373 { |
| 374 this.callback = callback; |
| 375 this.delay = delay; |
| 376 this.scheduleTimeout(); |
| 377 }, |
| 378 scheduleTimeout: function() |
| 379 { |
| 380 var me = this; |
| 381 Android.setTimeout(function() |
| 382 { |
| 383 try |
| 384 { |
| 385 me.callback(); |
| 386 } |
| 387 catch(e) |
| 388 { |
| 389 reportError(e); |
| 390 } |
| 391 me.scheduleTimeout(); |
| 392 }, this.delay); |
| 393 } |
| 394 }; |
| 395 |
| 396 function ElemHidePatch() |
| 397 { |
| 398 /** |
| 399 * Returns a list of selectors to be applied on a particular domain. With |
| 400 * specificOnly parameter set to true only the rules listing specific domains |
| 401 * will be considered. |
| 402 */ |
| 403 ElemHide.getSelectorsForDomain = function(/**String*/ domain, /**Boolean*/ spe
cificOnly) |
| 404 { |
| 405 var result = []; |
| 406 for (var key in filterByKey) |
| 407 { |
| 408 var filter = Filter.knownFilters[filterByKey[key]]; |
| 409 if (specificOnly && (!filter.domains || filter.domains[""])) |
| 410 continue; |
| 411 |
| 412 if (filter.isActiveOnDomain(domain)) |
| 413 result.push(filter.selector); |
| 414 } |
| 415 if (result.length) |
| 416 return "<style type=\"text/css\">" + result.join() + " { display: none !im
portant }</style>"; |
| 417 else |
| 418 return null; |
| 419 }; |
| 420 |
| 421 ElemHide.init = function() {}; |
| 422 } |
| 423 |
| 424 /** |
| 425 * Removes all subscriptions from storage. |
| 426 */ |
| 427 function clearSubscriptions() |
| 428 { |
| 429 while (FilterStorage.subscriptions.length) |
| 430 FilterStorage.removeSubscription(FilterStorage.subscriptions[0]); |
| 431 } |
| 432 |
| 433 /** |
| 434 * Adds selected subscription to storage. |
| 435 */ |
| 436 function addSubscription(jsonSub) |
| 437 { |
| 438 var newSub = JSON.parse(jsonSub); |
| 439 |
| 440 var subscription = Subscription.fromURL(newSub["url"]); |
| 441 if (subscription) |
| 442 { |
| 443 subscription.disabled = false; |
| 444 subscription.title = newSub["title"]; |
| 445 subscription.homepage = newSub["homepage"]; |
| 446 if (subscription instanceof DownloadableSubscription && !subscription.lastDo
wnload) |
| 447 { |
| 448 Synchronizer.execute(subscription); |
| 449 } |
| 450 FilterStorage.addSubscription(subscription); |
| 451 FilterStorage.saveToDisk(); |
| 452 } |
| 453 } |
| 454 |
| 455 /** |
| 456 * Forces subscriptions refresh. |
| 457 */ |
| 458 function refreshSubscriptions() |
| 459 { |
| 460 for (var i = 0; i < FilterStorage.subscriptions.length; i++) |
| 461 { |
| 462 var subscription = FilterStorage.subscriptions[i]; |
| 463 if (subscription instanceof DownloadableSubscription) |
| 464 Synchronizer.execute(subscription, true, true); |
| 465 } |
| 466 } |
| 467 |
| 468 /** |
| 469 * Verifies that subscriptions are loaded and returns flag of subscription prese
nce. |
| 470 */ |
| 471 function verifySubscriptions() |
| 472 { |
| 473 var hasSubscriptions = false; |
| 474 for (var i = 0; i < FilterStorage.subscriptions.length; i++) |
| 475 { |
| 476 var subscription = FilterStorage.subscriptions[i]; |
| 477 if (subscription instanceof DownloadableSubscription) |
| 478 { |
| 479 hasSubscriptions = true; |
| 480 updateSubscriptionStatus(subscription); |
| 481 if (!subscription.lastDownload) |
| 482 { |
| 483 Synchronizer.execute(subscription); |
| 484 } |
| 485 } |
| 486 } |
| 487 return hasSubscriptions; |
| 488 } |
| 489 |
| 490 /** |
| 491 * Callback for subscription status updates. |
| 492 */ |
| 493 function updateSubscriptionStatus(subscription) |
| 494 { |
| 495 var status = ""; |
| 496 var time = 0; |
| 497 if (Synchronizer.isExecuting(subscription.url)) |
| 498 status = "synchronize_in_progress"; |
| 499 else if (subscription.downloadStatus && subscription.downloadStatus != "synchr
onize_ok") |
| 500 status = subscription.downloadStatus; |
| 501 else if (subscription.lastDownload > 0) |
| 502 { |
| 503 time = subscription.lastDownload * 1000; |
| 504 status = "synchronize_last_at"; |
| 505 } |
| 506 else |
| 507 status = "synchronize_never"; |
| 508 |
| 509 Android.setStatus(status, time); |
| 510 } |
| 511 |
| 512 function onFilterChange(action, subscription, param1, param2) |
| 513 { |
| 514 switch (action) |
| 515 { |
| 516 case "subscription.lastDownload": |
| 517 case "subscription.downloadStatus": |
| 518 updateSubscriptionStatus(subscription); |
| 519 break; |
| 520 } |
| 521 } |
| 522 |
| 523 function startInteractive() |
| 524 { |
| 525 FilterNotifier.addListener(onFilterChange); |
| 526 } |
| 527 |
| 528 function stopInteractive() |
| 529 { |
| 530 FilterNotifier.removeListener(onFilterChange); |
| 531 } |
| 532 |
| 533 function matchesAny(url, query, reqHost, refHost, accept) |
| 534 { |
| 535 var contentType = null; |
| 536 var thirdParty = true; |
| 537 |
| 538 if (accept != "") |
| 539 { |
| 540 if (accept.indexOf("text/css") != -1) |
| 541 contentType = "STYLESHEET"; |
| 542 else if (accept.indexOf("image/*" != -1)) |
| 543 contentType = "IMAGE"; |
| 544 } |
| 545 |
| 546 if (contentType == null) |
| 547 { |
| 548 var lurl = url.toLowerCase(); |
| 549 if (/\.js$/.test(lurl)) |
| 550 contentType = "SCRIPT"; |
| 551 else if (/\.css$/.test(lurl)) |
| 552 contentType = "STYLESHEET"; |
| 553 else if (/\.(?:gif|png|jpe?g|bmp|ico)$/.test(lurl)) |
| 554 contentType = "IMAGE"; |
| 555 else if (/\.(?:ttf|woff)$/.test(lurl)) |
| 556 contentType = "FONT"; |
| 557 } |
| 558 if (contentType == null) |
| 559 contentType = "OTHER"; |
| 560 |
| 561 if (refHost != "") |
| 562 { |
| 563 thirdParty = isThirdParty(reqHost, refHost); |
| 564 } |
| 565 |
| 566 if (query != "") |
| 567 url = url + "?" + query; |
| 568 |
| 569 var filter = defaultMatcher.matchesAny(url, contentType, null, thirdParty); |
| 570 |
| 571 return (filter != null && !(filter instanceof WhitelistFilter)); |
| 572 } |
| 573 |
| 574 Android.load("XMLHttpRequest.jsm"); |
| 575 Android.load("FilterNotifier.jsm"); |
| 576 Android.load("FilterClasses.jsm"); |
| 577 Android.load("SubscriptionClasses.jsm"); |
| 578 Android.load("FilterStorage.jsm"); |
| 579 Android.load("FilterListener.jsm"); |
| 580 Android.load("Matcher.jsm"); |
| 581 Android.load("ElemHide.jsm"); |
| 582 Android.load("Synchronizer.jsm"); |
| 583 |
| 584 FilterListener.startup(); |
| 585 Synchronizer.startup(); |
| 586 |
| 587 Android.load("publicSuffixList.js"); |
| 588 Android.load("punycode.js"); |
| 589 Android.load("basedomain.js"); |
OLD | NEW |