vtapi 0.1.0 → 0.2.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.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # VtAPI
1
+ # VtAPI [![Gem Version](https://badge.fury.io/rb/vtapi.png)](http://badge.fury.io/rb/vtapi)
2
2
 
3
3
  Ruby gem for VirusTotal Public API v2.0
4
4
 
@@ -37,7 +37,24 @@ puts resp.positives # num of positives
37
37
  puts resp.scan_results # {"McAfee"=>nil, "Symantec"=>"Android.ZertSecurity", ... }
38
38
  ```
39
39
 
40
- ### File Upload
40
+ ### File Report (multiple resources)
41
+ ```ruby
42
+ # retrieve file report by file hash(SHA256, SHA1, MD5)
43
+ # up to 4 resources can assign
44
+ resources = ['00ce460c8b33711091206..', ..]
45
+ resps = api.file_report(resources)
46
+ resps.each do |r|
47
+ puts "#{r.sha256}: #{r.positives} / #{r.total}" if r.response_code == 1
48
+ end
49
+ ```
50
+ The following APIs are supported Posting multi resources(URLs).
51
+ * VtAPI#file_report
52
+ * VtAPI#file_rescan
53
+ * VtAPI#url_scan
54
+ * VtAPI#url_report
55
+
56
+
57
+ ### File Scan (File Upload)
41
58
  ```ruby
42
59
  # read file
43
60
  data = File.open(some_path, 'rb') {|f| f.read }
@@ -49,21 +66,43 @@ resp = api.file_scan(data)
49
66
  puts resp.response_code # 1: OK, 0: result doesn't exist, -2: still queued
