middle_squid 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +674 -0
  6. data/README.md +227 -0
  7. data/Rakefile +7 -0
  8. data/bin/middle_squid +7 -0
  9. data/lib/middle_squid/actions.rb +77 -0
  10. data/lib/middle_squid/adapter.rb +54 -0
  11. data/lib/middle_squid/adapters/squid.rb +57 -0
  12. data/lib/middle_squid/backends/keyboard.rb +31 -0
  13. data/lib/middle_squid/backends/thin.rb +14 -0
  14. data/lib/middle_squid/blacklist.rb +67 -0
  15. data/lib/middle_squid/builder.rb +159 -0
  16. data/lib/middle_squid/cli.rb +119 -0
  17. data/lib/middle_squid/core_ext/hash.rb +29 -0
  18. data/lib/middle_squid/database.rb +47 -0
  19. data/lib/middle_squid/exceptions.rb +4 -0
  20. data/lib/middle_squid/helpers.rb +74 -0
  21. data/lib/middle_squid/indexer.rb +194 -0
  22. data/lib/middle_squid/runner.rb +37 -0
  23. data/lib/middle_squid/server.rb +84 -0
  24. data/lib/middle_squid/uri.rb +31 -0
  25. data/lib/middle_squid/version.rb +3 -0
  26. data/lib/middle_squid.rb +46 -0
  27. data/middle_squid.gemspec +37 -0
  28. data/middle_squid_wrapper.sh +4 -0
  29. data/test/helper.rb +26 -0
  30. data/test/resources/backslash/cat/list +1 -0
  31. data/test/resources/black/ads/domains +2 -0
  32. data/test/resources/black/ads/urls +1 -0
  33. data/test/resources/black/tracker/domains +2 -0
  34. data/test/resources/black/tracker/urls +2 -0
  35. data/test/resources/copy_of_duplicates/cat/copy_of_list +2 -0
  36. data/test/resources/copy_of_duplicates/cat/list +2 -0
  37. data/test/resources/copy_of_duplicates/copy_of_cat/copy_of_list +2 -0
  38. data/test/resources/copy_of_duplicates/copy_of_cat/list +2 -0
  39. data/test/resources/duplicates/cat/copy_of_list +2 -0
  40. data/test/resources/duplicates/cat/list +2 -0
  41. data/test/resources/duplicates/copy_of_cat/copy_of_list +2 -0
  42. data/test/resources/duplicates/copy_of_cat/list +2 -0
  43. data/test/resources/empty/cat/emptylist +0 -0
  44. data/test/resources/empty_path/cat/list +1 -0
  45. data/test/resources/expressions/cat/list +3 -0
  46. data/test/resources/gray/isp/domains +2 -0
  47. data/test/resources/gray/isp/urls +1 -0
  48. data/test/resources/gray/news/domains +2 -0
  49. data/test/resources/hello.rb +2 -0
  50. data/test/resources/invalid_byte/cat/list +1 -0
  51. data/test/resources/mixed/cat/list +2 -0
  52. data/test/resources/subdirectory/cat/ignore/.gitkeep +0 -0
  53. data/test/resources/trailing_space/cat/list +2 -0
  54. data/test/test_actions.rb +76 -0
  55. data/test/test_adapter.rb +61 -0
  56. data/test/test_blacklist.rb +189 -0
  57. data/test/test_builder.rb +89 -0
  58. data/test/test_cli.rb +105 -0
  59. data/test/test_database.rb +20 -0
  60. data/test/test_hash.rb +28 -0
  61. data/test/test_helper.rb +76 -0
  62. data/test/test_indexer.rb +457 -0
  63. data/test/test_keyboard.rb +79 -0
  64. data/test/test_runner.rb +56 -0
  65. data/test/test_server.rb +86 -0
  66. data/test/test_squid.rb +110 -0
  67. data/test/test_thin.rb +7 -0
  68. data/test/test_uri.rb +69 -0
  69. metadata +363 -0
