noms-command 0.5.0 → 2.1.1
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 +4 -4
- data/README.rst +104 -37
- data/TODO.rst +3 -3
- data/fixture/dnc.rb +110 -1
- data/fixture/public/dnc.json +6 -5
- data/fixture/public/lib/dnc.js +171 -24
- data/fixture/public/lib/nomsargs.js +72 -0
- data/lib/noms/command.rb +37 -8
- data/lib/noms/command/application.rb +32 -26
- data/lib/noms/command/auth.rb +44 -62
- data/lib/noms/command/auth/identity.rb +205 -5
- data/lib/noms/command/base.rb +11 -1
- data/lib/noms/command/formatter.rb +5 -4
- data/lib/noms/command/home.rb +21 -0
- data/lib/noms/command/useragent.rb +117 -40
- data/lib/noms/command/useragent/cache.rb +124 -0
- data/lib/noms/command/useragent/requester.rb +48 -0
- data/lib/noms/command/useragent/requester/httpclient.rb +61 -0
- data/lib/noms/command/useragent/requester/typhoeus.rb +73 -0
- data/lib/noms/command/useragent/response.rb +202 -0
- data/lib/noms/command/useragent/response/httpclient.rb +59 -0
- data/lib/noms/command/useragent/response/typhoeus.rb +74 -0
- data/lib/noms/command/version.rb +1 -1
- data/lib/noms/command/window.rb +21 -3
- data/lib/noms/command/xmlhttprequest.rb +8 -8
- data/noms-command.gemspec +3 -1
- data/spec/07js_spec.rb +1 -1
- data/spec/10auth_spec.rb +132 -0
- data/spec/11useragent_cache_spec.rb +160 -0
- data/spec/12useragent_auth_cookie_spec.rb +53 -0
- data/spec/13useragent_auth_spec.rb +90 -0
- data/spec/spec_helper.rb +5 -0
- metadata +46 -4
- data/fixture/public/lib/noms-args.js +0 -13
@@ -0,0 +1,61 @@
|
|
1
|
+
#!ruby
|
2
|
+
|
3
|
+
require 'noms/command/version'
|
4
|
+
|
5
|
+
require 'httpclient'
|
6
|
+
|
7
|
+
require 'noms/command/base'
|
8
|
+
require 'noms/command/useragent/requester'
|
9
|
+
require 'noms/command/useragent/response/httpclient'
|
10
|
+
|
11
|
+
class NOMS
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class NOMS::Command
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class NOMS::Command::UserAgent < NOMS::Command::Base
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
class NOMS::Command::UserAgent::Requester < NOMS::Command::Base
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class NOMS::Command::UserAgent::Requester::HTTPClient < NOMS::Command::Base
|
28
|
+
|
29
|
+
def initialize(opt={})
|
30
|
+
@log = opt[:logger] || default_logger
|
31
|
+
@log.debug "Creating #{self.class} with options: #{opt.inspect}"
|
32
|
+
@client_opts = opt
|
33
|
+
@client = ::HTTPClient.new :agent_name => "noms/#{NOMS::Command::VERSION} httpclient/#{::HTTPClient::VERSION}"
|
34
|
+
@cookies = opt.has_key?(:cookies) ? opt[:cookies] : true
|
35
|
+
if @cookies
|
36
|
+
cookie_jar = File.join(NOMS::Command.home, 'cookies.txt')
|
37
|
+
@client.set_cookie_store(cookie_jar)
|
38
|
+
else
|
39
|
+
@client.cookie_manager = nil
|
40
|
+
end
|
41
|
+
@client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
42
|
+
end
|
43
|
+
|
44
|
+
def request(opt={})
|
45
|
+
response = @client.request(opt[:method].to_s.upcase, opt[:url], '', opt[:body], opt[:headers])
|
46
|
+
noms_response = NOMS::Command::UserAgent::Response::HTTPClient.new(response)
|
47
|
+
if @cookies
|
48
|
+
# @client.save_cookie_store - There is a bug where
|
49
|
+
# it thinks @is_saved is satisfied all the time and
|
50
|
+
# the file isn't written, which is why we call
|
51
|
+
# cookie manager directly.
|
52
|
+
@client.cookie_manager.save_all_cookies(true)
|
53
|
+
end
|
54
|
+
noms_response
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_auth(domain, username, password)
|
58
|
+
@client.set_auth(domain, username, password)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#!ruby
|
2
|
+
|
3
|
+
require 'noms/command/version'
|
4
|
+
|
5
|
+
require 'uri'
|
6
|
+
require 'typhoeus'
|
7
|
+
|
8
|
+
require 'noms/command/base'
|
9
|
+
require 'noms/command/useragent/requester'
|
10
|
+
require 'noms/command/useragent/response/typhoeus'
|
11
|
+
|
12
|
+
class NOMS
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class NOMS::Command
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class NOMS::Command::UserAgent < NOMS::Command::Base
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
class NOMS::Command::UserAgent::Requester < NOMS::Command::Base
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
class NOMS::Command::UserAgent::Requester::Typhoeus < NOMS::Command::Base
|
29
|
+
|
30
|
+
def initialize(opt={})
|
31
|
+
@log = opt[:logger] || default_logger
|
32
|
+
@log.debug "Creating #{self.class} with options: #{opt.inspect}"
|
33
|
+
@agent_name = "noms/#{NOMS::Command::VERSION} typhoeus/#{::Typhoeus::VERSION}"
|
34
|
+
@auth = { }
|
35
|
+
@client_opts = {
|
36
|
+
:ssl_verifypeer => false
|
37
|
+
}
|
38
|
+
cookies = opt.has_key?(:cookies) ? opt[:cookies] : true
|
39
|
+
|
40
|
+
if cookies
|
41
|
+
cookie_jar = File.join(NOMS::Command.home, 'cookies.txt')
|
42
|
+
@client_opts = @client_opts.merge({
|
43
|
+
:cookiefile => cookie_jar,
|
44
|
+
:cookiejar => cookie_jar
|
45
|
+
})
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_auth(url)
|
50
|
+
url = URI.parse(url) unless url.respond_to? :scheme
|
51
|
+
domain = url.scheme + '://' + url.host + ':' + url.port.to_s + '/'
|
52
|
+
@auth[domain] || { }
|
53
|
+
end
|
54
|
+
|
55
|
+
def request(opt={})
|
56
|
+
url = opt[:url]
|
57
|
+
spec = @client_opts.merge({
|
58
|
+
:method => opt[:method] || opt[:get],
|
59
|
+
:headers => {
|
60
|
+
'User-Agent' => @agent_name
|
61
|
+
}.merge(opt[:headers]),
|
62
|
+
:body => opt[:body]
|
63
|
+
}).merge(get_auth(url))
|
64
|
+
request = ::Typhoeus::Request.new(opt[:url], spec)
|
65
|
+
response = request.run
|
66
|
+
NOMS::Command::UserAgent::Response::Typhoeus.new(response)
|
67
|
+
end
|
68
|
+
|
69
|
+
def set_auth(domain, username, password)
|
70
|
+
@auth[domain] = { :userpwd => [username, password].join(':') }
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
#!ruby
|
2
|
+
|
3
|
+
require 'noms/command/version'
|
4
|
+
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
require 'noms/command'
|
8
|
+
|
9
|
+
class NOMS
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
class NOMS::Command
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class NOMS::Command::UserAgent < NOMS::Command::Base
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class NOMS::Command::UserAgent::Response < NOMS::Command::Base
|
22
|
+
|
23
|
+
attr_reader :expires, :cache_control, :etag, :last_modified
|
24
|
+
attr_accessor :auth_hash, :from_cache, :date, :original_date
|
25
|
+
|
26
|
+
def self.from_cache(cache_text, opts={})
|
27
|
+
unless cache_text.nil? or cache_text.empty?
|
28
|
+
self.from_json(cache_text, opts)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.from_json(json_text, opts={})
|
33
|
+
return self.new(JSON.parse(json_text), opts)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Constructor, pass in HTTP response subclass object
|
37
|
+
# In the base clase (which is what gets unfrozen)
|
38
|
+
# This is a hash.
|
39
|
+
def initialize(httpresponse, opts={})
|
40
|
+
@log = opts[:logger] || default_logger
|
41
|
+
@response = httpresponse
|
42
|
+
self.from_cache = false
|
43
|
+
self.auth_hash = nil
|
44
|
+
|
45
|
+
if httpresponse.respond_to? :[]
|
46
|
+
self.from_cache = httpresponse['from_cache']
|
47
|
+
self.auth_hash = httpresponse['auth_hash']
|
48
|
+
self.original_date = Time.httpdate(httpresponse['original_date'])
|
49
|
+
self.date = Time.httpdate(httpresponse['date'])
|
50
|
+
end
|
51
|
+
|
52
|
+
@cache_control = self.header 'Cache-Control'
|
53
|
+
@date = get_header_time('Date') || Time.now
|
54
|
+
@original_date ||= @date
|
55
|
+
@expires = get_expires
|
56
|
+
@etag = self.header 'Etag'
|
57
|
+
@last_modified = get_header_time 'Last-modified'
|
58
|
+
end
|
59
|
+
|
60
|
+
# The response body
|
61
|
+
def body
|
62
|
+
@response['body']
|
63
|
+
end
|
64
|
+
|
65
|
+
# The success status
|
66
|
+
def success?
|
67
|
+
# We created this object explicitly, so it's always successful
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
# A hash or headers, or specific header
|
72
|
+
def header(hdr=nil)
|
73
|
+
if hdr.nil?
|
74
|
+
@response['header']
|
75
|
+
else
|
76
|
+
Hash[@response['header'].map { |h, v| [h.downcase, v] }][hdr.downcase] unless @response.nil?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# An integer status code
|
81
|
+
def status
|
82
|
+
@response['status']
|
83
|
+
end
|
84
|
+
|
85
|
+
# The status message including code
|
86
|
+
def statusText
|
87
|
+
@response['statusText']
|
88
|
+
end
|
89
|
+
|
90
|
+
# MIME Type of content
|
91
|
+
def content_type
|
92
|
+
self.header 'Content-Type'
|
93
|
+
end
|
94
|
+
|
95
|
+
def header_params(s)
|
96
|
+
Hash[s.split(';').map do |field|
|
97
|
+
param, value = field.split('=', 2)
|
98
|
+
value ||= ''
|
99
|
+
[param.strip, value.strip]
|
100
|
+
end]
|
101
|
+
end
|
102
|
+
|
103
|
+
def content_encoding
|
104
|
+
if self.content_type
|
105
|
+
header_params(self.content_type)['charset']
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_json
|
110
|
+
self.to_hash.to_json
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_cache
|
114
|
+
self.to_json
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_hash
|
118
|
+
{
|
119
|
+
'body' => self.body,
|
120
|
+
'header' => self.header,
|
121
|
+
'status' => self.status,
|
122
|
+
'statusText' => self.statusText,
|
123
|
+
'auth_hash' => self.auth_hash,
|
124
|
+
'from_cache' => self.from_cache,
|
125
|
+
'date' => self.date.httpdate,
|
126
|
+
'original_date' => self.original_date.httpdate
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
def auth_hash=(hvalue)
|
131
|
+
@auth_hash = hvalue
|
132
|
+
end
|
133
|
+
|
134
|
+
def get_expires
|
135
|
+
|
136
|
+
expires = [ ]
|
137
|
+
|
138
|
+
if @cache_control and (m = /max-age=(\d+)/.match(@cache_control))
|
139
|
+
expires << (@date + m[1].to_i)
|
140
|
+
end
|
141
|
+
|
142
|
+
if self.header('Expires')
|
143
|
+
begin
|
144
|
+
expires << Time.httpdate(self.header('Expires'))
|
145
|
+
rescue ArgumentError => e
|
146
|
+
@log.debug "Response had 'Expires' header but could not parse (#{e.class}): #{e.message}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
return nil if expires.empty?
|
151
|
+
|
152
|
+
expires.empty? ? nil : expires.min
|
153
|
+
end
|
154
|
+
|
155
|
+
def get_header_time(hdr)
|
156
|
+
value = self.header hdr
|
157
|
+
if value
|
158
|
+
begin
|
159
|
+
Time.httpdate(value)
|
160
|
+
rescue ArgumentError => e
|
161
|
+
@log.debug "Response has a '#{hdr}' but could not parse (#{e.class}): #{e.message}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def cacheable?
|
167
|
+
return false unless self.status == 200
|
168
|
+
return false unless @expires
|
169
|
+
return false if self.from_cache?
|
170
|
+
return false if /no-cache/.match @cache_control
|
171
|
+
return false if /no-store/.match @cache_control
|
172
|
+
return false if self.header 'Pragma' and /no-cache/.match self.header('Pragma')
|
173
|
+
|
174
|
+
true
|
175
|
+
end
|
176
|
+
|
177
|
+
def cached!
|
178
|
+
@from_cache = true
|
179
|
+
end
|
180
|
+
|
181
|
+
def from_cache?
|
182
|
+
@from_cache
|
183
|
+
end
|
184
|
+
|
185
|
+
def current?
|
186
|
+
return false if (@cache_control and /must-revalidate/.match @cache_control)
|
187
|
+
return false unless @expires
|
188
|
+
return false if Time.now > @expires
|
189
|
+
true
|
190
|
+
end
|
191
|
+
|
192
|
+
def age
|
193
|
+
Time.now - self.original_date
|
194
|
+
end
|
195
|
+
|
196
|
+
def cacheable_copy
|
197
|
+
other = self.dup
|
198
|
+
other.logger = nil
|
199
|
+
other
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'noms/command/version'
|
4
|
+
|
5
|
+
require 'httpclient'
|
6
|
+
|
7
|
+
class NOMS
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
class NOMS::Command
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class NOMS::Command::UserAgent < NOMS::Command::Base
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class NOMS::Command::UserAgent::Response < NOMS::Command::Base
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
class NOMS::Command::UserAgent::Response::HTTPClient < NOMS::Command::UserAgent::Response
|
24
|
+
|
25
|
+
def initialize(httpresponse, opts={})
|
26
|
+
super
|
27
|
+
@log = opts[:logger] || default_logger
|
28
|
+
@response = httpresponse
|
29
|
+
end
|
30
|
+
|
31
|
+
def body
|
32
|
+
@response.content
|
33
|
+
end
|
34
|
+
|
35
|
+
def success?
|
36
|
+
@response.ok?
|
37
|
+
end
|
38
|
+
|
39
|
+
def header(hdr=nil)
|
40
|
+
if hdr.nil?
|
41
|
+
@response.headers
|
42
|
+
else
|
43
|
+
@response.header[hdr.downcase].first
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def status
|
48
|
+
@response.status.to_i unless @response.status.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
def statusText
|
52
|
+
@response.status.to_s + ' ' + @response.reason unless @response.status.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
def content_type
|
56
|
+
@response.contenttype
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'noms/command/version'
|
4
|
+
|
5
|
+
require 'typhoeus'
|
6
|
+
|
7
|
+
class NOMS
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
class NOMS::Command
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class NOMS::Command::UserAgent < NOMS::Command::Base
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class NOMS::Command::UserAgent::Response < NOMS::Command::Base
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
class NOMS::Command::UserAgent::Response::Typhoeus < NOMS::Command::UserAgent::Response
|
24
|
+
|
25
|
+
def initialize(httpresponse, opts={})
|
26
|
+
super
|
27
|
+
if @response.return_code != :ok
|
28
|
+
raise NOMS::Command::Error.new "Client error[#{@response.return_code.inspect}]: #{@response.return_message}"
|
29
|
+
end
|
30
|
+
content_encoding = self.content_encoding || 'utf-8'
|
31
|
+
@body = @response.body
|
32
|
+
|
33
|
+
if @body
|
34
|
+
|
35
|
+
begin
|
36
|
+
@log.debug "Forcing body string encoding to #{content_encoding}"
|
37
|
+
@body.force_encoding(content_encoding)
|
38
|
+
rescue Encoding::UndefinedConversionError
|
39
|
+
@log.debug " (coercing 'binary' to '#{content_encoding}')"
|
40
|
+
@body = @body.encode('utf8', 'binary', :undef => :replace, :invalid => :replace)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def body
|
47
|
+
@body || @response.body
|
48
|
+
end
|
49
|
+
|
50
|
+
def success?
|
51
|
+
@response.success?
|
52
|
+
end
|
53
|
+
|
54
|
+
def header(hdr=nil)
|
55
|
+
if hdr.nil?
|
56
|
+
@response.headers
|
57
|
+
else
|
58
|
+
Hash[@response.headers.map { |h, v| [h.downcase, v] }][hdr.downcase]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def status
|
63
|
+
@response.code.to_i
|
64
|
+
end
|
65
|
+
|
66
|
+
def statusText
|
67
|
+
@response.code.to_s + ' ' + @response.status_message
|
68
|
+
end
|
69
|
+
|
70
|
+
def content_type
|
71
|
+
@response.headers['Content-Type']
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|