File indexing completed on 2024-11-24 04:54:31

0001 /* This Source Code Form is subject to the terms of the Mozilla Public
0002  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
0003  * You can obtain one at http://mozilla.org/MPL/2.0/. */
0004 
0005 const request = require('request')
0006 const { sanitizeABPInput } = require('./filtering')
0007 const fs = require('fs')
0008 const { AdBlockClient, adBlockLists } = require('..')
0009 
0010 /**
0011  * Builds an adblock client, given one or more strings containing filter rules.
0012  *
0013  * @param filterRuleData -- either a string, describing adblock filter riles,
0014  *                          or an array of such strings.
0015  * @param options        -- an optional object, describing parse options.
0016  *                          currently the only used rule is "keepRuleText",
0017  *                          which is a boolean flag determine whether to keep
0018  *                          the original filter rule text.
0019  */
0020 const makeAdBlockClientFromString = (filterRuleData, options) => {
0021   const keepRuleText = !!(options && options.keepRuleText)
0022   return new Promise((resolve) => {
0023     const client = new AdBlockClient()
0024     if (filterRuleData.constructor === Array) {
0025       filterRuleData.forEach(filterRuleDataItem => client.parse(filterRuleDataItem, keepRuleText))
0026     } else {
0027       client.parse(filterRuleData, keepRuleText)
0028     }
0029     resolve(client)
0030   })
0031 }
0032 
0033 /**
0034  * Creates an ABlock client from a DAT file
0035  * @param datFilePath - The file path to the DAT file
0036  */
0037 const makeAdBlockClientFromDATFile = (datFilePath) => {
0038   return new Promise((resolve, reject) => {
0039     fs.readFile(datFilePath, (err, data) => {
0040       if (err) {
0041         reject(err)
0042         return
0043       }
0044       const client = new AdBlockClient()
0045       client.deserialize(data)
0046       resolve(client)
0047     })
0048   })
0049 }
0050 
0051 /**
0052  * Creates an ABlock client from a list URL
0053  * @param listURL - The URL to check for obtaining the list.
0054  * @param filter - A filtering function that can be optionally specified.
0055  * @param options        -- an optional object, describing parse options.
0056  *                          currently the only used rule is "keepRuleText",
0057  *                          which is a boolean flag determine whether to keep
0058  *                          the original filter rule text.
0059  * It will be called with the URL's body and it can filter and return a new string.
0060  */
0061 const getSingleListDataFromSingleURL = (listURL, filter, options) => {
0062   return new Promise((resolve, reject) => {
0063     request.get(listURL, function (error, response, body) {
0064       if (error) {
0065         reject(new Error(`Request error: ${error}`))
0066         return
0067       }
0068       if (response.statusCode !== 200) {
0069         reject(new Error(`Error status code ${response.statusCode} returned for URL: ${listURL}`))
0070         return
0071       }
0072       if (filter) {
0073         body = filter(body)
0074       }
0075       body = sanitizeABPInput(body)
0076       resolve(body)
0077     })
0078   })
0079 }
0080 
0081 /**
0082  * Creates an AdBlock client from a list URL
0083  * @param listURL  -- The URL to check for obtaining the list.
0084  * @param filter   -- A filtering function that can be optionally specified.
0085  * @param options  -- an optional object, describing parse options.
0086  *                    currently the only used rule is "keepRuleText",
0087  *                    which is a boolean flag determine whether to keep
0088  *                    the original filter rule text.
0089 
0090  * It will be called with the URL's body and it can filter and return a new string.
0091  */
0092 const makeAdBlockClientFromListURL = (listURL, filter, options) => {
0093   return new Promise((resolve, reject) => {
0094     if (listURL.constructor === Array) {
0095       const requestPromises = listURL.map((currentURL) => {
0096         console.log(`${currentURL}...`)
0097         return getSingleListDataFromSingleURL(currentURL, filter)
0098       })
0099       Promise.all(requestPromises).then((results) => {
0100         let body = results.join('\n')
0101         body = sanitizeABPInput(body)
0102         resolve(makeAdBlockClientFromString(body, options))
0103       }).catch((error) => {
0104         reject(new Error(`getSingleListDataFromSingleURL error: ${error}`))
0105       })
0106     } else {
0107       console.log(`${listURL}...`)
0108       getSingleListDataFromSingleURL(listURL, filter).then((listData) => {
0109         const body = sanitizeABPInput(listData)
0110         resolve(makeAdBlockClientFromString(body, options))
0111       }).catch((error) => {
0112         reject(new Error(`getSingleListDataFromSingleURL error: ${error}`))
0113       })
0114     }
0115   })
0116 }
0117 
0118 const getListFilterFunction = (uuid) => {
0119   if (uuid === 'FBB430E8-3910-4761-9373-840FC3B43FF2') {
0120     return (input) => input.split('\n').slice(4)
0121       .map((line) => `||${line}`).join('\n')
0122   }
0123   return undefined
0124 }
0125 
0126 /**
0127  * Creates an AdBlock client with the rules given in a list described by a UUID.
0128  *
0129  * See lists/* for the definitions of these UUIDs.
0130  *
0131  * @param uuid     -- a string, describing on of the UUIDs for a known filter
0132  *                    list.
0133  * @param options  -- an optional object, describing parse options.
0134  *                    currently the only used rule is "keepRuleText",
0135  *                    which is a boolean flag determine whether to keep
0136  *                    the original filter rule text.
0137  */
0138 const makeAdBlockClientFromListUUID = (uuid, options) => {
0139   let list = adBlockLists.default.find((l) => l.uuid === uuid)
0140   if (!list) {
0141     list = adBlockLists.regions.find((l) => l.uuid === uuid)
0142   }
0143   if (!list) {
0144     list = adBlockLists.malware.find((l) => l.uuid === uuid)
0145   }
0146   if (!list) {
0147     return Promise.reject(new Error(`No list found for UUID ${uuid}`))
0148   }
0149 
0150   const filterFn = getListFilterFunction(uuid)
0151   return makeAdBlockClientFromListURL(list.listURL, filterFn, options)
0152 }
0153 
0154 /**
0155  * Builds an adblock client by reading one or more files filter rules off disk.
0156  *
0157  * @param filePath -- either a string, describing the path to a file of filter
0158  *                    rules on disk, or an array of the same.
0159  * @param options  -- an optional object, describing parse options.
0160  *                    currently the only used rule is "keepRuleText",
0161  *                    which is a boolean flag determine whether to keep
0162  *                    the original filter rule text.
0163  */
0164 const makeAdBlockClientFromFilePath = (filePath, options) => {
0165   return new Promise((resolve, reject) => {
0166     let filterRuleData
0167     if (filePath.constructor === Array) {
0168       filterRuleData = filePath.map((filePath) => fs.readFileSync(filePath, 'utf8'))
0169     } else {
0170       filterRuleData = fs.readFileSync(filePath, 'utf8')
0171     }
0172     resolve(makeAdBlockClientFromString(filterRuleData, options))
0173   })
0174 }
0175 
0176 const getListBufferFromURL = (listURL, filter) => {
0177   return new Promise((resolve, reject) => {
0178     request.get(listURL, function (error, response, body) {
0179       if (error) {
0180         reject(new Error(`Request error: ${error}`))
0181         return
0182       }
0183       if (response.statusCode !== 200) {
0184         reject(new Error(`Error status code ${response.statusCode} returned for URL: ${listURL}`))
0185         return
0186       }
0187       if (filter) {
0188         body = filter(body)
0189       }
0190       body = sanitizeABPInput(body)
0191       resolve(body)
0192     })
0193   })
0194 }
0195 
0196 /**
0197  * Reads a list of sites in the format of one site per newline
0198  * from a file path and returns an array with the sites.
0199  */
0200 const readSiteList = (path) =>
0201   fs.readFileSync(path, 'utf8').split('\n')
0202 
0203 module.exports = {
0204   makeAdBlockClientFromString,
0205   makeAdBlockClientFromDATFile,
0206   makeAdBlockClientFromListURL,
0207   makeAdBlockClientFromFilePath,
0208   makeAdBlockClientFromListUUID,
0209   getListBufferFromURL,
0210   readSiteList,
0211   getListFilterFunction
0212 }