middle_squid 1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,84 @@
1
+ module MiddleSquid
2
+ # Manages the internal HTTP server.
3
+ class Server
4
+ DEFAULT_HOST = '127.0.0.1'.freeze
5
+ DEFAULT_PORT = 0
6
+
7
+ TOKEN_TIMEOUT = 10
8
+
9
+ Thin::Logging.logger = Logger.new STDERR
10
+ Thin::Logging.level = Logger::WARN
11
+
12
+ # @return [String]
13
+ attr_reader :host
14
+
15
+ # @return [Fixnum]
16
+ attr_reader :port
17
+
18
+ def initialize
19
+ @tokens = {}
20
+ @thin = Thin::Server.new DEFAULT_HOST, DEFAULT_PORT, method(:handler),
21
+ :backend => Backends::Thin,
22
+ :signals => false
23
+ end
24
+
25
+ def start
26
+ @thin.start
27
+
28
+ sockname = EM.get_sockname @thin.backend.signature
29
+ @port, @host = Socket.unpack_sockaddr_in sockname
30
+ end
31
+
32
+ def stop
33
+ @thin.stop
34
+ @port = @host = nil
35
+ end
36
+
37
+ # Creates a temporary token.
38
+ #
39
+ # @param block [#call] called when the token is requested
40
+ # @return [String] random token
41
+ def token_for(block)
42
+ token = SecureRandom.uuid
43
+ @tokens[token] = block
44
+
45
+ EM.add_timer(TOKEN_TIMEOUT) {
46
+ @tokens.delete token
47
+ }
48
+
49
+ token
50
+ end
51
+
52
+ private
53
+ def handler(env)
54
+ callback = @tokens[env['PATH_INFO'][1..-1]]
55
+
56
+ return [
57
+ 404,
58
+ {'Content-Type' => 'text/plain'},
59
+ ['[MiddleSquid] Invalid Token']
60
+ ] unless callback
61
+
62
+ request = Rack::Request.new env
63
+ response = Thin::AsyncResponse.new env
64
+
65
+ Fiber.new {
66
+ retval = callback.call request, response
67
+
68
+ if retval.is_a?(Array) && retval.size == 3
69
+ status, headers, body = retval
70
+
71
+ headers.sanitize_headers!
72
+
73
+ response.status = status
74
+ response.headers.merge! headers
75
+ response.write body
76
+ end
77
+
78
+ response.done
79
+ }.resume
80
+
81
+ response.finish
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,31 @@
1
+ module MiddleSquid
2
+ # Provides distinguishable host and path for the blacklists.
3
+ #
4
+ # @see http://rubydoc.info/gems/addressable/Addressable/URI
5
+ class URI < Addressable::URI
6
+ DOT = '.'.freeze
7
+ SLASH = '/'.freeze
8
+
9
+ # @return [String]
10
+ def cleanhost
11
+ clean = normalized_host.force_encoding Encoding::UTF_8
12
+ clean.sub! /\Awww\./, ''
13
+ clean.sub! /\.+\z/, ''
14
+ clean.insert 0, DOT
15
+ clean
16
+ end
17
+
18
+ # @return [String]
19
+ def cleanpath
20
+ dirty = normalized_path.force_encoding Encoding::UTF_8
21
+ p = Pathname.new(dirty).cleanpath
22
+
23
+ file = p.basename('.*').to_s.downcase
24
+ p = p.dirname if %w[index default].include? file
25
+
26
+ clean = p.to_s[1..-1]
27
+ clean << SLASH unless clean.empty?
28
+ clean
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module MiddleSquid
2
+ VERSION = '1.0'
3
+ end
@@ -0,0 +1,46 @@
1
+ require 'middle_squid/version'
2
+
3
+ require 'fiber'
4
+ require 'pathname'
5
+ require 'securerandom'
6
+
7
+ require 'addressable/uri'
8
+ require 'eventmachine'
9
+ require 'em-http-request'
10
+ require 'sqlite3'
11
+ require 'thin'
12
+ require 'thin/async'
13
+ require 'thor'
14
+
15
+ # Copyright (C) 2014 by Christian Fillion
16
+ #
17
+ # @see Builder Configuration syntax (DSL)
18
+ # @see Actions List of predefined actions
19
+ # @see Helpers List of predefined helpers
20
+ # @see Adapters Available adapters
21
+ module MiddleSquid
22
+ require 'middle_squid/actions'
23
+ require 'middle_squid/database'
24
+ require 'middle_squid/helpers'
25
+
26
+ module Adapters
27
+ require 'middle_squid/adapter'
28
+ require 'middle_squid/adapters/squid'
29
+ end
30
+
31
+ module Backends
32
+ require 'middle_squid/backends/keyboard'
33
+ require 'middle_squid/backends/thin'
34
+ end
35
+
36
+ require 'middle_squid/blacklist'
37
+ require 'middle_squid/builder'
38
+ require 'middle_squid/cli'
39
+ require 'middle_squid/exceptions'
40
+ require 'middle_squid/indexer'
41
+ require 'middle_squid/runner'
42
+ require 'middle_squid/server'
43
+ require 'middle_squid/uri'
44
+
45
+ require 'middle_squid/core_ext/hash'
46
+ end
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path '../lib', __FILE__
3
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include? lib
4
+
5
+ require 'middle_squid/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'middle_squid'
9
+ spec.version = MiddleSquid::VERSION
10
+ spec.authors = ['Christian Fillion']
11
+ spec.email = ['middle_squid@cfillion.tk']
12
+ spec.summary = 'A redirector, url mangler and webpage interceptor for the Squid HTTP proxy'
13
+ spec.homepage = 'https://github.com/cfillion/middle_squid'
14
+ spec.license = 'GPL-3.0+'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler', '~> 1.7'
22
+ spec.add_development_dependency 'coveralls', '~> 0.7'
23
+ spec.add_development_dependency 'minitest', '~> 5.4'
24
+ spec.add_development_dependency 'rack-test', '~> 0.6'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'simplecov', '~> 0.9'
27
+ spec.add_development_dependency 'thin-async-test', '~> 1.0'
28
+ spec.add_development_dependency 'webmock', '~> 1.18'
29
+
30
+ spec.add_runtime_dependency 'addressable', '~> 2.3'
31
+ spec.add_runtime_dependency 'em-http-request', '~> 1.1'
32
+ spec.add_runtime_dependency 'eventmachine', '~> 1.0'
33
+ spec.add_runtime_dependency 'sqlite3', '~> 1.3'
34
+ spec.add_runtime_dependency 'thin', '~> 1.6'
35
+ spec.add_runtime_dependency 'thin_async', '~> 0.1'
36
+ spec.add_runtime_dependency 'thor', '~> 0.19'
37
+ end
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+
3
+ GEM_HOME=$(ruby -e 'puts Gem.user_dir')
4
+ exec $GEM_HOME/bin/middle_squid $*
data/test/helper.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ Coveralls::Output.silent = true
5
+
6
+ SimpleCov.formatters = [
7
+ SimpleCov::Formatter::HTMLFormatter,
8
+ Coveralls::SimpleCov::Formatter
9
+ ]
10
+
11
+ SimpleCov.start {
12
+ project_name 'MiddleSquid'
13
+ add_filter '/test/'
14
+
15
+ add_group 'Adapters', '/adapter'
16
+ add_group 'Backends', '/backend'
17
+ }
18
+
19
+ require 'minitest/autorun'
20
+ require 'rack/test'
21
+ require 'thin/async/test'
22
+ require 'webmock/minitest'
23
+
24
+ require 'middle_squid'
25
+
26
+ MiddleSquid::Database.setup ':memory:'
@@ -0,0 +1 @@
1
+ google.com\path\to\file
@@ -0,0 +1,2 @@
1
+ ads.google.com
2
+ doubleclick.net
@@ -0,0 +1 @@
1
+ google.com/adsense
@@ -0,0 +1,2 @@
1
+ xiti.com
2
+ google-analytics.com
@@ -0,0 +1,2 @@
1
+ feedproxy.google.com/~r/
2
+ cloudfront-labs.amazonaws.com/x.png
@@ -0,0 +1,2 @@
1
+ host.com
2
+ host.com/path
@@ -0,0 +1,2 @@
1
+ host.com
2
+ host.com/path
@@ -0,0 +1,2 @@
1
+ host.com
2
+ host.com/path
@@ -0,0 +1,2 @@
1
+ host.com
2
+ host.com/path
@@ -0,0 +1,2 @@
1
+ host.com
2
+ host.com/path
@@ -0,0 +1,2 @@
1
+ host.com
2
+ host.com/path
@@ -0,0 +1,2 @@
1
+ host.com
2
+ host.com/path
@@ -0,0 +1,2 @@
1
+ host.com
2
+ host.com/path
File without changes
@@ -0,0 +1 @@
1
+ host.com/
@@ -0,0 +1,3 @@
1
+ (/ads/|/ad/|/banner/|/sponsor/|/event.ng/|/Advertisement/|adverts/)
2
+ [0-9]
3
+ ^[0-9]+
@@ -0,0 +1,2 @@
1
+ 000webhost.com
2
+ comcast.com
@@ -0,0 +1 @@
1
+ www.telus.com/content/internet/
@@ -0,0 +1,2 @@
1
+ reddit.com
2
+ news.ycombinator.com
@@ -0,0 +1,2 @@
1
+ puts "hello #{self}"
2
+ run proc {}
@@ -0,0 +1 @@
1
+ host.com/path_with_�_invalid_byte
@@ -0,0 +1,2 @@
1
+ url.com/path
2
+ domain.com
File without changes
@@ -0,0 +1,2 @@
1
+ before.com/path
2
+ after.com/path
@@ -0,0 +1,76 @@
1
+ require File.expand_path '../helper', __FILE__
2
+
3
+ class TestActions < MiniTest::Test
4
+ FakeClass = Class.new { include MiddleSquid::Actions }
5
+
6
+ def setup
7
+ @obj = FakeClass.new
8
+ end
9
+
10
+ def test_accept
11
+ action = catch :action do
12
+ @obj.accept
13
+ end
14
+
15
+ assert_equal [:accept, {}], action
16
+ end
17
+
18
+ def test_redirect_301
19
+ action = catch :action do
20
+ @obj.redirect_to 'url'
21
+ end
22
+
23
+ assert_equal [:redirect, {
24
+ :status => 301,
25
+ :url => 'url'
26
+ }], action
27
+ end
28
+
29
+ def test_redirect_custom_status
30
+ action = catch :action do
31
+ @obj.redirect_to 'new url', status: 418
32
+ end
33
+
34
+ assert_equal [:redirect, {
35
+ :status => 418,
36
+ :url => 'new url'
37
+ }], action
38
+ end
39
+
40
+ def test_replace
41
+ action = catch :action do
42
+ @obj.replace_by 'http://duckduckgo.com/'
43
+ end
44
+
45
+ assert_equal [:replace, {
46
+ :url => 'http://duckduckgo.com/'
47
+ }], action
48
+ end
49
+
50
+ def test_intercept
51
+ mock = MiniTest::Mock.new
52
+ mock.expect :token_for, 'qwfpgjluy', [Proc]
53
+ mock.expect :host, '127.0.0.1'
54
+ mock.expect :port, 8901
55
+ @obj.define_singleton_method(:server) { mock }
56
+
57
+ action = catch :action do
58
+ EM.run {
59
+ @obj.intercept {}
60
+ EM.next_tick { EM.stop }
61
+ }
62
+ end
63
+
64
+ assert_equal [:replace, {
65
+ :url => 'http://127.0.0.1:8901/qwfpgjluy'
66
+ }], action
67
+
68
+ mock.verify
69
+ end
70
+
71
+ def test_intercept_requires_a_block
72
+ assert_raises ArgumentError do
73
+ @obj.intercept
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,61 @@
1
+ require File.expand_path '../helper', __FILE__
2
+
3
+ class TestAdapter < MiniTest::Test
4
+ def setup
5
+ @obj = MiddleSquid::Adapter.new
6
+ end
7
+
8
+ def test_handler
9
+ bag = []
10
+
11
+ @obj.handler = proc {|*args| bag << args }
12
+
13
+ @obj.define_singleton_method(:output) { |*args| bag << args }
14
+
15
+ @obj.handle 'http://test.com', ['hello', 'world']
16
+
17
+ uri, extras = bag.shift
18
+ assert_instance_of MiddleSquid::URI, uri
19
+ assert_equal 'http://test.com', uri.to_s
20
+ assert_equal ['hello', 'world'], extras
21
+
22
+ assert_equal [:accept, {}], bag.shift # default action
23
+
24
+ assert_empty bag
25
+ end
26
+
27
+ def test_default_output
28
+ assert_raises NotImplementedError do
29
+ @obj.output :accept, {}
30
+ end
31
+ end
32
+
33
+ def test_overridden_output
34
+ bag = []
35
+
36
+ @obj.handler = proc { throw :action, [:type, :options] }
37
+
38
+ @obj.define_singleton_method(:output) { |*args| bag << args }
39
+
40
+ @obj.handle 'http://test.com', []
41
+
42
+ assert_equal [:type, :options], bag.shift
43
+ assert_empty bag
44
+ end
45
+
46
+ def test_empty_url
47
+ error = assert_raises MiddleSquid::InvalidURIError do
48
+ @obj.handle nil, []
49
+ end
50
+
51
+ assert_equal "invalid URL received: ''", error.message
52
+ end
53
+
54
+ def test_invalid_url
55
+ error = assert_raises MiddleSquid::InvalidURIError do
56
+ @obj.handle 'hello world', []
57
+ end
58
+
59
+ assert_equal "invalid URL received: 'hello world'", error.message
60
+ end
61
+ end
@@ -0,0 +1,189 @@
1
+ require File.expand_path '../helper', __FILE__
2
+
3
+ class TestBlackList < MiniTest::Test
4
+ include MiddleSquid::Database
5
+
6
+ DOMAINS = %w{
7
+ .cfillion.tk
8
+ .duckduckgo.com
9
+ .sub.stackoverflow.com
10
+ .analytics.google.com
11
+ .anidb.net
12
+ }
13
+
14
+ URLS = [
15
+ %w[.google.com analytics/],
16
+ %w[.youtube.com watch/],
17
+ %w[.github.com user/],
18
+ %w[.host.net file.html/],
19
+ %w[.host.net index.html/],
20
+ ]
21
+
22
+ def setup
23
+ db.transaction
24
+
25
+ db.execute 'DELETE FROM domains'
26
+ db.execute 'DELETE FROM urls'
27
+
28
+ DOMAINS.each_with_index {|domain, index|
29
+ db.execute 'INSERT INTO domains (category, host) VALUES (?, ?)',
30
+ [index % 2 == 0 ? 'even' : 'odd', domain]
31
+ }
32
+
33
+ URLS.each_with_index {|url, index|
34
+ db.execute 'INSERT INTO urls (category, host, path) VALUES (?, ?, ?)',
35
+ [index % 2 == 0 ? 'even' : 'odd', *url]
36
+ }
37
+
38
+ db.commit
39
+ end
40
+
41
+ def test_category
42
+ bl = MiddleSquid::BlackList.new 'cat_name'
43
+ assert_equal 'cat_name', bl.category
44
+ end
45
+
46
+ def test_aliases_default
47
+ bl = MiddleSquid::BlackList.new 'cat_name'
48
+ assert_equal [], bl.aliases
49
+ end
50
+
51
+ def test_aliases_custom
52
+ bl = MiddleSquid::BlackList.new 'cat_name', aliases: ['name_cat']
53
+ assert_equal ['name_cat'], bl.aliases
54
+ end
55
+
56
+ def test_unmatch
57
+ uri = MiddleSquid::URI.parse('http://anidb.net/a9002')
58
+ odd = MiddleSquid::BlackList.new 'odd'
59
+
60
+ refute odd.include_domain? uri
61
+ refute odd.include_url? uri
62
+ refute odd.include? uri
63
+ end
64
+
65
+ def test_domain
66
+ uri = MiddleSquid::URI.parse('http://cfillion.tk/love-of-babble/')
67
+ even = MiddleSquid::BlackList.new 'even'
68
+
69
+ assert even.include_domain? uri
70
+ refute even.include_url? uri
71
+ assert even.include? uri
72
+ end
73
+
74
+ def test_domain_trailing_dots
75
+ uri = MiddleSquid::URI.parse('http://cfillion.tk...')
76
+ even = MiddleSquid::BlackList.new 'even'
77
+
78
+ assert even.include_domain? uri
79
+ assert even.include? uri
80
+ end
81
+
82
+ def test_domain_partial_match
83
+ uri1 = MiddleSquid::URI.parse('http://www.anidb.net/')
84
+ uri2 = MiddleSquid::URI.parse('http://abcde.anidb.net/')
85
+ uri3 = MiddleSquid::URI.parse('http://bus.sub.stackoverflow.com/')
86
+ even = MiddleSquid::BlackList.new 'even'
87
+
88
+ assert even.include_domain?(uri1), 'www'
89
+ assert even.include? uri1
90
+
91
+ assert even.include_domain?(uri2), 'any subdomain'
92
+ assert even.include? uri2
93
+
94
+ assert even.include_domain?(uri3), 'sub-sub-domain'
95
+ assert even.include? uri3
96
+ end
97
+
98
+ def test_domain_right_match
99
+ uri = MiddleSquid::URI.parse('http://google.com')
100
+ odd = MiddleSquid::BlackList.new 'odd'
101
+
102
+ refute odd.include_domain? uri
103
+ refute odd.include? uri
104
+ end
105
+
106
+ def test_domain_left_match
107
+ uri = MiddleSquid::URI.parse('http://duckduckgo/')
108
+ odd = MiddleSquid::BlackList.new 'odd'
109
+
110
+ refute odd.include_domain? uri
111
+ refute odd.include? uri
112
+ end
113
+
114
+ def test_domain_wrong_subdomain
115
+ uri = MiddleSquid::URI.parse('http://fakeanalytics.google.com/')
116
+ odd = MiddleSquid::BlackList.new 'odd'
117
+
118
+ refute odd.include_domain? uri
119
+ refute odd.include? uri
120
+ end
121
+
122
+ def test_url
123
+ uri = MiddleSquid::URI.parse('http://google.com/analytics')
124
+ even = MiddleSquid::BlackList.new 'even'
125
+
126
+ assert even.include_url? uri
127
+ refute even.include_domain? uri
128
+ assert even.include? uri
129
+ end
130
+
131
+ def test_url_wrong_extension
132
+ uri = MiddleSquid::URI.parse('http://host.net/file.php')
133
+ odd = MiddleSquid::BlackList.new 'odd'
134
+
135
+ refute odd.include_url? uri
136
+ refute odd.include? uri
137
+ end
138
+
139
+ def test_url_partial_path
140
+ uri = MiddleSquid::URI.parse('http://github.com/us')
141
+ odd = MiddleSquid::BlackList.new 'odd'
142
+
143
+ refute odd.include_url? uri
144
+ refute odd.include? uri
145
+ end
146
+
147
+ def test_url_left_match
148
+ uri = MiddleSquid::URI.parse('http://host.net/file.html_ohno')
149
+ odd = MiddleSquid::BlackList.new 'odd'
150
+
151
+ refute odd.include_url? uri
152
+ refute odd.include? uri
153
+ end
154
+
155
+ def test_url_longer_path
156
+ uri = MiddleSquid::URI.parse('http://github.com/user/repository')
157
+ even = MiddleSquid::BlackList.new 'even'
158
+
159
+ assert even.include_url? uri
160
+ assert even.include? uri
161
+ end
162
+
163
+ def test_url_query_string
164
+ uri = MiddleSquid::URI.parse('http://github.com/user?tab=repositories')
165
+ even = MiddleSquid::BlackList.new 'even'
166
+
167
+ assert even.include_url? uri
168
+ assert even.include? uri
169
+ end
170
+
171
+ def test_url_cheap_tricks
172
+ uri = MiddleSquid::URI.parse('http://google.com//maps/../analytics/./')
173
+ even = MiddleSquid::BlackList.new 'even'
174
+
175
+ assert even.include_url? uri
176
+ assert even.include? uri
177
+ end
178
+
179
+ def test_group_demo
180
+ uri = MiddleSquid::URI.parse('http://youtube.com/watch?v=test')
181
+
182
+ even = MiddleSquid::BlackList.new 'even'
183
+ odd = MiddleSquid::BlackList.new 'odd'
184
+
185
+ group = [even, odd]
186
+
187
+ assert group.any? {|bl| bl.include? uri }
188
+ end
189
+ end