spectre-core 1.12.0 → 1.12.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/spectre/curl.rb CHANGED
@@ -1,397 +1,400 @@
1
- require_relative '../spectre'
2
-
3
- require 'open3'
4
- require 'ostruct'
5
-
6
-
7
- module Spectre::Curl
8
- class SpectreHttpRequest < Spectre::DslClass
9
- def initialize request
10
- @__req = request
11
- end
12
-
13
- def method method_name
14
- @__req['method'] = method_name.upcase
15
- end
16
-
17
- def url base_url
18
- @__req['base_url'] = base_url
19
- end
20
-
21
- def path url_path
22
- @__req['path'] = url_path
23
- end
24
-
25
- def header name, value
26
- @__req['headers'] = [] unless @__req['headers']
27
- @__req['headers'].append [name, value.to_s.strip]
28
- end
29
-
30
- def param name, value
31
- @__req['query'] = [] unless @__req['query']
32
- @__req['query'].append [name, value.to_s.strip]
33
- end
34
-
35
- def content_type media_type
36
- @__req['headers'] = [] unless @__req['headers']
37
- @__req['headers'].append ['Content-Type', media_type]
38
- end
39
-
40
- def json data
41
- body JSON.pretty_generate(data)
42
- content_type 'application/json'
43
- end
44
-
45
- def body body_content
46
- @__req['body'] = body_content
47
- end
48
-
49
- def ensure_success!
50
- @__req['ensure_success'] = true
51
- end
52
-
53
- def ensure_success?
54
- @__req['ensure_success']
55
- end
56
-
57
- def authenticate method
58
- @__req['auth'] = method
59
- end
60
-
61
- def certificate path
62
- @__req['cert'] = path
63
- use_ssl!
64
- end
65
-
66
- def use_ssl!
67
- @__req['use_ssl'] = true
68
- end
69
-
70
- alias_method :auth, :authenticate
71
- alias_method :cert, :certificate
72
- alias_method :media_type, :content_type
73
- end
74
-
75
- class SpectreHttpHeader
76
- def initialize headers
77
- @headers = headers || {}
78
- end
79
-
80
- def [] key
81
- @headers[key.downcase]
82
- end
83
- end
84
-
85
- class SpectreHttpResponse
86
- def initialize res
87
- @res = res
88
- @data = nil
89
- end
90
-
91
- def code
92
- @res[:code]
93
- end
94
-
95
- def message
96
- @res[:message]
97
- end
98
-
99
- def protocol
100
- @res[:protocol]
101
- end
102
-
103
- def version
104
- @res[:version]
105
- end
106
-
107
- def headers
108
- SpectreHttpHeader.new @res[:headers]
109
- end
110
-
111
- def body
112
- @res[:body]
113
- end
114
-
115
- def json
116
- return nil unless @res[:body]
117
-
118
- if @data == nil
119
- begin
120
- @data = JSON.parse(@res[:body], object_class: OpenStruct)
121
- rescue
122
- raise 'invalid json'
123
- end
124
- end
125
-
126
- @data
127
- end
128
-
129
- def success?
130
- @res[:code] < 400
131
- end
132
-
133
- def pretty
134
- @res.pretty
135
- end
136
- end
137
-
138
- # DEFAULT_HTTP_REQUEST = {
139
- # 'method' => 'GET', # -X, --request <cmd>
140
- # 'base_url' => nil,
141
- # 'path' => nil,
142
- # 'headers' => nil, # -H, --header <header/@file>
143
- # 'query' => nil,
144
- # 'body' => nil, # -d, --data <data>
145
- # 'cert' => nil, # --cacert
146
- # 'follow' => false, # -L, --location
147
- # 'username' => nil, # -u, --user <user:password>
148
- # 'password' => nil,
149
- # 'use_ssl' => false, # -k
150
- # }
151
-
152
-
153
- class << self
154
- @@http_cfg = {}
155
- @@response = nil
156
- @@request = nil
157
- @@modules = []
158
-
159
- def curl name, secure: false, &block
160
- req = {
161
- 'use_ssl' => secure,
162
- }
163
-
164
- if @@http_cfg.key? name
165
- req.merge! @@http_cfg[name]
166
- raise "No `base_url' set for HTTP client '#{name}'. Check your HTTP config in your environment." unless req['base_url']
167
- else
168
- req['base_url'] = name
169
- end
170
-
171
- SpectreHttpRequest.new(req)._evaluate(&block) if block_given?
172
-
173
- invoke(req)
174
- end
175
-
176
- def curl_request
177
- raise 'No request has been invoked yet' unless @@request
178
-
179
- @@request
180
- end
181
-
182
- def curl_response
183
- raise 'There is no response. No request has been invoked yet.' unless @@response
184
-
185
- @@response
186
- end
187
-
188
- def register mod
189
- raise 'Module must not be nil' unless mod
190
-
191
- @@modules << mod
192
- end
193
-
194
- private
195
-
196
- def try_format_json str, pretty: false
197
- return str unless str or str.empty?
198
-
199
- begin
200
- json = JSON.parse(str)
201
- json.obfuscate!(@@secure_keys) unless @@debug
202
-
203
- if pretty
204
- str = JSON.pretty_generate(json)
205
- else
206
- str = JSON.dump(json)
207
- end
208
- rescue
209
- # do nothing
210
- end
211
-
212
- str
213
- end
214
-
215
- def secure? key
216
- @@secure_keys.any? { |x| key.to_s.downcase.include? x.downcase }
217
- end
218
-
219
- def header_to_s headers
220
- s = ''
221
-
222
- return s unless headers
223
-
224
- headers.each do |header|
225
- key = header[0].to_s
226
- value = header[1].to_s
227
- value = '*****' if secure?(key) and not @@debug
228
- s += "#{key.ljust(30, '.')}: #{value}\n"
229
- end
230
-
231
- s
232
- end
233
-
234
- def invoke req
235
- cmd = [@@curl_path]
236
-
237
- if req['cert'] or req['use_ssl']
238
- scheme = 'https'
239
- else
240
- scheme = 'http'
241
- end
242
-
243
- uri = req['base_url']
244
-
245
- unless uri.match /http(?:s)?:\/\//
246
- uri = scheme + '://' + uri
247
- end
248
-
249
- if req['path']
250
- uri += '/' unless uri.end_with? '/'
251
- uri += req['path']
252
- end
253
-
254
- if req['query']
255
- uri += '?'
256
- uri += req['query']
257
- .map { |x| x.join '='}
258
- .join('&')
259
- end
260
-
261
- cmd.append('"' + uri + '"')
262
- cmd.append('-X', req['method']) unless req['method'] == 'GET' or (req['body'] and req['method'] == 'POST')
263
-
264
- # Call all registered modules
265
- @@modules.each do |mod|
266
- mod.on_req(req, cmd) if mod.respond_to? :on_req
267
- end
268
-
269
- # Add headers to curl command
270
- req['headers'].each do |header|
271
- cmd.append('-H', '"' + header.join(':') + '"')
272
- end if req['headers']
273
-
274
- # Add request body
275
- if req['body'] != nil and not req['body'].empty?
276
- req_body = try_format_json(req['body']).gsub(/"/, '\\"')
277
- cmd.append('-d', '"' + req_body + '"')
278
- elsif ['POST', 'PUT', 'PATCH'].include? req['method'].upcase
279
- cmd.append('-d', '"\n"')
280
- end
281
-
282
- # Add certificate path if one if given
283
- if req['cert']
284
- raise "Certificate '#{req['cert']}' does not exist" unless File.exists? req['cert']
285
-
286
- cmd.append('--cacert', req['cert'])
287
- elsif req['use_ssl'] or uri.start_with? 'https'
288
- cmd.append('-k')
289
- end
290
-
291
- cmd.append('-i')
292
- cmd.append('-v')
293
-
294
- @@request = OpenStruct.new(req)
295
-
296
- sys_cmd = cmd.join(' ')
297
-
298
- @@logger.debug(sys_cmd)
299
-
300
- req_id = SecureRandom.uuid()[0..5]
301
-
302
- req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
303
- req_log += header_to_s(req['headers'])
304
- req_log += try_format_json(req['body'], pretty: true)
305
-
306
- @@logger.info(req_log)
307
-
308
- start_time = Time.now
309
-
310
- stdin, stdout, stderr, wait_thr = Open3.popen3(sys_cmd)
311
-
312
- end_time = Time.now
313
-
314
- output = stdout.gets(nil)
315
- stdout.close
316
-
317
- debug_log = stderr.gets(nil)
318
- stderr.close
319
-
320
- # debug_log.lines.each { |x| @@logger.debug x unless x.empty? }
321
-
322
- raise "Unable to request #{uri}. Please check if this URL is correctly configured and reachable." unless output
323
-
324
- @@logger.debug("[<] #{req_id} stdout:\n#{output}")
325
-
326
- header, body = output.split /\r?\n\r?\n/
327
-
328
- result = header.lines.first
329
-
330
- exit_code = wait_thr.value.exitstatus
331
-
332
- raise Exception.new "An error occured while executing curl:\n#{debug_log.lines.map { |x| not x.empty? }}" unless exit_code == 0
333
-
334
- # Parse protocol, version, status code and status message from response
335
- match = /^(?<protocol>[A-Za-z0-9]+)\/(?<version>\d+\.?\d*) (?<code>\d+) (?<message>.*)/.match result
336
-
337
- raise "Unexpected result from curl request:\n#{result}" unless match
338
-
339
- res_headers = header.lines[1..-1]
340
- .map { |x| /^(?<key>[A-Za-z0-9-]+):\s*(?<value>.*)$/.match x }
341
- .select { |x| x != nil }
342
- .map { |x| [x[:key].downcase, x[:value]] }
343
-
344
- res = {
345
- protocol: match[:protocol],
346
- version: match[:version],
347
- code: match[:code].to_i,
348
- message: match[:message],
349
- headers: Hash[res_headers],
350
- body: body,
351
- }
352
-
353
- # Call all registered modules
354
- @@modules.each do |mod|
355
- mod.on_res(res, output) if mod.respond_to? :on_res
356
- end
357
-
358
- res_log = "[<] #{req_id} #{res[:code]} #{res[:message]} (#{end_time - start_time}s)\n"
359
- res_headers.each do |header|
360
- res_log += "#{header[0].to_s.ljust(30, '.')}: #{header[1].to_s}\n"
361
- end
362
-
363
- if res[:body] != nil and not res[:body].empty?
364
- res_log += try_format_json(res[:body], pretty: true)
365
- end
366
-
367
- @@logger.info res_log
368
-
369
- @@response = SpectreHttpResponse.new(res)
370
-
371
- raise "Response did not indicate success: #{@@response.code} #{@@response.message}" if req['ensure_success'] and not @@response.success?
372
-
373
- @@response
374
- end
375
- end
376
-
377
- Spectre.register do |config|
378
- @@debug = config['debug']
379
-
380
- @@logger = ::Logger.new(config['log_file'], progname: 'spectre/curl')
381
- @@logger.level = @@debug ? Logger::DEBUG : Logger::INFO
382
-
383
- @@secure_keys = config['secure_keys'] || []
384
-
385
- @@curl_path = config['curl_path'] || 'curl'
386
-
387
- if config.key? 'http'
388
- @@http_cfg = {}
389
-
390
- config['http'].each do |name, cfg|
391
- @@http_cfg[name] = cfg
392
- end
393
- end
394
- end
395
-
396
- Spectre.delegate :curl, :curl_response, :curl_request, to: self
397
- end
1
+ require_relative '../spectre'
2
+
3
+ require 'open3'
4
+ require 'ostruct'
5
+
6
+
7
+ module Spectre::Curl
8
+ class SpectreHttpRequest < Spectre::DslClass
9
+ def initialize request
10
+ @__req = request
11
+ end
12
+
13
+ def method method_name
14
+ @__req['method'] = method_name.upcase
15
+ end
16
+
17
+ def url base_url
18
+ @__req['base_url'] = base_url
19
+ end
20
+
21
+ def path url_path
22
+ @__req['path'] = url_path
23
+ end
24
+
25
+ def header name, value
26
+ @__req['headers'] = [] unless @__req['headers']
27
+ @__req['headers'].append [name, value.to_s.strip]
28
+ end
29
+
30
+ def param name, value
31
+ @__req['query'] = [] unless @__req['query']
32
+ @__req['query'].append [name, value.to_s.strip]
33
+ end
34
+
35
+ def content_type media_type
36
+ @__req['headers'] = [] unless @__req['headers']
37
+ @__req['headers'].append ['Content-Type', media_type]
38
+ end
39
+
40
+ def json data
41
+ body JSON.pretty_generate(data)
42
+ content_type 'application/json'
43
+ end
44
+
45
+ def body body_content
46
+ @__req['body'] = body_content
47
+ end
48
+
49
+ def ensure_success!
50
+ @__req['ensure_success'] = true
51
+ end
52
+
53
+ def ensure_success?
54
+ @__req['ensure_success']
55
+ end
56
+
57
+ def authenticate method
58
+ @__req['auth'] = method
59
+ end
60
+
61
+ def certificate path
62
+ @__req['cert'] = path
63
+ use_ssl!
64
+ end
65
+
66
+ def use_ssl!
67
+ @__req['use_ssl'] = true
68
+ end
69
+
70
+ alias_method :auth, :authenticate
71
+ alias_method :cert, :certificate
72
+ alias_method :media_type, :content_type
73
+ end
74
+
75
+ class SpectreHttpHeader
76
+ def initialize headers
77
+ @headers = headers || {}
78
+ end
79
+
80
+ def [] key
81
+ @headers[key.downcase]
82
+ end
83
+ end
84
+
85
+ class SpectreHttpResponse
86
+ def initialize res
87
+ @res = res
88
+ @data = nil
89
+ end
90
+
91
+ def code
92
+ @res[:code]
93
+ end
94
+
95
+ def message
96
+ @res[:message]
97
+ end
98
+
99
+ def protocol
100
+ @res[:protocol]
101
+ end
102
+
103
+ def version
104
+ @res[:version]
105
+ end
106
+
107
+ def headers
108
+ SpectreHttpHeader.new @res[:headers]
109
+ end
110
+
111
+ def body
112
+ @res[:body]
113
+ end
114
+
115
+ def json
116
+ return nil unless @res[:body]
117
+
118
+ if @data == nil
119
+ begin
120
+ @data = JSON.parse(@res[:body], object_class: OpenStruct)
121
+ rescue
122
+ raise 'invalid json'
123
+ end
124
+ end
125
+
126
+ @data
127
+ end
128
+
129
+ def success?
130
+ @res[:code] < 400
131
+ end
132
+
133
+ def pretty
134
+ @res.pretty
135
+ end
136
+ end
137
+
138
+ # DEFAULT_HTTP_REQUEST = {
139
+ # 'method' => 'GET', # -X, --request <cmd>
140
+ # 'base_url' => nil,
141
+ # 'path' => nil,
142
+ # 'headers' => nil, # -H, --header <header/@file>
143
+ # 'query' => nil,
144
+ # 'body' => nil, # -d, --data <data>
145
+ # 'cert' => nil, # --cacert
146
+ # 'follow' => false, # -L, --location
147
+ # 'username' => nil, # -u, --user <user:password>
148
+ # 'password' => nil,
149
+ # 'use_ssl' => false, # -k
150
+ # }
151
+
152
+
153
+ class << self
154
+ @@http_cfg = {}
155
+ @@response = nil
156
+ @@request = nil
157
+ @@modules = []
158
+
159
+ def curl name, secure: false, &block
160
+ req = {
161
+ 'use_ssl' => secure,
162
+ }
163
+
164
+ if @@http_cfg.key? name
165
+ req.merge! @@http_cfg[name]
166
+ raise "No `base_url' set for HTTP client '#{name}'. Check your HTTP config in your environment." unless req['base_url']
167
+ else
168
+ req['base_url'] = name
169
+ end
170
+
171
+ SpectreHttpRequest.new(req)._evaluate(&block) if block_given?
172
+
173
+ invoke(req)
174
+ end
175
+
176
+ def curl_request
177
+ raise 'No request has been invoked yet' unless @@request
178
+
179
+ @@request
180
+ end
181
+
182
+ def curl_response
183
+ raise 'There is no response. No request has been invoked yet.' unless @@response
184
+
185
+ @@response
186
+ end
187
+
188
+ def register mod
189
+ raise 'Module must not be nil' unless mod
190
+
191
+ @@modules << mod
192
+ end
193
+
194
+ private
195
+
196
+ def try_format_json str, pretty: false
197
+ return str unless str or str.empty?
198
+
199
+ begin
200
+ json = JSON.parse(str)
201
+ json.obfuscate!(@@secure_keys) unless @@debug
202
+
203
+ if pretty
204
+ str = JSON.pretty_generate(json)
205
+ else
206
+ str = JSON.dump(json)
207
+ end
208
+ rescue
209
+ # do nothing
210
+ end
211
+
212
+ str
213
+ end
214
+
215
+ def secure? key
216
+ @@secure_keys.any? { |x| key.to_s.downcase.include? x.downcase }
217
+ end
218
+
219
+ def header_to_s headers
220
+ s = ''
221
+
222
+ return s unless headers
223
+
224
+ headers.each do |header|
225
+ key = header[0].to_s
226
+ value = header[1].to_s
227
+ value = '*****' if secure?(key) and not @@debug
228
+ s += "#{key.ljust(30, '.')}: #{value}\n"
229
+ end
230
+
231
+ s
232
+ end
233
+
234
+ def invoke req
235
+ cmd = [@@curl_path]
236
+
237
+ if req['cert'] or req['use_ssl']
238
+ scheme = 'https'
239
+ else
240
+ scheme = 'http'
241
+ end
242
+
243
+ uri = req['base_url']
244
+
245
+ unless uri.match /http(?:s)?:\/\//
246
+ uri = scheme + '://' + uri
247
+ end
248
+
249
+ if req['path']
250
+ uri += '/' unless uri.end_with? '/'
251
+ uri += req['path']
252
+ end
253
+
254
+ if req['query']
255
+ uri += '?'
256
+ uri += req['query']
257
+ .map { |x| x.join '='}
258
+ .join('&')
259
+ end
260
+
261
+ cmd.append('"' + uri + '"')
262
+ cmd.append('-X', req['method']) unless req['method'] == 'GET' or (req['body'] and req['method'] == 'POST')
263
+
264
+ # Call all registered modules
265
+ @@modules.each do |mod|
266
+ mod.on_req(req, cmd) if mod.respond_to? :on_req
267
+ end
268
+
269
+ # Add headers to curl command
270
+ req['headers'].each do |header|
271
+ cmd.append('-H', '"' + header.join(':') + '"')
272
+ end if req['headers']
273
+
274
+ # Add request body
275
+ if req['body'] != nil and not req['body'].empty?
276
+ req_body = try_format_json(req['body']).gsub(/"/, '\\"')
277
+ cmd.append('-d', '"' + req_body + '"')
278
+ elsif ['POST', 'PUT', 'PATCH'].include? req['method'].upcase
279
+ cmd.append('-d', '"\n"')
280
+ end
281
+
282
+ # Add certificate path if one if given
283
+ if req['cert']
284
+ raise "Certificate '#{req['cert']}' does not exist" unless File.exists? req['cert']
285
+
286
+ cmd.append('--cacert', req['cert'])
287
+ elsif req['use_ssl'] or uri.start_with? 'https'
288
+ cmd.append('-k')
289
+ end
290
+
291
+ cmd.append('-i')
292
+ cmd.append('-v')
293
+
294
+ @@request = OpenStruct.new(req)
295
+
296
+ sys_cmd = cmd.join(' ')
297
+
298
+ @@logger.debug(sys_cmd)
299
+
300
+ req_id = SecureRandom.uuid()[0..5]
301
+
302
+ req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
303
+ req_log += header_to_s(req['headers'])
304
+
305
+ if req[:body] != nil and not req[:body].empty?
306
+ req_log += try_format_json(req['body'], pretty: true)
307
+ end
308
+
309
+ @@logger.info(req_log)
310
+
311
+ start_time = Time.now
312
+
313
+ stdin, stdout, stderr, wait_thr = Open3.popen3(sys_cmd)
314
+
315
+ end_time = Time.now
316
+
317
+ output = stdout.gets(nil)
318
+ stdout.close
319
+
320
+ debug_log = stderr.gets(nil)
321
+ stderr.close
322
+
323
+ # debug_log.lines.each { |x| @@logger.debug x unless x.empty? }
324
+
325
+ raise "Unable to request #{uri}. Please check if this URL is correctly configured and reachable." unless output
326
+
327
+ @@logger.debug("[<] #{req_id} stdout:\n#{output}")
328
+
329
+ header, body = output.split /\r?\n\r?\n/
330
+
331
+ result = header.lines.first
332
+
333
+ exit_code = wait_thr.value.exitstatus
334
+
335
+ raise Exception.new "An error occurred while executing curl:\n#{debug_log.lines.map { |x| not x.empty? }}" unless exit_code == 0
336
+
337
+ # Parse protocol, version, status code and status message from response
338
+ match = /^(?<protocol>[A-Za-z0-9]+)\/(?<version>\d+\.?\d*) (?<code>\d+) (?<message>.*)/.match result
339
+
340
+ raise "Unexpected result from curl request:\n#{result}" unless match
341
+
342
+ res_headers = header.lines[1..-1]
343
+ .map { |x| /^(?<key>[A-Za-z0-9-]+):\s*(?<value>.*)$/.match x }
344
+ .select { |x| x != nil }
345
+ .map { |x| [x[:key].downcase, x[:value]] }
346
+
347
+ res = {
348
+ protocol: match[:protocol],
349
+ version: match[:version],
350
+ code: match[:code].to_i,
351
+ message: match[:message],
352
+ headers: Hash[res_headers],
353
+ body: body,
354
+ }
355
+
356
+ # Call all registered modules
357
+ @@modules.each do |mod|
358
+ mod.on_res(res, output) if mod.respond_to? :on_res
359
+ end
360
+
361
+ res_log = "[<] #{req_id} #{res[:code]} #{res[:message]} (#{end_time - start_time}s)\n"
362
+ res_headers.each do |header|
363
+ res_log += "#{header[0].to_s.ljust(30, '.')}: #{header[1].to_s}\n"
364
+ end
365
+
366
+ if res[:body] != nil and not res[:body].empty?
367
+ res_log += try_format_json(res[:body], pretty: true)
368
+ end
369
+
370
+ @@logger.info res_log
371
+
372
+ @@response = SpectreHttpResponse.new(res)
373
+
374
+ raise "Response did not indicate success: #{@@response.code} #{@@response.message}" if req['ensure_success'] and not @@response.success?
375
+
376
+ @@response
377
+ end
378
+ end
379
+
380
+ Spectre.register do |config|
381
+ @@debug = config['debug']
382
+
383
+ @@logger = ::Logger.new(config['log_file'], progname: 'spectre/curl')
384
+ @@logger.level = @@debug ? Logger::DEBUG : Logger::INFO
385
+
386
+ @@secure_keys = config['secure_keys'] || []
387
+
388
+ @@curl_path = config['curl_path'] || 'curl'
389
+
390
+ if config.key? 'http'
391
+ @@http_cfg = {}
392
+
393
+ config['http'].each do |name, cfg|
394
+ @@http_cfg[name] = cfg
395
+ end
396
+ end
397
+ end
398
+
399
+ Spectre.delegate :curl, :curl_response, :curl_request, to: self
400
+ end