nexpose_servicenow 0.4.15
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.
- 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
|