data/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # MiddleSquid
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/middle_squid.svg)](http://badge.fury.io/rb/middle_squid)
4
+ [![Build Status](https://travis-ci.org/cfillion/middle_squid.svg?branch=master)](https://travis-ci.org/cfillion/middle_squid)
5
+ [![Dependency Status](https://gemnasium.com/cfillion/middle_squid.svg)](https://gemnasium.com/cfillion/middle_squid)
6
+ [![Code Climate](https://codeclimate.com/github/cfillion/middle_squid/badges/gpa.svg)](https://codeclimate.com/github/cfillion/middle_squid)
7
+ [![Coverage Status](https://img.shields.io/coveralls/cfillion/middle_squid.svg)](https://coveralls.io/r/cfillion/middle_squid?branch=master)
8
+
9
+ MiddleSquid is a redirector, url mangler and webpage interceptor for the Squid HTTP proxy.
10
+
11
+ **Features**
12
+
13
+ - Configuration is done by writing a ruby script
14
+ - Supports plain-text domains/urls blacklists
15
+ - Can intercept and modify any HTTP request or response
16
+ - Works with HTTPS
17
+ if [SslBump](http://wiki.squid-cache.org/Features/SslBump) is enabled.
18
+
19
+ ## Installation & Setup
20
+
21
+ Assuming [Squid](http://www.squid-cache.org/) is installed and running as user 'proxy'.
22
+ These instructions were written for [Arch Linux](https://www.archlinux.org/).
23
+ Some adaptation to your favorite operating system may be necessary, at your
24
+ discretion.
25
+
26
+ **Dependencies:**
27
+
28
+ - Squid version 3.4 or newer
29
+ - Ruby version 2.1 or newer
30
+
31
+ ### Step 1: Set a home folder for user 'proxy'
32
+
33
+ ```sh
34
+ sudo mkdir /home/proxy
35
+ sudo chown proxy:proxy /home/proxy
36
+ sudo usermod --home /home/proxy proxy
37
+ ```
38
+
39
+ ### Step 2: Install MiddleSquid
40
+
41
+ ```sh
42
+ sudo su - proxy
43
+
44
+ gem install middle_squid
45
+ echo 'run lambda {|uri, extras| }' > middle_squid_config.rb
46
+
47
+ exit
48
+ ```
49
+
50
+ ### Step 3: Create a launcher script
51
+
52
+ Create the file `/usr/local/bin/middle_squid_wrapper.sh`:
53
+
54
+ ```sh
55
+ #!/bin/sh
56
+
57
+ GEM_HOME=$(ruby -e 'puts Gem.user_dir')
58
+ exec $GEM_HOME/bin/middle_squid $*
59
+ ```
60
+
61
+ ### Step 4: Setup Squid
62
+
63
+ Add these lines to your `/etc/squid/squid.conf`:
64
+
65
+ ```squidconf
66
+ url_rewrite_program /usr/bin/sh /usr/local/bin/middle_squid_wrapper.sh start -C /home/proxy/middle_squid_config.rb
67
+
68
+ # required to fix HTTPS sites (if SslBump is enabled)
69
+ acl fix_ssl_rewrite method GET
70
+ acl fix_ssl_rewrite method POST
71
+ url_rewrite_access allow fix_ssl_rewrite
72
+ url_rewrite_access deny all
73
+ ```
74
+
75
+ Finish with `sudo squid -k reconfigure`. Check `/var/log/squid/cache.log` for errors.
76
+
77
+ ## Configuration
78
+
79
+ MiddleSquid is configured by the ruby script specified in the command line by the `-C` or `--config-file` argument.
80
+
81
+ The script must call the `run` method:
82
+
83
+ ```ruby
84
+ run lambda {|uri, extras|
85
+ # decide what to do with uri
86
+ }
87
+ ```
88
+
89
+ The argument must be an object that responds to the `call` method and taking two arguments:
90
+ the URI to process and an array of extra data received from squid
91
+ (see url_rewrite_extras in
92
+ [squid's documentation](http://www.squid-cache.org/Doc/config/url_rewrite_extras/)).
93
+
94
+ Write this in the file `/home/proxy/middle_squid_config.rb` we have created earlier:
95
+
96
+ ```ruby
97
+ run lambda {|uri, extras|
98
+ redirect_to 'http://duckduckgo.com' if uri.host.end_with? 'google.com'
99
+ }
100
+ ```
101
+
102
+ Run `sudo squid -k reconfigure` again to restart all MiddleSquid processes.
103
+ You should now be redirected to http://duckduckgo.com each time you visit
104
+ Google under your Squid proxy.
105
+
106
+ ### Black Lists
107
+
108
+ While it may be fun to redirect yourself to an alternate search engine,
109
+ MiddleSquid is more useful at blocking annoying advertisements and tracking
110
+ services that are constantly watching your whereabouts.
111
+
112
+ MiddleSquid can scan any black list collection distributed in plain-text format
113
+ and compatible with SquidGuard or Dansguardian, such as:
114
+
115
+ - [Shalla's Blacklists](http://www.shallalist.de/) (free for personal use)
116
+ - [URLBlackList.com](http://www.urlblacklist.com/) (commercial)
117
+
118
+ Replace the previous configuration in `/home/proxy/middle_squid_config.rb`
119
+ by this one:
120
+
121
+ ```ruby
122
+ database '/home/proxy/blacklist.db'
123
+
124
+ adv = blacklist 'adv'
125
+ tracker = blacklist 'tracker'
126
+
127
+ run lambda {|uri, extras|
128
+ if adv.include? uri
129
+ redirect_to 'http://your.webserver/block_pages/advertising.html'
130
+ end
131
+
132
+ if tracker.include? uri
133
+ redirect_to 'http://your.webserver/block_pages/tracker.html'
134
+ end
135
+ }
136
+ ```
137
+
138
+ Next we have to download a blacklist and ask MiddleSquid to index its content
139
+ in the database for fast access:
140
+
141
+ ```sh
142
+ sudo su - proxy
143
+
144
+ # Download Shalla's Blacklists
145
+ wget "http://www.shallalist.de/Downloads/shallalist.tar.gz" -O shallalist.tar.gz
146
+ tar xzf shallalist.tar.gz
147
+ mv BL ShallaBlackList
148
+
149
+ # Construct the blacklist database
150
+ /usr/local/bin/middle_squid_wrapper.sh index ShallaBlackList -C /etc/squid/middle_squid.rb
151
+
152
+ exit
153
+ ```
154
+
155
+ The `index` command above may take a while to complete. Once it's done, re-run `squid -k reconfigure` and
156
+ enjoy an internet without ads or tracking beacons.
157
+
158
+ ### Content Interception
159
+
160
+ MiddleSquid can also intercept the client's requests and modify the data sent to the
161
+ browser. Let's translate a few click-bait headlines on BuzzFeed
162
+ (check out [Downworthy](http://downworthy.snipe.net/) while you are at it):
163
+
164
+ ```ruby
165
+ CLICK_BAITS = {
166
+ 'Literally' => 'Figuratively',
167
+ 'Mind-Blowing' => 'Painfully Ordinary',
168
+ 'Will Blow Your Mind' => 'Might Perhaps Mildly Entertain You For a Moment',
169
+ # ...
170
+ }.freeze
171
+
172
+ define_action :translate do |uri|
173
+ intercept {|req, res|
174
+ status, headers, body = download_like req, uri
175
+
176
+ content_type = headers['Content-Type'].to_s
177
+
178
+ if content_type.include? 'text/html'
179
+ CLICK_BAITS.each {|before, after|
180
+ body.gsub! before, after
181
+ }
182
+ end
183
+
184
+ [status, headers, body]
185
+ }
186
+ end
187
+
188
+ run lambda {|uri, extras|
189
+ if uri.host == 'www.buzzfeed.com'
190
+ translate uri
191
+ end
192
+ }
193
+ ```
194
+
195
+ Don't use this feature unless you have the permission from all your users to do so.
196
+ This indeed constitutes a man-in-the-middle attack and should be used with
197
+ moderation.
198
+
199
+ ## Documentation
200
+
201
+ MiddleSquid's documentation is hosted at
202
+ [http://rubydoc.info/gems/middle_squid/MiddleSquid](http://rubydoc.info/gems/middle_squid/MiddleSquid).
203
+
204
+ - [Configuration syntax (DSL)](http://rubydoc.info/gems/middle_squid/MiddleSquid/Builder)
205
+ - [List of predefined actions](http://rubydoc.info/gems/middle_squid/MiddleSquid/Actions)
206
+ - [List of predefined helpers](http://rubydoc.info/gems/middle_squid/MiddleSquid/Helpers)
207
+ - [Available adapters](http://rubydoc.info/gems/middle_squid/MiddleSquid/Adapters)
208
+
209
+ ## Changelog
210
+
211
+ ### v1.0 (2014-10-05)
212
+
213
+ First public release.
214
+
215
+ ## Future Plans
216
+
217
+ - Find out why HTTPS is not always working under Squid without the ACL hack.
218
+ - Write new adapters for other proxies or softwares.
219
+
220
+ ## Contributing
221
+
222
+ 1. [Fork it](https://github.com/cfillion/middle_squid/fork)
223
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
224
+ 3. Test your changes (`rake`)
225
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
226
+ 5. Push to the branch (`git push origin my-new-feature`)
227
+ 6. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ task :default => [:test]
5
+ Rake::TestTask.new do |t|
6
+ t.test_files = FileList['test/helper.rb', 'test/test_*.rb']
7
+ end
data/bin/middle_squid ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ Signal.trap('INT') { abort }
4
+
5
+ require 'middle_squid'
6
+
7
+ MiddleSquid::CLI.start ARGV
@@ -0,0 +1,77 @@
1
+ module MiddleSquid::Actions
2
+ #
3
+ # @!group Predefined Actions
4
+ #
5
+
6
+ # Allow the request to pass through. This is the default action.
7
+ #
8
+ # @example Whitelist a domain
9
+ # run lambda {|uri, extras|
10
+ # accept if uri.host == 'github.com'
11
+ # }
12
+ def accept
13
+ action :accept
14
+ end
15
+
16
+ # Redirect the browser to another URL.
17
+ #
18
+ # @example Redirect google.com to duckduckgo.com
19
+ # run lambda {|uri, extras|
20
+ # redirect_to "http://duckduckgo.com/#{uri.request_uri}" if uri.host == 'google.com'
21
+ # }
22
+ # @param url [String] the new url
23
+ # @param status [Fixnum] HTTP status code (see http://tools.ietf.org/html/rfc7231#section-6.4)
24
+ def redirect_to(url, status: 301)
25
+ action :redirect, status: status, url: url
26
+ end
27
+
28
+ # Serve another page in place of the requested one.
29
+ # Avoid in favor of {#redirect_to} when possible.
30
+ #
31
+ # @example Block Google advertisements
32
+ # run lambda {|uri, extras|
33
+ # redirect_to 'http://webserver.lan/blocked.html' if uri.host == 'ads.google.com'
34
+ # }
35
+ # @param url [String] the substitute url
36
+ def replace_by(url)
37
+ action :replace, url: url
38
+ end
39
+
40
+ # Hijack the request and generate a dynamic reply.
41
+ # This can be used to skip landing pages,
42
+ # change the behaviour of a website depending on the browser's headers or to
43
+ # generate an entire virtual website using your favorite Rack framework.
44
+ #
45
+ # The block is called inside a fiber.
46
+ # If the return value is a Rack triplet, it will be sent to the browser.
47
+ #
48
+ # @note
49
+ # With great power comes great responsibility.
50
+ # Please respect the privacy of your users.
51
+ # @example Hello World
52
+ # run lambda {|uri, extras|
53
+ # intercept {|req, res|
54
+ # [200, {}, 'Hello World']
55
+ # }
56
+ # }
57
+ # @yieldparam req [Rack::Request] the browser request
58
+ # @yieldparam res [Thin::AsyncResponse] the response to send back
59
+ # @yieldreturn Rack triplet or anything else
60
+ # @see Helpers#download_like
61
+ def intercept(&block)
62
+ raise ArgumentError, 'no block given' unless block_given?
63
+
64
+ token = server.token_for block
65
+
66
+ replace_by "http://#{server.host}:#{server.port}/#{token}"
67
+ end
68
+
69
+ #
70
+ # @!endgroup
71
+ #
72
+
73
+ private
74
+ def action(name, options = {})
75
+ throw :action, [name, options]
76
+ end
77
+ end
@@ -0,0 +1,54 @@
1
+ module MiddleSquid
2
+ # Base class for MiddleSquid's adapters.
3
+ # Subclasses should call {#handle} when they have received and parsed a request.
4
+ #
5
+ # @abstract Subclass and override {#output} to implement a custom adapter.
6
+ class Adapter
7
+ # Returns whatever was passed to {Builder#run}.
8
+ #
9
+ # @return [#call]
10
+ attr_accessor :handler
11
+
12
+ # Returns a new instance of Adapter.
13
+ # Use {Builder#use} instead.
14
+ def initialize(options = {})
15
+ @options = options
16
+ end
17
+
18
+ # Execute the user handler (see {#handler}) and calls +#output+.
19
+ #
20
+ # @param url <String> string representation of the url to be processed
21
+ # @param extras <Array> extra data to pass to the user's handler
22
+ def handle(url, extras = [])
23
+ uri = MiddleSquid::URI.parse url
24
+ raise InvalidURIError, "invalid URL received: '#{url}'" if !uri || !uri.host
25
+
26
+ action, options = catch :action do
27
+ @handler.call uri, extras
28
+ throw :action, [:accept, {}]
29
+ end
30
+
31
+ output action, options
32
+ end
33
+
34
+ # Pass an action to an underlying software.
35
+ #
36
+ # accept::
37
+ # (no options)
38
+ #
39
+ # redirect::
40
+ # Options:
41
+ # - +status+ [+Fixnum+]
42
+ # - +url+ [+String+]
43
+ #
44
+ # replace::
45
+ # Options:
46
+ # - +url+ [+String+]
47
+ #
48
+ # @param action [Symbol]
49
+ # @param options [Hash]
50
+ def output(action, options)
51
+ raise NotImplementedError
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,57 @@
1
+ module MiddleSquid
2
+ # Adapter for the {http://www.squid-cache.org Squid HTTP Proxy}.
3
+ #
4
+ # *Options:*
5
+ #
6
+ # concurrency::
7
+ # Whether to expect a channel ID from Squid.
8
+ #
9
+ # Enable this option if the concurrency option is set to > 0 in Squid's
10
+ # {http://www.squid-cache.org/Doc/config/url_rewrite_children/ url_rewrite_children} directive.
11
+ #
12
+ # Extra data is configured in Squid with the {http://www.squid-cache.org/Doc/config/url_rewrite_extras/ url_rewrite_extras} directive.
13
+ #
14
+ # @see http://wiki.squid-cache.org/Features/Redirectors
15
+ class Adapters::Squid < Adapter
16
+ def start
17
+ warn 'WARNING: STDOUT is a terminal. This command should be launched from squid.' if STDOUT.tty?
18
+
19
+ EM.open_keyboard Backends::Keyboard, method(:input)
20
+ end
21
+
22
+ def input(line)
23
+ parts = line.split
24
+
25
+ @chan_id = @options[:concurrency] ? parts.shift : nil
26
+ url, *extras = parts
27
+
28
+ extras.map! {|str| URI.unescape str }
29
+
30
+ handle url, extras
31
+ end
32
+
33
+ def output(action, options)
34
+ case action
35
+ when :accept
36
+ reply 'ERR'
37
+ when :redirect
38
+ reply 'OK', status: options[:status], url: options[:url]
39
+ when :replace
40
+ reply 'OK', :'rewrite-url' => options[:url]
41
+ else
42
+ raise Error, "unsupported action: #{action}"
43
+ end
44
+ end
45
+
46
+ private
47
+ def reply(result, **kv_pairs)
48
+ parts = []
49
+ parts << @chan_id if @chan_id
50
+ parts << result
51
+ parts.concat kv_pairs.map {|k,v| "#{k}=#{URI.escape v.to_s}" }
52
+
53
+ $stdout.puts parts.join("\x20")
54
+ $stdout.flush
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,31 @@
1
+ module MiddleSquid::Backends
2
+ # Receives data from the standard input.
3
+ class Keyboard < EventMachine::Connection
4
+ # @param handler [#call] called when a full line has been received
5
+ def initialize(handler)
6
+ @buffer = []
7
+ @handler = handler
8
+ end
9
+
10
+ # @param char [String] single character
11
+ def receive_data(char)
12
+ case char
13
+ when "\x00"
14
+ EM.stop
15
+ when "\n"
16
+ line = @buffer.join
17
+ @buffer.clear
18
+
19
+ receive_line line
20
+ else
21
+ @buffer << char
22
+ end
23
+ end
24
+
25
+ # @param line [String] full line without the trailing linebreak
26
+ def receive_line(line)
27
+ # EventMachine sends ASCII-8BIT strings, somehow preventing the databases queries to match
28
+ @handler.call line.force_encoding(Encoding::UTF_8)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ module MiddleSquid::Backends
2
+ # Exposes the signature of Thin's TCP socket.
3
+ #
4
+ # @example Extract the current host and port
5
+ # sockname = EM.get_sockname @thin.backend.signature
6
+ # @port, @host = Socket.unpack_sockaddr_in sockname
7
+ class Thin < Thin::Backends::TcpServer
8
+ attr_reader :signature
9
+
10
+ def initialize(host, port, options)
11
+ super host, port
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,67 @@
1
+ module MiddleSquid
2
+ # Use to query the blacklist database.
3
+ # URIs can be matched by hostname (see {#include_domain?}), path (see {#include_url?}) or both (see {#include?}).
4
+ #
5
+ # Instances of this class should be created using {Builder#blacklist Builder#blacklist}
6
+ # (otherwise they would not be seen by the "<code>middle_squid index</code>" command unless the "+--full+" flag is enabled).
7
+ class BlackList
8
+ include Database
9
+
10
+ # @return [String] the category passed to {#initialize}
11
+ attr_reader :category
12
+
13
+ # @return [Array<String>] the aliases passed to {#initialize}
14
+ attr_reader :aliases
15
+
16
+ # Returns a new instance of BlackList. Use {Builder#blacklist Builder#blacklist} instead.
17
+ # @param category [String]
18
+ # @param aliases [Array<String>]
19
+ def initialize(category, aliases: [])
20
+ @category = category
21
+ @aliases = aliases
22
+ end
23
+
24
+ # Whether the blacklist category contains the URI's hostname or an upper-level domain.
25
+ #
26
+ # Rules to the <code>www</code> subdomain match any subdomains.
27
+ #
28
+ # @example Rule: sub.domain.com
29
+ # Matches:
30
+ # - http://sub.domain.com/...
31
+ # - http://second.sub.domain.com/...
32
+ # - http://infinite.level.of.sub.domain.com/...
33
+ # @param uri [URI] the URI to search
34
+ def include_domain?(uri)
35
+ !!db.get_first_value(
36
+ "SELECT 1 FROM domains WHERE category = ? AND ? LIKE '%' || host LIMIT 1",
37
+ [@category, uri.cleanhost]
38
+ )
39
+ end
40
+
41
+ # Whether the blacklist category contains the URI. Matches by partial domain (like {#include_domain?}) and path. The query string is ignored.
42
+ #
43
+ # Rules to index files (index.html, Default.aspx and friends) match the whole directory.
44
+ #
45
+ # @example Rule: domain.com/path
46
+ # Matches:
47
+ # - http://domain.com/path
48
+ # - http://domain.com/dummy/../path
49
+ # - http://domain.com/path/extra_path
50
+ # - http://domain.com/path?query=string
51
+ # @param uri [URI] the URI to search
52
+ def include_url?(uri)
53
+ !!db.get_first_value(
54
+ "SELECT 1 FROM urls WHERE category = ? AND ? LIKE '%' || host AND ? LIKE path || '%' LIMIT 1",
55
+ [@category, uri.cleanhost, uri.cleanpath]
56
+ )
57
+ end
58
+
59
+ # Whether this blacklists contains the uri.
60
+ # Matches by domain and/or path.
61
+ #
62
+ # @param uri [URI] the uri to search
63
+ def include?(uri)
64
+ include_domain?(uri) || include_url?(uri)
65
+ end
66
+ end
67
+ end