nexpose_servicenow 0.4.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +16 -0
- data/Rakefile +2 -0
- data/bin/nexpose_servicenow +4 -0
- data/bin/setup +8 -0
- data/lib/nexpose_servicenow.rb +173 -0
- data/lib/nexpose_servicenow/arg_parser.rb +173 -0
- data/lib/nexpose_servicenow/chunker.rb +106 -0
- data/lib/nexpose_servicenow/historical_data.rb +234 -0
- data/lib/nexpose_servicenow/nexpose_helper.rb +162 -0
- data/lib/nexpose_servicenow/nx_logger.rb +166 -0
- data/lib/nexpose_servicenow/queries.rb +245 -0
- data/lib/nexpose_servicenow/queries_original.rb +162 -0
- data/lib/nexpose_servicenow/version.rb +5 -0
- data/nexpose_servicenow.gemspec +25 -0
- metadata +106 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NmE4MDhhMzQ0NDE1ZmZhN2M1NjBjYzI5ODRlYmViMzczOTlkNDRmZg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NmEyMjJjOGM2YmUzZGRmYThhZGMzNGI5MWFjNGNkMmU3MzYxMjc4Mg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
OTMzZmFmYTM3YjBkYjliZGI4ZWY4Mzk2YTIxYzg2M2QxYjE4Mzg0NDI5NmUx
|
10
|
+
NmJmNGMzYzEzODRkNGI5YTk2ZTJjNTA0NTFjODRlM2E2NjZjZDkyNzZmYjhl
|
11
|
+
MTEwZWRhOWFiZjI0ZTQzZGI5ODZjYzZlZDAzY2U4ZTdlMTI3ZDM=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
Y2ZiZTllNWM2ZDgyM2IwOWRkM2RmY2MwNDQ0NjE5NTI3ODM2YzQ2MGZkYzMw
|
14
|
+
M2U0YzdhNGQ4MzI4Mjg2MWM3NjVjOTYxZjI0MjJhOGJhNzkyMmEwOTI3ZjE2
|
15
|
+
NzUxNjg1YjgxMjAwODZiZjVjMDVmODI1Mjc2NjQ0MjIzZDc4MmQ=
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 David Valente
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# NexposeServicenow
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
gem install nexpose_servicenow
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
## Development
|
10
|
+
|
11
|
+
## Contributing
|
12
|
+
|
13
|
+
## License
|
14
|
+
|
15
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
16
|
+
|
data/Rakefile
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'optparse'
|
3
|
+
require 'nexpose'
|
4
|
+
require 'uri'
|
5
|
+
require 'nexpose_servicenow/queries'
|
6
|
+
require 'nexpose_servicenow/nexpose_helper'
|
7
|
+
require 'nexpose_servicenow/arg_parser'
|
8
|
+
require 'nexpose_servicenow/chunker'
|
9
|
+
require 'nexpose_servicenow/nx_logger'
|
10
|
+
require 'nexpose_servicenow/historical_data'
|
11
|
+
require "nexpose_servicenow/version"
|
12
|
+
|
13
|
+
module NexposeServiceNow
|
14
|
+
class Main
|
15
|
+
def self.start(args)
|
16
|
+
options = ArgParser.parse(args)
|
17
|
+
|
18
|
+
log = setup_logging(options)
|
19
|
+
log.log_message("Options: #{options}")
|
20
|
+
|
21
|
+
if options[:nexpose_ids].first.to_s == "0"
|
22
|
+
log.log_message('Retrieving array of all site IDs')
|
23
|
+
options[:nexpose_ids] = get_nexpose_helper(options).all_sites.sort
|
24
|
+
end
|
25
|
+
|
26
|
+
report_details = NexposeHelper.get_report_names(options[:query],
|
27
|
+
options[:nexpose_ids])
|
28
|
+
report_details.each do |r|
|
29
|
+
r[:report_name] = NexposeHelper.get_filepath(r[:report_name],
|
30
|
+
options[:output_dir])
|
31
|
+
end
|
32
|
+
|
33
|
+
update_last_scan_data(options)
|
34
|
+
create_report(report_details, options)
|
35
|
+
|
36
|
+
log.log_message("Initialising #{options[:mode]} mode")
|
37
|
+
self.send("#{options[:mode]}_mode", report_details, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.get_nexpose_helper(options)
|
41
|
+
NexposeHelper.new(options[:nexpose_url],
|
42
|
+
options[:nexpose_port],
|
43
|
+
options[:nexpose_username],
|
44
|
+
options[:nexpose_password])
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.setup_logging(options)
|
48
|
+
log = NexposeServiceNow::NxLogger.instance
|
49
|
+
log.setup_statistics_collection(NexposeServiceNow::VENDOR,
|
50
|
+
NexposeServiceNow::PRODUCT,
|
51
|
+
NexposeServiceNow::VERSION)
|
52
|
+
log.setup_logging(true,
|
53
|
+
options[:log_level] || 'info',
|
54
|
+
false)
|
55
|
+
log
|
56
|
+
end
|
57
|
+
|
58
|
+
#Merges in the details from the last time the
|
59
|
+
#integration ran reports.
|
60
|
+
def self.update_last_scan_data(options)
|
61
|
+
return unless options[:mode] == "latest_scans"
|
62
|
+
historical_data = HistoricalData.new(options)
|
63
|
+
historical_data.update_last_scan_data
|
64
|
+
end
|
65
|
+
|
66
|
+
#Create a report if explicitly required or else an existing
|
67
|
+
#report file isn't found
|
68
|
+
def self.create_report(report_details, options)
|
69
|
+
return if options[:mode].start_with? 'update_'
|
70
|
+
|
71
|
+
unless options[:gen_report]
|
72
|
+
#If any file is missing, perform all queries
|
73
|
+
return if report_details.all? { |f| File.exists?(f[:report_name]) }
|
74
|
+
end
|
75
|
+
|
76
|
+
#Filter it down to sites which actively need queried
|
77
|
+
sites_to_scan = filter_sites(options)
|
78
|
+
nexpose_helper = get_nexpose_helper(options)
|
79
|
+
hist_data = HistoricalData.new(options)
|
80
|
+
vuln_query = options[:query].to_s.start_with? 'vulnerabili'
|
81
|
+
last_run = nil
|
82
|
+
|
83
|
+
start_time = Time.new
|
84
|
+
query_options = { last_scans: hist_data.last_scan_ids(sites_to_scan) }
|
85
|
+
query_options[:vuln_query_date] = hist_data.last_vuln_run if vuln_query
|
86
|
+
|
87
|
+
filename = nexpose_helper.create_report(options[:query],
|
88
|
+
sites_to_scan,
|
89
|
+
options[:id_type],
|
90
|
+
options[:output_dir],
|
91
|
+
query_options)
|
92
|
+
|
93
|
+
hist_data.create_last_vuln_data(start_time, sites_to_scan) if vuln_query
|
94
|
+
|
95
|
+
#A single String may be returned or an Array of Strings
|
96
|
+
if filename.class.to_s == "Array"
|
97
|
+
filename.map! { |f| File.expand_path(options[:output_dir], f) }
|
98
|
+
return filename.join("\n")
|
99
|
+
end
|
100
|
+
|
101
|
+
File.expand_path(options[:output_dir], filename)
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.filter_sites(options)
|
105
|
+
#These queries always run to make sure certain data is up to date
|
106
|
+
exceptions = ['vulnerabili', 'asset_groups', 'sites', 'tags']
|
107
|
+
if exceptions.any? { |e| options[:query].to_s.start_with? e }
|
108
|
+
return options[:nexpose_ids]
|
109
|
+
end
|
110
|
+
|
111
|
+
#Always run the query for latest scans or vulnerabilities
|
112
|
+
if options[:mode] == 'latest_scans' ||
|
113
|
+
options[:mode] == 'get_chunk'
|
114
|
+
return options[:nexpose_ids]
|
115
|
+
end
|
116
|
+
|
117
|
+
historical_data = HistoricalData.new(options)
|
118
|
+
sites_to_scan = historical_data.sites_to_scan
|
119
|
+
|
120
|
+
return sites_to_scan unless (sites_to_scan.nil? || sites_to_scan.empty?)
|
121
|
+
|
122
|
+
log = NexposeServiceNow::NxLogger.instance
|
123
|
+
log.log_message "Sites #{options[:nexpose_ids]} are up to date."
|
124
|
+
log.log_message "Query requested was: #{options[:query]}."
|
125
|
+
exit 0
|
126
|
+
end
|
127
|
+
|
128
|
+
#Print the chunk info
|
129
|
+
def self.chunk_info_mode(report_details, options)
|
130
|
+
chunker = Chunker.new(report_details, options[:row_limit])
|
131
|
+
filtered_sites = filter_sites(options)
|
132
|
+
puts chunker.preprocess(filtered_sites)
|
133
|
+
end
|
134
|
+
|
135
|
+
#Prints a chunk of CSV to the console
|
136
|
+
def self.get_chunk_mode(report_details, options)
|
137
|
+
#Get the byte offset and length
|
138
|
+
chunker = Chunker.new(report_details, options[:row_limit])
|
139
|
+
filtered_sites = filter_sites(options)
|
140
|
+
|
141
|
+
puts chunker.read_chunk(options[:chunk_start],
|
142
|
+
options[:chunk_length],
|
143
|
+
filtered_sites.first)
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.latest_scans_mode(report_details, options)
|
147
|
+
historical_data = HistoricalData.new(options)
|
148
|
+
puts historical_data.filter_report
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.remove_last_scan_mode(report_details, options)
|
152
|
+
historical_data = HistoricalData.new(options)
|
153
|
+
historical_data.remove_last_scan_data
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.update_last_scan_mode(report_details, options)
|
157
|
+
historical_data = HistoricalData.new(options)
|
158
|
+
historical_data.set_last_scan(options[:nexpose_ids].first,
|
159
|
+
options[:last_scan_data])
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.remove_last_vuln_mode(report_details, options)
|
163
|
+
historical_data = HistoricalData.new(options)
|
164
|
+
historical_data.remove_last_vuln_data
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.update_last_vuln_mode(report_details, options)
|
168
|
+
historical_data = HistoricalData.new(options)
|
169
|
+
historical_data.set_last_vuln(options[:last_scan_data],
|
170
|
+
options[:nexpose_ids])
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'json'
|
3
|
+
require_relative './queries'
|
4
|
+
require_relative './nx_logger'
|
5
|
+
|
6
|
+
module NexposeServiceNow
|
7
|
+
class ArgParser
|
8
|
+
NX_ID_TYPES = %i[site tag]
|
9
|
+
MODES = %i[chunk_info get_chunk latest_scans
|
10
|
+
remove_last_scan update_last_scan
|
11
|
+
remove_last_vuln update_last_vuln]
|
12
|
+
|
13
|
+
QUERY_NAMES = Queries.methods(false)
|
14
|
+
|
15
|
+
QUERY_ALIASES = { 'devices' => 'cmdb_ci_outofband_device',
|
16
|
+
'vuln_items' => 'sn_vul_vulnerable_item',
|
17
|
+
'vuln_entries' => 'sn_vul_third_party_entry' }
|
18
|
+
|
19
|
+
def self.parse(args)
|
20
|
+
options = Hash.new
|
21
|
+
|
22
|
+
opt_parser = OptionParser.new do |opts|
|
23
|
+
opts.banner = "Usage: example.rb [options]"
|
24
|
+
|
25
|
+
opts.on("-o", "--output-dir DIRECTORY",
|
26
|
+
"Directory in which to save reports") do |output_dir|
|
27
|
+
options[:output_dir] = output_dir
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on("-m", "--mode MODE",
|
31
|
+
"Mode for program output. (#{MODES.join(', ')})") do |mode|
|
32
|
+
options[:mode] = mode
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-g", "--generate-report BOOLEAN",
|
36
|
+
"True to generate and download new report.") do |gen|
|
37
|
+
char = gen.downcase[0]
|
38
|
+
options[:gen_report] = char == 'y' || char == 't'
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.separator ""
|
42
|
+
opts.separator "Query options:"
|
43
|
+
|
44
|
+
opts.on("-q", "--query QUERY", QUERY_NAMES,
|
45
|
+
"Select query (#{QUERY_NAMES.join(', ')})") do |query|
|
46
|
+
options[:query] = query
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on("-t", "--type TYPE", NX_ID_TYPES,
|
50
|
+
"Select type (#{NX_ID_TYPES.join(', ')})") do |type|
|
51
|
+
options[:id_type] = type
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on("-i", "--items x,y,z", Array,
|
55
|
+
"IDs of the nexpose items to scan") do |items|
|
56
|
+
options[:nexpose_ids] = items
|
57
|
+
end
|
58
|
+
|
59
|
+
opts.separator ""
|
60
|
+
opts.separator "Nexpose options:"
|
61
|
+
|
62
|
+
opts.on("-n", "--nexpose-address URL",
|
63
|
+
"URL of the Nexpose server") do |url|
|
64
|
+
port = url.slice!(/:(\d+)$/)
|
65
|
+
port.slice! ':' unless port.nil?
|
66
|
+
|
67
|
+
url.slice! /https:\/\//
|
68
|
+
options[:nexpose_url] = url
|
69
|
+
options[:nexpose_port] = port || '3780'
|
70
|
+
@full_url = options[:nexpose_url] + ':' + options[:nexpose_port]
|
71
|
+
end
|
72
|
+
|
73
|
+
opts.on("-u", "--user USER",
|
74
|
+
"Username for Nexpose console") do |username|
|
75
|
+
options[:nexpose_username] = username
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on("-p", "--password PASSWORD",
|
79
|
+
"Password for the Nexpose user") do |password|
|
80
|
+
options[:nexpose_password] = password
|
81
|
+
end
|
82
|
+
|
83
|
+
opts.separator ""
|
84
|
+
opts.separator "Chunk info mode options:"
|
85
|
+
|
86
|
+
opts.on("-r", "--row-limit LIMIT",
|
87
|
+
"Maximum number of rows per chunk (with header).") do |limit|
|
88
|
+
options[:row_limit] = limit.to_i
|
89
|
+
options[:row_limit] = 9_999_999 if options[:row_limit] <= 0
|
90
|
+
end
|
91
|
+
|
92
|
+
opts.separator ""
|
93
|
+
opts.separator "Get chunk mode options:"
|
94
|
+
|
95
|
+
opts.on("-s", "--start START",
|
96
|
+
"The chunk starting offset.") do |start|
|
97
|
+
options[:chunk_start] = start.to_i
|
98
|
+
end
|
99
|
+
|
100
|
+
opts.on("-l", "--length LENGTH",
|
101
|
+
"The chunk length.") do |length|
|
102
|
+
options[:chunk_length] = length.to_i
|
103
|
+
end
|
104
|
+
|
105
|
+
opts.separator ""
|
106
|
+
opts.separator "Last scan file modification options:"
|
107
|
+
|
108
|
+
opts.on("-d", "--data DATA",
|
109
|
+
"Date or scan ID to be inserted in last scan file.") do |data|
|
110
|
+
options[:last_scan_data] = data
|
111
|
+
end
|
112
|
+
|
113
|
+
opts.separator ""
|
114
|
+
opts.separator "Common options:"
|
115
|
+
|
116
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
117
|
+
puts opts
|
118
|
+
exit
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
opt_parser.parse!(args)
|
123
|
+
options = self.validate_input(options)
|
124
|
+
options = self.get_env_settings(options)
|
125
|
+
options
|
126
|
+
end
|
127
|
+
|
128
|
+
#TODO: Validate input depending on mode AND whether generating a report
|
129
|
+
# is required.
|
130
|
+
def self.validate_input(options)
|
131
|
+
#Insert defaults. Some are mode-specific.
|
132
|
+
options[:output_dir] ||= '.'
|
133
|
+
options[:row_limit] ||= 9_999_999
|
134
|
+
options[:id_type] ||= 'site'
|
135
|
+
options[:nexpose_ids] ||= []
|
136
|
+
|
137
|
+
options[:query] = 'latest_scans' if options[:mode] == 'latest_scans'
|
138
|
+
|
139
|
+
#By default, a report won't be generated if a chunk's being retrieved
|
140
|
+
if options[:gen_report].nil?
|
141
|
+
options[:gen_report] = options[:mode] == "chunk_info" ||
|
142
|
+
options[:mode] == "latest_scans"
|
143
|
+
end
|
144
|
+
|
145
|
+
options
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.get_env_settings(options)
|
149
|
+
#Only need these if a query is being performed
|
150
|
+
return options if options[:gen_report] == false
|
151
|
+
|
152
|
+
log = NexposeServiceNow::NxLogger.instance
|
153
|
+
log.log_message "Retrieving environment variables."
|
154
|
+
|
155
|
+
# Retrieve environment variable settings
|
156
|
+
%i[url port username password].each do |setting|
|
157
|
+
option = "nexpose_#{setting}"
|
158
|
+
setting = ENV[option.upcase]
|
159
|
+
sym = option.intern
|
160
|
+
options[sym] ||= setting
|
161
|
+
|
162
|
+
if options[sym].nil?
|
163
|
+
error = "Option #{sym} wasn't supplied."
|
164
|
+
log.log_error_message error
|
165
|
+
$stderr.puts "ERROR: #{error}"
|
166
|
+
exit -1
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
options
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
|
2
|
+
module NexposeServiceNow
|
3
|
+
class Chunker
|
4
|
+
def initialize(report_details, row_limit)
|
5
|
+
@row_limit = row_limit
|
6
|
+
@size_limit = 4_500_000
|
7
|
+
@report_details = report_details
|
8
|
+
@header = get_header
|
9
|
+
|
10
|
+
setup_logging
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup_logging
|
14
|
+
@log = NexposeServiceNow::NxLogger.instance
|
15
|
+
@log.log_message("Chunker File Limit: #{@size_limit}MB");
|
16
|
+
@log.log_message("Chunk Row Limit: #{@row_limit}")
|
17
|
+
end
|
18
|
+
|
19
|
+
#Grab the header from the first file
|
20
|
+
def get_header
|
21
|
+
file = File.open(@report_details.first[:report_name], "r")
|
22
|
+
header = file.readline
|
23
|
+
file.close
|
24
|
+
|
25
|
+
header
|
26
|
+
end
|
27
|
+
|
28
|
+
def preprocess(nexpose_ids=nil)
|
29
|
+
@log.log_message("Breaking file #{@file_path} down into chunks.")
|
30
|
+
|
31
|
+
all_chunks = []
|
32
|
+
@report_details.each do |report|
|
33
|
+
chunks = process_file(report[:report_name], report[:id])
|
34
|
+
all_chunks.concat chunks
|
35
|
+
end
|
36
|
+
|
37
|
+
@log.log_message("Files broken down into #{all_chunks.count} chunks")
|
38
|
+
|
39
|
+
puts all_chunks.to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
def process_file(file_path, site_id=nil)
|
43
|
+
relative_size_limit = @size_limit - @header.bytesize
|
44
|
+
chunk = { site_id: site_id,
|
45
|
+
start: @header.bytesize,
|
46
|
+
length: 0,
|
47
|
+
row_count: 0 }
|
48
|
+
|
49
|
+
chunks = []
|
50
|
+
csv_file = CSV.open(file_path, "r", headers: true)
|
51
|
+
while(true)
|
52
|
+
position = csv_file.pos
|
53
|
+
line = csv_file.shift
|
54
|
+
row_length = line.to_s.bytesize
|
55
|
+
|
56
|
+
if line.nil?
|
57
|
+
chunks << chunk
|
58
|
+
break
|
59
|
+
elsif chunk[:length]+row_length < relative_size_limit &&
|
60
|
+
chunk[:row_count] + 1 < @row_limit
|
61
|
+
chunk[:length] += row_length
|
62
|
+
chunk[:row_count] += 1
|
63
|
+
else
|
64
|
+
chunks << chunk
|
65
|
+
|
66
|
+
#Initialise chunk with this row information
|
67
|
+
chunk = { site_id: site_id,
|
68
|
+
start: position,
|
69
|
+
length: row_length,
|
70
|
+
row_count: 1 }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
csv_file.close
|
74
|
+
|
75
|
+
#Should we include the row count?
|
76
|
+
chunks.each do |c|
|
77
|
+
c.delete :row_count
|
78
|
+
|
79
|
+
#Should we do this...?
|
80
|
+
c.delete :site_id if c[:site_id].nil? || c[:site_id] == -1
|
81
|
+
end
|
82
|
+
|
83
|
+
chunks
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_file(site_id=nil)
|
87
|
+
#-1 indicates a single query report
|
88
|
+
return @report_details.first[:report_name] if site_id.to_i <= 0
|
89
|
+
|
90
|
+
report = @report_details.find { |r| r[:id].to_s == site_id.to_s }
|
91
|
+
report[:report_name]
|
92
|
+
end
|
93
|
+
|
94
|
+
def read_chunk(start, length, site_id=nil)
|
95
|
+
@log.log_message("Returning chunk. Start: #{start}, Length: #{length}, File: #{@file_path}")
|
96
|
+
|
97
|
+
#If the header isn't in the chunk, prepend it
|
98
|
+
header = start == 0 ? "" : @header
|
99
|
+
|
100
|
+
file = File.open(get_file(site_id), "rb")
|
101
|
+
file.seek(start)
|
102
|
+
puts header + file.read(length)
|
103
|
+
file.close
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|