50
67
  ```
51
68
 
69
+
70
+ ### URL Scan
71
+ ```ruby
72
+ # upload url
73
+ resp = api.url_scan(url)
74
+
75
+ # confirm response_code
76
+ puts resp.response_code # 1: OK, 0: result doesn't exist, -2: still queued
77
+ ```
78
+
79
+
80
+ ### URL Report
81
+ ```ruby
82
+ # upload url
83
+ resp = api.url_scan(url)
84
+
85
+ # confirm result
86
+ puts resp.scans
87
+ ```
88
+
89
+
52
90
  ## Features
53
- ### Supported API
91
+ ### Support API
54
92
  * file/scan
55
93
  * file/resan
56
94
  * file/report
57
-
58
- ### not implemented yet
59
95
  * url/scan
60
96
  * url/report
97
+
98
+ ### Not implemented yet
61
99
  * ip-address/report
62
100
  * domain/report
63
101
 
64
- ### unsupported API
102
+ ### Unsupported
65
103
  * comments/puts
66
104
 
105
+
67
106
  ## Contributing
68
107
 
69
108
  1. Fork it
data/lib/vtapi/api.rb CHANGED
@@ -24,13 +24,36 @@ class VtAPI
24
24
  end
25
25
 
26
26
  def file_rescan(resource)
27
+ if resource.is_a? Array
28
+ raise 'limit is up to 25 items' if resource.size > 25
29
+ resource = resource.join(', ')
30
+ end
27
31
  http_post('file/rescan', resource: resource)
28
32
  end
29
33
 
30
34
  def file_report(resource)
35
+ if resource.is_a? Array
36
+ raise 'limit is up to 4 items' if resource.size > 4
37
+ resource = resource.join(', ')
38
+ end
31
39
  http_post('file/report', resource: resource)
32
40
  end
33
41
 
42
+ def url_scan(url)
43
+ if url.is_a? Array
44
+ raise 'limit is up to 4 items' if url.size > 4
45
+ url = url.join("\n")
46
+ end
47
+ http_post('url/scan', url: url)
48
+ end
49
+
50
+ def url_report(url)
51
+ if url.is_a? Array
52
+ raise 'limit is up to 4 items' if url.size > 4
53
+ url = url.join(", ")
54
+ end
55
+ http_post('url/report', resource: url)
56
+ end
34
57
 
35
58
  def http_post(path, params = {})
36
59
  uri = BASE_URL + path
@@ -45,6 +68,6 @@ class VtAPI
45
68
  resp.return!(req, result, &block)
46
69
  end
47
70
  end
48
- Response.new(resp.body)
71
+ Response.parse(resp.body)
49
72
  end
50
73
  end
@@ -3,8 +3,17 @@ require 'time'
3
3
 
4
4
  class VtAPI
5
5
  class Response
6
- def initialize(response_body)
7
- @json = JSON.parse(response_body)
6
+ def self.parse(response_body)
7
+ json = JSON.parse(response_body)
8
+ if json.is_a? Array
9
+ return json.map{|e| Response.new(e) }
10
+ else
11
+ return Response.new(json)
12
+ end
13
+ end
14
+
15
+ def initialize(json)
16
+ @json = json
8
17
  @json.keys.each do |key|
9
18
  Response.class_eval {
10
19
  define_method key.to_s do |*args|
@@ -34,5 +43,9 @@ class VtAPI
34
43
  def [](key)
35
44
  @json.fetch(key.to_s) # raise KeyError when key doesn't exist.
36
45
  end
46
+
47
+ def to_s
48
+ @json.to_json
49
+ end
37
50
  end
38
51
  end
data/lib/vtapi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Vtapi
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -5,7 +5,9 @@ describe VtAPI::Response do
5
5
  let(:sample_response) {
6
6
  "{\"scans\":{\"McAfee\":{\"detected\":false,\"version\":\"5.400.0.1158\",\"result\":null,\"update\":\"20130512\"},\"Symantec\":{\"detected\":true,\"version\":\"20121.3.0.76\",\"result\":\"Android.ZertSecurity\",\"update\":\"20130512\"},\"Kaspersky\":{\"detected\":true,\"version\":\"9.0.0.837\",\"result\":\"HEUR:Trojan-Banker.AndroidOS.Zitmo.a\",\"update\":\"20130512\"},\"TrendMicro\":{\"detected\":false,\"version\":\"9.740.0.1012\",\"result\":null,\"update\":\"20130512\"},\"Microsoft\":{\"detected\":false,\"version\":\"1.9402\",\"result\":null,\"update\":\"20130512\"}},\"scan_id\":\"00ce460c8b337110912066f746731a916e85bf1d7f4b44f09ca3cc39f9b52a98-1368320515\",\"sha1\":\"e1b727b3e9336033606df79eeba03dd218b56c20\",\"resource\":\"00ce460c8b337110912066f746731a916e85bf1d7f4b44f09ca3cc39f9b52a98\",\"response_code\":1,\"scan_date\":\"2013-05-12 01:01:55\",\"permalink\":\"https://www.virustotal.com/file/00ce460c8b337110912066f746731a916e85bf1d7f4b44f09ca3cc39f9b52a98/analysis/1368320515/\",\"verbose_msg\":\"Scan finished, scan information embedded in this object\",\"total\":46,\"positives\":22,\"sha256\":\"00ce460c8b337110912066f746731a916e85bf1d7f4b44f09ca3cc39f9b52a98\",\"md5\":\"1cf41bdc0fdd409774eb755031a6f49d\"}"
7
7
  }
8
- let(:response) { VtAPI::Response.new(sample_response) }
8
+ let(:sample_json) { JSON.parse(sample_response) }
9
+ let(:response) { VtAPI::Response.new(sample_json) }
10
+
9
11
 
10
12
  describe '#md5' do
11
13
  it { expect(response.md5).to eq '1cf41bdc0fdd409774eb755031a6f49d' }
@@ -57,6 +59,14 @@ describe VtAPI::Response do
57
59
  subject { response.scan_results }
58
60
  it { expect(subject).to eq({"McAfee"=>nil, "Symantec"=>"Android.ZertSecurity", "Kaspersky"=>"HEUR:Trojan-Banker.AndroidOS.Zitmo.a", "TrendMicro"=>nil, "Microsoft"=>nil}) }
59
61
  end
62
+
63
+ describe '#to_s' do
64
+ subject { response.to_s }
65
+ it 'should be able to parse as json' do
66
+ expect{ JSON.parse(subject) }.to_not raise_error
67
+ end
68
+ it { expect(JSON.parse(subject).keys).to match_array ['scans', 'scan_id', 'sha1', 'resource', 'response_code', 'scan_date', 'permalink', 'verbose_msg', 'total', 'positives', 'sha256', 'md5'] }
69
+ end
60
70
  end
61
71
 
62
72
  context 'with no scan result' do
@@ -67,5 +77,28 @@ describe VtAPI::Response do
67
77
  describe '#positive_threats' do
68
78
  pending 'not implemented yet'
69
79
  end
80
+
81
+ end
82
+
83
+ describe '.parse' do
84
+ subject { VtAPI::Response.parse(response) }
85
+ context 'assigns single response' do
86
+ let(:response) { '{"md5":"1cf41bdc0fdd409774eb755031a6f49d"}' }
87
+ it { expect(subject).to be_a VtAPI::Response }
88
+ it { expect(subject.md5).to eq "1cf41bdc0fdd409774eb755031a6f49d" }
89
+ end
90
+
91
+ context 'assigns array response' do
92
+ subject {VtAPI::Response.parse(response) }
93
+ let(:response) { '[{"md5":"1cf41bdc0fdd409774eb755031a6f49d"}, {"md5":"1eef5bdc64241652f8a0df0c2dc92df6"}]' }
94
+ it { expect(subject).to be_a Array }
95
+ it { expect(subject).to have(2).items }
96
+
97
+ context 'about first object' do
98
+ subject {VtAPI::Response.parse(response).first }
99
+ it { expect(subject).to be_a VtAPI::Response }
100
+ end
101
+ end
70
102
  end
103
+
71
104
  end
data/spec/vtapi_spec.rb CHANGED
@@ -48,6 +48,22 @@ describe VtAPI do
48
48
  .to_return(:body => sample_response, :status => 200)
49
49
  subject
50
50
  end
51
+
52
+ context 'assigns 2 resources' do
53
+ let(:resource) { ['ff' * 32, '00' * 32] }
54
+ let(:sample_response) { '[{}]' }
55
+ it "should post resource parameter as comma separete value" do
56
+ stub_request(:post, api_url)
57
+ .with(:body => {'resource' => resource.join(', '), 'apikey' => apikey} )
58
+ .to_return(:body => sample_response, :status => 200)
59
+ subject
60
+ end
61
+ end
62
+
63
+ context 'when assign 25 resources(over the limitation)' do
64
+ let(:resource) { ['ff' * 32] * 25 }
65
+ it { expect{ subject }.to raise_error }
66
+ end
51
67
  end
52
68
 
53
69
  describe '#file_report' do
@@ -74,5 +90,85 @@ describe VtAPI do
74
90
  expect{ subject }.to raise_error(VtAPI::AuthError)
75
91
  end
76
92
  end
93
+
94
+ context 'when assign 2 resources' do
95
+ let(:resource) { ['ff' * 32, '00' * 32] }
96
+ it "should post resource parameter as comma separete value" do
97
+ stub_request(:post, api_url)
98
+ .with(:body => {'resource' => resource.join(', '), 'apikey' => apikey} )
99
+ .to_return(:body => sample_response, :status => 200)
100
+ subject
101
+ end
102
+ end
103
+
104
+ context 'when assign 5 resources(over the limitation)' do
105
+ let(:resource) { ['ff' * 32] * 5 }
106
+ it { expect{ subject }.to raise_error }
107
+ end
77
108
  end
109
+
110
+ describe '#url_scan' do
111
+ let(:sample_response) { '{}' }
112
+ let(:api_url) { 'https://www.virustotal.com/vtapi/v2/url/scan' }
113
+ let(:target_url) { 'http://www.foobar.com/' }
114
+ subject { api.url_scan(target_url) }
115
+
116
+ it "should connect to 'https://www.virustotal.com/vtapi/v2/url/scan'" do
117
+ stub_request(:post, api_url)
118
+ .with(:body => {'url' => target_url, 'apikey' => apikey} )
119
+ .to_return(:body => sample_response, :status => 200)
120
+ subject
121
+ end
122
+
123
+ context 'when assign 2 urls' do
124
+ let(:target_url) { ['http://foobar.com/', 'http://abc.com'] }
125
+
126
+ it "should post url parameter which is joined by '\\n'" do
127
+ stub_request(:post, api_url)
128
+ .with(:body => {'url' => target_url.join("\n"), 'apikey' => apikey} )
129
+ .to_return(:body => sample_response, :status => 200)
130
+ subject
131
+ end
132
+ end
133
+
134
+ context 'when assign 5 urls (over the limitation)' do
135
+ let(:target_url) { ['http://www.foobar.com/'] * 5 }
136
+
137
+ it { expect{ subject }.to raise_error }
138
+ end
139
+ end
140
+
141
+ describe '#url_report' do
142
+ let(:sample_response) { '{}' }
143
+ let(:api_url) { 'https://www.virustotal.com/vtapi/v2/url/report' }
144
+ let(:target_url) { 'http://www.foobar.com/' }
145
+ subject { api.url_report(target_url) }
146
+
147
+ it "should connect to 'https://www.virustotal.com/vtapi/v2/url/report'" do
148
+ stub_request(:post, api_url)
149
+ .with(:body => {'resource' => target_url, 'apikey' => apikey} )
150
+ .to_return(:body => sample_response, :status => 200)
151
+ subject
152
+ end
153
+
154
+ context 'when assign 2 urls' do
155
+ let(:target_url) { ['http://foobar.com/', 'http://abc.com'] }
156
+
157
+ it "should post url parameter which is joined by ', '" do
158
+ stub_request(:post, api_url)
159
+ .with(:body => {'resource' => target_url.join(", "), 'apikey' => apikey} )
160
+ .to_return(:body => sample_response, :status => 200)
161
+ subject
162
+ end
163
+ end
164
+
165
+ context 'when assign 5 urls (over the limitation)' do
166
+ let(:target_url) { ['http://www.foobar.com/'] * 5 }
167
+
168
+ it { expect{ subject }.to raise_error }
169
+ end
170
+ end
171
+
172
+
173
+
78
174
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vtapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-20 00:00:00.000000000 Z
12
+ date: 2013-05-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
16
- requirement: &70305944392860 !ruby/object:Gem::Requirement
16
+ requirement: &70343748341080 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '1.3'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70305944392860
24
+ version_requirements: *70343748341080
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70305944392140 !ruby/object:Gem::Requirement
27
+ requirement: &70343748340660 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70305944392140
35
+ version_requirements: *70343748340660
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70305944391080 !ruby/object:Gem::Requirement
38
+ requirement: &70343748340120 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 2.13.0
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70305944391080
46
+ version_requirements: *70343748340120
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: webmock
49
- requirement: &70305944390140 !ruby/object:Gem::Requirement
49
+ requirement: &70343748339620 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 1.11.0
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70305944390140
57
+ version_requirements: *70343748339620
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rest-client
60
- requirement: &70305944388880 !ruby/object:Gem::Requirement
60
+ requirement: &70343748339160 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,7 +65,7 @@ dependencies:
65
65
  version: 1.6.7
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *70305944388880
68
+ version_requirements: *70343748339160
69
69
  description: gem for VirusTotal Public API version2.0.
70
70
  email:
71
71
  - masatanish@gmail.com
@@ -102,7 +102,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
102
102
  version: '0'
103
103
  segments:
104
104
  - 0
105
- hash: -530785710278489622
105
+ hash: -803017513526154134
106
106
  required_rubygems_version: !ruby/object:Gem::Requirement
107
107
  none: false
108
108
  requirements:
@@ -111,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
111
  version: '0'
112
112
  segments:
113
113
  - 0
114
- hash: -530785710278489622
114
+ hash: -803017513526154134
115
115
  requirements: []
116
116
  rubyforge_project:
117
117
  rubygems_version: 1.8.16