noms-command 0.5.0 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|