google_safe_browsing 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.mkd +101 -0
- data/Rakefile +26 -0
- data/lib/generators/install_generator.rb +25 -0
- data/lib/generators/templates/create_google_safe_browsing_tables.rb +37 -0
- data/lib/google_safe_browsing.rb +58 -0
- data/lib/google_safe_browsing/add_shavar.rb +5 -0
- data/lib/google_safe_browsing/api_v2.rb +60 -0
- data/lib/google_safe_browsing/binary_helper.rb +40 -0
- data/lib/google_safe_browsing/canonicalize.rb +181 -0
- data/lib/google_safe_browsing/chunk_helper.rb +77 -0
- data/lib/google_safe_browsing/effective_tld_names.dat.txt +5197 -0
- data/lib/google_safe_browsing/full_hash.rb +5 -0
- data/lib/google_safe_browsing/google_safe_browsing_railtie.rb +19 -0
- data/lib/google_safe_browsing/hash_helper.rb +29 -0
- data/lib/google_safe_browsing/http_helper.rb +42 -0
- data/lib/google_safe_browsing/rescheduler.rb +14 -0
- data/lib/google_safe_browsing/response_helper.rb +181 -0
- data/lib/google_safe_browsing/sub_shavar.rb +6 -0
- data/lib/google_safe_browsing/top_level_domain.rb +50 -0
- data/lib/google_safe_browsing/version.rb +3 -0
- data/lib/tasks/google_safe_browsing_tasks.rake +11 -0
- metadata +173 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module GoogleSafeBrowsing
|
2
|
+
class GoogleSafeBrowsingRailtie < Rails::Railtie
|
3
|
+
config.google_safe_browsing = ActiveSupport::OrderedOptions.new
|
4
|
+
|
5
|
+
rake_tasks do
|
6
|
+
load File.expand_path('../../tasks/google_safe_browsing_tasks.rake', __FILE__)
|
7
|
+
end
|
8
|
+
|
9
|
+
generators do
|
10
|
+
require File.expand_path('../../generators/install_generator', __FILE__)
|
11
|
+
end
|
12
|
+
|
13
|
+
initializer 'google_safe_browsing.set_api_key' do |app|
|
14
|
+
GoogleSafeBrowsing.configure do |config|
|
15
|
+
config.api_key = app.config.google_safe_browsing[:api_key]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module GoogleSafeBrowsing
|
2
|
+
class HashHelper
|
3
|
+
|
4
|
+
class GsbHash
|
5
|
+
def initialize(hash)
|
6
|
+
@hash = hash
|
7
|
+
end
|
8
|
+
|
9
|
+
def prefix
|
10
|
+
@hash[0..7]
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
@hash
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.urls_to_hashes(urls)
|
20
|
+
hashes = []
|
21
|
+
urls.each do |u|
|
22
|
+
hash = ( Digest::SHA256.new << u ).to_s
|
23
|
+
hashes << GsbHash.new(hash)
|
24
|
+
#puts "#{u} -- #{hash}"
|
25
|
+
end
|
26
|
+
hashes
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module GoogleSafeBrowsing
|
2
|
+
class HttpHelper
|
3
|
+
def self.uri_builder(action)
|
4
|
+
uri = URI("#{GoogleSafeBrowsing.config.host}/#{action}#{encoded_params}")
|
5
|
+
uri
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.encoded_params
|
9
|
+
"?client=#{GoogleSafeBrowsing.config.client}" +
|
10
|
+
"&apikey=#{GoogleSafeBrowsing.config.api_key}" +
|
11
|
+
"&appver=#{GoogleSafeBrowsing.config.app_ver}" +
|
12
|
+
"&pver=#{GoogleSafeBrowsing.config.p_ver}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.request_full_hashes(hash_array)
|
16
|
+
uri = uri_builder('gethash')
|
17
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
18
|
+
request.body = "4:#{hash_array.length * 4}\n"
|
19
|
+
hash_array.each do |h|
|
20
|
+
request.body << BinaryHelper.hex_to_bin(h[0..7])
|
21
|
+
end
|
22
|
+
|
23
|
+
response = Net::HTTP.start(uri.host) { |http| http.request request }
|
24
|
+
|
25
|
+
ResponseHelper.parse_full_hash_response(response.body)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.get_data(list=nil)
|
29
|
+
# Get (via Post) List Data
|
30
|
+
uri = uri_builder('downloads')
|
31
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
32
|
+
request.body = ChunkHelper.build_chunk_list(list)
|
33
|
+
|
34
|
+
Net::HTTP.start(uri.host) { |http| http.request request }
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_lists
|
38
|
+
uri = uri_builder('list')
|
39
|
+
lists = Net::HTTP.get(uri).split("\n")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module GoogleSafeBrowsing
|
2
|
+
class Rescheduler
|
3
|
+
@queue = :google_safe_browsing
|
4
|
+
|
5
|
+
def self.perform
|
6
|
+
puts "Running Update"
|
7
|
+
delay = APIv2.update
|
8
|
+
puts "Scheduling new update in #{delay} seconds"
|
9
|
+
Resque.enqueue_in(delay.seconds, Rescheduler)
|
10
|
+
puts "Update scheduled"
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
module GoogleSafeBrowsing
|
2
|
+
class ResponseHelper
|
3
|
+
def self.parse_full_hash_response(response)
|
4
|
+
f = StringIO.new(response)
|
5
|
+
|
6
|
+
full_hashes = []
|
7
|
+
while(! f.eof? )
|
8
|
+
hash = {}
|
9
|
+
|
10
|
+
meta = f.gets.chomp.split(':')
|
11
|
+
hash[:list] = meta[0]
|
12
|
+
hash[:add_chunk_num] = meta[1]
|
13
|
+
|
14
|
+
hash[:full_hash] = f.read(meta[2].to_i).unpack('H*')[0]
|
15
|
+
full_hashes << hash
|
16
|
+
end
|
17
|
+
full_hashes
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parse_data_response(response)
|
21
|
+
#print "\n\n#{response}\n\n"
|
22
|
+
data_urls = []
|
23
|
+
ret = {}
|
24
|
+
|
25
|
+
ret[:lists] = []
|
26
|
+
ret[:data_urls] = {}
|
27
|
+
|
28
|
+
current_list = ''
|
29
|
+
response.split("\n").each do |line|
|
30
|
+
vals = line.split(':')
|
31
|
+
case vals[0]
|
32
|
+
when 'n'
|
33
|
+
ret[:delay_seconds] = vals[1].to_i
|
34
|
+
@delay_seconds = ret[:delay_seconds].to_i
|
35
|
+
when 'i'
|
36
|
+
ret[:lists] << vals[1]
|
37
|
+
current_list = vals[1]
|
38
|
+
ret[:data_urls][current_list] = []
|
39
|
+
when 'u'
|
40
|
+
ret[:data_urls][current_list] << vals[1]
|
41
|
+
when 'r'
|
42
|
+
# reset (delete all data and try again)
|
43
|
+
when 'ad'
|
44
|
+
# vals[1] is a CHUNKLIST number or range representing add chunks to delete
|
45
|
+
# we can also delete the associated Shavar Hashes
|
46
|
+
# we no longer have to report hat we received these chunks
|
47
|
+
chunk_number_clause = ChunkHelper.chunklist_to_sql(vals[1])
|
48
|
+
AddShavar.delete_all([ "list = ? and (#{chunk_number_clause})", current_list ])
|
49
|
+
when 'sd'
|
50
|
+
# vals[1] is a CHUNKLIST number or range representing sub chunks to delete
|
51
|
+
# we no longer have to report hat we received these chunks
|
52
|
+
chunk_number_clause = ChunkHelper.chunklist_to_sql(vals[1])
|
53
|
+
SubShavar.delete_all([ "list = ? and (#{chunk_number_clause})", current_list ])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#ret[:data_urls] = data_urls
|
58
|
+
|
59
|
+
ret
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.receive_data(url, list)
|
63
|
+
|
64
|
+
open(url) do |f|
|
65
|
+
while(line = f.gets)
|
66
|
+
line_actions = parse_data_line(line)
|
67
|
+
|
68
|
+
chunk = f.read(line_actions[:chunk_length])
|
69
|
+
# f iterator is now set for next chunk
|
70
|
+
|
71
|
+
add_attrs = { :chunk_number => line_actions[:chunk_number],
|
72
|
+
:list => list, :prefix => nil, :host_key => nil
|
73
|
+
}
|
74
|
+
|
75
|
+
case line_actions[:action]
|
76
|
+
when 'a'
|
77
|
+
if line_actions[:chunk_length] == 0
|
78
|
+
record_add_shavar_to_insert(add_attrs)
|
79
|
+
else
|
80
|
+
chunk_iterator = chunk.bytes
|
81
|
+
counter = 0
|
82
|
+
begin
|
83
|
+
while true
|
84
|
+
add_attrs[:host_key] = BinaryHelper.read_bytes_as_hex(chunk_iterator, 4)
|
85
|
+
count = chunk_iterator.next
|
86
|
+
if count > 0
|
87
|
+
count.times do |i|
|
88
|
+
add_attrs[:prefix] = BinaryHelper.read_bytes_as_hex(chunk_iterator, line_actions[:hash_length])
|
89
|
+
record_add_shavar_to_insert(add_attrs)
|
90
|
+
end
|
91
|
+
else
|
92
|
+
add_attrs[:prefix] = add_attrs[:host_key]
|
93
|
+
record_add_shavar_to_insert(add_attrs)
|
94
|
+
end
|
95
|
+
counter += 1
|
96
|
+
end
|
97
|
+
rescue StopIteration
|
98
|
+
puts "Added #{counter} host_keys for add chunk number #{line_actions[:chunk_number]}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
when 's'
|
102
|
+
sub_attrs = add_attrs.merge({ :add_chunk_number => nil })
|
103
|
+
if line_actions[:chunk_length] == 0
|
104
|
+
record_sub_shavar_to_insert(sub_attrs)
|
105
|
+
else
|
106
|
+
chunk_iterator = chunk.bytes
|
107
|
+
counter = 0
|
108
|
+
begin
|
109
|
+
while true
|
110
|
+
sub_attrs[:host_key] = BinaryHelper.read_bytes_as_hex(chunk_iterator, 4)
|
111
|
+
count = chunk_iterator.next
|
112
|
+
if count > 0
|
113
|
+
count.times do |i|
|
114
|
+
sub_attrs[:add_chunk_number] = BinaryHelper.unpack_add_chunk_num(BinaryHelper.read_bytes_from(chunk_iterator, 4))
|
115
|
+
sub_attrs[:prefix] = BinaryHelper.read_bytes_as_hex(chunk_iterator, line_actions[:hash_length])
|
116
|
+
record_sub_shavar_to_insert(sub_attrs)
|
117
|
+
end
|
118
|
+
else
|
119
|
+
sub_attrs[:add_chunk_number] = BinaryHelper.unpack_add_chunk_num(BinaryHelper.read_bytes_from(chunk_iterator, 4))
|
120
|
+
sub_attrs[:prefix] = sub_attrs[:host_key]
|
121
|
+
record_sub_shavar_to_insert(sub_attrs)
|
122
|
+
end
|
123
|
+
counter += 1
|
124
|
+
end
|
125
|
+
rescue StopIteration
|
126
|
+
puts "Added #{counter} host_keys for sub chunk number #{line_actions[:chunk_number]}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
else
|
130
|
+
puts "neither a nor s ======================================================="
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# actually perform inserts
|
137
|
+
while @add_shavar_values && @add_shavar_values.any?
|
138
|
+
AddShavar.connection.execute( "insert into gsb_add_shavars (prefix, host_key, chunk_number, list) " +
|
139
|
+
" values #{@add_shavar_values.pop(10000).join(', ')}")
|
140
|
+
end
|
141
|
+
while @sub_shavar_values && @sub_shavar_values.any?
|
142
|
+
SubShavar.connection.execute( "insert into gsb_sub_shavars (prefix, host_key, add_chunk_number, chunk_number, list) " +
|
143
|
+
" values #{@sub_shavar_values.pop(10000).join(', ')}")
|
144
|
+
end
|
145
|
+
# remove invalid full_hases
|
146
|
+
FullHash.connection.execute("delete from gsb_full_hashes using gsb_full_hashes " +
|
147
|
+
"inner join gsb_sub_shavars on " +
|
148
|
+
"gsb_sub_shavars.add_chunk_number = gsb_full_hashes.add_chunk_number " +
|
149
|
+
"and gsb_sub_shavars.list = gsb_full_hashes.list;")
|
150
|
+
@add_shavar_values = []
|
151
|
+
@sub_shavar_values = []
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.record_add_shavar_to_insert(h)
|
155
|
+
@add_shavar_values ||= []
|
156
|
+
@add_shavar_values << "('#{h[:prefix]}', '#{h[:host_key]}', '#{h[:chunk_number]}', '#{h[:list]}')"
|
157
|
+
end
|
158
|
+
def self.record_sub_shavar_to_insert(h)
|
159
|
+
@sub_shavar_values ||= []
|
160
|
+
@sub_shavar_values << "('#{h[:prefix]}', '#{h[:host_key]}', '#{h[:add_chunk_number]}', '#{h[:chunk_number]}', '#{h[:list]}')"
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.parse_data_line(line)
|
164
|
+
split_line = line.split(':')
|
165
|
+
|
166
|
+
ret = {}
|
167
|
+
ret[ :action ] = split_line[0]
|
168
|
+
ret[ :chunk_number ] = split_line[1].to_i
|
169
|
+
ret[ :hash_length ] = split_line[2].to_i
|
170
|
+
ret[ :chunk_length ] = split_line[3].to_i
|
171
|
+
|
172
|
+
#puts "Chunk ##{s_chunk_count + a_chunk_count}"
|
173
|
+
#puts "Action: #{action}"
|
174
|
+
#puts "Chunk Number: #{split_line[1]}"
|
175
|
+
#puts "Hash Length: #{hash_length}"
|
176
|
+
#puts "Chunk Length: #{chunk_length}"
|
177
|
+
##puts "Chuch Data:\n#{chunk}\nend"
|
178
|
+
ret
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module GoogleSafeBrowsing
|
2
|
+
class TopLevelDomain
|
3
|
+
|
4
|
+
def self.from_host(host)
|
5
|
+
components = host.split('.')
|
6
|
+
|
7
|
+
tlds = parse_tld_to_hash
|
8
|
+
|
9
|
+
tld = components.pop
|
10
|
+
components.reverse.each do |comp|
|
11
|
+
next_tld = "#{comp}.#{tld}"
|
12
|
+
|
13
|
+
if tlds[next_tld]
|
14
|
+
tld = next_tld
|
15
|
+
else
|
16
|
+
break
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
tld
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.split_from_host(host)
|
24
|
+
components = host.split('.')
|
25
|
+
|
26
|
+
tlds = parse_tld_to_hash
|
27
|
+
|
28
|
+
next_tld = components[-2..-1].join('.')
|
29
|
+
while tlds[next_tld]
|
30
|
+
tmp = components.pop
|
31
|
+
components[-1] = components.last + '.' + tmp
|
32
|
+
next_tld = components[-2..-1].join('.')
|
33
|
+
end
|
34
|
+
|
35
|
+
components
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def self.parse_tld_to_hash
|
42
|
+
hash = Hash.new(nil)
|
43
|
+
f = File.open(File.dirname(__FILE__) + '/effective_tld_names.dat.txt', 'r')
|
44
|
+
while(line = f.gets)
|
45
|
+
hash[line.chomp] = true unless line[0..1] == '//'
|
46
|
+
end
|
47
|
+
hash
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
namespace :google_safe_browsing do
|
2
|
+
desc "Performs an Update"
|
3
|
+
task :update => :environment do
|
4
|
+
GoogleSafeBrowsing::APIv2.update
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "Enqueues an Update via Rescheduler, which will reschedule another Update the appropriate number of seconds in the future"
|
8
|
+
task :update_and_reschedule => :environment do
|
9
|
+
GoogleSafeBrowsing.kick_off
|
10
|
+
end
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: google_safe_browsing
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Chris Marshall
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-02-28 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
hash: 3
|
28
|
+
segments:
|
29
|
+
- 3
|
30
|
+
- 1
|
31
|
+
- 0
|
32
|
+
version: 3.1.0
|
33
|
+
requirement: *id001
|
34
|
+
name: rails
|
35
|
+
type: :runtime
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 57
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
- 9
|
47
|
+
- 1
|
48
|
+
version: 0.9.1
|
49
|
+
requirement: *id002
|
50
|
+
name: ruby-ip
|
51
|
+
type: :runtime
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
prerelease: false
|
54
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
requirement: *id003
|
64
|
+
name: activerecord
|
65
|
+
type: :runtime
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
requirement: *id004
|
78
|
+
name: sqlite3
|
79
|
+
type: :development
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
prerelease: false
|
82
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
requirement: *id005
|
92
|
+
name: rspec-rails
|
93
|
+
type: :development
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
prerelease: false
|
96
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
hash: 3
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
requirement: *id006
|
106
|
+
name: generator_spec
|
107
|
+
type: :development
|
108
|
+
description: Rails 3 plugin using Google's Safe Browsing API for url lookup against Malware and Phishing blacklists. Implementation includes storing and updating locally stored shavar lists and url lookup methods.
|
109
|
+
email: chris@chrismar035.com
|
110
|
+
executables: []
|
111
|
+
|
112
|
+
extensions: []
|
113
|
+
|
114
|
+
extra_rdoc_files: []
|
115
|
+
|
116
|
+
files:
|
117
|
+
- lib/generators/install_generator.rb
|
118
|
+
- lib/generators/templates/create_google_safe_browsing_tables.rb
|
119
|
+
- lib/google_safe_browsing/add_shavar.rb
|
120
|
+
- lib/google_safe_browsing/api_v2.rb
|
121
|
+
- lib/google_safe_browsing/binary_helper.rb
|
122
|
+
- lib/google_safe_browsing/canonicalize.rb
|
123
|
+
- lib/google_safe_browsing/chunk_helper.rb
|
124
|
+
- lib/google_safe_browsing/effective_tld_names.dat.txt
|
125
|
+
- lib/google_safe_browsing/full_hash.rb
|
126
|
+
- lib/google_safe_browsing/google_safe_browsing_railtie.rb
|
127
|
+
- lib/google_safe_browsing/hash_helper.rb
|
128
|
+
- lib/google_safe_browsing/http_helper.rb
|
129
|
+
- lib/google_safe_browsing/rescheduler.rb
|
130
|
+
- lib/google_safe_browsing/response_helper.rb
|
131
|
+
- lib/google_safe_browsing/sub_shavar.rb
|
132
|
+
- lib/google_safe_browsing/top_level_domain.rb
|
133
|
+
- lib/google_safe_browsing/version.rb
|
134
|
+
- lib/google_safe_browsing.rb
|
135
|
+
- lib/tasks/google_safe_browsing_tasks.rake
|
136
|
+
- MIT-LICENSE
|
137
|
+
- Rakefile
|
138
|
+
- README.mkd
|
139
|
+
homepage: https://github.com/chrismar035/google_safe_browsing
|
140
|
+
licenses: []
|
141
|
+
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
|
145
|
+
require_paths:
|
146
|
+
- lib
|
147
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
148
|
+
none: false
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
hash: 3
|
153
|
+
segments:
|
154
|
+
- 0
|
155
|
+
version: "0"
|
156
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
157
|
+
none: false
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
hash: 3
|
162
|
+
segments:
|
163
|
+
- 0
|
164
|
+
version: "0"
|
165
|
+
requirements: []
|
166
|
+
|
167
|
+
rubyforge_project:
|
168
|
+
rubygems_version: 1.8.16
|
169
|
+
signing_key:
|
170
|
+
specification_version: 3
|
171
|
+
summary: Rails 3 plugin for Google's Safe Browsing API v2
|
172
|
+
test_files: []
|
173
|
+
|