httpclient-2.3.3/ 0000755 0000041 0000041 00000000000 12155433207 013727 5 ustar www-data www-data httpclient-2.3.3/sample/ 0000755 0000041 0000041 00000000000 12155433207 015210 5 ustar www-data www-data httpclient-2.3.3/sample/oauth_salesforce_10.rb 0000644 0000041 0000041 00000004443 12155433207 021370 0 ustar www-data www-data require 'oauthclient'
# Get your own consumer token from http://twitter.com/apps
consumer_key = '3MVG9y6x0357HledNGHa9tJrrlOmpCSo5alTv4W4AG1M0f9a8cGBIwo5wN2bQ7hjAEsjD7SBWf3H2Oycc9Qql'
consumer_secret = '1404017425765973464'
callback = ARGV.shift # can be nil for OAuth 1.0. (not 1.0a)
request_token_url = 'https://login.salesforce.com/_nc_external/system/security/oauth/RequestTokenHandler'
oob_authorize_url = 'https://login.salesforce.com/setup/secur/RemoteAccessAuthorizationPage.apexp'
access_token_url = 'https://login.salesforce.com/_nc_external/system/security/oauth/AccessTokenHandler'
STDOUT.sync = true
# create OAuth client.
client = OAuthClient.new
client.oauth_config.consumer_key = consumer_key
client.oauth_config.consumer_secret = consumer_secret
client.oauth_config.signature_method = 'HMAC-SHA1'
client.oauth_config.http_method = :get # Twitter does not allow :post
client.debug_dev = STDERR if $DEBUG
client.ssl_config.ssl_version = "TLSv1_1"
# Get request token.
res = client.get_request_token(request_token_url, callback)
p res.status
p res.oauth_params
p res.content
p client.oauth_config
token = res.oauth_params['oauth_token']
secret = res.oauth_params['oauth_token_secret']
raise if token.nil? or secret.nil?
# You need to confirm authorization out of band.
puts
puts "Go here and do confirm: #{oob_authorize_url}?oauth_token=#{token}&oauth_consumer_key=#{consumer_key}"
puts "Type oauth_verifier/PIN (if given) and hit [enter] to go"
verifier = gets.chomp
verifier = nil if verifier.empty?
# Get access token.
# FYI: You may need to re-construct OAuthClient instance here.
# In normal web app flow, getting access token and getting request token
# must be done in different HTTP requests.
# client = OAuthClient.new
# client.oauth_config.consumer_key = consumer_key
# client.oauth_config.consumer_secret = consumer_secret
# client.oauth_config.signature_method = 'HMAC-SHA1'
# client.oauth_config.http_method = :get # Twitter does not allow :post
res = client.get_access_token(access_token_url, token, secret, verifier)
p res.status
p res.oauth_params
p res.content
p client.oauth_config
id = res.oauth_params['user_id']
puts
puts "Access token usage example"
puts "Hit [enter] to go"
gets
# Access to a protected resource. (DM)
puts client.get("http://twitter.com/direct_messages.json")
httpclient-2.3.3/sample/howto.rb 0000644 0000041 0000041 00000002045 12155433207 016676 0 ustar www-data www-data #!/usr/bin/env ruby
$:.unshift(File.join('..', 'lib'))
require 'httpclient'
proxy = ENV['HTTP_PROXY']
clnt = HTTPClient.new(proxy)
clnt.set_cookie_store("cookie.dat")
target = ARGV.shift || "http://localhost/foo.cgi"
puts
puts '= GET content directly'
puts clnt.get_content(target)
puts '= GET result object'
result = clnt.get(target)
puts '== Header object'
p result.header
puts "== Content-type"
p result.contenttype
puts '== Body object'
p result.body
puts '== Content'
print result.content
puts '== GET with Block'
clnt.get(target) do |str|
puts str
end
puts
puts '= GET with query'
puts clnt.get(target, { "foo" => "bar", "baz" => "quz" }).content
puts
puts '= GET with query 2'
puts clnt.get(target, [["foo", "bar1"], ["foo", "bar2"]]).content
clnt.debug_dev = STDERR
puts
puts '= GET with extra header'
puts clnt.get(target, nil, { "SOAPAction" => "HelloWorld" }).content
puts
puts '= GET with extra header 2'
puts clnt.get(target, nil, [["Accept", "text/plain"], ["Accept", "text/html"]]).content
clnt.debug_dev = nil
clnt.save_cookie_store
httpclient-2.3.3/sample/dav.rb 0000644 0000041 0000041 00000003657 12155433207 016322 0 ustar www-data www-data require 'uri'
require 'httpclient'
class DAV
attr_reader :headers
def initialize(uri = nil)
@uri = nil
@headers = {}
open(uri) if uri
proxy = ENV['HTTP_PROXY'] || ENV['http_proxy'] || nil
@client = HTTPClient.new(proxy)
end
def open(uri)
@uri = if uri.is_a?(URI)
uri
else
URI.parse(uri)
end
end
def set_basic_auth(user_id, passwd)
@client.set_basic_auth(@uri, user_id, passwd)
end
# TODO: propget/propset support
def propfind(target)
target_uri = @uri + target
res = @client.propfind(target_uri)
res.body.content
end
def get(target, local = nil)
local ||= target
target_uri = @uri + target
if FileTest.exist?(local)
raise RuntimeError.new("File #{ local } exists.")
end
f = File.open(local, "wb")
res = @client.get(target_uri, nil, @headers) do |data|
f << data
end
f.close
STDOUT.puts("#{ res.header['content-length'][0] } bytes saved to file #{ target }.")
end
def debug_dev=(dev)
@client.debug_dev = dev
end
def get_content(target)
target_uri = @uri + target
@client.get_content(target_uri, nil, @headers)
end
def put_content(target, content)
target_uri = @uri + target
res = @client.put(target_uri, content, @headers)
if res.status < 200 or res.status >= 300
raise "HTTP PUT failed: #{res.inspect}"
end
end
class Mock
attr_reader :headers
def initialize(uri = nil)
@uri = nil
@headers = {}
open(uri) if uri
@cache = {}
end
def open(uri)
@uri = uri.is_a?(URI) ? uri : URI.parse(uri)
end
def set_basic_auth(user_id, passwd)
# ignore
end
def propfind(target)
# not found
nil
end
def get(target, local = nil)
# ignore
end
def get_content(target)
@cache[target]
end
def put_content(target, content)
@cache[target] = content
nil
end
end
end
httpclient-2.3.3/sample/wcat.rb 0000644 0000041 0000041 00000000611 12155433207 016471 0 ustar www-data www-data #!/usr/bin/env ruby
# wcat for http-access2
# Copyright (C) 2001 TAKAHASHI Masayoshi
$:.unshift(File.join('..', 'lib'))
require 'httpclient'
if ENV['HTTP_PROXY']
h = HTTPClient.new(ENV['HTTP_PROXY'])
else
h = HTTPClient.new()
end
while urlstr = ARGV.shift
response = h.get(urlstr){ |data|
print data
}
p response.contenttype
p response.peer_cert if /^https/i =~ urlstr
end
httpclient-2.3.3/sample/oauth_twitter.rb 0000644 0000041 0000041 00000003773 12155433207 020451 0 ustar www-data www-data require 'oauthclient'
# Get your own consumer token from http://twitter.com/apps
consumer_key = 'EDIT HERE'
consumer_secret = 'EDIT HERE'
callback = ARGV.shift # can be nil for OAuth 1.0. (not 1.0a)
request_token_url = 'http://twitter.com/oauth/request_token'
oob_authorize_url = 'http://twitter.com/oauth/authorize'
access_token_url = 'http://twitter.com/oauth/access_token'
STDOUT.sync = true
# create OAuth client.
client = OAuthClient.new
client.oauth_config.consumer_key = consumer_key
client.oauth_config.consumer_secret = consumer_secret
client.oauth_config.signature_method = 'HMAC-SHA1'
client.oauth_config.http_method = :get # Twitter does not allow :post
client.debug_dev = STDERR if $DEBUG
# Get request token.
res = client.get_request_token(request_token_url, callback)
p res.status
p res.oauth_params
p res.content
p client.oauth_config
token = res.oauth_params['oauth_token']
secret = res.oauth_params['oauth_token_secret']
raise if token.nil? or secret.nil?
# You need to confirm authorization out of band.
puts
puts "Go here and do confirm: #{oob_authorize_url}?oauth_token=#{token}"
puts "Type oauth_verifier/PIN (if given) and hit [enter] to go"
verifier = gets.chomp
verifier = nil if verifier.empty?
# Get access token.
# FYI: You may need to re-construct OAuthClient instance here.
# In normal web app flow, getting access token and getting request token
# must be done in different HTTP requests.
# client = OAuthClient.new
# client.oauth_config.consumer_key = consumer_key
# client.oauth_config.consumer_secret = consumer_secret
# client.oauth_config.signature_method = 'HMAC-SHA1'
# client.oauth_config.http_method = :get # Twitter does not allow :post
res = client.get_access_token(access_token_url, token, secret, verifier)
p res.status
p res.oauth_params
p res.content
p client.oauth_config
id = res.oauth_params['user_id']
puts
puts "Access token usage example"
puts "Hit [enter] to go"
gets
# Access to a protected resource. (DM)
puts client.get("http://twitter.com/direct_messages.json")
httpclient-2.3.3/sample/oauth_friendfeed.rb 0000644 0000041 0000041 00000003655 12155433207 021041 0 ustar www-data www-data require 'oauthclient'
# Get your own consumer token from http://friendfeed.com/api/applications
consumer_key = 'EDIT HERE'
consumer_secret = 'EDIT HERE'
request_token_url = 'https://friendfeed.com/account/oauth/request_token'
oob_authorize_url = 'https://friendfeed.com/account/oauth/authorize'
access_token_url = 'https://friendfeed.com/account/oauth/access_token'
STDOUT.sync = true
# create OAuth client.
client = OAuthClient.new
client.oauth_config.consumer_key = consumer_key
client.oauth_config.consumer_secret = consumer_secret
client.oauth_config.signature_method = 'HMAC-SHA1'
client.oauth_config.http_method = :get # FriendFeed does not allow :post
client.debug_dev = STDERR if $DEBUG
# Get request token.
res = client.get_request_token(request_token_url)
p res.status
p res.oauth_params
p res.content
p client.oauth_config
token = res.oauth_params['oauth_token']
secret = res.oauth_params['oauth_token_secret']
raise if token.nil? or secret.nil?
# You need to confirm authorization out of band.
puts
puts "Go here and do confirm: #{oob_authorize_url}?oauth_token=#{token}"
puts "Hit [enter] to go"
gets
# Get access token.
# FYI: You may need to re-construct OAuthClient instance here.
# In normal web app flow, getting access token and getting request token
# must be done in different HTTP requests.
# client = OAuthClient.new
# client.oauth_config.consumer_key = consumer_key
# client.oauth_config.consumer_secret = consumer_secret
# client.oauth_config.signature_method = 'HMAC-SHA1'
# client.oauth_config.http_method = :get # Twitter does not allow :post
res = client.get_access_token(access_token_url, token, secret)
p res.status
p res.oauth_params
p res.content
p client.oauth_config
username = res.oauth_params['username']
puts
puts "Access token usage example"
puts "Hit [enter] to go"
gets
# Access to a protected resource. (user profile)
puts client.get("http://friendfeed-api.com/v2/feedinfo/#{username}?format=json")
httpclient-2.3.3/sample/stream.rb 0000644 0000041 0000041 00000000505 12155433207 017030 0 ustar www-data www-data $:.unshift(File.join('..', 'lib'))
require "httpclient"
c = HTTPClient.new
piper, pipew = IO.pipe
conn = c.post_async("http://localhost:8080/stream", piper)
Thread.new do
res = conn.pop
while str = res.content.read(10)
p str
end
end
p "type here"
while line = STDIN.gets
pipew << line
end
pipew.close
sleep 5
httpclient-2.3.3/sample/ssl/ 0000755 0000041 0000041 00000000000 12155433207 016011 5 ustar www-data www-data httpclient-2.3.3/sample/ssl/htdocs/ 0000755 0000041 0000041 00000000000 12155433207 017275 5 ustar www-data www-data httpclient-2.3.3/sample/ssl/htdocs/index.html 0000644 0000041 0000041 00000000176 12155433207 021276 0 ustar www-data www-data
SSL test
Verification succeeded?
httpclient-2.3.3/sample/ssl/webrick_httpsd.rb 0000644 0000041 0000041 00000001460 12155433207 021353 0 ustar www-data www-data #!/usr/bin/env ruby
require 'webrick/https'
require 'getopts'
getopts nil, 'r:', 'p:8808'
dir = File::dirname(File::expand_path(__FILE__))
# Pass phrase of '1000key.pem' is '1000'.
data = open(File::join(dir, "1000key.pem")){|io| io.read }
pkey = OpenSSL::PKey::RSA.new(data)
data = open(File::join(dir, "1000cert.pem")){|io| io.read }
cert = OpenSSL::X509::Certificate.new(data)
s = WEBrick::HTTPServer.new(
:BindAddress => "localhost",
:Port => $OPT_p.to_i,
:Logger => nil,
:DocumentRoot => $OPT_r || File::join(dir, "/htdocs"),
:SSLEnable => true,
:SSLVerifyClient => ::OpenSSL::SSL::VERIFY_NONE,
:SSLCertificate => cert,
:SSLPrivateKey => pkey,
:SSLCertName => nil,
:SSLCACertificateFile => "all.pem"
)
trap("INT"){ s.shutdown }
s.start
httpclient-2.3.3/sample/ssl/1000cert.pem 0000644 0000041 0000041 00000002133 12155433207 017751 0 ustar www-data www-data -----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwLTELMAkGA1UEBhMCQ1ox
DTALBgNVBAoTBFJ1YnkxDzANBgNVBAMTBlJ1YnlDQTAeFw0wMzA1MzEwMzUwNDFa
Fw0wNDA1MzAwMzUwNDFaMDAxCzAJBgNVBAYTAkNaMQ0wCwYDVQQKEwRSdWJ5MRIw
EAYDVQQDEwlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALhs
fh4i1c+K57vFG7SUDfdxuSlbPUqaV0sbiuvWb0f7B2T7bHicaIRsDYW7PQRLLwwR
Pd+aKg3KuwhWN47faRam19Z3yWCD7Tg+BhXDqlXnz6snnr4APpAxc22kJKjzuil6
sp+QTkl/EFKI3+ocDur1UB+kSOmTzsDmepaWUZwTAgMBAAGjgbMwgbAwCQYDVR0T
BAIwADAtBglghkgBhvhCAQ0EIBYeR2VuZXJhdGVkIGJ5IE9wZW5TU0wgZm9yIFJ1
YnkuMB0GA1UdDgQWBBQlYESgTYdkTOYy02+/jGSqa+OpzjBVBgNVHSMETjBMgBQN
iKV64A56Sfaem3wZrTOo9CHo5qExpC8wLTELMAkGA1UEBhMCQ1oxDTALBgNVBAoT
BFJ1YnkxDzANBgNVBAMTBlJ1YnlDQYIBADANBgkqhkiG9w0BAQUFAAOCAQEAJh9v
ehhUv69oilVWGvGB6xCr8LgErnO9QdAyqJE2xBhbNaB3crjWDdQTz4UNvCQoJG/4
Oa9Vp10vM8E0ZMVHer87WM9tPEOg09r38U/1c7gSYBkPSGQfeWtZNjQ1YOm6RDx4
JJp9sp9v/CdBlVXaBQQd+MQFny7E+EkMHRfiv89KTfOs0wYdirLrM1C90CZUEj0i
cMcBdHzH5XcNpWB1ag4cNiUn2ljsaWlUpEg48gLe2FLJVPBio+iZnOm/C6KIwBMO
BCVxkZ6oIR87JT4xbr8SxRS9d/irhVU9MtGYwMe4MPSztefASdmEyj59ZFCLKQHV
+ltGb7/b7DetoT1spA==
-----END CERTIFICATE-----
httpclient-2.3.3/sample/ssl/0key.pem 0000644 0000041 0000041 00000003327 12155433207 017371 0 ustar www-data www-data -----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,BC9A85421E11A052
dlFN8woboqu1mLWHSe6HnBCzrJUFw1NLv4sJM8MFPcSGkPEJoqceRQ5XFFlwnhwT
WStidVQLAaCqWxPlQbvKs/eW2yEZRX3JhNHlGkuZUbAuqC7FGcIPCFtXmshmR3/K
wSxM3DuEBA31kHoyH/DGPEOcp/L9M95fU2w/1Zz895nXgECdlYfMB7KEXJ7S0TVD
NvcH6dcNS2I56ikUfRd9MA+tTtJTDsolBii3HpNkI+i8GeAAznfuyhCa+RasbMnR
3SRHSgiZpK01JkepKEumG1iyGXBE4GMlF3dK9TL4UTfK7PjbW1BRGVdmGayA5L8X
vpn9uIMbpFUD7nU/cSs6+ECYy9Fdq/d8oE7YRVpqGA2mX+Vvjzc6ZByiW8eH8uYW
d1YCySAzttQUcDFx4YlpbQN+NSE67amGY/nkXn3CnQuB2uDiUm6818p1jjhEbIj6
SQpNgpuhHPhyawMuC98BvxEXoSTFRmXx5h+Qgo8nfCBUpGUCA8STuJG+EebIXMHH
yNdZGYMdkkuzow1G+bcHaIV4pgD0hOX6D+AzhO8/11gsJNiZHWAkxRtXres0B7JQ
BTv6mrpmRsr1TbY7K55y5QePkvroiqI4BX2//RgAcUw+aBzPCSt87YU55pPxxDCv
KnoMQJapUIlXgYBs+2GBiFQlr3dAnzduXrXkZa/TuE0853QDDnKWN3aWapW0EieH
sDxYz6kZ6c/vjDJtNjjgysKvi2ZFnJMk92fi1sNd2MrH9w1vSmjHw6+b9REH+r6K
YCcMzCUnIV5Y5jgbnrY5jWlB5Jt5PlU+QDFTBNsdctyoES3h5yQh48hcpnJOy4YT
tn9jEmIAYM7QZtGZrY5yiZRZbM5tLL7VeXA0M7N0ivjZUVP4NBUV1IFhLpeus3Yo
yXB99Sib/M8fqlmnRlyKKaRseB9a80/LJdLJC7Q1aJG9JYTTpumydppDzvwwUFV/
yHQibPzWhnHTElyXSGeWWQ/4gKJXJFkSyrBeKt/DcDggEnpsVw7kAeo0KJLWvBq2
0CwnWxPsseIYSLdwE0wOZTnWvHC6viCCqxaFwROa+bHsgCtRR8xwOKVg0RQRexQi
SKsCEyq4hfKH3Kd7KkI5c4iCHrNDxQiYVAHGo0kuVWPNP9PH8XrRuP7f2Aq6ObEU
cGLN+OKlzChKJQZZzdthGTjO52cZERokS7etLs5yPNM27CUZIy2gUsWwANG700ov
DYi4S9j4y2fK9qVFGuNmZ7nNQ6juHIN8ZpZObmzGz/GEsVy8zYGV7jH2bC8fhCg1
wiTn0CHyfI0AfJ5zQMQ48z/ATI5+/pP5DuJ4kPLYU90EdIlIQ/9zQDRxVPvUalas
kskX8qdsbILaLqKzqvPa6jkhncV7SVQPhxIrs6vU2RWrsU1Qw9wHhzaITu9yb1l1
s8uialiJWnWOwkhIAUULOoGRnBB7U6sseFh7LcYHc2sZYJzQQO3g14ANvtsu/ILT
abUPXtGHSUlI4kUjI82NEkyQz/pDD9VkhTefiFHNymIkUaZBulmR5QdcEcQn6bku
J/P3M/T7CtsCPdBUrzDI71eAYqXPjikg+8unKWk9c/p+V7wXtvsdgJf3+xht/YUQ
-----END RSA PRIVATE KEY-----
httpclient-2.3.3/sample/ssl/0cert.pem 0000644 0000041 0000041 00000002416 12155433207 017534 0 ustar www-data www-data -----BEGIN CERTIFICATE-----
MIIDjzCCAnegAwIBAgIBADANBgkqhkiG9w0BAQUFADAtMQswCQYDVQQGEwJDWjEN
MAsGA1UEChMEUnVieTEPMA0GA1UEAxMGUnVieUNBMB4XDTAzMDUzMTAyNDcyOFoX
DTA1MDUzMDAyNDcyOFowLTELMAkGA1UEBhMCQ1oxDTALBgNVBAoTBFJ1YnkxDzAN
BgNVBAMTBlJ1YnlDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANfw
JSR2OxME6WBRYekhvN1M7uZwJLC3qHhRtBm583x7MS3yzF/HwFNH1oAmOmzUcDSz
Y9OmTDVIMU9b0bSYHuu8KswrRWmx/iEhgU/hODS1MQKi+uHoMTtY/dVXwTLAfw5d
UMPQ9F5EhiV57sKWrS7vIVBtpxUNgfJW61FP+Ru3Lr8uhUgXPECHck8fjFu8w2Sw
JQBQ8ePrfKBiTHpOzKmDVXXWzVYzIQN0zQfpu/FSjOJ4xWV3wmfltN4FK+UkpALW
3RKsNFx+Pmji0fr2/PeEPhzKkhWk5b+pYrIlTNkagS7u8EoeLtY1y3LSZvopbcPI
l0QFQHCMtxS7ngC6wsECAwEAAaOBuTCBtjAPBgNVHRMECDAGAQH/AgEAMC0GCWCG
SAGG+EIBDQQgFh5HZW5lcmF0ZWQgYnkgT3BlblNTTCBmb3IgUnVieS4wHQYDVR0O
BBYEFA2IpXrgDnpJ9p6bfBmtM6j0IejmMFUGA1UdIwROMEyAFA2IpXrgDnpJ9p6b
fBmtM6j0IejmoTGkLzAtMQswCQYDVQQGEwJDWjENMAsGA1UEChMEUnVieTEPMA0G
A1UEAxMGUnVieUNBggEAMA0GCSqGSIb3DQEBBQUAA4IBAQB1b4iTezqgZj6FhcAc
0AtleAc8TpUn8YOX6zSlHG6I7tKcLfnWen9eFs4Jx73Bup5wHvcrHF6U+/nAdpb5
R7lkNbjWFWwoi5cQC36mh4ym8GWhUZUf8362yPzimbdKMBIiDoIBY8NyIBBORU2m
BlXsHr0dqGpeTmNnFhgi1FCN+F/VitplOUP9E8NzsgLS/GY9HO160HbPXoZc4yp5
KFweqmyhbxZvkrrD6LanvB23cJP+TeHzA5mLJtIBciFq+AJe+cDTlJYjtLcsxkAy
Evojd//5gePjgSz3gbzU5Zq0/tI2T0rQYnmySoSQ1u2+i5FrMvEjh/H6tZtQ6nO0
ROTL
-----END CERTIFICATE-----
httpclient-2.3.3/sample/ssl/ssl_client.rb 0000644 0000041 0000041 00000001103 12155433207 020470 0 ustar www-data www-data #!/usr/bin/env ruby
$:.unshift(File.join('..', '..', 'lib'))
require 'httpclient'
url = ARGV.shift || 'https://localhost:8808/'
uri = URI.parse(url)
#ca_file = "0cert.pem"
#crl_file = '0crl.pem'
# create CA's cert in pem format and run 'c_rehash' in trust_certs dir. before
# using this.
ca_path = File.join(File.dirname(File.expand_path(__FILE__)), "trust_certs")
proxy = ENV['HTTP_PROXY'] || ENV['http_proxy'] || nil
h = HTTPClient.new(proxy)
#h.ssl_config.add_trust_ca(ca_file)
#h.ssl_config.add_crl(crl_file)
h.ssl_config.add_trust_ca(ca_path)
print h.get_content(url)
httpclient-2.3.3/sample/ssl/1000key.pem 0000644 0000041 0000041 00000001703 12155433207 017606 0 ustar www-data www-data -----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,5E9A9AC8F0F62A4A
3/VDlc8E13xKZr+MXt+HVZCiuWN1gsVx1bTZE5fN5FVoJ45Bgy2zAegnqZiP1NNy
j/76Vy9Ru/G9HPh5QPbIPl+md+tOyJV2M23a+5jESk1tLuWt5lRqmTtHN000844l
uCgZPPhRV7nhYPC5f6Gqw/xDl/EZsElqJM/hl2JbqiVKfT5/1i0STU8LNkoaLWrJ
kQhc7hOR5ihmDPeD8mA99bmGD+UyyqzzLTtKvRbBObyi9dy7cQ5Q+iptQWTUuEKI
+W7b8f8/Iiin4JJZGpFuhQSx0ARjT0fuYNXddmz1L3Gu3sjzN1GvT2T3GlpiF4/7
ERS8Q43zjoCP8nC2MTSvdNRRoPMBg2SDS2ZIq4GSiKsKjeN+GnPCycAMeZSr5yG6
VMBJLoAJ7XIcl3se8gF6hH1AfhCzDaK/2pKLP9jH/W4g6VvUBazKEQCNbZXSTpQ4
8EfvJBPpplFs3Zid6iwC/WjKhFBuBBfycwNJjXG9x1fMPkBM8HeiZXgrXoUXJMEP
RF05Beo0HPPEOPIxcG6EVmnpDvs8uC+xIQ6UE6DrLGK5TnR6kdz3BDpeAehujSss
wfZiOvuJQZQl+oovOH54pcwAwhicgRcNdIX47kHrXNL1vQMYTXte+ZzDGyoOXd0W
qf1CZbrjULT9nfJFWMMicTnLM/6iQx+3bTkXXvk0qP0qAoIPqtY4rwt6yHgq937A
LubDxudMWV0hqcnH8cBCPHfWdE4HELw4RcVXmQH43yHs1gwShyG9rTS+PCKoRr8u
bpssW6J0xJmilf1KprbNWJyof9i0CtSVOlUt6ttoinwqj8Me01dHqQ==
-----END RSA PRIVATE KEY-----
httpclient-2.3.3/sample/auth.rb 0000644 0000041 0000041 00000000526 12155433207 016501 0 ustar www-data www-data require 'httpclient'
c = HTTPClient.new
c.debug_dev = STDOUT
# for Proxy authentication: supports Basic, Negotiate and NTLM.
#c.set_proxy_auth("admin", "admin")
# for WWW authentication: supports Basic, Digest and Negotiate.
c.set_auth("http://dev.ctor.org/http-access2/", "user", "user")
p c.get("http://dev.ctor.org/http-access2/login")
httpclient-2.3.3/sample/async.rb 0000644 0000041 0000041 00000000216 12155433207 016651 0 ustar www-data www-data require 'httpclient'
c = HTTPClient.new
conn = c.get_async("http://dev.ctor.org/")
io = conn.pop.content
while str = io.read(40)
p str
end
httpclient-2.3.3/sample/thread.rb 0000644 0000041 0000041 00000000571 12155433207 017007 0 ustar www-data www-data $:.unshift(File.join('..', 'lib'))
require 'httpclient'
urlstr = ARGV.shift
proxy = ENV['HTTP_PROXY'] || ENV['http_proxy']
h = HTTPClient.new(proxy)
count = 20
res = []
g = []
for i in 0..count
g << Thread.new {
res[i] = h.get(urlstr)
}
end
g.each do |th|
th.join
end
for i in 0..(count - 1)
raise unless (res[i].content == res[i + 1].content)
end
puts 'ok'
httpclient-2.3.3/sample/cookie.rb 0000644 0000041 0000041 00000000524 12155433207 017007 0 ustar www-data www-data #!/usr/bin/env ruby
$:.unshift(File.join('..', 'lib'))
require 'httpclient'
proxy = ENV['HTTP_PROXY']
clnt = HTTPClient.new(proxy)
clnt.set_cookie_store("cookie.dat")
clnt.debug_dev = STDOUT if $DEBUG
while urlstr = ARGV.shift
response = clnt.get(urlstr){ |data|
print data
}
p response.contenttype
end
clnt.save_cookie_store
httpclient-2.3.3/sample/oauth_buzz.rb 0000644 0000041 0000041 00000003543 12155433207 017734 0 ustar www-data www-data require 'oauthclient'
require 'zlib'
require 'stringio'
# Get your own consumer token from http://code.google.com/apis/accounts/docs/RegistrationForWebAppsAuto.html
consumer_key = nil
consumer_secret = nil
callback = 'http://localhost/' # should point somewhere else...
scope = 'https://www.googleapis.com/auth/buzz'
request_token_url = 'https://www.google.com/accounts/OAuthGetRequestToken'
access_token_url = 'https://www.google.com/accounts/OAuthGetAccessToken'
STDOUT.sync = true
# create OAuth client.
client = OAuthClient.new
client.oauth_config.consumer_key = consumer_key
client.oauth_config.consumer_secret = consumer_secret
client.oauth_config.signature_method = 'HMAC-SHA1'
client.oauth_config.http_method = :get # Twitter does not allow :post
client.debug_dev = STDERR if $DEBUG
# Get request token.
res = client.get_request_token(request_token_url, callback, :scope => scope)
p res.status
p res.oauth_params
p res.content
p client.oauth_config
token = res.oauth_params['oauth_token']
secret = res.oauth_params['oauth_token_secret']
raise if token.nil? or secret.nil?
# You need to confirm authorization out of band.
puts
puts "Go here and do confirm: https://www.google.com/buzz/api/auth/OAuthAuthorizeToken?oauth_token=#{token}&domain=#{consumer_key}&scope=#{scope}"
puts "Type oauth_verifier (if given) and hit [enter] to go"
require 'cgi'
verifier = CGI.unescape(gets.chomp)
verifier = nil if verifier.empty?
# Get access token.
res = client.get_access_token(access_token_url, token, secret, verifier)
p res.status
p res.oauth_params
p res.content
p client.oauth_config
id = res.oauth_params['user_id']
puts
puts "Access token usage example"
puts "Hit [enter] to go"
gets
# Access to a protected resource.
# @consumption requires Buzz API
puts client.get_content("https://www.googleapis.com/buzz/v1/activities/@me/@consumption", :alt => :json, :prettyprint => true)
httpclient-2.3.3/test/ 0000755 0000041 0000041 00000000000 12155433207 014706 5 ustar www-data www-data httpclient-2.3.3/test/client.cert 0000644 0000041 0000041 00000002203 12155433207 017040 0 ustar www-data www-data -----BEGIN CERTIFICATE-----
MIIDKDCCAhCgAwIBAgIBAjANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGDAJKUDES
MBAGA1UECgwJSklOLkdSLkpQMQwwCgYDVQQLDANSUlIxCzAJBgNVBAMMAkNBMB4X
DTA0MDEzMTAzMTQ1OFoXDTM1MDEyMzAzMTQ1OFowZTELMAkGA1UEBgwCSlAxEjAQ
BgNVBAoMCUpJTi5HUi5KUDEMMAoGA1UECwwDUlJSMRAwDgYDVQQDDAdleGFtcGxl
MSIwIAYJKoZIhvcNAQkBDBNleGFtcGxlQGV4YW1wbGUub3JnMIGfMA0GCSqGSIb3
DQEBAQUAA4GNADCBiQKBgQDRWssrK8Gyr+500hpLjCGR3+AHL8/hEJM5zKi/MgLW
jTkvsgOwbYwXOiNtAbR9y4/ucDq7EY+cMUMHES4uFaPTcOaAV0aZRmk8AgslN1tQ
gNS6ew7/Luq3DcVeWkX8PYgR9VG0mD1MPfJ6+IFA5d3vKpdBkBgN4l46jjO0/2Xf
ewIDAQABo4GPMIGMMAwGA1UdEwEB/wQCMAAwMQYJYIZIAYb4QgENBCQWIlJ1Ynkv
T3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFOFvay0H7lr2
xUx6waYEV2bVDYQhMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcDAgYI
KwYBBQUHAwQwDQYJKoZIhvcNAQEFBQADggEBABd2dYWqbDIWf5sWFvslezxJv8gI
w64KCJBuyJAiDuf+oazr3016kMzAlt97KecLZDusGNagPrq02UX7YMoQFsWJBans
cDtHrkM0al5r6/WGexNMgtYbNTYzt/IwodISGBgZ6dsOuhznwms+IBsTNDAvWeLP
lt2tOqD8kEmjwMgn0GDRuKjs4EoboA3kMULb1p9akDV9ZESU3eOtpS5/G5J5msLI
9WXbYBjcjvkLuJH9VsJhb+R58Vl0ViemvAHhPilSl1SPWVunGhv6FcIkdBEi1k9F
e8BNMmsEjFiANiIRvpdLRbiGBt0KrKTndVfsmoKCvY48oCOvnzxtahFxfs8=
-----END CERTIFICATE-----
httpclient-2.3.3/test/test_hexdump.rb 0000644 0000041 0000041 00000001163 12155433207 017745 0 ustar www-data www-data # -*- encoding: utf-8 -*-
require File.expand_path('helper', File.dirname(__FILE__))
require 'hexdump'
class TestHexDump < Test::Unit::TestCase
def test_encode
str = "\032l\277\370\2429\216\236\351[{\{\262\350\274\376"
str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
assert_equal(["00000000 1a6cbff8 a2398e9e e95b7b7b b2e8bcfe .l...9...[{{...."], HexDump.encode(str))
end
end if defined?(RUBY_ENGINE) && RUBY_ENGINE != "rbx" && RUBY_VERSION >= "1.9.0"
# Rubinius 1.8 mode does not support Regexp.quote(raw, 'n') I don't want put
# a pressure on supporting it because 1.9 mode works fine.
httpclient-2.3.3/test/ca.cert 0000644 0000041 0000041 00000002547 12155433207 016160 0 ustar www-data www-data -----BEGIN CERTIFICATE-----
MIID0DCCArigAwIBAgIBADANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGDAJKUDES
MBAGA1UECgwJSklOLkdSLkpQMQwwCgYDVQQLDANSUlIxCzAJBgNVBAMMAkNBMB4X
DTA0MDEzMDAwNDIzMloXDTM2MDEyMjAwNDIzMlowPDELMAkGA1UEBgwCSlAxEjAQ
BgNVBAoMCUpJTi5HUi5KUDEMMAoGA1UECwwDUlJSMQswCQYDVQQDDAJDQTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANbv0x42BTKFEQOE+KJ2XmiSdZpR
wjzQLAkPLRnLB98tlzs4xo+y4RyY/rd5TT9UzBJTIhP8CJi5GbS1oXEerQXB3P0d
L5oSSMwGGyuIzgZe5+vZ1kgzQxMEKMMKlzA73rbMd4Jx3u5+jdbP0EDrPYfXSvLY
bS04n2aX7zrN3x5KdDrNBfwBio2/qeaaj4+9OxnwRvYP3WOvqdW0h329eMfHw0pi
JI0drIVdsEqClUV4pebT/F+CPUPkEh/weySgo9wANockkYu5ujw2GbLFcO5LXxxm
dEfcVr3r6t6zOA4bJwL0W/e6LBcrwiG/qPDFErhwtgTLYf6Er67SzLyA66UCAwEA
AaOB3DCB2TAPBgNVHRMBAf8EBTADAQH/MDEGCWCGSAGG+EIBDQQkFiJSdWJ5L09w
ZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBRJ7Xd380KzBV7f
USKIQ+O/vKbhDzAOBgNVHQ8BAf8EBAMCAQYwZAYDVR0jBF0wW4AUSe13d/NCswVe
31EiiEPjv7ym4Q+hQKQ+MDwxCzAJBgNVBAYMAkpQMRIwEAYDVQQKDAlKSU4uR1Iu
SlAxDDAKBgNVBAsMA1JSUjELMAkGA1UEAwwCQ0GCAQAwDQYJKoZIhvcNAQEFBQAD
ggEBAIu/mfiez5XN5tn2jScgShPgHEFJBR0BTJBZF6xCk0jyqNx/g9HMj2ELCuK+
r/Y7KFW5c5M3AQ+xWW0ZSc4kvzyTcV7yTVIwj2jZ9ddYMN3nupZFgBK1GB4Y05GY
MJJFRkSu6d/Ph5ypzBVw2YMT/nsOo5VwMUGLgS7YVjU+u/HNWz80J3oO17mNZllj
PvORJcnjwlroDnS58KoJ7GDgejv3ESWADvX1OHLE4cRkiQGeLoEU4pxdCxXRqX0U
PbwIkZN9mXVcrmPHq8MWi4eC/V7hnbZETMHuWhUoiNdOEfsAXr3iP4KjyyRdwc7a
d/xgcK06UVQRL/HbEYGiQL056mc=
-----END CERTIFICATE-----
httpclient-2.3.3/test/test_http-access2.rb 0000644 0000041 0000041 00000034727 12155433207 020607 0 ustar www-data www-data require 'http-access2'
require File.expand_path('helper', File.dirname(__FILE__))
module HTTPAccess2
class TestClient < Test::Unit::TestCase
include Helper
include HTTPClient::Util
def setup
super
setup_server
setup_client
end
def teardown
super
end
def test_initialize
setup_proxyserver
escape_noproxy do
@proxyio.string = ""
@client = HTTPAccess2::Client.new(proxyurl)
assert_equal(urify(proxyurl), @client.proxy)
assert_equal(200, @client.head(serverurl).status)
assert(!@proxyio.string.empty?)
end
end
def test_agent_name
@client = HTTPAccess2::Client.new(nil, "agent_name_foo")
str = ""
@client.debug_dev = str
@client.get(serverurl)
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_match(/^User-Agent: agent_name_foo/, lines[4])
end
def test_from
@client = HTTPAccess2::Client.new(nil, nil, "from_bar")
str = ""
@client.debug_dev = str
@client.get(serverurl)
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_match(/^From: from_bar/, lines[5])
end
def test_debug_dev
str = ""
@client.debug_dev = str
assert(str.empty?)
@client.get(serverurl)
assert(!str.empty?)
end
def _test_protocol_version_http09
@client.protocol_version = 'HTTP/0.9'
str = ""
@client.debug_dev = str
@client.get(serverurl + 'hello')
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_equal("! CONNECTION ESTABLISHED", lines[2])
assert_equal("GET /hello HTTP/0.9", lines[3])
assert_equal("Connection: close", lines[5])
assert_equal("= Response", lines[6])
assert_match(/^hello/, lines[7])
end
def test_protocol_version_http10
@client.protocol_version = 'HTTP/1.0'
str = ""
@client.debug_dev = str
@client.get(serverurl + 'hello')
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_equal("! CONNECTION ESTABLISHED", lines[2])
assert_equal("GET /hello HTTP/1.0", lines[3])
assert_equal("Connection: close", lines[7])
assert_equal("= Response", lines[8])
end
def test_protocol_version_http11
str = ""
@client.debug_dev = str
@client.get(serverurl)
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_equal("! CONNECTION ESTABLISHED", lines[2])
assert_equal("GET / HTTP/1.1", lines[3])
assert_equal("Host: localhost:#{serverport}", lines[7])
@client.protocol_version = 'HTTP/1.1'
str = ""
@client.debug_dev = str
@client.get(serverurl)
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_equal("! CONNECTION ESTABLISHED", lines[2])
assert_equal("GET / HTTP/1.1", lines[3])
@client.protocol_version = 'HTTP/1.0'
str = ""
@client.debug_dev = str
@client.get(serverurl)
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_equal("! CONNECTION ESTABLISHED", lines[2])
assert_equal("GET / HTTP/1.0", lines[3])
end
def test_proxy
setup_proxyserver
escape_noproxy do
begin
@client.proxy = "http://"
rescue
assert_match(/InvalidURIError/, $!.class.to_s)
end
@client.proxy = ""
assert_nil(@client.proxy)
@client.proxy = "http://foo:1234"
assert_equal(urify("http://foo:1234"), @client.proxy)
uri = urify("http://bar:2345")
@client.proxy = uri
assert_equal(uri, @client.proxy)
#
@proxyio.string = ""
@client.proxy = nil
assert_equal(200, @client.head(serverurl).status)
assert(@proxyio.string.empty?)
#
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(!@proxyio.string.empty?)
end
end
def test_noproxy_for_localhost
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(@proxyio.string.empty?)
end
def test_no_proxy
setup_proxyserver
escape_noproxy do
# proxy is not set.
@client.no_proxy = 'localhost'
@proxyio.string = ""
@client.proxy = nil
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ !~ @proxyio.string)
#
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ !~ @proxyio.string)
#
@client.no_proxy = 'foobar'
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ =~ @proxyio.string)
#
@client.no_proxy = 'foobar,localhost:baz'
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ !~ @proxyio.string)
#
@client.no_proxy = 'foobar,localhost:443'
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ =~ @proxyio.string)
#
@client.no_proxy = "foobar,localhost:443:localhost:#{serverport},baz"
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ !~ @proxyio.string)
end
end
def test_get_content
assert_equal('hello', @client.get_content(serverurl + 'hello'))
assert_equal('hello', @client.get_content(serverurl + 'redirect1'))
assert_equal('hello', @client.get_content(serverurl + 'redirect2'))
assert_raises(HTTPClient::Session::BadResponse) do
@client.get_content(serverurl + 'notfound')
end
assert_raises(HTTPClient::Session::BadResponse) do
@client.get_content(serverurl + 'redirect_self')
end
called = false
@client.redirect_uri_callback = lambda { |uri, res|
newuri = res.header['location'][0]
called = true
newuri
}
assert_equal('hello', @client.get_content(serverurl + 'relative_redirect'))
assert(called)
end
def test_post_content
assert_equal('hello', @client.post_content(serverurl + 'hello'))
assert_equal('hello', @client.post_content(serverurl + 'redirect1'))
assert_equal('hello', @client.post_content(serverurl + 'redirect2'))
assert_raises(HTTPClient::Session::BadResponse) do
@client.post_content(serverurl + 'notfound')
end
assert_raises(HTTPClient::Session::BadResponse) do
@client.post_content(serverurl + 'redirect_self')
end
called = false
@client.redirect_uri_callback = lambda { |uri, res|
newuri = res.header['location'][0]
called = true
newuri
}
assert_equal('hello', @client.post_content(serverurl + 'relative_redirect'))
assert(called)
end
def test_head
assert_equal("head", @client.head(serverurl + 'servlet').header["x-head"][0])
param = {'1'=>'2', '3'=>'4'}
res = @client.head(serverurl + 'servlet', param)
assert_equal(param, params(res.header["x-query"][0]))
end
def test_get
assert_equal("get", @client.get(serverurl + 'servlet').content)
param = {'1'=>'2', '3'=>'4'}
res = @client.get(serverurl + 'servlet', param)
assert_equal(param, params(res.header["x-query"][0]))
end
def test_post
assert_equal("post", @client.post(serverurl + 'servlet').content)
param = {'1'=>'2', '3'=>'4'}
res = @client.get(serverurl + 'servlet', param)
assert_equal(param, params(res.header["x-query"][0]))
end
def test_put
assert_equal("put", @client.put(serverurl + 'servlet').content)
param = {'1'=>'2', '3'=>'4'}
res = @client.get(serverurl + 'servlet', param)
assert_equal(param, params(res.header["x-query"][0]))
end
def test_delete
assert_equal("delete", @client.delete(serverurl + 'servlet').content)
param = {'1'=>'2', '3'=>'4'}
res = @client.get(serverurl + 'servlet', param)
assert_equal(param, params(res.header["x-query"][0]))
end
def test_options
assert_equal("options", @client.options(serverurl + 'servlet').content)
param = {'1'=>'2', '3'=>'4'}
res = @client.get(serverurl + 'servlet', param)
assert_equal(param, params(res.header["x-query"][0]))
end
def test_trace
assert_equal("trace", @client.trace(serverurl + 'servlet').content)
param = {'1'=>'2', '3'=>'4'}
res = @client.get(serverurl + 'servlet', param)
assert_equal(param, params(res.header["x-query"][0]))
end
def test_get_query
assert_equal({'1'=>'2'}, check_query_get({1=>2}))
assert_equal({'a'=>'A', 'B'=>'b'}, check_query_get({"a"=>"A", "B"=>"b"}))
assert_equal({'&'=>'&'}, check_query_get({"&"=>"&"}))
assert_equal({'= '=>' =+'}, check_query_get({"= "=>" =+"}))
assert_equal(
['=', '&'].sort,
check_query_get([["=", "="], ["=", "&"]])['='].to_ary.sort
)
assert_equal({'123'=>'45'}, check_query_get('123=45'))
assert_equal({'12 3'=>'45', ' '=>' '}, check_query_get('12+3=45&+=+'))
assert_equal({}, check_query_get(''))
end
def test_post_body
assert_equal({'1'=>'2'}, check_query_post({1=>2}))
assert_equal({'a'=>'A', 'B'=>'b'}, check_query_post({"a"=>"A", "B"=>"b"}))
assert_equal({'&'=>'&'}, check_query_post({"&"=>"&"}))
assert_equal({'= '=>' =+'}, check_query_post({"= "=>" =+"}))
assert_equal(
['=', '&'].sort,
check_query_post([["=", "="], ["=", "&"]])['='].to_ary.sort
)
assert_equal({'123'=>'45'}, check_query_post('123=45'))
assert_equal({'12 3'=>'45', ' '=>' '}, check_query_post('12+3=45&+=+'))
assert_equal({}, check_query_post(''))
#
post_body = StringIO.new("foo=bar&foo=baz")
assert_equal(
["bar", "baz"],
check_query_post(post_body)["foo"].to_ary.sort
)
end
def test_extra_headers
str = ""
@client.debug_dev = str
@client.head(serverurl, nil, {"ABC" => "DEF"})
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_match("ABC: DEF", lines[4])
#
str = ""
@client.debug_dev = str
@client.get(serverurl, nil, [["ABC", "DEF"], ["ABC", "DEF"]])
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_match("ABC: DEF", lines[4])
assert_match("ABC: DEF", lines[5])
end
def test_timeout
assert_equal(60, @client.connect_timeout)
assert_equal(120, @client.send_timeout)
assert_equal(60, @client.receive_timeout)
end
def test_connect_timeout
# ToDo
end
def test_send_timeout
# ToDo
end
def test_receive_timeout
# this test takes 2 sec
assert_equal('hello', @client.get_content(serverurl + 'sleep?sec=2'))
@client.receive_timeout = 1
assert_equal('hello', @client.get_content(serverurl + 'sleep?sec=0'))
assert_raise(HTTPClient::ReceiveTimeoutError) do
@client.get_content(serverurl + 'sleep?sec=2')
end
@client.receive_timeout = 3
assert_equal('hello', @client.get_content(serverurl + 'sleep?sec=2'))
end
def test_cookies
cookiefile = File.join(File.dirname(File.expand_path(__FILE__)),
'test_cookies_file')
# from [ruby-talk:164079]
File.open(cookiefile, "wb") do |f|
f << "http://rubyforge.org//account/login.php session_ser LjEwMy45Ni40Ni0q%2A-fa0537de8cc31 1131676286 .rubyforge.org / 13\n"
end
cm = WebAgent::CookieManager::new(cookiefile)
cm.load_cookies
cookie = cm.cookies.first
url = cookie.url
assert(cookie.domain_match(url.host, cookie.domain))
end
private
def check_query_get(query)
WEBrick::HTTPUtils.parse_query(
@client.get(serverurl + 'servlet', query).header["x-query"][0]
)
end
def check_query_post(query)
WEBrick::HTTPUtils.parse_query(
@client.post(serverurl + 'servlet', query).header["x-query"][0]
)
end
def setup_server
@server = WEBrick::HTTPServer.new(
:BindAddress => "localhost",
:Logger => @logger,
:Port => 0,
:AccessLog => [],
:DocumentRoot => File.dirname(File.expand_path(__FILE__))
)
@serverport = @server.config[:Port]
[:hello, :sleep, :redirect1, :redirect2, :redirect3, :redirect_self, :relative_redirect].each do |sym|
@server.mount(
"/#{sym}",
WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
)
end
@server.mount('/servlet', TestServlet.new(@server))
@server_thread = start_server_thread(@server)
end
def escape_noproxy
backup = HTTPAccess2::Client::NO_PROXY_HOSTS.dup
HTTPAccess2::Client::NO_PROXY_HOSTS.clear
yield
ensure
HTTPAccess2::Client::NO_PROXY_HOSTS.replace(backup)
end
def do_hello(req, res)
res['content-type'] = 'text/html'
res.body = "hello"
end
def do_sleep(req, res)
sec = req.query['sec'].to_i
sleep sec
res['content-type'] = 'text/html'
res.body = "hello"
end
def do_redirect1(req, res)
res.set_redirect(WEBrick::HTTPStatus::MovedPermanently, serverurl + "hello")
end
def do_redirect2(req, res)
res.set_redirect(WEBrick::HTTPStatus::TemporaryRedirect, serverurl + "redirect3")
end
def do_redirect3(req, res)
res.set_redirect(WEBrick::HTTPStatus::Found, serverurl + "hello")
end
def do_redirect_self(req, res)
res.set_redirect(WEBrick::HTTPStatus::Found, serverurl + "redirect_self")
end
def do_relative_redirect(req, res)
res.set_redirect(WEBrick::HTTPStatus::Found, "hello")
end
class TestServlet < WEBrick::HTTPServlet::AbstractServlet
def get_instance(*arg)
self
end
def do_HEAD(req, res)
res["x-head"] = 'head' # use this for test purpose only.
res["x-query"] = query_response(req)
end
def do_GET(req, res)
res.body = 'get'
res["x-query"] = query_response(req)
end
def do_POST(req, res)
res.body = 'post'
res["x-query"] = body_response(req)
end
def do_PUT(req, res)
res.body = 'put'
end
def do_DELETE(req, res)
res.body = 'delete'
end
def do_OPTIONS(req, res)
# check RFC for legal response.
res.body = 'options'
end
def do_TRACE(req, res)
# client SHOULD reflect the message received back to the client as the
# entity-body of a 200 (OK) response. [RFC2616]
res.body = 'trace'
res["x-query"] = query_response(req)
end
private
def query_response(req)
query_escape(WEBrick::HTTPUtils.parse_query(req.query_string))
end
def body_response(req)
query_escape(WEBrick::HTTPUtils.parse_query(req.body))
end
def query_escape(query)
escaped = []
query.collect do |k, v|
v.to_ary.each do |ve|
escaped << CGI.escape(k) + '=' + CGI.escape(ve)
end
end
escaped.join('&')
end
end
end
end
httpclient-2.3.3/test/test_include_client.rb 0000644 0000041 0000041 00000003071 12155433207 021254 0 ustar www-data www-data # -*- encoding: utf-8 -*-
require File.expand_path('helper', File.dirname(__FILE__))
require 'httpclient/include_client'
class TestIncludeClient < Test::Unit::TestCase
class Widget
extend HTTPClient::IncludeClient
include_http_client("http://example.com") do |client|
client.cookie_manager = nil
client.agent_name = "iMonkey 4k"
end
end
class OtherWidget
extend HTTPClient::IncludeClient
include_http_client
include_http_client(:method_name => :other_http_client)
end
class UnrelatedBlankClass ; end
def test_client_class_level_singleton
assert_equal Widget.http_client.object_id, Widget.http_client.object_id
assert_equal Widget.http_client.object_id, Widget.new.http_client.object_id
assert_not_equal Widget.http_client.object_id, OtherWidget.http_client.object_id
end
def test_configured
assert_equal Widget.http_client.agent_name, "iMonkey 4k"
assert_nil Widget.http_client.cookie_manager
assert_equal Widget.http_client.proxy.to_s, "http://example.com"
end
def test_two_includes
assert_not_equal OtherWidget.http_client.object_id, OtherWidget.other_http_client.object_id
assert_equal OtherWidget.other_http_client.object_id, OtherWidget.new.other_http_client.object_id
end
# meta-programming gone wrong sometimes accidentally
# adds the class method to _everyone_, a mistake we've made before.
def test_not_infected_class_hieararchy
assert ! Class.respond_to?(:http_client)
assert ! UnrelatedBlankClass.respond_to?(:http_client)
end
end
httpclient-2.3.3/test/client.key 0000644 0000041 0000041 00000001567 12155433207 016707 0 ustar www-data www-data -----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDRWssrK8Gyr+500hpLjCGR3+AHL8/hEJM5zKi/MgLWjTkvsgOw
bYwXOiNtAbR9y4/ucDq7EY+cMUMHES4uFaPTcOaAV0aZRmk8AgslN1tQgNS6ew7/
Luq3DcVeWkX8PYgR9VG0mD1MPfJ6+IFA5d3vKpdBkBgN4l46jjO0/2XfewIDAQAB
AoGAZcz8llWErtsV3QB9gNb3S/PNADGjqBFjReva8n3jG2k4sZSibpwWTwUaTNtT
ZQgjSRKRvH1hk9XwffNAvXAQZNNkuj/16gO2oO45nyLj4dO365ujLptWnVIWDHOE
uN0GeiZO+VzcCisT0WCq4tvtLeH8svrxzA8cbXIEyOK7NiECQQDwo2zPFyKAZ/Cu
lDJ6zKT+RjfWwW7DgWzirAlTrt4ViMaW+IaDH29TmQpb4V4NuR3Xi+2Xl4oicu6S
36TW9+/FAkEA3rgfOQJuLlWSnw1RTGwvnC816a/W7iYYY7B+0U4cDbfWl7IoXT4y
M8nV/HESooviZLqBwzAYSoj3fFKYBKpGPwJAUO8GN5iWWA2dW3ooiDiv/X1sZmRk
dojfMFWgRW747tEzya8Ivq0h6kH8w+5GjeMG8Gn1nRiwsulo6Ckj7dEx6QJACyui
7UIQ8qP6GZ4aYMHgVW4Mvy7Bkeo5OO7GPYs0Xv/EdJFL8vlGnVBXOjUVoS9w6Gpu
TbLg1QQvnX2rADjmEwJANxZO2GUkaWGsEif8aGW0x5g/IdaMGG27pTWk5zqix7P3
1UDrdo/JOXhptovhRi06EppIxAxYmbh9vd9VN8Itlw==
-----END RSA PRIVATE KEY-----
httpclient-2.3.3/test/ca-chain.cert 0000644 0000041 0000041 00000005100 12155433207 017224 0 ustar www-data www-data -----BEGIN CERTIFICATE-----
MIID0DCCArigAwIBAgIBADANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGDAJKUDES
MBAGA1UECgwJSklOLkdSLkpQMQwwCgYDVQQLDANSUlIxCzAJBgNVBAMMAkNBMB4X
DTA0MDEzMDAwNDIzMloXDTM2MDEyMjAwNDIzMlowPDELMAkGA1UEBgwCSlAxEjAQ
BgNVBAoMCUpJTi5HUi5KUDEMMAoGA1UECwwDUlJSMQswCQYDVQQDDAJDQTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANbv0x42BTKFEQOE+KJ2XmiSdZpR
wjzQLAkPLRnLB98tlzs4xo+y4RyY/rd5TT9UzBJTIhP8CJi5GbS1oXEerQXB3P0d
L5oSSMwGGyuIzgZe5+vZ1kgzQxMEKMMKlzA73rbMd4Jx3u5+jdbP0EDrPYfXSvLY
bS04n2aX7zrN3x5KdDrNBfwBio2/qeaaj4+9OxnwRvYP3WOvqdW0h329eMfHw0pi
JI0drIVdsEqClUV4pebT/F+CPUPkEh/weySgo9wANockkYu5ujw2GbLFcO5LXxxm
dEfcVr3r6t6zOA4bJwL0W/e6LBcrwiG/qPDFErhwtgTLYf6Er67SzLyA66UCAwEA
AaOB3DCB2TAPBgNVHRMBAf8EBTADAQH/MDEGCWCGSAGG+EIBDQQkFiJSdWJ5L09w
ZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBRJ7Xd380KzBV7f
USKIQ+O/vKbhDzAOBgNVHQ8BAf8EBAMCAQYwZAYDVR0jBF0wW4AUSe13d/NCswVe
31EiiEPjv7ym4Q+hQKQ+MDwxCzAJBgNVBAYMAkpQMRIwEAYDVQQKDAlKSU4uR1Iu
SlAxDDAKBgNVBAsMA1JSUjELMAkGA1UEAwwCQ0GCAQAwDQYJKoZIhvcNAQEFBQAD
ggEBAIu/mfiez5XN5tn2jScgShPgHEFJBR0BTJBZF6xCk0jyqNx/g9HMj2ELCuK+
r/Y7KFW5c5M3AQ+xWW0ZSc4kvzyTcV7yTVIwj2jZ9ddYMN3nupZFgBK1GB4Y05GY
MJJFRkSu6d/Ph5ypzBVw2YMT/nsOo5VwMUGLgS7YVjU+u/HNWz80J3oO17mNZllj
PvORJcnjwlroDnS58KoJ7GDgejv3ESWADvX1OHLE4cRkiQGeLoEU4pxdCxXRqX0U
PbwIkZN9mXVcrmPHq8MWi4eC/V7hnbZETMHuWhUoiNdOEfsAXr3iP4KjyyRdwc7a
d/xgcK06UVQRL/HbEYGiQL056mc=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDaDCCAlCgAwIBAgIBATANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGDAJKUDES
MBAGA1UECgwJSklOLkdSLkpQMQwwCgYDVQQLDANSUlIxCzAJBgNVBAMMAkNBMB4X
DTA0MDEzMDAwNDMyN1oXDTM1MDEyMjAwNDMyN1owPzELMAkGA1UEBgwCSlAxEjAQ
BgNVBAoMCUpJTi5HUi5KUDEMMAoGA1UECwwDUlJSMQ4wDAYDVQQDDAVTdWJDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ0Ou7AyRcRXnB/kVHv/6kwe
ANzgg/DyJfsAUqW90m7Lu1nqyug8gK0RBd77yU0w5HOAMHTVSdpjZK0g2sgx4Mb1
d/213eL9TTl5MRVEChTvQr8q5DVG/8fxPPE7fMI8eOAzd98/NOAChk+80r4Sx7fC
kGVEE1bKwY1MrUsUNjOY2d6t3M4HHV3HX1V8ShuKfsHxgCmLzdI8U+5CnQedFgkm
3e+8tr8IX5RR1wA1Ifw9VadF7OdI/bGMzog/Q8XCLf+WPFjnK7Gcx6JFtzF6Gi4x
4dp1Xl45JYiVvi9zQ132wu8A1pDHhiNgQviyzbP+UjcB/tsOpzBQF8abYzgEkWEC
AwEAAaNyMHAwDwYDVR0TAQH/BAUwAwEB/zAxBglghkgBhvhCAQ0EJBYiUnVieS9P
cGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUlCjXWLsReYzH
LzsxwVnCXmKoB/owCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCJ/OyN
rT8Cq2Y+G2yA/L1EMRvvxwFBqxavqaqHl/6rwsIBFlB3zbqGA/0oec6MAVnYynq4
c4AcHTjx3bQ/S4r2sNTZq0DH4SYbQzIobx/YW8PjQUJt8KQdKMcwwi7arHP7A/Ha
LKu8eIC2nsUBnP4NhkYSGhbmpJK+PFD0FVtD0ZIRlY/wsnaZNjWWcnWF1/FNuQ4H
ySjIblqVQkPuzebv3Ror6ZnVDukn96Mg7kP4u6zgxOeqlJGRe1M949SS9Vudjl8X
SF4aZUUB9pQGhsqQJVqaz2OlhGOp9D0q54xko/rekjAIcuDjl1mdX4F2WRrzpUmZ
uY/bPeOBYiVsOYVe
-----END CERTIFICATE-----
httpclient-2.3.3/test/sslsvr.rb 0000644 0000041 0000041 00000002531 12155433207 016570 0 ustar www-data www-data require 'webrick/https'
require 'logger'
require 'rbconfig'
PORT = 17171
DIR = File.dirname(File.expand_path(__FILE__))
def cert(filename)
OpenSSL::X509::Certificate.new(File.open(File.join(DIR, filename)) { |f|
f.read
})
end
def key(filename)
OpenSSL::PKey::RSA.new(File.open(File.join(DIR, filename)) { |f|
f.read
})
end
def do_hello(req, res)
res['content-type'] = 'text/html'
res.body = "hello"
end
logger = Logger.new(STDERR)
logger.level = Logger::Severity::FATAL # avoid logging SSLError (ERROR level)
server = WEBrick::HTTPServer.new(
:BindAddress => "localhost",
:Logger => logger,
:Port => PORT,
:AccessLog => [],
:DocumentRoot => DIR,
:SSLEnable => true,
:SSLCACertificateFile => File.join(DIR, 'ca.cert'),
:SSLCertificate => cert('server.cert'),
:SSLPrivateKey => key('server.key'),
:SSLVerifyClient => nil, #OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT|OpenSSL::SSL::VERIFY_PEER,
:SSLClientCA => cert('ca.cert'),
:SSLCertName => nil
)
trap(:INT) do
server.shutdown
end
[:hello].each do |sym|
server.mount(
"/#{sym}",
WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
)
end
t = Thread.new {
Thread.current.abort_on_exception = true
server.start
}
while server.status != :Running
sleep 0.1
unless t.alive?
t.join
raise
end
end
STDOUT.sync = true
puts $$
t.join
httpclient-2.3.3/test/htpasswd 0000644 0000041 0000041 00000000050 12155433207 016461 0 ustar www-data www-data admin:Qg266hq/YYKe2
guest:gbPc4vPCH.h12
httpclient-2.3.3/test/runner.rb 0000644 0000041 0000041 00000000114 12155433207 016540 0 ustar www-data www-data require 'test/unit'
exit Test::Unit::AutoRunner.run(true, File.dirname($0))
httpclient-2.3.3/test/helper.rb 0000644 0000041 0000041 00000004501 12155433207 016512 0 ustar www-data www-data # -*- encoding: utf-8 -*-
require 'test/unit'
begin
require 'simplecov'
require 'simplecov-rcov'
SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
SimpleCov.start
rescue LoadError
end
require 'httpclient'
require 'webrick'
require 'webrick/httpproxy.rb'
require 'logger'
require 'stringio'
require 'cgi'
require 'webrick/httputils'
module Helper
Port = 17171
ProxyPort = 17172
def serverport
@serverport
end
def proxyport
@proxyport
end
def serverurl
"http://localhost:#{serverport}/"
end
def proxyurl
"http://localhost:#{proxyport}/"
end
def setup
@logger = Logger.new(STDERR)
@logger.level = Logger::Severity::FATAL
@proxyio = StringIO.new
@proxylogger = Logger.new(@proxyio)
@proxylogger.level = Logger::Severity::DEBUG
@server = @proxyserver = @client = nil
@server_thread = @proxyserver_thread = nil
@serverport = Port
@proxyport = ProxyPort
end
def teardown
teardown_client if @client
teardown_proxyserver if @proxyserver
teardown_server if @server
end
def setup_client
@client = HTTPClient.new
end
#def setup_server
# override it
# @server = WEBrick::HTTPServer.new(...)
# @server_thread = start_server_thread(@server)
#end
def setup_proxyserver
@proxyserver = WEBrick::HTTPProxyServer.new(
:BindAddress => "localhost",
:Logger => @proxylogger,
:Port => 0,
:AccessLog => []
)
@proxyport = @proxyserver.config[:Port]
@proxyserver_thread = start_server_thread(@proxyserver)
end
def teardown_client
@client.reset_all
end
def teardown_server
@server.shutdown
#@server_thread.kill
end
def teardown_proxyserver
@proxyserver.shutdown
#@proxyserver_thread.kill
end
def start_server_thread(server)
t = Thread.new {
Thread.current.abort_on_exception = true
server.start
}
while server.status != :Running
Thread.pass
unless t.alive?
t.join
raise
end
end
t
end
def params(str)
HTTP::Message.parse(str).inject({}) { |r, (k, v)| r[k] = v.first; r }
end
def silent
begin
back, $VERBOSE = $VERBOSE, nil
yield
ensure
$VERBOSE = back
end
end
def escape_env
env = {}
env.update(ENV)
yield
ensure
ENV.clear
ENV.update(env)
end
end
httpclient-2.3.3/test/test_auth.rb 0000644 0000041 0000041 00000031675 12155433207 017247 0 ustar www-data www-data require File.expand_path('helper', File.dirname(__FILE__))
require 'digest/md5'
class TestAuth < Test::Unit::TestCase
include Helper
def setup
super
setup_server
end
def teardown
super
end
def setup_server
@server = WEBrick::HTTPServer.new(
:BindAddress => "localhost",
:Logger => @logger,
:Port => 0,
:AccessLog => [],
:DocumentRoot => File.dirname(File.expand_path(__FILE__))
)
@serverport = @server.config[:Port]
@server.mount(
'/basic_auth',
WEBrick::HTTPServlet::ProcHandler.new(method(:do_basic_auth).to_proc)
)
@server.mount(
'/digest_auth',
WEBrick::HTTPServlet::ProcHandler.new(method(:do_digest_auth).to_proc)
)
@server.mount(
'/digest_sess_auth',
WEBrick::HTTPServlet::ProcHandler.new(method(:do_digest_sess_auth).to_proc)
)
htpasswd = File.join(File.dirname(__FILE__), 'htpasswd')
htpasswd_userdb = WEBrick::HTTPAuth::Htpasswd.new(htpasswd)
htdigest = File.join(File.dirname(__FILE__), 'htdigest')
htdigest_userdb = WEBrick::HTTPAuth::Htdigest.new(htdigest)
@basic_auth = WEBrick::HTTPAuth::BasicAuth.new(
:Logger => @logger,
:Realm => 'auth',
:UserDB => htpasswd_userdb
)
@digest_auth = WEBrick::HTTPAuth::DigestAuth.new(
:Logger => @logger,
:Algorithm => 'MD5',
:Realm => 'auth',
:UserDB => htdigest_userdb
)
@digest_sess_auth = WEBrick::HTTPAuth::DigestAuth.new(
:Logger => @logger,
:Algorithm => 'MD5-sess',
:Realm => 'auth',
:UserDB => htdigest_userdb
)
@server_thread = start_server_thread(@server)
@proxy_digest_auth = WEBrick::HTTPAuth::ProxyDigestAuth.new(
:Logger => @proxylogger,
:Algorithm => 'MD5',
:Realm => 'auth',
:UserDB => htdigest_userdb
)
@proxyserver = WEBrick::HTTPProxyServer.new(
:ProxyAuthProc => @proxy_digest_auth.method(:authenticate).to_proc,
:BindAddress => "localhost",
:Logger => @proxylogger,
:Port => 0,
:AccessLog => []
)
@proxyport = @proxyserver.config[:Port]
@proxyserver_thread = start_server_thread(@proxyserver)
end
def do_basic_auth(req, res)
@basic_auth.authenticate(req, res)
res['content-type'] = 'text/plain'
res.body = 'basic_auth OK'
end
def do_digest_auth(req, res)
@digest_auth.authenticate(req, res)
res['content-type'] = 'text/plain'
res['x-query'] = req.body
res.body = 'digest_auth OK' + req.query_string.to_s
end
def do_digest_sess_auth(req, res)
@digest_sess_auth.authenticate(req, res)
res['content-type'] = 'text/plain'
res['x-query'] = req.body
res.body = 'digest_sess_auth OK' + req.query_string.to_s
end
def test_basic_auth
c = HTTPClient.new
c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
assert_equal('basic_auth OK', c.get_content("http://localhost:#{serverport}/basic_auth"))
end
def test_basic_auth_compat
c = HTTPClient.new
c.set_basic_auth("http://localhost:#{serverport}/", 'admin', 'admin')
assert_equal('basic_auth OK', c.get_content("http://localhost:#{serverport}/basic_auth"))
end
def test_BASIC_auth
c = HTTPClient.new
webrick_backup = @basic_auth.instance_eval { @auth_scheme }
#httpaccess2_backup = c.www_auth.basic_auth.instance_eval { @scheme }
begin
@basic_auth.instance_eval { @auth_scheme = "BASIC" }
c.www_auth.basic_auth.instance_eval { @scheme = "BASIC" }
c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
assert_equal('basic_auth OK', c.get_content("http://localhost:#{serverport}/basic_auth"))
ensure
@basic_auth.instance_eval { @auth_scheme = webrick_backup }
#c.www_auth.basic_auth.instance_eval { @scheme = httpaccess2_backup }
end
end
def test_basic_auth_reuses_credentials
c = HTTPClient.new
c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
assert_equal('basic_auth OK', c.get_content("http://localhost:#{serverport}/basic_auth/"))
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
c.debug_dev = str = ''
c.get_content("http://localhost:#{serverport}/basic_auth/sub/dir/")
assert_match /Authorization: Basic YWRtaW46YWRtaW4=/, str
end
def test_digest_auth
c = HTTPClient.new
c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
assert_equal('digest_auth OK', c.get_content("http://localhost:#{serverport}/digest_auth"))
end
def test_digest_auth_reuses_credentials
c = HTTPClient.new
c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
assert_equal('digest_auth OK', c.get_content("http://localhost:#{serverport}/digest_auth/"))
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
c.debug_dev = str = ''
c.get_content("http://localhost:#{serverport}/digest_auth/sub/dir/")
assert_match /Authorization: Digest/, str
end
def test_digest_auth_with_block
c = HTTPClient.new
c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
called = false
c.get_content("http://localhost:#{serverport}/digest_auth") do |str|
assert_equal('digest_auth OK', str)
called = true
end
assert(called)
#
called = false
c.get("http://localhost:#{serverport}/digest_auth") do |str|
assert_equal('digest_auth OK', str)
called = true
end
assert(called)
end
def test_digest_auth_with_post_io
c = HTTPClient.new
c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
post_body = StringIO.new("1234567890")
assert_equal('1234567890', c.post("http://localhost:#{serverport}/digest_auth", post_body).header['x-query'][0])
#
post_body = StringIO.new("1234567890")
post_body.read(5)
assert_equal('67890', c.post("http://localhost:#{serverport}/digest_auth", post_body).header['x-query'][0])
end
def test_digest_auth_with_querystring
c = HTTPClient.new
c.debug_dev = STDERR if $DEBUG
c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
assert_equal('digest_auth OKbar=baz', c.get_content("http://localhost:#{serverport}/digest_auth/foo?bar=baz"))
end
def test_perfer_digest
c = HTTPClient.new
c.set_auth('http://example.com/', 'admin', 'admin')
c.test_loopback_http_response << "HTTP/1.0 401 Unauthorized\nWWW-Authenticate: Basic realm=\"foo\"\nWWW-Authenticate: Digest realm=\"foo\", nonce=\"nonce\", stale=false\nContent-Length: 2\n\nNG"
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
c.debug_dev = str = ''
c.get_content('http://example.com/')
assert_match(/^Authorization: Digest/, str)
end
def test_digest_sess_auth
c = HTTPClient.new
c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
assert_equal('digest_sess_auth OK', c.get_content("http://localhost:#{serverport}/digest_sess_auth"))
end
def test_proxy_auth
c = HTTPClient.new
c.set_proxy_auth('admin', 'admin')
c.test_loopback_http_response << "HTTP/1.0 407 Unauthorized\nProxy-Authenticate: Basic realm=\"foo\"\nContent-Length: 2\n\nNG"
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
c.debug_dev = str = ''
c.get_content('http://example.com/')
assert_match(/Proxy-Authorization: Basic YWRtaW46YWRtaW4=/, str)
end
def test_proxy_auth_reuses_credentials
c = HTTPClient.new
c.set_proxy_auth('admin', 'admin')
c.test_loopback_http_response << "HTTP/1.0 407 Unauthorized\nProxy-Authenticate: Basic realm=\"foo\"\nContent-Length: 2\n\nNG"
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
c.get_content('http://www1.example.com/')
c.debug_dev = str = ''
c.get_content('http://www2.example.com/')
assert_match(/Proxy-Authorization: Basic YWRtaW46YWRtaW4=/, str)
end
def test_digest_proxy_auth_loop
c = HTTPClient.new
c.set_proxy_auth('admin', 'admin')
c.test_loopback_http_response << "HTTP/1.0 407 Unauthorized\nProxy-Authenticate: Digest realm=\"foo\", nonce=\"nonce\", stale=false\nContent-Length: 2\n\nNG"
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
md5 = Digest::MD5.new
ha1 = md5.hexdigest("admin:foo:admin")
ha2 = md5.hexdigest("GET:/")
response = md5.hexdigest("#{ha1}:nonce:#{ha2}")
c.debug_dev = str = ''
c.get_content('http://example.com/')
assert_match(/Proxy-Authorization: Digest/, str)
assert_match(%r"response=\"#{response}\"", str)
end
def test_digest_proxy_auth
c=HTTPClient.new("http://localhost:#{proxyport}/")
c.set_proxy_auth('admin', 'admin')
c.set_auth("http://127.0.0.1:#{serverport}/", 'admin', 'admin')
assert_equal('basic_auth OK', c.get_content("http://127.0.0.1:#{serverport}/basic_auth"))
end
def test_digest_proxy_invalid_auth
c=HTTPClient.new("http://localhost:#{proxyport}/")
c.set_proxy_auth('admin', 'wrong')
c.set_auth("http://127.0.0.1:#{serverport}/", 'admin', 'admin')
assert_raises(HTTPClient::BadResponseError) do
c.get_content("http://127.0.0.1:#{serverport}/basic_auth")
end
end
def test_prefer_digest_to_basic_proxy_auth
c = HTTPClient.new
c.set_proxy_auth('admin', 'admin')
c.test_loopback_http_response << "HTTP/1.0 407 Unauthorized\nProxy-Authenticate: Digest realm=\"foo\", nonce=\"nonce\", stale=false\nProxy-Authenticate: Basic realm=\"bar\"\nContent-Length: 2\n\nNG"
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
md5 = Digest::MD5.new
ha1 = md5.hexdigest("admin:foo:admin")
ha2 = md5.hexdigest("GET:/")
response = md5.hexdigest("#{ha1}:nonce:#{ha2}")
c.debug_dev = str = ''
c.get_content('http://example.com/')
assert_match(/Proxy-Authorization: Digest/, str)
assert_match(%r"response=\"#{response}\"", str)
end
def test_digest_proxy_auth_reuses_credentials
c = HTTPClient.new
c.set_proxy_auth('admin', 'admin')
c.test_loopback_http_response << "HTTP/1.0 407 Unauthorized\nProxy-Authenticate: Digest realm=\"foo\", nonce=\"nonce\", stale=false\nContent-Length: 2\n\nNG"
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
md5 = Digest::MD5.new
ha1 = md5.hexdigest("admin:foo:admin")
ha2 = md5.hexdigest("GET:/")
response = md5.hexdigest("#{ha1}:nonce:#{ha2}")
c.get_content('http://www1.example.com/')
c.debug_dev = str = ''
c.get_content('http://www2.example.com/')
assert_match(/Proxy-Authorization: Digest/, str)
assert_match(%r"response=\"#{response}\"", str)
end
def test_oauth
c = HTTPClient.new
config = HTTPClient::OAuth::Config.new(
:realm => 'http://photos.example.net/',
:consumer_key => 'dpf43f3p2l4k3l03',
:consumer_secret => 'kd94hf93k423kf44',
:token => 'nnch734d00sl2jdk',
:secret => 'pfkkdhi9sl3r4s00',
:version => '1.0',
:signature_method => 'HMAC-SHA1'
)
config.debug_timestamp = '1191242096'
config.debug_nonce = 'kllo9940pd9333jh'
c.www_auth.oauth.set_config('http://photos.example.net/', config)
c.www_auth.oauth.challenge('http://photos.example.net/')
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
c.debug_dev = str = ''
c.get_content('http://photos.example.net/photos', [[:file, 'vacation.jpg'], [:size, 'original']])
assert(str.index(%q(GET /photos?file=vacation.jpg&size=original)))
assert(str.index(%q(Authorization: OAuth realm="http://photos.example.net/", oauth_consumer_key="dpf43f3p2l4k3l03", oauth_nonce="kllo9940pd9333jh", oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1191242096", oauth_token="nnch734d00sl2jdk", oauth_version="1.0")))
#
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
c.debug_dev = str = ''
c.get_content('http://photos.example.net/photos?file=vacation.jpg&size=original')
assert(str.index(%q(GET /photos?file=vacation.jpg&size=original)))
assert(str.index(%q(Authorization: OAuth realm="http://photos.example.net/", oauth_consumer_key="dpf43f3p2l4k3l03", oauth_nonce="kllo9940pd9333jh", oauth_signature="tR3%2BTy81lMeYAr%2FFid0kMTYa%2FWM%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1191242096", oauth_token="nnch734d00sl2jdk", oauth_version="1.0")))
#
c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
c.debug_dev = str = ''
c.post_content('http://photos.example.net/photos', [[:file, 'vacation.jpg'], [:size, 'original']])
assert(str.index(%q(POST /photos)))
assert(str.index(%q(Authorization: OAuth realm="http://photos.example.net/", oauth_consumer_key="dpf43f3p2l4k3l03", oauth_nonce="kllo9940pd9333jh", oauth_signature="wPkvxykrw%2BBTdCcGqKr%2B3I%2BPsiM%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1191242096", oauth_token="nnch734d00sl2jdk", oauth_version="1.0")))
end
end
httpclient-2.3.3/test/htdigest 0000644 0000041 0000041 00000000054 12155433207 016443 0 ustar www-data www-data admin:auth:4302fe65caa32f27721949149ccd3083
httpclient-2.3.3/test/subca.cert 0000644 0000041 0000041 00000002331 12155433207 016661 0 ustar www-data www-data -----BEGIN CERTIFICATE-----
MIIDaDCCAlCgAwIBAgIBATANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGDAJKUDES
MBAGA1UECgwJSklOLkdSLkpQMQwwCgYDVQQLDANSUlIxCzAJBgNVBAMMAkNBMB4X
DTA0MDEzMDAwNDMyN1oXDTM1MDEyMjAwNDMyN1owPzELMAkGA1UEBgwCSlAxEjAQ
BgNVBAoMCUpJTi5HUi5KUDEMMAoGA1UECwwDUlJSMQ4wDAYDVQQDDAVTdWJDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ0Ou7AyRcRXnB/kVHv/6kwe
ANzgg/DyJfsAUqW90m7Lu1nqyug8gK0RBd77yU0w5HOAMHTVSdpjZK0g2sgx4Mb1
d/213eL9TTl5MRVEChTvQr8q5DVG/8fxPPE7fMI8eOAzd98/NOAChk+80r4Sx7fC
kGVEE1bKwY1MrUsUNjOY2d6t3M4HHV3HX1V8ShuKfsHxgCmLzdI8U+5CnQedFgkm
3e+8tr8IX5RR1wA1Ifw9VadF7OdI/bGMzog/Q8XCLf+WPFjnK7Gcx6JFtzF6Gi4x
4dp1Xl45JYiVvi9zQ132wu8A1pDHhiNgQviyzbP+UjcB/tsOpzBQF8abYzgEkWEC
AwEAAaNyMHAwDwYDVR0TAQH/BAUwAwEB/zAxBglghkgBhvhCAQ0EJBYiUnVieS9P
cGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUlCjXWLsReYzH
LzsxwVnCXmKoB/owCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCJ/OyN
rT8Cq2Y+G2yA/L1EMRvvxwFBqxavqaqHl/6rwsIBFlB3zbqGA/0oec6MAVnYynq4
c4AcHTjx3bQ/S4r2sNTZq0DH4SYbQzIobx/YW8PjQUJt8KQdKMcwwi7arHP7A/Ha
LKu8eIC2nsUBnP4NhkYSGhbmpJK+PFD0FVtD0ZIRlY/wsnaZNjWWcnWF1/FNuQ4H
ySjIblqVQkPuzebv3Ror6ZnVDukn96Mg7kP4u6zgxOeqlJGRe1M949SS9Vudjl8X
SF4aZUUB9pQGhsqQJVqaz2OlhGOp9D0q54xko/rekjAIcuDjl1mdX4F2WRrzpUmZ
uY/bPeOBYiVsOYVe
-----END CERTIFICATE-----
httpclient-2.3.3/test/server.key 0000644 0000041 0000041 00000001567 12155433207 016737 0 ustar www-data www-data -----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDRSU8Vqrqd51fXbCQJLqflml5zzSM3KQAN6jBmQyUybH2vA7u2
cUz9FySuZhE7bQa5+9dXCcQLj9yo6p1vUr7WfZWNrFyemY+trDeS8Bt1VXtuXSNv
MkdRLGx4yeWY3/bZoIDAumpudncbh27ef7x6OJ4CbihP1PnnhkYrzJ+oBQIDAQAB
AoGBAIf4CstW2ltQO7+XYGoex7Hh8s9lTSW/G2vu5Hbr1LTHy3fzAvdq8MvVR12O
rk9fa+lU9vhzPc0NMB0GIDZ9GcHuhW5hD1Wg9OSCbTOkZDoH3CAFqonjh4Qfwv5W
IPAFn9KHukdqGXkwEMdErsUaPTy9A1V/aROVEaAY+HJgq/eZAkEA/BP1QMV04WEZ
Oynzz7/lLizJGGxp2AOvEVtqMoycA/Qk+zdKP8ufE0wbmCE3Qd6GoynavsHb6aGK
gQobb8zDZwJBANSK6MrXlrZTtEaeZuyOB4mAmRzGzOUVkUyULUjEx2GDT93ujAma
qm/2d3E+wXAkNSeRpjUmlQXy/2oSqnGvYbMCQQDRM+cYyEcGPUVpWpnj0shrF/QU
9vSot/X1G775EMTyaw6+BtbyNxVgOIu2J+rqGbn3c+b85XqTXOPL0A2RLYkFAkAm
syhSDtE9X55aoWsCNZY/vi+i4rvaFoQ/WleogVQAeGVpdo7/DK9t9YWoFBIqth0L
mGSYFu9ZhvZkvQNV8eYrAkBJ+rOIaLDsmbrgkeDruH+B/9yrm4McDtQ/rgnOGYnH
LjLpLLOrgUxqpzLWe++EwSLwK2//dHO+SPsQJ4xsyQJy
-----END RSA PRIVATE KEY-----
httpclient-2.3.3/test/server.cert 0000644 0000041 0000041 00000002113 12155433207 017070 0 ustar www-data www-data -----BEGIN CERTIFICATE-----
MIIC/zCCAeegAwIBAgIBATANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQGDAJKUDES
MBAGA1UECgwJSklOLkdSLkpQMQwwCgYDVQQLDANSUlIxDjAMBgNVBAMMBVN1YkNB
MB4XDTA0MDEzMTAzMTMxNloXDTMzMDEyMzAzMTMxNlowQzELMAkGA1UEBgwCSlAx
EjAQBgNVBAoMCUpJTi5HUi5KUDEMMAoGA1UECwwDUlJSMRIwEAYDVQQDDAlsb2Nh
bGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANFJTxWqup3nV9dsJAku
p+WaXnPNIzcpAA3qMGZDJTJsfa8Du7ZxTP0XJK5mETttBrn711cJxAuP3KjqnW9S
vtZ9lY2sXJ6Zj62sN5LwG3VVe25dI28yR1EsbHjJ5Zjf9tmggMC6am52dxuHbt5/
vHo4ngJuKE/U+eeGRivMn6gFAgMBAAGjgYUwgYIwDAYDVR0TAQH/BAIwADAxBglg
hkgBhvhCAQ0EJBYiUnVieS9PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAd
BgNVHQ4EFgQUpZIyygD9JxFYHHOTEuWOLbCKfckwCwYDVR0PBAQDAgWgMBMGA1Ud
JQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBBQUAA4IBAQBwAIj5SaBHaA5X31IP
CFCJiep96awfp7RANO0cuUj+ZpGoFn9d6FXY0g+Eg5wAkCNIzZU5NHN9xsdOpnUo
zIBbyTfQEPrge1CMWMvL6uGaoEXytq84VTitF/xBTky4KtTn6+es4/e7jrrzeUXQ
RC46gkHObmDT91RkOEGjHLyld2328jo3DIN/VTHIryDeVHDWjY5dENwpwdkhhm60
DR9IrNBbXWEe9emtguNXeN0iu1ux0lG1Hc6pWGQxMlRKNvGh0yZB9u5EVe38tOV0
jQaoNyL7qzcQoXD3Dmbi1p0iRmg/+HngISsz8K7k7MBNVsSclztwgCzTZOBiVtkM
rRlQ
-----END CERTIFICATE-----
httpclient-2.3.3/test/test_cookie.rb 0000644 0000041 0000041 00000031515 12155433207 017550 0 ustar www-data www-data require 'test/unit'
require 'uri'
require 'httpclient/cookie'
class TestCookie < Test::Unit::TestCase
include HTTPClient::Util
def setup()
@c = WebAgent::Cookie.new()
end
def test_s_new()
assert_instance_of(WebAgent::Cookie, @c)
end
def test_discard?
assert_equal(false, !!(@c.discard?))
@c.discard = true
assert_equal(true, !!(@c.discard?))
end
def test_match()
url = urify('http://www.rubycolor.org/hoge/funi/#919191')
@c.domain = 'www.rubycolor.org'
assert_equal(true, @c.match?(url))
@c.domain = '.rubycolor.org'
assert_equal(true, @c.match?(url))
@c.domain = 'aaa.www.rubycolor.org'
assert_equal(false, @c.match?(url))
@c.domain = 'aaa.www.rubycolor.org'
assert_equal(false, @c.match?(url))
@c.domain = 'www.rubycolor.org'
@c.path = '/'
assert_equal(true, @c.match?(url))
@c.domain = 'www.rubycolor.org'
@c.path = '/hoge'
assert_equal(true, @c.match?(url))
@c.domain = 'www.rubycolor.org'
@c.path = '/hoge/hoge'
assert_equal(false, @c.match?(url))
@c.domain = 'www.rubycolor.org'
@c.path = '/hoge'
@c.secure = true
assert_equal(false, @c.match?(url))
url2 = urify('https://www.rubycolor.org/hoge/funi/#919191')
@c.domain = 'www.rubycolor.org'
@c.path = '/hoge'
@c.secure = true
assert_equal(true, @c.match?(url2))
@c.domain = 'www.rubycolor.org'
@c.path = '/hoge'
@c.secure = nil
assert_equal(true, @c.match?(url2)) ## not false!
url.port = 80
@c.domain = 'www.rubycolor.org'
@c.path = '/hoge'
# @c.port = [80,8080]
assert_equal(true, @c.match?(url))
url_nopath = URI.parse('http://www.rubycolor.org')
@c.domain = 'www.rubycolor.org'
@c.path = '/'
assert_equal(true, @c.match?(url_nopath))
end
def test_head_match?()
assert_equal(true, @c.head_match?("",""))
assert_equal(false, @c.head_match?("a",""))
assert_equal(true, @c.head_match?("","a"))
assert_equal(true, @c.head_match?("abcde","abcde"))
assert_equal(true, @c.head_match?("abcde","abcdef"))
assert_equal(false, @c.head_match?("abcdef","abcde"))
assert_equal(false, @c.head_match?("abcde","bcde"))
assert_equal(false, @c.head_match?("bcde","abcde"))
end
def test_tail_match?()
assert_equal(true, @c.tail_match?("",""))
assert_equal(false, @c.tail_match?("a",""))
assert_equal(true, @c.tail_match?("","a"))
assert_equal(true, @c.tail_match?("abcde","abcde"))
assert_equal(false, @c.tail_match?("abcde","abcdef"))
assert_equal(false, @c.tail_match?("abcdef","abcde"))
assert_equal(false, @c.tail_match?("abcde","bcde"))
assert_equal(true, @c.tail_match?("bcde","abcde"))
end
def test_domain_match()
extend WebAgent::CookieUtils
assert_equal(true, !!domain_match("hoge.co.jp","."))
# assert_equal(true, !!domain_match("locahost",".local"))
assert_equal(true, !!domain_match("192.168.10.1","192.168.10.1"))
assert_equal(false, !!domain_match("192.168.10.1","192.168.10.2"))
# assert_equal(false, !!domain_match("hoge.co.jp",".hoge.co.jp"))
# allows; host == rubyforge.org, domain == .rubyforge.org
assert_equal(true, !!domain_match("hoge.co.jp",".hoge.co.jp"))
assert_equal(true, !!domain_match("www.hoge.co.jp", "www.hoge.co.jp"))
assert_equal(false, !!domain_match("www.hoge.co.jp", "www2.hoge.co.jp"))
assert_equal(true, !!domain_match("www.hoge.co.jp", ".hoge.co.jp"))
assert_equal(true, !!domain_match("www.aa.hoge.co.jp", ".hoge.co.jp"))
assert_equal(false, !!domain_match("www.hoge.co.jp", "hoge.co.jp"))
end
def test_join_quotedstr()
arr1 = ['hoge=funi', 'hoge2=funi2']
assert_equal(arr1, @c.instance_eval{join_quotedstr(arr1,';')})
arr2 = ['hoge="fu', 'ni"', 'funi=funi']
assert_equal(['hoge="fu;ni"','funi=funi'],
@c.instance_eval{join_quotedstr(arr2,';')})
arr3 = ['hoge="funi";hoge2="fu','ni2";hoge3="hoge"', 'funi="funi"']
assert_equal(['hoge="funi";hoge2="fu,ni2";hoge3="hoge"', 'funi="funi"'],
@c.instance_eval{join_quotedstr(arr3,',')})
end
end
class TestCookieManager < Test::Unit::TestCase
include HTTPClient::Util
def setup()
@cm = WebAgent::CookieManager.new()
end
def teardown()
end
def test_parse()
str = "inkid=n92b0ADOgACIgUb9lsjHqAAAHu2a; expires=Wed, 01-Dec-2010 00:00:00 GMT; path=/"
@cm.parse(str, urify('http://www.test.jp'))
cookie = @cm.cookies[0]
assert_instance_of(WebAgent::Cookie, cookie)
assert_equal("inkid", cookie.name)
assert_equal("n92b0ADOgACIgUb9lsjHqAAAHu2a", cookie.value)
assert_equal(Time.gm(2010, 12, 1, 0,0,0), cookie.expires)
assert_equal("/", cookie.path)
end
def test_parse2()
str = "xmen=off,0,0,1; path=/; domain=.excite.co.jp; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
@cm.parse(str, urify('http://www.excite.co.jp'))
cookie = @cm.cookies[0]
assert_instance_of(WebAgent::Cookie, cookie)
assert_equal("xmen", cookie.name)
assert_equal("off,0,0,1", cookie.value)
assert_equal("/", cookie.path)
assert_equal(".excite.co.jp", cookie.domain)
assert_equal(Time.gm(2037,12,31,12,0,0), cookie.expires)
end
def test_parse3()
str = "xmen=off,0,0,1; path=/; domain=.excite.co.jp; expires=Wednesday, 31-Dec-2037 12:00:00 GMT;Secure;HTTPOnly"
@cm.parse(str, urify('http://www.excite.co.jp'))
cookie = @cm.cookies[0]
assert_instance_of(WebAgent::Cookie, cookie)
assert_equal("xmen", cookie.name)
assert_equal("off,0,0,1", cookie.value)
assert_equal("/", cookie.path)
assert_equal(".excite.co.jp", cookie.domain)
assert_equal(Time.gm(2037,12,31,12,0,0), cookie.expires)
assert_equal(true, cookie.secure?)
assert_equal(true, cookie.http_only?)
end
def test_parse_double_semicolon()
str = "xmen=off,0,0,1;; path=\"/;;\"; domain=.excite.co.jp; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
@cm.parse(str, urify('http://www.excite.co.jp'))
cookie = @cm.cookies[0]
assert_instance_of(WebAgent::Cookie, cookie)
assert_equal("xmen", cookie.name)
assert_equal("off,0,0,1", cookie.value)
assert_equal("/;;", cookie.path)
assert_equal(".excite.co.jp", cookie.domain)
assert_equal(Time.gm(2037,12,31,12,0,0), cookie.expires)
end
# def test_make_portlist()
# assert_equal([80,8080], @cm.instance_eval{make_portlist("80,8080")})
# assert_equal([80], @cm.instance_eval{make_portlist("80")})
# assert_equal([80,8080,10080], @cm.instance_eval{make_portlist(" 80, 8080, 10080 \n")})
# end
def test_check_expired_cookies()
c1 = WebAgent::Cookie.new()
c2 = c1.dup
c3 = c1.dup
c4 = c1.dup
c1.expires = Time.now - 100
c2.expires = Time.now + 100
c3.expires = Time.now - 10
c4.expires = nil
cookies = [c1,c2,c3,c4]
@cm.cookies = cookies
@cm.check_expired_cookies()
# expires == nil cookies (session cookie) exists.
assert_equal([c2,c4], @cm.cookies)
end
def test_parse_expires
str = "inkid=n92b0ADOgACIgUb9lsjHqAAAHu2a; expires=; path=/"
@cm.parse(str, urify('http://www.test.jp'))
cookie = @cm.cookies[0]
assert_equal("inkid", cookie.name)
assert_equal("n92b0ADOgACIgUb9lsjHqAAAHu2a", cookie.value)
assert_equal(nil, cookie.expires)
assert_equal("/", cookie.path)
#
str = "inkid=n92b0ADOgACIgUb9lsjHqAAAHu2a; path=/; expires="
@cm.parse(str, urify('http://www.test.jp'))
cookie = @cm.cookies[0]
assert_equal("inkid", cookie.name)
assert_equal("n92b0ADOgACIgUb9lsjHqAAAHu2a", cookie.value)
assert_equal(nil, cookie.expires)
assert_equal("/", cookie.path)
#
str = "inkid=n92b0ADOgACIgUb9lsjHqAAAHu2a; path=/; expires=\"\""
@cm.parse(str, urify('http://www.test.jp'))
cookie = @cm.cookies[0]
assert_equal("inkid", cookie.name)
assert_equal("n92b0ADOgACIgUb9lsjHqAAAHu2a", cookie.value)
assert_equal(nil, cookie.expires)
assert_equal("/", cookie.path)
end
def test_find_cookie()
str = "xmen=off,0,0,1; path=/; domain=.excite2.co.jp; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
@cm.parse(str, urify("http://www.excite2.co.jp/"))
str = "xmen=off,0,0,2; path=/; domain=.excite.co.jp; expires=Wednesday, 31-Dec-2037 12:00:00 GMT"
@cm.parse(str, urify("http://www.excite.co.jp/"))
@cm.cookies[0].use = true
@cm.cookies[1].use = true
url = urify('http://www.excite.co.jp/hoge/funi/')
cookie_str = @cm.find(url)
assert_equal("xmen=off,0,0,2", cookie_str)
end
def test_load_cookies()
begin
File.open("tmp_test.tmp","w") {|f|
f.write < same as URL
c.url = urify("http://www.inac.co.jp/hoge/hoge2/hoge3")
@cm.add(c)
#
c1, c2 = @cm.cookies
assert_equal('', c1.path)
assert_equal('/hoge/hoge2', c2.path)
end
def test_check_cookie_accept_domain()
@cm.accept_domains = [".example1.co.jp", "www1.example.jp"]
@cm.reject_domains = [".example2.co.jp", "www2.example.jp"]
check1 = @cm.check_cookie_accept_domain("www.example1.co.jp")
assert_equal(true, check1)
check2 = @cm.check_cookie_accept_domain("www.example2.co.jp")
assert_equal(false, check2)
check3 = @cm.check_cookie_accept_domain("www1.example.jp")
assert_equal(true, check3)
check4 = @cm.check_cookie_accept_domain("www2.example.jp")
assert_equal(false, check4)
check5 = @cm.check_cookie_accept_domain("aa.www2.example.jp")
assert_equal(true, check5)
check6 = @cm.check_cookie_accept_domain("aa.www2.example.jp")
assert_equal(true, check6)
assert_equal(false, @cm.check_cookie_accept_domain(nil))
end
end
httpclient-2.3.3/test/test_ssl.rb 0000644 0000041 0000041 00000014413 12155433207 017076 0 ustar www-data www-data require File.expand_path('helper', File.dirname(__FILE__))
require 'webrick/https'
class TestSSL < Test::Unit::TestCase
include Helper
DIR = File.dirname(File.expand_path(__FILE__))
def setup
super
@serverpid = @client = nil
@verify_callback_called = false
@verbose, $VERBOSE = $VERBOSE, nil
setup_server
setup_client
@url = "https://localhost:#{serverport}/hello"
end
def teardown
super
$VERBOSE = @verbose
end
def path(filename)
File.expand_path(filename, DIR)
end
def test_options
cfg = @client.ssl_config
assert_nil(cfg.client_cert)
assert_nil(cfg.client_key)
assert_nil(cfg.client_ca)
assert_equal(OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT, cfg.verify_mode)
assert_nil(cfg.verify_callback)
assert_nil(cfg.timeout)
assert_equal(OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2, cfg.options)
assert_equal("ALL:!aNULL:!eNULL:!SSLv2", cfg.ciphers)
assert_instance_of(OpenSSL::X509::Store, cfg.cert_store)
end
def test_sync
cfg = @client.ssl_config
cfg.set_client_cert_file(path('client.cert'), path('client.key'))
cfg.add_trust_ca(path('ca.cert'))
cfg.add_trust_ca(path('subca.cert'))
assert_equal("hello", @client.get_content(@url))
@client.socket_sync = false
@client.reset_all
assert_equal("hello", @client.get_content(@url))
end
def test_debug_dev
str = @client.debug_dev = ''
cfg = @client.ssl_config
cfg.client_cert = cert("client.cert")
cfg.client_key = key("client.key")
cfg.add_trust_ca(path('ca.cert'))
cfg.add_trust_ca(path('subca.cert'))
assert_equal("hello", @client.get_content(@url))
assert(str.scan(/^hello$/)[0])
end
def test_verification
cfg = @client.ssl_config
cfg.verify_callback = method(:verify_callback).to_proc
begin
@verify_callback_called = false
@client.get(@url)
assert(false)
rescue OpenSSL::SSL::SSLError => ssle
assert_match(/certificate verify failed/, ssle.message)
assert(@verify_callback_called)
end
#
cfg.client_cert = cert("client.cert")
cfg.client_key = key("client.key")
@verify_callback_called = false
begin
@client.get(@url)
assert(false)
rescue OpenSSL::SSL::SSLError => ssle
assert_match(/certificate verify failed/, ssle.message)
assert(@verify_callback_called)
end
#
cfg.add_trust_ca(path('ca.cert'))
@verify_callback_called = false
begin
@client.get(@url)
assert(false)
rescue OpenSSL::SSL::SSLError => ssle
assert_match(/certificate verify failed/, ssle.message)
assert(@verify_callback_called)
end
#
cfg.add_trust_ca(path('subca.cert'))
@verify_callback_called = false
assert_equal("hello", @client.get_content(@url))
assert(@verify_callback_called)
#
unless ENV['TRAVIS']
# On travis environment, verify_depth seems to not work properly.
# Ubuntu 10.04 + OpenSSL 0.9.8k issue?
cfg.verify_depth = 1 # 2 required: root-sub
@verify_callback_called = false
begin
@client.get(@url)
assert(false, "verify_depth is not supported? #{OpenSSL::OPENSSL_VERSION}")
rescue OpenSSL::SSL::SSLError => ssle
assert_match(/certificate verify failed/, ssle.message)
assert(@verify_callback_called)
end
#
cfg.verify_depth = 2 # 2 required: root-sub
@verify_callback_called = false
@client.get(@url)
assert(@verify_callback_called)
#
end
cfg.verify_depth = nil
cfg.cert_store = OpenSSL::X509::Store.new
cfg.verify_mode = OpenSSL::SSL::VERIFY_PEER
begin
@client.get_content(@url)
assert(false)
rescue OpenSSL::SSL::SSLError => ssle
assert_match(/certificate verify failed/, ssle.message)
end
#
cfg.verify_mode = nil
assert_equal("hello", @client.get_content(@url))
end
def test_ciphers
cfg = @client.ssl_config
cfg.set_client_cert_file(path('client.cert'), path('client.key'))
cfg.add_trust_ca(path('ca.cert'))
cfg.add_trust_ca(path('subca.cert'))
cfg.timeout = 123
assert_equal("hello", @client.get_content(@url))
#
cfg.ciphers = "!ALL"
begin
@client.get(@url)
assert(false)
rescue OpenSSL::SSL::SSLError => ssle
assert_match(/no cipher match/, ssle.message)
end
#
cfg.ciphers = "ALL"
assert_equal("hello", @client.get_content(@url))
#
cfg.ciphers = "DEFAULT"
assert_equal("hello", @client.get_content(@url))
end
def test_set_default_paths
assert_raise(OpenSSL::SSL::SSLError) do
@client.get(@url)
end
escape_env do
ENV['SSL_CERT_FILE'] = File.join(DIR, 'ca-chain.cert')
@client.ssl_config.set_default_paths
@client.get(@url)
end
end
private
def cert(filename)
OpenSSL::X509::Certificate.new(File.read(File.join(DIR, filename)))
end
def key(filename)
OpenSSL::PKey::RSA.new(File.read(File.join(DIR, filename)))
end
def q(str)
%Q["#{str}"]
end
def setup_server
logger = Logger.new(STDERR)
logger.level = Logger::Severity::FATAL # avoid logging SSLError (ERROR level)
@server = WEBrick::HTTPServer.new(
:BindAddress => "localhost",
:Logger => logger,
:Port => 0,
:AccessLog => [],
:DocumentRoot => DIR,
:SSLEnable => true,
:SSLCACertificateFile => File.join(DIR, 'ca.cert'),
:SSLCertificate => cert('server.cert'),
:SSLPrivateKey => key('server.key'),
:SSLVerifyClient => nil, #OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT|OpenSSL::SSL::VERIFY_PEER,
:SSLClientCA => cert('ca.cert'),
:SSLCertName => nil
)
@serverport = @server.config[:Port]
[:hello].each do |sym|
@server.mount(
"/#{sym}",
WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
)
end
@server_thread = start_server_thread(@server)
end
def do_hello(req, res)
res['content-type'] = 'text/html'
res.body = "hello"
end
def start_server_thread(server)
t = Thread.new {
Thread.current.abort_on_exception = true
server.start
}
while server.status != :Running
sleep 0.1
unless t.alive?
t.join
raise
end
end
t
end
def verify_callback(ok, cert)
@verify_callback_called = true
p ["client", ok, cert] if $DEBUG
ok
end
end
httpclient-2.3.3/test/test_httpclient.rb 0000644 0000041 0000041 00000161765 12155433207 020470 0 ustar www-data www-data # -*- encoding: utf-8 -*-
require File.expand_path('helper', File.dirname(__FILE__))
class TestHTTPClient < Test::Unit::TestCase
include Helper
include HTTPClient::Util
def setup
super
setup_server
setup_client
end
def teardown
super
end
def test_initialize
setup_proxyserver
escape_noproxy do
@proxyio.string = ""
@client = HTTPClient.new(proxyurl)
assert_equal(urify(proxyurl), @client.proxy)
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ =~ @proxyio.string)
end
end
def test_agent_name
@client = HTTPClient.new(nil, "agent_name_foo")
str = ""
@client.debug_dev = str
@client.get(serverurl)
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_match(/^User-Agent: agent_name_foo \(#{HTTPClient::VERSION}/, lines[4])
end
def test_from
@client = HTTPClient.new(nil, nil, "from_bar")
str = ""
@client.debug_dev = str
@client.get(serverurl)
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_match(/^From: from_bar/, lines[5])
end
def test_debug_dev
str = ""
@client.debug_dev = str
assert_equal(str.object_id, @client.debug_dev.object_id)
assert(str.empty?)
@client.get(serverurl)
assert(!str.empty?)
end
def test_debug_dev_stream
str = ""
@client.debug_dev = str
conn = @client.get_async(serverurl)
Thread.pass while !conn.finished?
assert(!str.empty?)
end
def test_protocol_version_http09
@client.protocol_version = 'HTTP/0.9'
@client.debug_dev = str = ''
@client.test_loopback_http_response << "hello\nworld\n"
res = @client.get(serverurl + 'hello')
assert_equal('0.9', res.http_version)
assert_equal(nil, res.status)
assert_equal(nil, res.reason)
assert_equal("hello\nworld\n", res.content)
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_equal("! CONNECTION ESTABLISHED", lines[2])
assert_equal("GET /hello HTTP/0.9", lines[3])
assert_equal("Connection: close", lines[7])
assert_equal("= Response", lines[8])
assert_match(/^hello$/, lines[9])
assert_match(/^world$/, lines[10])
end
def test_protocol_version_http10
assert_equal(nil, @client.protocol_version)
@client.protocol_version = 'HTTP/1.0'
assert_equal('HTTP/1.0', @client.protocol_version)
str = ""
@client.debug_dev = str
@client.get(serverurl + 'hello')
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_equal("! CONNECTION ESTABLISHED", lines[2])
assert_equal("GET /hello HTTP/1.0", lines[3])
assert_equal("Connection: close", lines[7])
assert_equal("= Response", lines[8])
end
def test_header_accept_by_default
str = ""
@client.debug_dev = str
@client.get(serverurl)
lines = str.split(/(?:\r?\n)+/)
assert_equal("Accept: */*", lines[5])
end
def test_header_accept
str = ""
@client.debug_dev = str
@client.get(serverurl, :header => {:Accept => 'text/html'})
lines = str.split(/(?:\r?\n)+/)
assert_equal("Accept: text/html", lines[4])
end
def test_host_given
str = ""
@client.debug_dev = str
@client.get(serverurl)
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_equal("! CONNECTION ESTABLISHED", lines[2])
assert_equal("GET / HTTP/1.1", lines[3])
assert_equal("Host: localhost:#{serverport}", lines[7])
#
@client.reset_all
str = ""
@client.debug_dev = str
@client.get(serverurl, nil, {'Host' => 'foo'})
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_equal("! CONNECTION ESTABLISHED", lines[2])
assert_equal("GET / HTTP/1.1", lines[3])
assert_equal("Host: foo", lines[4]) # use given param
end
def test_redirect_returns_not_modified
assert_nothing_raised do
timeout(2) do
@client.get(serverurl + 'status', {:status => 306}, {:follow_redirect => true})
end
end
end
def test_protocol_version_http11
assert_equal(nil, @client.protocol_version)
str = ""
@client.debug_dev = str
@client.get(serverurl)
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_equal("! CONNECTION ESTABLISHED", lines[2])
assert_equal("GET / HTTP/1.1", lines[3])
assert_equal("Host: localhost:#{serverport}", lines[7])
@client.protocol_version = 'HTTP/1.1'
assert_equal('HTTP/1.1', @client.protocol_version)
str = ""
@client.debug_dev = str
@client.get(serverurl)
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_equal("! CONNECTION ESTABLISHED", lines[2])
assert_equal("GET / HTTP/1.1", lines[3])
@client.protocol_version = 'HTTP/1.0'
str = ""
@client.debug_dev = str
@client.get(serverurl)
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_equal("! CONNECTION ESTABLISHED", lines[2])
assert_equal("GET / HTTP/1.0", lines[3])
end
def test_proxy
setup_proxyserver
escape_noproxy do
begin
@client.proxy = "http://"
rescue
assert_match(/InvalidURIError/, $!.class.to_s)
end
@client.proxy = ""
assert_nil(@client.proxy)
@client.proxy = "http://admin:admin@foo:1234"
assert_equal(urify("http://admin:admin@foo:1234"), @client.proxy)
uri = urify("http://bar:2345")
@client.proxy = uri
assert_equal(uri, @client.proxy)
#
@proxyio.string = ""
@client.proxy = nil
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ !~ @proxyio.string)
#
@proxyio.string = ""
@client.proxy = proxyurl
@client.debug_dev = str = ""
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ =~ @proxyio.string)
assert(/Host: localhost:#{serverport}/ =~ str)
end
end
def test_host_header
@client.proxy = proxyurl
@client.debug_dev = str = ""
@client.test_loopback_http_response << "HTTP/1.0 200 OK\r\n\r\n"
assert_equal(200, @client.head('http://www.example.com/foo').status)
# ensure no ':80' is added. some servers dislike that.
assert(/\r\nHost: www\.example\.com\r\n/ =~ str)
#
@client.debug_dev = str = ""
@client.test_loopback_http_response << "HTTP/1.0 200 OK\r\n\r\n"
assert_equal(200, @client.head('http://www.example.com:12345/foo').status)
# ensure ':12345' exists.
assert(/\r\nHost: www\.example\.com:12345\r\n/ =~ str)
end
def test_proxy_env
setup_proxyserver
escape_env do
ENV['http_proxy'] = "http://admin:admin@foo:1234"
ENV['NO_PROXY'] = "foobar"
client = HTTPClient.new
assert_equal(urify("http://admin:admin@foo:1234"), client.proxy)
assert_equal('foobar', client.no_proxy)
end
end
def test_proxy_env_cgi
setup_proxyserver
escape_env do
ENV['REQUEST_METHOD'] = 'GET' # CGI environment emulation
ENV['http_proxy'] = "http://admin:admin@foo:1234"
ENV['no_proxy'] = "foobar"
client = HTTPClient.new
assert_equal(nil, client.proxy)
ENV['CGI_HTTP_PROXY'] = "http://admin:admin@foo:1234"
client = HTTPClient.new
assert_equal(urify("http://admin:admin@foo:1234"), client.proxy)
end
end
def test_empty_proxy_env
setup_proxyserver
escape_env do
ENV['http_proxy'] = ""
client = HTTPClient.new
assert_equal(nil, client.proxy)
end
end
def test_noproxy_for_localhost
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ !~ @proxyio.string)
end
def test_no_proxy
setup_proxyserver
escape_noproxy do
# proxy is not set.
assert_equal(nil, @client.no_proxy)
@client.no_proxy = 'localhost'
assert_equal('localhost', @client.no_proxy)
@proxyio.string = ""
@client.proxy = nil
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ !~ @proxyio.string)
#
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ !~ @proxyio.string)
#
@client.no_proxy = 'foobar'
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ =~ @proxyio.string)
#
@client.no_proxy = 'foobar,localhost:baz'
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ !~ @proxyio.string)
#
@client.no_proxy = 'foobar,localhost:443'
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ =~ @proxyio.string)
#
@client.no_proxy = "foobar,localhost:443:localhost:#{serverport},baz"
@proxyio.string = ""
@client.proxy = proxyurl
assert_equal(200, @client.head(serverurl).status)
assert(/accept/ !~ @proxyio.string)
end
end
def test_no_proxy_with_initial_dot
@client.debug_dev = str = ""
@client.test_loopback_http_response << "HTTP/1.0 200 OK\r\n\r\n"
@client.no_proxy = ''
@client.proxy = proxyurl
@client.head('http://www.foo.com')
assert(/CONNECT TO localhost/ =~ str, 'via proxy')
#
@client.debug_dev = str = ""
@client.test_loopback_http_response << "HTTP/1.0 200 OK\r\n\r\n"
@client.no_proxy = '.foo.com'
@client.proxy = proxyurl
@client.head('http://www.foo.com')
assert(/CONNECT TO www.foo.com/ =~ str, 'no proxy because .foo.com matches with www.foo.com')
#
@client.debug_dev = str = ""
@client.test_loopback_http_response << "HTTP/1.0 200 OK\r\n\r\n"
@client.no_proxy = '.foo.com'
@client.proxy = proxyurl
@client.head('http://foo.com')
assert(/CONNECT TO localhost/ =~ str, 'via proxy because .foo.com does not matche with foo.com')
#
@client.debug_dev = str = ""
@client.test_loopback_http_response << "HTTP/1.0 200 OK\r\n\r\n"
@client.no_proxy = 'foo.com'
@client.proxy = proxyurl
@client.head('http://foo.com')
assert(/CONNECT TO foo.com/ =~ str, 'no proxy because foo.com matches with foo.com')
end
def test_cookie_update_while_authentication
escape_noproxy do
@client.test_loopback_http_response << < true).header.request_uri)
assert_equal(expected, @client.get(serverurl + 'redirect2', :follow_redirect => true).header.request_uri)
end
def test_redirect_non_https
url = serverurl + 'redirect1'
https_url = urify(url)
https_url.scheme = 'https'
#
redirect_to_http = "HTTP/1.0 302 OK\nLocation: #{url}\n\n"
redirect_to_https = "HTTP/1.0 302 OK\nLocation: #{https_url}\n\n"
#
# https -> http is denied
@client.test_loopback_http_response << redirect_to_http
assert_raises(HTTPClient::BadResponseError) do
@client.get_content(https_url)
end
#
# http -> http is OK
@client.reset_all
@client.test_loopback_http_response << redirect_to_http
assert_equal('hello', @client.get_content(url))
#
# http -> https is OK
@client.reset_all
@client.test_loopback_http_response << redirect_to_https
assert_raises(OpenSSL::SSL::SSLError) do
# trying to normal endpoint with SSL -> SSL negotiation failure
@client.get_content(url)
end
#
# https -> https is OK
@client.reset_all
@client.test_loopback_http_response << redirect_to_https
assert_raises(OpenSSL::SSL::SSLError) do
# trying to normal endpoint with SSL -> SSL negotiation failure
@client.get_content(https_url)
end
#
# https -> http with strict_redirect_uri_callback
@client.redirect_uri_callback = @client.method(:strict_redirect_uri_callback)
@client.test_loopback_http_response << redirect_to_http
assert_raises(HTTPClient::BadResponseError) do
@client.get_content(https_url)
end
end
def test_redirect_see_other
assert_equal('hello', @client.post_content(serverurl + 'redirect_see_other'))
end
def test_redirect_relative
@client.test_loopback_http_response << "HTTP/1.0 302 OK\nLocation: hello\n\n"
silent do
assert_equal('hello', @client.get_content(serverurl + 'redirect1'))
end
#
@client.reset_all
@client.redirect_uri_callback = @client.method(:strict_redirect_uri_callback)
assert_equal('hello', @client.get_content(serverurl + 'redirect1'))
@client.reset_all
@client.test_loopback_http_response << "HTTP/1.0 302 OK\nLocation: hello\n\n"
begin
@client.get_content(serverurl + 'redirect1')
assert(false)
rescue HTTPClient::BadResponseError => e
assert_equal(302, e.res.status)
end
end
def test_redirect_https_relative
url = serverurl + 'redirect1'
https_url = urify(url)
https_url.scheme = 'https'
@client.test_loopback_http_response << "HTTP/1.0 302 OK\nLocation: /foo\n\n"
@client.test_loopback_http_response << "HTTP/1.0 200 OK\n\nhello"
silent do
assert_equal('hello', @client.get_content(https_url))
end
end
def test_no_content
assert_nothing_raised do
timeout(2) do
@client.get(serverurl + 'status', :status => 101)
@client.get(serverurl + 'status', :status => 204)
@client.get(serverurl + 'status', :status => 304)
end
end
end
def test_get_content
assert_equal('hello', @client.get_content(serverurl + 'hello'))
assert_equal('hello', @client.get_content(serverurl + 'redirect1'))
assert_equal('hello', @client.get_content(serverurl + 'redirect2'))
url = serverurl.sub(/localhost/, '127.0.0.1')
assert_equal('hello', @client.get_content(url + 'hello'))
assert_equal('hello', @client.get_content(url + 'redirect1'))
assert_equal('hello', @client.get_content(url + 'redirect2'))
@client.reset(serverurl)
@client.reset(url)
@client.reset(serverurl)
@client.reset(url)
assert_raises(HTTPClient::BadResponseError) do
@client.get_content(serverurl + 'notfound')
end
assert_raises(HTTPClient::BadResponseError) do
@client.get_content(serverurl + 'redirect_self')
end
called = false
@client.redirect_uri_callback = lambda { |uri, res|
newuri = res.header['location'][0]
called = true
newuri
}
assert_equal('hello', @client.get_content(serverurl + 'relative_redirect'))
assert(called)
end
GZIP_CONTENT = "\x1f\x8b\x08\x00\x1a\x96\xe0\x4c\x00\x03\xcb\x48\xcd\xc9\xc9\x07\x00\x86\xa6\x10\x36\x05\x00\x00\x00"
DEFLATE_CONTENT = "\x78\x9c\xcb\x48\xcd\xc9\xc9\x07\x00\x06\x2c\x02\x15"
GZIP_CONTENT.force_encoding('BINARY') if GZIP_CONTENT.respond_to?(:force_encoding)
DEFLATE_CONTENT.force_encoding('BINARY') if DEFLATE_CONTENT.respond_to?(:force_encoding)
def test_get_gzipped_content
@client.transparent_gzip_decompression = false
content = @client.get_content(serverurl + 'compressed?enc=gzip')
assert_not_equal('hello', content)
assert_equal(GZIP_CONTENT, content)
@client.transparent_gzip_decompression = true
assert_equal('hello', @client.get_content(serverurl + 'compressed?enc=gzip'))
assert_equal('hello', @client.get_content(serverurl + 'compressed?enc=deflate'))
@client.transparent_gzip_decompression = false
end
def test_get_content_with_block
@client.get_content(serverurl + 'hello') do |str|
assert_equal('hello', str)
end
@client.get_content(serverurl + 'redirect1') do |str|
assert_equal('hello', str)
end
@client.get_content(serverurl + 'redirect2') do |str|
assert_equal('hello', str)
end
end
def test_post_content
assert_equal('hello', @client.post_content(serverurl + 'hello'))
assert_equal('hello', @client.post_content(serverurl + 'redirect1'))
assert_equal('hello', @client.post_content(serverurl + 'redirect2'))
assert_raises(HTTPClient::BadResponseError) do
@client.post_content(serverurl + 'notfound')
end
assert_raises(HTTPClient::BadResponseError) do
@client.post_content(serverurl + 'redirect_self')
end
called = false
@client.redirect_uri_callback = lambda { |uri, res|
newuri = res.header['location'][0]
called = true
newuri
}
assert_equal('hello', @client.post_content(serverurl + 'relative_redirect'))
assert(called)
end
def test_post_content_io
post_body = StringIO.new("1234567890")
assert_equal('post,1234567890', @client.post_content(serverurl + 'servlet', post_body))
post_body = StringIO.new("1234567890")
assert_equal('post,1234567890', @client.post_content(serverurl + 'servlet_redirect', post_body))
#
post_body = StringIO.new("1234567890")
post_body.read(5)
assert_equal('post,67890', @client.post_content(serverurl + 'servlet_redirect', post_body))
end
def test_head
assert_equal("head", @client.head(serverurl + 'servlet').header["x-head"][0])
param = {'1'=>'2', '3'=>'4'}
res = @client.head(serverurl + 'servlet', param)
assert_equal(param, params(res.header["x-query"][0]))
end
def test_head_async
param = {'1'=>'2', '3'=>'4'}
conn = @client.head_async(serverurl + 'servlet', param)
Thread.pass while !conn.finished?
res = conn.pop
assert_equal(param, params(res.header["x-query"][0]))
end
def test_get
assert_equal("get", @client.get(serverurl + 'servlet').content)
param = {'1'=>'2', '3'=>'4'}
res = @client.get(serverurl + 'servlet', param)
assert_equal(param, params(res.header["x-query"][0]))
assert_nil(res.contenttype)
#
url = serverurl.to_s + 'servlet?5=6&7=8'
res = @client.get(url, param)
assert_equal(param.merge("5"=>"6", "7"=>"8"), params(res.header["x-query"][0]))
assert_nil(res.contenttype)
end
def test_head_follow_redirect
expected = urify(serverurl + 'hello')
assert_equal(expected, @client.head(serverurl + 'hello', :follow_redirect => true).header.request_uri)
assert_equal(expected, @client.head(serverurl + 'redirect1', :follow_redirect => true).header.request_uri)
assert_equal(expected, @client.head(serverurl + 'redirect2', :follow_redirect => true).header.request_uri)
end
def test_get_follow_redirect
assert_equal('hello', @client.get(serverurl + 'hello', :follow_redirect => true).body)
assert_equal('hello', @client.get(serverurl + 'redirect1', :follow_redirect => true).body)
assert_equal('hello', @client.get(serverurl + 'redirect2', :follow_redirect => true).body)
end
def test_get_async
param = {'1'=>'2', '3'=>'4'}
conn = @client.get_async(serverurl + 'servlet', param)
Thread.pass while !conn.finished?
res = conn.pop
assert_equal(param, params(res.header["x-query"][0]))
end
def test_get_async_for_largebody
conn = @client.get_async(serverurl + 'largebody')
res = conn.pop
assert_equal(1000*1000, res.content.read.length)
end
def test_get_with_block
called = false
res = @client.get(serverurl + 'servlet') { |str|
assert_equal('get', str)
called = true
}
assert(called)
# res does not have a content
assert_nil(res.content)
end
def test_get_with_block_chunk_string_recycle
@client.read_block_size = 2
body = []
res = @client.get(serverurl + 'servlet') { |str|
body << str
}
assert_equal(2, body.size)
assert_equal("get", body.join) # Was "tt" by String object recycle...
end
def test_post
assert_equal("post", @client.post(serverurl + 'servlet').content[0, 4])
param = {'1'=>'2', '3'=>'4'}
res = @client.post(serverurl + 'servlet', param)
assert_equal(param, params(res.header["x-query"][0]))
end
def test_post_follow_redirect
assert_equal('hello', @client.post(serverurl + 'hello', :follow_redirect => true).body)
assert_equal('hello', @client.post(serverurl + 'redirect1', :follow_redirect => true).body)
assert_equal('hello', @client.post(serverurl + 'redirect2', :follow_redirect => true).body)
end
def test_post_with_content_type
param = [['1', '2'], ['3', '4']]
ext = {'content-type' => 'application/x-www-form-urlencoded', 'hello' => 'world'}
assert_equal("post", @client.post(serverurl + 'servlet').content[0, 4], ext)
res = @client.post(serverurl + 'servlet', param, ext)
assert_equal(Hash[param], params(res.header["x-query"][0]))
#
ext = [['content-type', 'multipart/form-data'], ['hello', 'world']]
assert_equal("post", @client.post(serverurl + 'servlet').content[0, 4], ext)
res = @client.post(serverurl + 'servlet', param, ext)
assert_match(/Content-Disposition: form-data; name="1"/, res.content)
assert_match(/Content-Disposition: form-data; name="3"/, res.content)
#
ext = {'content-type' => 'multipart/form-data; boundary=hello'}
assert_equal("post", @client.post(serverurl + 'servlet').content[0, 4], ext)
res = @client.post(serverurl + 'servlet', param, ext)
assert_match(/Content-Disposition: form-data; name="1"/, res.content)
assert_match(/Content-Disposition: form-data; name="3"/, res.content)
assert_equal("post,--hello\r\nContent-Disposition: form-data; name=\"1\"\r\n\r\n2\r\n--hello\r\nContent-Disposition: form-data; name=\"3\"\r\n\r\n4\r\n--hello--\r\n\r\n", res.content)
end
def test_post_with_custom_multipart_and_boolean_params
param = [['boolean_true', true]]
ext = { 'content-type' => 'multipart/form-data' }
assert_equal("post", @client.post(serverurl + 'servlet').content[0, 4], ext)
res = @client.post(serverurl + 'servlet', param, ext)
assert_match(/Content-Disposition: form-data; name="boolean_true"\r\n\r\ntrue\r\n/, res.content)
#
param = [['boolean_false', false]]
res = @client.post(serverurl + 'servlet', param, ext)
assert_match(/Content-Disposition: form-data; name="boolean_false"\r\n\r\nfalse\r\n/, res.content)
#
param = [['nil', nil]]
res = @client.post(serverurl + 'servlet', param, ext)
assert_match(/Content-Disposition: form-data; name="nil"\r\n\r\n\r\n/, res.content)
end
def test_post_with_file
STDOUT.sync = true
File.open(__FILE__) do |file|
res = @client.post(serverurl + 'servlet', {1=>2, 3=>file})
assert_match(/^Content-Disposition: form-data; name="1"\r\n/nm, res.content)
assert_match(/^Content-Disposition: form-data; name="3";/, res.content)
assert_match(/FIND_TAG_IN_THIS_FILE/, res.content)
end
end
def test_post_with_file_without_size
STDOUT.sync = true
File.open(__FILE__) do |file|
def file.size
# Simulates some strange Windows behaviour
raise SystemCallError.new "Unknown Error (20047)"
end
assert_nothing_raised do
@client.post(serverurl + 'servlet', {1=>2, 3=>file})
end
end
end
def test_post_with_io # streaming, but not chunked
myio = StringIO.new("X" * (HTTP::Message::Body::DEFAULT_CHUNK_SIZE + 1))
def myio.read(*args)
@called ||= 0
@called += 1
super
end
def myio.called
@called
end
@client.debug_dev = str = StringIO.new
res = @client.post(serverurl + 'servlet', {1=>2, 3=>myio})
assert_match(/\r\nContent-Disposition: form-data; name="1"\r\n/m, res.content)
assert_match(/\r\n2\r\n/m, res.content)
assert_match(/\r\nContent-Disposition: form-data; name="3"; filename=""\r\n/m, res.content)
assert_match(/\r\nContent-Length:/m, str.string)
assert_equal(3, myio.called)
end
def test_post_with_io_nosize # streaming + chunked post
myio = StringIO.new("4")
def myio.size
nil
end
@client.debug_dev = str = StringIO.new
res = @client.post(serverurl + 'servlet', {1=>2, 3=>myio})
assert_match(/\r\nContent-Disposition: form-data; name="1"\r\n/m, res.content)
assert_match(/\r\n2\r\n/m, res.content)
assert_match(/\r\nContent-Disposition: form-data; name="3"; filename=""\r\n/m, res.content)
assert_match(/\r\n4\r\n/m, res.content)
assert_match(/\r\nTransfer-Encoding: chunked\r\n/m, str.string)
end
def test_post_async
param = {'1'=>'2', '3'=>'4'}
conn = @client.post_async(serverurl + 'servlet', param)
Thread.pass while !conn.finished?
res = conn.pop
assert_equal(param, params(res.header["x-query"][0]))
end
def test_post_with_block
called = false
res = @client.post(serverurl + 'servlet') { |str|
assert_equal('post,', str)
called = true
}
assert(called)
assert_nil(res.content)
#
called = false
param = [['1', '2'], ['3', '4']]
res = @client.post(serverurl + 'servlet', param) { |str|
assert_equal('post,1=2&3=4', str)
called = true
}
assert(called)
assert_equal('1=2&3=4', res.header["x-query"][0])
assert_nil(res.content)
end
def test_post_with_custom_multipart
ext = {'content-type' => 'multipart/form-data'}
assert_equal("post", @client.post(serverurl + 'servlet').content[0, 4], ext)
body = [{ 'Content-Disposition' => 'form-data; name="1"', :content => "2"},
{ 'Content-Disposition' => 'form-data; name="3"', :content => "4"}]
res = @client.post(serverurl + 'servlet', body, ext)
assert_match(/Content-Disposition: form-data; name="1"/, res.content)
assert_match(/Content-Disposition: form-data; name="3"/, res.content)
#
ext = {'content-type' => 'multipart/form-data; boundary=hello'}
assert_equal("post", @client.post(serverurl + 'servlet').content[0, 4], ext)
res = @client.post(serverurl + 'servlet', body, ext)
assert_match(/Content-Disposition: form-data; name="1"/, res.content)
assert_match(/Content-Disposition: form-data; name="3"/, res.content)
assert_equal("post,--hello\r\nContent-Disposition: form-data; name=\"1\"\r\n\r\n2\r\n--hello\r\nContent-Disposition: form-data; name=\"3\"\r\n\r\n4\r\n--hello--\r\n\r\n", res.content)
end
def test_post_with_custom_multipart_and_file
STDOUT.sync = true
File.open(__FILE__) do |file|
ext = { 'Content-Type' => 'multipart/alternative' }
body = [{ 'Content-Type' => 'text/plain', :content => "this is only a test" },
{ 'Content-Type' => 'application/x-ruby', :content => file }]
res = @client.post(serverurl + 'servlet', body, ext)
assert_match(/^Content-Type: text\/plain\r\n/m, res.content)
assert_match(/^this is only a test\r\n/m, res.content)
assert_match(/^Content-Type: application\/x-ruby\r\n/m, res.content)
assert_match(/FIND_TAG_IN_THIS_FILE/, res.content)
end
end
def test_put
assert_equal("put", @client.put(serverurl + 'servlet').content)
param = {'1'=>'2', '3'=>'4'}
@client.debug_dev = str = ''
res = @client.put(serverurl + 'servlet', param)
assert_equal(param, params(res.header["x-query"][0]))
assert_equal('Content-Type: application/x-www-form-urlencoded', str.split(/\r?\n/)[5])
end
def test_put_bytesize
res = @client.put(serverurl + 'servlet', 'txt' => 'あいうえお')
assert_equal('txt=%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A', res.header["x-query"][0])
assert_equal('15', res.header["x-size"][0])
end
def test_put_async
param = {'1'=>'2', '3'=>'4'}
conn = @client.put_async(serverurl + 'servlet', param)
Thread.pass while !conn.finished?
res = conn.pop
assert_equal(param, params(res.header["x-query"][0]))
end
def test_delete
assert_equal("delete", @client.delete(serverurl + 'servlet').content)
end
# Not prohibited by spec, but normally it's ignored
def test_delete_with_body
param = {'1'=>'2', '3'=>'4'}
@client.debug_dev = str = ''
assert_equal("delete", @client.delete(serverurl + 'servlet', param).content)
assert_equal({'1' => ['2'], '3' => ['4']}, HTTP::Message.parse(str.split(/\r?\n\r?\n/)[2]))
end
def test_delete_async
conn = @client.delete_async(serverurl + 'servlet')
Thread.pass while !conn.finished?
res = conn.pop
assert_equal('delete', res.content.read)
end
def test_options
assert_equal("options", @client.options(serverurl + 'servlet').content)
end
def test_options_async
conn = @client.options_async(serverurl + 'servlet')
Thread.pass while !conn.finished?
res = conn.pop
assert_equal('options', res.content.read)
end
def test_propfind
assert_equal("propfind", @client.propfind(serverurl + 'servlet').content)
end
def test_propfind_async
conn = @client.propfind_async(serverurl + 'servlet')
Thread.pass while !conn.finished?
res = conn.pop
assert_equal('propfind', res.content.read)
end
def test_proppatch
assert_equal("proppatch", @client.proppatch(serverurl + 'servlet').content)
param = {'1'=>'2', '3'=>'4'}
res = @client.proppatch(serverurl + 'servlet', param)
assert_equal('proppatch', res.content)
assert_equal(param, params(res.header["x-query"][0]))
end
def test_proppatch_async
param = {'1'=>'2', '3'=>'4'}
conn = @client.proppatch_async(serverurl + 'servlet', param)
Thread.pass while !conn.finished?
res = conn.pop
assert_equal('proppatch', res.content.read)
assert_equal(param, params(res.header["x-query"][0]))
end
def test_trace
assert_equal("trace", @client.trace(serverurl + 'servlet').content)
param = {'1'=>'2', '3'=>'4'}
res = @client.trace(serverurl + 'servlet', param)
assert_equal(param, params(res.header["x-query"][0]))
end
def test_trace_async
param = {'1'=>'2', '3'=>'4'}
conn = @client.trace_async(serverurl + 'servlet', param)
Thread.pass while !conn.finished?
res = conn.pop
assert_equal(param, params(res.header["x-query"][0]))
end
def test_chunked
assert_equal('chunked', @client.get_content(serverurl + 'chunked', { 'msg' => 'chunked' }))
end
def test_chunked_empty
assert_equal('', @client.get_content(serverurl + 'chunked', { 'msg' => '' }))
end
def test_get_query
assert_equal({'1'=>'2'}, check_query_get({1=>2}))
assert_equal({'a'=>'A', 'B'=>'b'}, check_query_get({"a"=>"A", "B"=>"b"}))
assert_equal({'&'=>'&'}, check_query_get({"&"=>"&"}))
assert_equal({'= '=>' =+'}, check_query_get({"= "=>" =+"}))
assert_equal(
['=', '&'].sort,
check_query_get([["=", "="], ["=", "&"]])['='].to_ary.sort
)
assert_equal({'123'=>'45'}, check_query_get('123=45'))
assert_equal({'12 3'=>'45', ' '=>' '}, check_query_get('12+3=45&+=+'))
assert_equal({}, check_query_get(''))
assert_equal({'1'=>'2'}, check_query_get({1=>StringIO.new('2')}))
assert_equal({'1'=>'2', '3'=>'4'}, check_query_get(StringIO.new('3=4&1=2')))
hash = check_query_get({"a"=>["A","a"], "B"=>"b"})
assert_equal({'a'=>'A', 'B'=>'b'}, hash)
assert_equal(['A','a'], hash['a'].to_ary)
hash = check_query_get({"a"=>WEBrick::HTTPUtils::FormData.new("A","a"), "B"=>"b"})
assert_equal({'a'=>'A', 'B'=>'b'}, hash)
assert_equal(['A','a'], hash['a'].to_ary)
hash = check_query_get({"a"=>[StringIO.new("A"),StringIO.new("a")], "B"=>StringIO.new("b")})
assert_equal({'a'=>'A', 'B'=>'b'}, hash)
assert_equal(['A','a'], hash['a'].to_ary)
end
def test_post_body
assert_equal({'1'=>'2'}, check_query_post({1=>2}))
assert_equal({'a'=>'A', 'B'=>'b'}, check_query_post({"a"=>"A", "B"=>"b"}))
assert_equal({'&'=>'&'}, check_query_post({"&"=>"&"}))
assert_equal({'= '=>' =+'}, check_query_post({"= "=>" =+"}))
assert_equal(
['=', '&'].sort,
check_query_post([["=", "="], ["=", "&"]])['='].to_ary.sort
)
assert_equal({'123'=>'45'}, check_query_post('123=45'))
assert_equal({'12 3'=>'45', ' '=>' '}, check_query_post('12+3=45&+=+'))
assert_equal({}, check_query_post(''))
#
post_body = StringIO.new("foo=bar&foo=baz")
assert_equal(
["bar", "baz"],
check_query_post(post_body)["foo"].to_ary.sort
)
end
def test_extra_headers
str = ""
@client.debug_dev = str
@client.head(serverurl, nil, {"ABC" => "DEF"})
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_match("ABC: DEF", lines[4])
#
str = ""
@client.debug_dev = str
@client.get(serverurl, nil, [["ABC", "DEF"], ["ABC", "DEF"]])
lines = str.split(/(?:\r?\n)+/)
assert_equal("= Request", lines[0])
assert_match("ABC: DEF", lines[4])
assert_match("ABC: DEF", lines[5])
end
def test_http_custom_date_header
@client.debug_dev = (str = "")
res = @client.get(serverurl + 'hello', :header => {'Date' => 'foo'})
lines = str.split(/(?:\r?\n)+/)
assert_equal('Date: foo', lines[4])
end
def test_timeout
assert_equal(60, @client.connect_timeout)
assert_equal(120, @client.send_timeout)
assert_equal(60, @client.receive_timeout)
#
@client.connect_timeout = 1
@client.send_timeout = 2
@client.receive_timeout = 3
assert_equal(1, @client.connect_timeout)
assert_equal(2, @client.send_timeout)
assert_equal(3, @client.receive_timeout)
end
def test_connect_timeout
# ToDo
end
def test_send_timeout
# ToDo
end
def test_receive_timeout
# this test takes 2 sec
assert_equal('hello', @client.get_content(serverurl + 'sleep?sec=2'))
@client.receive_timeout = 1
assert_equal('hello', @client.get_content(serverurl + 'sleep?sec=0'))
assert_raise(HTTPClient::ReceiveTimeoutError) do
@client.get_content(serverurl + 'sleep?sec=2')
end
@client.receive_timeout = 3
assert_equal('hello', @client.get_content(serverurl + 'sleep?sec=2'))
end
def test_receive_timeout_post
# this test takes 2 sec
assert_equal('hello', @client.post(serverurl + 'sleep', :sec => 2).content)
@client.receive_timeout = 1
assert_equal('hello', @client.post(serverurl + 'sleep', :sec => 0).content)
assert_raise(HTTPClient::ReceiveTimeoutError) do
@client.post(serverurl + 'sleep', :sec => 2)
end
@client.receive_timeout = 3
assert_equal('hello', @client.post(serverurl + 'sleep', :sec => 2).content)
end
def test_async_error
assert_raise( SocketError ) do
conn = @client.get_async("http://non-existing-host/")
conn.pop
end
end
def test_reset
url = serverurl + 'servlet'
assert_nothing_raised do
5.times do
@client.get(url)
@client.reset(url)
end
end
end
def test_reset_all
assert_nothing_raised do
5.times do
@client.get(serverurl + 'servlet')
@client.reset_all
end
end
end
def test_cookies
cookiefile = File.join(File.dirname(File.expand_path(__FILE__)), 'test_cookies_file')
File.open(cookiefile, "wb") do |f|
f << "http://rubyforge.org/account/login.php\tsession_ser\tLjEwMy45Ni40Ni0q%2A-fa0537de8cc31\t2000000000\t.rubyforge.org\t/\t13\n"
end
@client.set_cookie_store(cookiefile)
cookie = @client.cookie_manager.cookies.first
url = cookie.url
assert(cookie.domain_match(url.host, cookie.domain))
#
@client.reset_all
@client.test_loopback_http_response << "HTTP/1.0 200 OK\nSet-Cookie: foo=bar; expires=#{Time.at(1924873200).gmtime.httpdate}\n\nOK"
@client.get_content('http://rubyforge.org/account/login.php')
@client.save_cookie_store
str = File.read(cookiefile)
assert_match(%r(http://rubyforge.org/account/login.php\tfoo\tbar\t1924873200\trubyforge.org\t/account\t1), str)
File.unlink(cookiefile)
end
def test_eof_error_length
io = StringIO.new('')
def io.gets(*arg)
@buf ||= ["HTTP/1.0 200 OK\n", "content-length: 123\n", "\n"]
@buf.shift
end
def io.readpartial(size, buf)
@second ||= false
if !@second
@second = '1st'
buf << "abc"
buf
elsif @second == '1st'
@second = '2nd'
raise EOFError.new
else
raise Exception.new
end
end
def io.eof?
true
end
@client.test_loopback_http_response << io
assert_nothing_raised do
@client.get('http://foo/bar')
end
end
def test_eof_error_rest
io = StringIO.new('')
def io.gets(*arg)
@buf ||= ["HTTP/1.0 200 OK\n", "\n"]
@buf.shift
end
def io.readpartial(size, buf)
@second ||= false
if !@second
@second = '1st'
buf << "abc"
buf
elsif @second == '1st'
@second = '2nd'
raise EOFError.new
else
raise Exception.new
end
end
def io.eof?
true
end
@client.test_loopback_http_response << io
assert_nothing_raised do
@client.get('http://foo/bar')
end
end
def test_urify
extend HTTPClient::Util
assert_nil(urify(nil))
uri = 'http://foo'
assert_equal(urify(uri), urify(uri))
assert_equal(urify(uri), urify(urify(uri)))
end
def test_connection
c = HTTPClient::Connection.new
assert(c.finished?)
assert_nil(c.join)
end
def test_site
site = HTTPClient::Site.new
assert_equal('tcp', site.scheme)
assert_equal('0.0.0.0', site.host)
assert_equal(0, site.port)
assert_equal('tcp://0.0.0.0:0', site.addr)
assert_equal('tcp://0.0.0.0:0', site.to_s)
assert_nothing_raised do
site.inspect
end
#
site = HTTPClient::Site.new(urify('http://localhost:12345/foo'))
assert_equal('http', site.scheme)
assert_equal('localhost', site.host)
assert_equal(12345, site.port)
assert_equal('http://localhost:12345', site.addr)
assert_equal('http://localhost:12345', site.to_s)
assert_nothing_raised do
site.inspect
end
#
site1 = HTTPClient::Site.new(urify('http://localhost:12341/'))
site2 = HTTPClient::Site.new(urify('http://localhost:12342/'))
site3 = HTTPClient::Site.new(urify('http://localhost:12342/'))
assert(!(site1 == site2))
h = { site1 => 'site1', site2 => 'site2' }
h[site3] = 'site3'
assert_equal('site1', h[site1])
assert_equal('site3', h[site2])
end
def test_http_header
res = @client.get(serverurl + 'hello')
assert_equal('text/html', res.contenttype)
assert_equal(5, res.header.get(nil).size)
#
res.header.delete('connection')
assert_equal(4, res.header.get(nil).size)
#
res.header['foo'] = 'bar'
assert_equal(['bar'], res.header['foo'])
#
assert_equal([['foo', 'bar']], res.header.get('foo'))
res.header['foo'] = ['bar', 'bar2']
assert_equal([['foo', 'bar'], ['foo', 'bar2']], res.header.get('foo'))
end
def test_mime_type
assert_equal('text/plain', HTTP::Message.mime_type('foo.txt'))
assert_equal('text/html', HTTP::Message.mime_type('foo.html'))
assert_equal('text/html', HTTP::Message.mime_type('foo.htm'))
assert_equal('application/msword', HTTP::Message.mime_type('foo.doc'))
assert_equal('image/png', HTTP::Message.mime_type('foo.png'))
assert_equal('image/gif', HTTP::Message.mime_type('foo.gif'))
assert_equal('image/jpeg', HTTP::Message.mime_type('foo.jpg'))
assert_equal('image/jpeg', HTTP::Message.mime_type('foo.jpeg'))
assert_equal('application/octet-stream', HTTP::Message.mime_type('foo.unknown'))
#
handler = lambda { |path| 'hello/world' }
assert_nil(HTTP::Message.mime_type_handler)
assert_nil(HTTP::Message.get_mime_type_func)
HTTP::Message.mime_type_handler = handler
assert_not_nil(HTTP::Message.mime_type_handler)
assert_not_nil(HTTP::Message.get_mime_type_func)
assert_equal('hello/world', HTTP::Message.mime_type('foo.txt'))
HTTP::Message.mime_type_handler = nil
assert_equal('text/plain', HTTP::Message.mime_type('foo.txt'))
HTTP::Message.set_mime_type_func(nil)
assert_equal('text/plain', HTTP::Message.mime_type('foo.txt'))
#
handler = lambda { |path| nil }
HTTP::Message.mime_type_handler = handler
assert_equal('application/octet-stream', HTTP::Message.mime_type('foo.txt'))
end
def test_connect_request
req = HTTP::Message.new_connect_request(urify('https://foo/bar'))
assert_equal("CONNECT foo:443 HTTP/1.0\r\n\r\n", req.dump)
req = HTTP::Message.new_connect_request(urify('https://example.com/'))
assert_equal("CONNECT example.com:443 HTTP/1.0\r\n\r\n", req.dump)
end
def test_response
res = HTTP::Message.new_response('response')
res.contenttype = 'text/plain'
res.header.body_date = Time.at(946652400)
assert_equal(
[
"",
"Content-Length: 8",
"Content-Type: text/plain",
"Last-Modified: Fri, 31 Dec 1999 15:00:00 GMT",
"Status: 200 OK",
"response"
],
res.dump.split(/\r\n/).sort
)
assert_equal(['8'], res.header['Content-Length'])
assert_equal('8', res.headers['Content-Length'])
res.header.set('foo', 'bar')
assert_equal(
[
"",
"Content-Length: 8",
"Content-Type: text/plain",
"Last-Modified: Fri, 31 Dec 1999 15:00:00 GMT",
"Status: 200 OK",
"foo: bar",
"response"
],
res.dump.split(/\r\n/).sort
)
# nil body
res = HTTP::Message.new_response(nil)
assert_equal(
[
"Content-Length: 0",
"Content-Type: text/html; charset=us-ascii",
"Status: 200 OK"
],
res.dump.split(/\r\n/).sort
)
# for mod_ruby env
Object.const_set('Apache', nil)
begin
res = HTTP::Message.new_response('response')
assert(res.dump.split(/\r\n/).any? { |line| /^Date/ =~ line })
#
res = HTTP::Message.new_response('response')
res.contenttype = 'text/plain'
res.header.body_date = Time.at(946652400)
res.header['Date'] = Time.at(946652400).httpdate
assert_equal(
[
"",
"Content-Length: 8",
"Content-Type: text/plain",
"Date: Fri, 31 Dec 1999 15:00:00 GMT",
"HTTP/1.1 200 OK",
"Last-Modified: Fri, 31 Dec 1999 15:00:00 GMT",
"response"
],
res.dump.split(/\r\n/).sort
)
ensure
Object.instance_eval { remove_const('Apache') }
end
end
def test_response_cookies
res = HTTP::Message.new_response('response')
res.contenttype = 'text/plain'
res.header.body_date = Time.at(946652400)
assert_nil(res.cookies)
#
res.header['Set-Cookie'] = [
'CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT',
'PART_NUMBER=ROCKET_LAUNCHER_0001; path=/'
]
assert_equal(
[
"",
"Content-Length: 8",
"Content-Type: text/plain",
"Last-Modified: Fri, 31 Dec 1999 15:00:00 GMT",
"Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT",
"Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/",
"Status: 200 OK",
"response"
],
res.dump.split(/\r\n/).sort
)
assert_equal(2, res.cookies.size)
assert_equal('CUSTOMER', res.cookies[0].name)
assert_equal('PART_NUMBER', res.cookies[1].name)
end
def test_ok_response_success
res = HTTP::Message.new_response('response')
assert_equal(true, res.ok?)
res.status = 404
assert_equal(false, res.ok?)
res.status = 500
assert_equal(false, res.ok?)
res.status = 302
assert_equal(false, res.ok?)
end
if !defined?(JRUBY_VERSION) and RUBY_VERSION < '1.9'
def test_timeout_scheduler
assert_equal('hello', @client.get_content(serverurl + 'hello'))
status = HTTPClient.timeout_scheduler.instance_eval { @thread.kill; @thread.join; @thread.status }
assert(!status) # dead
assert_equal('hello', @client.get_content(serverurl + 'hello'))
end
end
def test_session_manager
mgr = HTTPClient::SessionManager.new(@client)
assert_nil(mgr.instance_eval { @proxy })
assert_nil(mgr.debug_dev)
@client.debug_dev = Object.new
@client.proxy = 'http://myproxy:12345'
mgr = HTTPClient::SessionManager.new(@client)
assert_equal('http://myproxy:12345', mgr.instance_eval { @proxy }.to_s)
assert_equal(@client.debug_dev, mgr.debug_dev)
end
def create_keepalive_disconnected_thread(idx, sock)
Thread.new {
# return "12345" for the first connection
sock.gets
sock.gets
sock.write("HTTP/1.1 200 OK\r\n")
sock.write("Content-Length: 5\r\n")
sock.write("\r\n")
sock.write("12345")
# for the next connection, close while reading the request for emulating
# KeepAliveDisconnected
sock.gets
sock.close
}
end
def test_keepalive_disconnected
client = HTTPClient.new
server = TCPServer.open('127.0.0.1', 0)
server.listen(30) # set enough backlogs
endpoint = "http://127.0.0.1:#{server.addr[1]}/"
Thread.new {
Thread.abort_on_exception = true
# emulate 10 keep-alive connections
10.times do |idx|
sock = server.accept
create_keepalive_disconnected_thread(idx, sock)
end
# return "23456" for the request which gets KeepAliveDisconnected
5.times do
sock = server.accept
sock.gets
sock.gets
sock.write("HTTP/1.1 200 OK\r\n")
sock.write("\r\n")
sock.write("23456")
sock.close
end
# return "34567" for the rest requests
while true
sock = server.accept
sock.gets
sock.gets
sock.write("HTTP/1.1 200 OK\r\n")
sock.write("Connection: close\r\n")
sock.write("Content-Length: 5\r\n")
sock.write("\r\n")
sock.write("34567")
sock.close
end
}
# allocate 10 keep-alive connections
(0...10).to_a.map {
Thread.new {
assert_equal("12345", client.get(endpoint).content)
}
}.each { |th| th.join }
# send 5 requests, which should get KeepAliveDesconnected.
# doing these requests, rest keep-alive connections are invalidated.
(0...5).to_a.map {
Thread.new {
assert_equal("23456", client.get(endpoint).content)
}
}.each { |th| th.join }
# rest requests won't get KeepAliveDisconnected; how can I check this?
(0...10).to_a.map {
Thread.new {
assert_equal("34567", client.get(endpoint).content)
}
}.each { |th| th.join }
end
def create_keepalive_thread(count, sock)
Thread.new {
Thread.abort_on_exception = true
count.times do
req = sock.gets
while line = sock.gets
break if line.chomp.empty?
end
case req
when /chunked/
sock.write("HTTP/1.1 200 OK\r\n")
sock.write("Transfer-Encoding: chunked\r\n")
sock.write("\r\n")
sock.write("1a\r\n")
sock.write("abcdefghijklmnopqrstuvwxyz\r\n")
sock.write("10\r\n")
sock.write("1234567890abcdef\r\n")
sock.write("0\r\n")
sock.write("\r\n")
else
sock.write("HTTP/1.1 200 OK\r\n")
sock.write("Content-Length: 5\r\n")
sock.write("\r\n")
sock.write("12345")
end
end
sock.close
}
end
def test_keepalive
server = TCPServer.open('localhost', 0)
server_thread = Thread.new {
Thread.abort_on_exception = true
sock = server.accept
create_keepalive_thread(10, sock)
}
url = "http://localhost:#{server.addr[1]}/"
begin
# content-length
5.times do
assert_equal('12345', @client.get(url).body)
end
# chunked
5.times do
assert_equal('abcdefghijklmnopqrstuvwxyz1234567890abcdef', @client.get(url + 'chunked').body)
end
ensure
server.close
server_thread.join
end
end
def test_socket_local
@client.socket_local.host = '127.0.0.1'
assert_equal('hello', @client.get_content(serverurl + 'hello'))
@client.reset_all
@client.socket_local.port = serverport
begin
@client.get_content(serverurl + 'hello')
rescue Errno::EADDRINUSE, SocketError
assert(true)
end
end
def test_body_param_order
ary = ('b'..'d').map { |k| ['key2', k] } << ['key1', 'a'] << ['key3', 'z']
assert_equal("key2=b&key2=c&key2=d&key1=a&key3=z", HTTP::Message.escape_query(ary))
end
if RUBY_VERSION > "1.9"
def test_charset
body = @client.get(serverurl + 'charset').body
assert_equal(Encoding::EUC_JP, body.encoding)
assert_equal('あいうえお'.encode(Encoding::EUC_JP), body)
end
end
if RUBY_VERSION >= "1.9.3"
def test_continue
@client.debug_dev = str = ''
res = @client.get(serverurl + 'continue', :header => {:Expect => '100-continue'})
assert_equal(200, res.status)
assert_equal('done!', res.body)
assert_match(/Expect: 100-continue/, str)
end
end
def test_ipv6literaladdress_in_uri
server = TCPServer.open('::1', 0) rescue return # Skip if IPv6 is unavailable.
server_thread = Thread.new {
Thread.abort_on_exception = true
sock = server.accept
while line = sock.gets
break if line.chomp.empty?
end
sock.write("HTTP/1.1 200 OK\r\n")
sock.write("Content-Length: 5\r\n")
sock.write("\r\n")
sock.write("12345")
sock.close
}
uri = "http://[::1]:#{server.addr[1]}/"
begin
assert_equal('12345', @client.get(uri).body)
ensure
server.close
server_thread.kill
server_thread.join
end
end
private
def check_query_get(query)
WEBrick::HTTPUtils.parse_query(
@client.get(serverurl + 'servlet', query).header["x-query"][0]
)
end
def check_query_post(query)
WEBrick::HTTPUtils.parse_query(
@client.post(serverurl + 'servlet', query).header["x-query"][0]
)
end
def setup_server
@server = WEBrick::HTTPServer.new(
:BindAddress => "localhost",
:Logger => @logger,
:Port => 0,
:AccessLog => [],
:DocumentRoot => File.dirname(File.expand_path(__FILE__))
)
@serverport = @server.config[:Port]
[
:hello, :sleep, :servlet_redirect, :redirect1, :redirect2, :redirect3,
:redirect_self, :relative_redirect, :redirect_see_other, :chunked,
:largebody, :status, :compressed, :charset, :continue
].each do |sym|
@server.mount(
"/#{sym}",
WEBrick::HTTPServlet::ProcHandler.new(method("do_#{sym}").to_proc)
)
end
@server.mount('/servlet', TestServlet.new(@server))
@server_thread = start_server_thread(@server)
end
def escape_noproxy
backup = HTTPClient::NO_PROXY_HOSTS.dup
HTTPClient::NO_PROXY_HOSTS.clear
yield
ensure
HTTPClient::NO_PROXY_HOSTS.replace(backup)
end
def do_hello(req, res)
res['content-type'] = 'text/html'
res.body = "hello"
end
def do_sleep(req, res)
sec = req.query['sec'].to_i
sleep sec
res['content-type'] = 'text/html'
res.body = "hello"
end
def do_servlet_redirect(req, res)
res.set_redirect(WEBrick::HTTPStatus::Found, serverurl + "servlet")
end
def do_redirect1(req, res)
res.set_redirect(WEBrick::HTTPStatus::MovedPermanently, serverurl + "hello")
end
def do_redirect2(req, res)
res.set_redirect(WEBrick::HTTPStatus::TemporaryRedirect, serverurl + "redirect3")
end
def do_redirect3(req, res)
res.set_redirect(WEBrick::HTTPStatus::Found, serverurl + "hello")
end
def do_redirect_self(req, res)
res.set_redirect(WEBrick::HTTPStatus::Found, serverurl + "redirect_self")
end
def do_relative_redirect(req, res)
res.set_redirect(WEBrick::HTTPStatus::Found, "hello")
end
def do_redirect_see_other(req, res)
if req.request_method == 'POST'
res.set_redirect(WEBrick::HTTPStatus::SeeOther, serverurl + "redirect_see_other") # self
else
res.body = 'hello'
end
end
def do_chunked(req, res)
res.chunked = true
piper, pipew = IO.pipe
res.body = piper
pipew << req.query['msg']
pipew.close
end
def do_largebody(req, res)
res['content-type'] = 'text/html'
res.body = "a" * 1000 * 1000
end
def do_compressed(req, res)
res['content-type'] = 'application/octet-stream'
if req.query['enc'] == 'gzip'
res['content-encoding'] = 'gzip'
res.body = GZIP_CONTENT
elsif req.query['enc'] == 'deflate'
res['content-encoding'] = 'deflate'
res.body = DEFLATE_CONTENT
end
end
def do_charset(req, res)
if RUBY_VERSION > "1.9"
res.body = 'あいうえお'.encode("euc-jp")
res['Content-Type'] = 'text/plain; charset=euc-jp'
else
res.body = 'this endpoint is for 1.9 or later'
end
end
def do_status(req, res)
res.status = req.query['status'].to_i
end
def do_continue(req, res)
req.continue
res.body = 'done!'
end
class TestServlet < WEBrick::HTTPServlet::AbstractServlet
def get_instance(*arg)
self
end
def do_HEAD(req, res)
res["x-head"] = 'head' # use this for test purpose only.
res["x-query"] = query_response(req)
end
def do_GET(req, res)
res.body = 'get'
res["x-query"] = query_response(req)
end
def do_POST(req, res)
res["content-type"] = "text/plain" # iso-8859-1, not US-ASCII
res.body = 'post,' + req.body.to_s
res["x-query"] = body_response(req)
end
def do_PUT(req, res)
res["x-query"] = body_response(req)
param = WEBrick::HTTPUtils.parse_query(req.body) || {}
res["x-size"] = (param['txt'] || '').size
res.body = param['txt'] || 'put'
end
def do_DELETE(req, res)
res.body = 'delete'
end
def do_OPTIONS(req, res)
# check RFC for legal response.
res.body = 'options'
end
def do_PROPFIND(req, res)
res.body = 'propfind'
end
def do_PROPPATCH(req, res)
res.body = 'proppatch'
res["x-query"] = body_response(req)
end
def do_TRACE(req, res)
# client SHOULD reflect the message received back to the client as the
# entity-body of a 200 (OK) response. [RFC2616]
res.body = 'trace'
res["x-query"] = query_response(req)
end
private
def query_response(req)
query_escape(WEBrick::HTTPUtils.parse_query(req.query_string))
end
def body_response(req)
query_escape(WEBrick::HTTPUtils.parse_query(req.body))
end
def query_escape(query)
escaped = []
query.sort_by { |k, v| k }.collect do |k, v|
v.to_ary.each do |ve|
escaped << CGI.escape(k) + '=' + CGI.escape(ve)
end
end
escaped.join('&')
end
end
end
httpclient-2.3.3/bin/ 0000755 0000041 0000041 00000000000 12155433207 014477 5 ustar www-data www-data httpclient-2.3.3/bin/httpclient 0000755 0000041 0000041 00000002466 12155433207 016613 0 ustar www-data www-data #!/usr/bin/env ruby
# httpclient shell command.
#
# Usage: 1) % httpclient get https://www.google.co.jp/ q=ruby
# Usage: 2) % httpclient
#
# For 1) it issues a GET request to the given URI and shows the wiredump and
# the parsed result. For 2) it invokes irb shell with the binding that has a
# HTTPClient as 'self'. You can call HTTPClient instance methods like;
# > get "https://www.google.co.jp/", :q => :ruby
require 'httpclient'
METHODS = ['head', 'get', 'post', 'put', 'delete', 'options', 'propfind', 'proppatch', 'trace']
if ARGV.size >= 2 && METHODS.include?(ARGV[0])
client = HTTPClient.new
client.debug_dev = STDERR
$DEBUG = true
require 'pp'
pp client.send(*ARGV)
exit
end
require 'irb'
require 'irb/completion'
class Runner
def initialize
@httpclient = HTTPClient.new
end
def method_missing(msg, *a, &b)
debug, $DEBUG = $DEBUG, true
begin
@httpclient.send(msg, *a, &b)
ensure
$DEBUG = debug
end
end
def run
IRB.setup(nil)
ws = IRB::WorkSpace.new(binding)
irb = IRB::Irb.new(ws)
IRB.conf[:MAIN_CONTEXT] = irb.context
trap("SIGINT") do
irb.signal_handle
end
begin
catch(:IRB_EXIT) do
irb.eval_input
end
ensure
IRB.irb_at_exit
end
end
def to_s
'HTTPClient'
end
end
Runner.new.run
httpclient-2.3.3/metadata.yml 0000644 0000041 0000041 00000004352 12155433207 016236 0 ustar www-data www-data --- !ruby/object:Gem::Specification
name: httpclient
version: !ruby/object:Gem::Version
version: 2.3.3
prerelease:
platform: ruby
authors:
- Hiroshi Nakamura
autorequire:
bindir: bin
cert_chain: []
date: 2013-02-24 00:00:00.000000000 Z
dependencies: []
description:
email: nahi@ruby-lang.org
executables:
- httpclient
extensions: []
extra_rdoc_files: []
files:
- bin/httpclient
- lib/httpclient/auth.rb
- lib/httpclient/ssl_config.rb
- lib/httpclient/http.rb
- lib/httpclient/timeout.rb
- lib/httpclient/version.rb
- lib/httpclient/connection.rb
- lib/httpclient/include_client.rb
- lib/httpclient/session.rb
- lib/httpclient/cacert.p7s
- lib/httpclient/cookie.rb
- lib/httpclient/util.rb
- lib/oauthclient.rb
- lib/httpclient.rb
- lib/http-access2.rb
- lib/http-access2/http.rb
- lib/http-access2/cookie.rb
- lib/hexdump.rb
- sample/auth.rb
- sample/dav.rb
- sample/stream.rb
- sample/async.rb
- sample/wcat.rb
- sample/ssl/1000cert.pem
- sample/ssl/1000key.pem
- sample/ssl/ssl_client.rb
- sample/ssl/0key.pem
- sample/ssl/htdocs/index.html
- sample/ssl/0cert.pem
- sample/ssl/webrick_httpsd.rb
- sample/oauth_salesforce_10.rb
- sample/thread.rb
- sample/oauth_friendfeed.rb
- sample/oauth_twitter.rb
- sample/cookie.rb
- sample/howto.rb
- sample/oauth_buzz.rb
- test/client.cert
- test/test_http-access2.rb
- test/server.cert
- test/htdigest
- test/test_ssl.rb
- test/test_hexdump.rb
- test/test_cookie.rb
- test/runner.rb
- test/htpasswd
- test/test_auth.rb
- test/client.key
- test/test_include_client.rb
- test/helper.rb
- test/ca.cert
- test/sslsvr.rb
- test/subca.cert
- test/ca-chain.cert
- test/server.key
- test/test_httpclient.rb
- README.txt
homepage: http://github.com/nahi/httpclient
licenses: []
post_install_message:
rdoc_options: []
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
required_rubygems_version: !ruby/object:Gem::Requirement
none: false
requirements:
- - ! '>='
- !ruby/object:Gem::Version
version: '0'
requirements: []
rubyforge_project:
rubygems_version: 1.8.23
signing_key:
specification_version: 3
summary: gives something like the functionality of libwww-perl (LWP) in Ruby
test_files: []
has_rdoc:
httpclient-2.3.3/README.txt 0000644 0000041 0000041 00000074212 12155433207 015433 0 ustar www-data www-data httpclient - HTTP accessing library.
Copyright (C) 2000-2012 NAKAMURA, Hiroshi .
'httpclient' gives something like the functionality of libwww-perl (LWP) in
Ruby. 'httpclient' formerly known as 'http-access2'.
See HTTPClient for documentation.
== Features
* methods like GET/HEAD/POST/* via HTTP/1.1.
* HTTPS(SSL), Cookies, proxy, authentication(Digest, NTLM, Basic), etc.
* asynchronous HTTP request, streaming HTTP request.
* debug mode CLI.
* by contrast with net/http in standard distribution;
* Cookies support
* MT-safe
* streaming POST (POST with File/IO)
* Digest auth
* Negotiate/NTLM auth for WWW-Authenticate (requires net/ntlm module; rubyntlm gem)
* NTLM auth for Proxy-Authenticate (requires 'win32/sspi' module; rubysspi gem)
* extensible with filter interface
* you don't have to care HTTP/1.1 persistent connection
(httpclient cares instead of you)
* Not supported now
* Cache
* Rather advanced HTTP/1.1 usage such as Range, deflate, etc.
(of course you can set it in header by yourself)
== httpclient command
Usage: 1) % httpclient get https://www.google.co.jp/ q=ruby
Usage: 2) % httpclient
For 1) it issues a GET request to the given URI and shows the wiredump and
the parsed result. For 2) it invokes irb shell with the binding that has a
HTTPClient as 'self'. You can call HTTPClient instance methods like;
> get "https://www.google.co.jp/", :q => :ruby
== Author
Name:: Hiroshi Nakamura
E-mail:: nahi@ruby-lang.org
Project web site:: http://github.com/nahi/httpclient
== License
This program is copyrighted free software by NAKAMURA, Hiroshi. You can
redistribute it and/or modify it under the same terms of Ruby's license;
either the dual license version in 2003, or any later version.
httpclient/session.rb is based on http-access.rb in http-access/0.0.4. Some
part of it is copyrighted by Maebashi-san who made and published
http-access/0.0.4. http-access/0.0.4 did not include license notice but when
I asked Maebashi-san he agreed that I can redistribute it under the same terms
of Ruby. Many thanks to Maebashi-san.
== Install
=== Gem
You can install httpclient with rubygems.
% gem install httpclient
=== Package
You can install httpclient with the bundled installer script.
$ ruby install.rb
It will install lib/* to your site_ruby directory such as
/usr/local/lib/ruby/site_ruby/1.8/.
For uninstall, delete installed files from your site_ruby directory.
== Usage
See HTTPClient for documentation.
You can also check sample/howto.rb how to use APIs.
== Download
* Gem repository
* https://rubygems.org/gems/httpclient
* git: git://github.com/nahi/httpclient.git
== Bug report or Feature request
Please file a ticket at the project web site.
1. find a similar ticket from https://github.com/nahi/httpclient/issues
2. create a new ticket by clicking 'Create Issue' button.
3. you can use github features such as pull-request if you like.
Thanks in advance.
== Changes
= Changes in 2.3.3 =
February 24, 2013 - version 2.3.3
* Changes
* #144 Add User-Agent field by default. You can remove the header by
setting nil to HTTPClient#agent_name.
= Changes in 2.3.2 =
January 5, 2013 - version 2.3.2
* Changes
* #138 Revert Timeout change unintentionally included in v2.3.1. It's
reported that the change causes background processes not terminated
properly.
= Changes in 2.3.1 =
January 1, 2013 - version 2.3.1
* Changes
* #137 Signing key is expiring for cacert_sha1.p7s.
Deleted p7s signature check for default cacerts. Sorry for many troubles
in the past. This feature is not useful without having online/real-time
CA certs update but I don't think I can implement it in near future.
Users depend on this signature check (who puts cacert.p7s in R/W
filesystem and ssl_config.rb in R/O filesystem) should take care the
tampering by themself.
* Bug fixes
* #122 Support IPv6 address in URI
= Changes in 2.3.0 =
October 10, 2012 - version 2.3.0
* Features
* Added debug mode CLI. bin/httpclient is installed as CLI.
Usage: 1) % httpclient get https://www.google.co.jp/ q=ruby
Usage: 2) %httpclient
For 1) it issues a GET request to the given URI and shows the wiredump
and the parsed result. For 2) it invokes irb shell with the binding
that has a HTTPClient as 'self'. You can call HTTPClient instance
methods like;
> get "https://www.google.co.jp/", :q => :ruby
* #119 Addressable gem support (only if it exists); should handle IRI
properly.
* Bug fixes
* #115 Cookies couldn't work properly if the path in an URI is ommited.
* #112, #117 Proper handling of sized IO (the IO object that responds to
:size) for chunked POST. HTTPClient did read till EOF even if the
given IO has :size method.
* Handle '303 See Other' properly. RFC2616 says it should be redirected
with GET.
* #116 Fix "100-continue" support. It was just ignored.
* #118 Support for boolean values when making POST/PUT requests with
multiipart/form Content-Type.
* #110 Allows leading dots in no_proxy hostname suffixes.
= Changes in 2.2.7 =
August 14, 2012 - version 2.2.7
* Bug fixes
* Fix arity incompatibility introduced in 2.2.6. It broke Webmock.
Thanks Andrew France for the report!
= Changes in 2.2.6 =
August 14, 2012 - version 2.2.6
* Bug fixes
* Make get_content doesn't raise a BadResponseError for perfectly good
responses like 304 Not Modified. Thanks to Florian Hars.
* Add 'Content-Type: application/x-www-form-urlencoded' for the PUT
request that has urlencoded entity-body.
* Features
* Add HTTPClient::IncludeClient by Jonathan Rochkind, a mix-in for easily
adding a thread-safe lazily initialized class-level HTTPClient object
to your class.
* Proxy DigestAuth support. Thanks to Alexander Kotov and Florian Hars.
* Accept an array of strings (and IO-likes) as a query value
e.g. `{ x: 'a', y: [1,2,3] }` is encoded into `"x=a&y=1&y=2&y=3"`.
Thanks to Akinori MUSHA.
* Allow body for DELETE method.
* Allow :follow_redirect => true for HEAD request.
* Fill request parameters request_method, request_uri and request_query
as part of response Message::Header.
= Changes in 2.2.5 =
May 06, 2012 - version 2.2.5
* Bug fixes
* Added Magic encoding comment to hexdump.rb to avoid encoding error.
* Add workaround for JRuby issue on Windows (JRUBY-6136)
On Windows, calling File#size fails with an Unknown error (20047).
This workaround uses File#lstat instead.
* Require open-uri only on ruby 1.9, since it is not needed on 1.8.
* Features
* Allow symbol Header name for HTTP request.
* Dump more SSL certificate information under $DEBUG.
* Add HTTPClient::SSLConfig#ssl_version property.
* Add 'Accept: */*' header to request by default. Rails requies it.
It doesn't override given Accept header from API.
* Add HTTPClient::SSLConfig#set_default_paths. This method makes
HTTPClient instance to use OpenSSL's default trusted CA certificates.
* Allow to set Date header manually.
ex. clent.get(uri, :header => {'Date' => Time.now.httpdate})
= Changes in 2.2.4 =
Dec 08, 2011 - version 2.2.4
* Bug fixes
* Do not recycle buffer String object for yielding. When the response is
not chunked and the size of the response > 16KB, API with block style
yields recycled String object for each yields.
* Set VERSION string in User-Agent header. $Id$ didn't work long time...
Bugs are reported by Seamus Abshere. Thanks!
= Changes in 2.2.3 =
Oct 28, 2011 - version 2.2.3
* Bug fixes
* Ruby 1.8.6 support. It's broken from 2.2.0.
= Changes in 2.2.2 =
Oct 17, 2011 - version 2.2.2
* Bug fixes
* Do not sort query params on request: Wrongly sorted query params for
easier debugging but the order of request parameter should be
preserved. #65
* Changes
* Set responce String encoding if possible. Parse content-type response
header with some helps from OpenURI::Meta and set response String
encoding. #26
* Improve connection cache strategy. Reuse cached session in MRU order,
not in LRU. MRU is more server friendly than LRU because it reduces
number of cached sessions when a number of requests drops after an
usaage spike.
With reusing sessions in LRU order, all sessions are equally checked if
it's closed or not, as far as there's a request to the same site. With
reusing sessions in MRU order, old cold sessions are kept in cache long
time even if there's a request to the same site. To avoid this leakage,
this version adds keep_alive_timeout property and let SessionManager
scrub all sessions with checking the timeout for each session. When the
session expires against the last used time, it's closed and collected.
keep_alive_timeout is 15[sec] by default. The value is from the default
value for KeepAliveTimeout of Apache httpd 2. #68 #69
= Changes in 2.2.1 =
Jun 2, 2011 - version 2.2.1
* Bug fixes
* For Lighttpd + PUT/POST support, do not send a request using chunked
encoding when IO respond to :size, File for example.
- There is no need to send query with Transfer-Encoding: chuncked when
IO respond to :size.
- Lighttpd does not support PUT, POST with Transfer-Encoding: chuncked.
You will see that the lighty respond with 200 OK, but there is a file
whose size is zero.
LIMITATION:
timeout occurs certainly when you send very large file and
@send_timeout is default since HTTPClient::Session#query() assumes
that *all* write are finished in @send_timeout sec not each write.
WORKAROUND:
increment @send_timeout and @receive_timeout or set @send_timeout and
@receive_timeout to 0 not to be timeout.
This fix is by TANABE Ken-ichi . Thanks!
* Allow empty http_proxy ENV variable. Just treat it the same as if it's
nil/unset. This fix is by Ash Berlin .
Thanks!
* Check EOF while reading chunked response and close the session. It
raised NoMethodError.
* Changes
* Updated trusted CA certificates file (cacert.p7s and cacert_sha1.p7s).
CA certs are imported from
'Java(TM) SE Runtime Environment (build 1.6.0_25-b06)'.
* Changed default chunk size from 4K to 16K. It's used for reading size
at a time.
= Changes in 2.2.0 =
Apr 8, 2011 - version 2.2.0
* Features
* Add HTTPClient#cookies as an alias of #cookie_manager.cookies.
* Add res.cookies method. It returns parsed cookie in response header.
It's different from client.cookie_manager.cookies. Manager keeps
persistent cookies in it.
* Add res.headers method which returns a Hash of headers.
Hash key and value are both String. Each key has a single value so you
can't extract exact value when a message has multiple headers like
'Set-Cookie'. Use header['Set-Cookie'] for that purpose.
(It returns an Array always)
* Allow keyword style argument for HTTPClient#get, post, etc.
Introduced keywords are: :body, :query, and :header.
You can write
HTTPClient.get(uri, :header => {'X-custom' => '1'})
instead of;
HTTPClient.get(uri, nil, {'X-custom' => '1'})
* Add new keyword argument :follow_redirect to get/post. Now you can
follow redirection response with passing :follow_redirect => true.
* [INCOMPAT] Rename HTTPClient::HTTP::Message#body to #http_body, then
add #body as an alias of #content. It's incompatible change though
users rarely depends on this method. (I've never seen such a case)
Users who are using req.body and/or res.body should follow this
change. (req.http_body and res.http_body)
* Bug fixes
* Reenable keep-alive for chunked response.
This feature was disabled by c206b687952e1ad3e20c20e69bdbd1a9cb38609e at
2008-12-09. I should have written a test for keep-alive. Now I added it.
Thanks Takahiro Nishimura(@dr_taka_n) for finding this bug.
= Changes in 2.1.7 =
Mar 22, 2011 - version 2.1.7
* Features
* Add MD5-sess auth support. Thanks to wimm-dking. (#47)
* Add SNI support. (Server Name Indication of HTTPS connection) (#49)
* Add GSSAPI auth support using gssapi gem. Thanks to zenchild. (#50)
* NTLM logon to exchange Web Services. [experimental] Thanks to curzonj and mccraigmccraig (#52)
* Add HTTPOnly cookie support. Thanks to nbrosnahan. (#55)
* Add HTTPClient#socket_local for specifying local binding hostname and port of TCP socket. Thanks to icblenke.
= Changes in 2.1.6 =
Dec 20, 2010 - version 2.1.6
* IMPORTANT update for HTTPS(SSL) connection
* Trusted CA bundle file cacert_sha1.p7s for older environment (where
you cannot use SHA512 algorithm such as an old Mac OS X) included in
httpclient 2.1.5 expires in Dec 31, 2010. Please update to 2.1.6 if
you're on such an environment.
* Updated trusted CA certificates file (cacert.p7s and cacert_sha1.p7s).
CA certs are imported from
'Java(TM) SE Runtime Environment (build 1.6.0_22-b04)'.
* IMPORTANT bug fix for persistent connection
* #29 Resource Leak: If httpclient establishes two connections to the
same server in parallel, one of these connections will be leaked, patch
by xb.
* #30 When retrying a failed persistent connection, httpclient should use
a fresh connection, reported by xb.
These 2 fixes should fix 'Too many open files' error as well if you're
getting this. Please check 2.1.6 and let me know how it goes!
* Features
* #4 Added OAuthClient. See sample clients in sample/ dir.
* #42 Added transparent_gzip_decompression property, patch by Teshootub7.
All you need to use it is done by;
client.transparent_gzip_decompression = true
Then you can retrieve a document as usural in decompressed format.
* #38 Debug dump binary data (checking it includes \0 or not) in hex
encoded format, patch by chetan.
* Bug fixes
* #8 Opened certificate and key files for SSL not closed properly.
* #10 "get" method gets blocked in "readpartial" when receiving a 304
with no Content-Length.
* #11 Possible data corruption problem in asynchronous methods, patch by
a user. (http://dev.ctor.org/http-access2/ticket/228)
* #13 illegal Cookie PATH handling. When no PATH part given in Set-Cookie
header, URL's path part should be used for path variable.
* #16 httpclient doesn't support multiline server headers.
* #19 set_request_header clobbers 'Host' header setting if given, patch
by meuserj.
* #20 Relative Location on https redirect fails, patch by zenchild.
* #22 IIS/6 + MicrosoftSharePointTeamServices uses "NTLM" instead of
"Negotiate".
* #27 DigestAuth header: 'qop' parameter must not be enclosed between
double quotation, patch by ibc.
* #36 Wrong HTTP version in headers with Qt4 applications, reported by
gauleng.
* #38 DigestAuth + posting IO fails, patch by chetan.
* #41 https-over-proxy fails with IIS, patch by tai.
= Changes in 2.1.5 =
Jun 25, 2009 - version 2.1.5.2
* Added another cacert distribution certificate which uses
sha1WithRSAEncryption. OpenSSL/0.9.7 cannot handle non-SHA1 digest
algorithm for certificate. The new certificate is
RSA 2048 bit + SHA1 + notAfter:2010/12/31. Corresponding CA bundle file
is cacert_sha1.p7s. It is loaded only when cacert.p7s cannot be loaded
with the original distribution certificate.
Jun 11, 2009 - version 2.1.5.1
* README update.
Jun 8, 2009 - version 2.1.5
* IMPORTANT update for HTTPS(SSL) connection
* Trusted CA bundle file included in httpclient <= 2.1.4 expires in
Nov 2009. Please update to 2.1.5 by Oct 2009 if your application
depends on trusted CA bundle file.
* Updated trusted CA certificates file (cacert.p7s). CA certs are
imported from 'Java(TM) SE Runtime Environment (build 1.6.0_13-b03)'.
* Updated a cacert distribution certificate.
RSA 2048 bit + SHA512 + notAfter:2037/12/31. (#215)
* Feature
* WWW authentication with Negotiate based on win32/sspi as same as Proxy
authentication. Applied a patch from Paul Casto. Thanks! (#212)
* Bug fixes
* Infinite loop caused by EOF error while reading response message body
without Content-Length. IO#readpartial does not clear the second
argument (buffer) when an exception raised. Fixed by a patch from an
user. Thanks! (#216)
* NoMethodError caused by the cookie string that includes a double
semicolons ";;". Fixed by a patch from an user. Thanks! (#211)
* CNONCE attribute in Digest Authentication was not properly generated by
itself (used same nonce sent from the connecting server). Fixed by a
patch from bterlson
[http://github.com/bterlson/httpclient/commit/6d0df734840985a7be88a2d54443bbf892d50b9a]
Thanks! (#209)
* Cookie header was not set in authentication negotiation. Fixed. This
bug was found and pointed out by bterlson at
[http://github.com/bterlson/httpclient/commits/master]. Thanks! (#210)
* Do not send 'Content-Length: 0' when a request doesn't have message
body. Some server application (!EasySoap++/0.6 for example) corrupts
with the request with Content-Length: 0. This bug was found by clay
[http://daemons.net/~clay/2009/05/03/ruby-why-do-you-torment-me/].
Thanks! (#217)
* Ensure to reset connection after invoking HTTPClient singleton methods
for accessing such as HTTPClient.get_content. Thanks to @xgavin! (#214)
Feb 13, 2009 - version 2.1.4
* Bug fixes
* When we hit some site through http-proxy we get a response without
Content-Length header. httpclient/2.1.3 drops response body for such
case. fixed. (#199)
* Avoid duplicated 'Date' header in request. Fixed. (#194)
* Avoid to add port number to 'Host' header. Some servers like GFE/1.3
dislike it. Thanks to anonymous user for investigating the behavior.
(#195)
* httpclient/2.1.3 does not work when you fork a process after requiring
httpclient module (Passenger). Thanks to Akira Yamada for tracing down
this bug. (#197)
* httpclient/2.1.3 cannot handle Cookie header with 'expires=' and
'expires=""'. Empty String for Time.parse returns Time.now unlike
ParseDate.parsedate. Thanks to Mark for the patch. (#200)
Jan 8, 2009 - version 2.1.3.1
* Security fix introduced at 2.1.3.
* get_content/post_content of httpclient/2.1.3 may send secure cookies
for a https site to non-secure (non-https) site when the https site
redirects the request to a non-https site. httpclient/2.1.3 caches
request object and reuses it for redirection. It should not be cached
and recreated for each time as httpclient <= 2.1.2 and http-access2.
* I realized this bug when I was reading open-uri story on
[ruby-core:21205]. Ruby users should use open-uri rather than using
net/http directly wherever possible.
Dec 29, 2008 - version 2.1.3
* Features
* Proxy Authentication for SSL.
* Performance improvements.
* Full RDoc. Please tell me any English problem. Thanks in advance.
* Do multipart file upload when a given body includes a File. You don't
need to set 'Content-Type' and boundary String any more.
* Added propfind and proppatch methods.
* Changes
* Avoid unnecessary memory consuming for get_content/post_content with
block. get_content returns nil when you call it with a block.
* post_content with IO did not work when redirect/auth cycle is required.
(CAUTION: post_content now correctly follows redirection and posts the
given content)
* Exception handling cleanups.
* Raises HTTPClient::ConfigurationError? for environment problem.
(trying to do SSL without openssl installed for example)
* Raises HTTPClient::BadResponse? for HTTP response problem. You can
get the response HTTPMessage returned via $!.res.
* Raises SocketError? for connection problem (as same as before).
* Bug fixes
* Avoid unnecessary negotiation cycle for Negotiate(NTLM) authentication.
Thanks Rishav for great support for debugging Negotiate authentication.
* get_content/post_content with block yielded unexpected message body
during redirect/auth cycle.
* Relative URI redirection should be allowed from 2.1.2 but it did not
work... fixed.
* Avoid unnecessary timeout waiting when no message body returned such as
'204 No Content' for DAV.
* Avoid blocking on socket closing when the socket is already closed by
foreign host and the client runs under MT-condition.
Sep 22, 2007 - version 2.1.2
* HTTP
* implemented Negotiate authentication with a support from exterior
modules. 'rubyntlm' module is required for Negotiate auth with IIS.
'win32/sspi' module is required for Negotiate auth with ISA.
* a workaround for Ubuntu + SonicWALL timeout problem. try to send HTTP
request in one chunk.
* SSL
* create new self-signing dist-cert which has serial number 0x01 and
embed it in httpclient.rb.
* update cacert.p7s. certificates are imported from cacerts in JRE 6
Update 2. 1 expired CA certificate
'C=US, O=GTE Corporation, CN=GTE CyberTrust Root' is removed.
* Bug fix
* [BUG] SSL + debug_dev didn't work under version 2.1.1.
* [BUG] Reason-Phrase of HTTP response status line can be empty according
* to RFC2616.
Aug 28, 2007 - version 2.1.1
* bug fix
* domain_match should be case insensitive. thanks to Brian for the patch.
* before calling SSLSocket#post_connection_check, check if
RUBY_VERSION > "1.8.4" for CN based wildcard certificate. when
RUBY_VERSION <= "1.8.4", it fallbacks to the post_connection_check
method in HTTPClient so httpclient should run on 1.8.4 fine as before.
* misc
* added HTTPClient#test_loopback_http_response which accepts test
loopback response which contains HTTP header.
Jul 14, 2007 - version 2.1.0
* program/project renamed from 'http-access2' to 'httpclient'.
there's compatibility layer included so existing programs for
http-access2 which uses HTTPAccess2::Client should work with
httpclient/2.1.0 correctly.
* misc
* install.rb did not install cacerts.p7s. Thanks to knu.
* now HTTPClient loads http_proxy/HTTP_PROXY and no_proxy/NO_PROXY
environment variable at initialization time. bear in mind that it
doesn't load http_proxy/HTTP_PROXY when a library is considered to be
running under CGI environment (checked by ENVREQUEST_METHOD existence.
cgi_http_proxy/CGI_HTTP_PROXY is loaded instead.
Jul 4, 2007 - version 2.0.9
* bug fix
* fix the BasicAuth regression problem in 2.0.8. A server may return
"BASIC" as an authenticate scheme label instead of "Basic". It must be
treated as a case-insensitive token according to RFC2617 section 1.2.
Thanks to mwedeme for contributing the patch. (#159)
Jun 30, 2007 - version 2.0.8
* HTTP
* added request/response filter interface and implemented DigestAuth
based on the filter interface. DigestAuth calc engine is based on
http://tools.assembla.com/breakout/wiki/DigestForSoap
Thanks to sromano. (#155)
* re-implemented BasicAuth based on the filter interface. send BasicAuth
header only if it's needed. (#31)
* handle a response which has 2XX status code as a successfull response
while retry check. applied the patch from Micah Wedemeyer.
Thanks! (#158)
* Connection
* show more friendly error message for unconnectable URL. (#156)
* bug fixes
* to avoid MIME format incompatibility, add empty epilogue chunk
explicitly. Thanks to the anonymous user who reported #154 (#154)
* rescue EPIPE for keep-alive reconnecting. Thanks to anonymous user
who posted a patch at #124. (#124)
May 13, 2007 - version 2.0.7
* HTTP
* added proxyauth support. (#6)
* let developer allow to rescue a redirect with relative URI. (#28)
* changed last-chunk condition statement to allow "0000\r\n" marker from
WebLogic Server 7.0 SP5 instead of "0\r\n". (#30)
* fixed multipart form submit. (#29, #116)
* use http_date format as a date in a request header. (#35)
* avoid duplicated Date header when running under mod_ruby. (#127)
* reason phrase in Message#reason contains \r. (#122)
* trim "\n"s in base64 encoded BasicAuth value for interoperability.
(#149)
* let retry_connect return a Message not a content. (#119)
* rescue SocketError and dump a message when a wrong address given. (#152)
* HTTP-Cookies
* changed "domain" parameter matching condition statement to allow
followings; (#24, #32, #118, #147)
* [host, domain] = [rubyforge.com, .rubyforge.com]
* [host, domain] = [reddit.com, reddit.com]
* SSL
* bundles CA certificates as trust anchors.
* allow user to get peer_cert. (#117, #123)
* added wildcard certificate support. (#151)
* SSL + HTTP keep-alive + long wait causes uncaught exception. fixed.
(#120)
* Connection
* fixed a loop condition bug that caused intermittent empty response.
(#150, #26, #125)
September 16, 2005 - version 2.0.6
* HTTP
* allows redirects from a "POST" request. imported a patch from sveit.
Thanks! (#7)
* add 'content-type: application/application/x-www-form-urlencoded' when
a request contains message-body. (#11)
* HTTP/0.9 support. (#15)
* allows submitting multipart forms. imported a patch from sveit.
Thanks! (#7)
* HTTP-Cookies
* avoid NameError when a cookie value is nil. (#10)
* added netscape_rule property to CookieManager (false by default). You
can turn on the domain attribute test of Netscape rule with the
property. cf. http://wp.netscape.com/newsref/std/cookie_spec.html
* added HTTPClient#cookie_manager property for accessing its properties.
(#13)
* added save_all_cookies method to save unused and discarded cookies as
well. The patch is from Christian Lademann. Thanks! (#21)
* allow to set cookie_manager. raise an error when set_cookie_store
called and cookie_store has already been set. (#20)
* SSL
* allows SSL connection debugging when debug_dev != nil. (#14)
* skip post_connection_check when
verify_mode == OpenSSL::SSL::VERIFY_NONE. Thanks to kdraper. (#12)
* post_connection_check: support a certificate with a wildcard in the
hostname. (#18)
* avoid NameError when no peer_cert and VERIFY_FAIL_IF_NO_PEER_CERT
given. Thanks to Christian Lademann.
* Connection
* insert a connecting host and port to an exception message when
connecting failed. (#5)
* added socket_sync property to HTTPClient(HTTPAccess2::Client) that
controls socket's sync property. the default value is true. CAUTION:
if your ruby is older than 2005-09-06 and you want to use SSL
connection, do not set socket_sync = false to avoid a blocking bug of
openssl/buffering.rb.
December 24, 2004 - version 2.0.5
This is a minor bug fix release.
- Connect/Send/Receive timeout cannot be configured. fixed.
- IPSocket#addr caused SocketError? on Mac OS X 10.3.6 + ruby-1.8.1 GA.
fixed.
- There is a server which does not like 'foo.bar.com:80' style Host header.
The server for http://rubyforge.org/export/rss_sfnews.php seems to
dislike HTTP/1.1 Host header "Host: rubyforge.net:80". It returns
HTTP 302: Found and redirects to the page again, causes
HTTPAccess2::Client to raise "retry count exceeded". Keat found that the
server likes "Host: rubyforge.net" (not with port number).
February 11, 2004 - version 2.0.4
- add Client#redirect_uri_callback interface.
- refactorings and bug fixes found during negative test.
- add SSL test.
December 16, 2003 - version 2.0.3
- no_proxy was broken in 2.0.2.
- do not dump 'Host' header under protocol_version == 'HTTP/1.0'
December ?, 2003 - version 2.0.2
- do not trust HTTP_PROXY environment variable. set proxy server manually.
http://ftp.ics.uci.edu/pub/websoft/libwww-perl/archive/2001h1/0072.html
http://ftp.ics.uci.edu/pub/websoft/libwww-perl/archive/2001h1/0241.html
http://curl.haxx.se/mail/archive-2001-12/0034.html
- follow ossl2 change.
October 4, 2003 - version 2.0.1
Query was not escaped when query was given as an Array or a Hash. Fixed.
Do not use http_proxy defined by ENV['http_proxy'] or ENV['HTTP_PROXY'] if
the destination host is 'localhost'.
Hosts which matches ENV['no_proxy'] or ENV['NO_PROXY'] won't be proxyed.
[,:] separated. ("ruby-lang.org:rubyist.net")
No regexp. (give "ruby-lang.org", not "*.ruby-lang.org")
If you want specify hot by IP address, give full address.
("192.168.1.1, 192.168.1.2")
September 10, 2003 - version 2.0
CamelCase to non_camel_case.
SSL support (requires Ruby/OpenSSL).
Cookies support. lib/http-access2/cookie.rb is redistributed file which is
originally included in Webagent by TAKAHASHI `Maki' Masayoshi. You can
download the entire package from http://www.rubycolor.org/arc/.
January 11, 2003 - version J
ruby/1.8 support.
httpclient-2.3.3/lib/ 0000755 0000041 0000041 00000000000 12155433207 014475 5 ustar www-data www-data httpclient-2.3.3/lib/oauthclient.rb 0000644 0000041 0000041 00000006627 12155433207 017354 0 ustar www-data www-data # HTTPClient - HTTP client library.
# Copyright (C) 2000-2009 NAKAMURA, Hiroshi .
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
# either the dual license version in 2003, or any later version.
require 'httpclient'
module HTTP
class Message
attr_accessor :oauth_params
end
end
# OAuthClient provides OAuth related methods in addition to HTTPClient.
#
# See sample/ dir how to use OAuthClient. There are sample clients for Twitter,
# FriendFeed and Google Buzz.
class OAuthClient < HTTPClient
# HTTPClient::OAuth::Config:: OAuth configurator.
attr_accessor :oauth_config
# Creates a OAuthClient instance which provides OAuth related methods in
# addition to HTTPClient.
#
# Method signature is as same as HTTPClient. See HTTPClient.new
def initialize(*arg)
super
@oauth_config = HTTPClient::OAuth::Config.new
self.www_auth.oauth.set_config(nil, @oauth_config)
self.www_auth.oauth.challenge(nil)
end
# Get request token.
# uri:: URI for request token.
# callback:: callback String. This can be nil for OAuth 1.0a
# param:: Additional query parameter Hash.
#
# It returns a HTTP::Message instance as a response. When the request is made
# successfully, you can retrieve a pair of request token and secret like
# following;
# res = client.get_request_token(...)
# token = res.oauth_params['oauth_token']
# secret = res.oauth_params['oauth_token_secret']
def get_request_token(uri, callback = nil, param = nil)
oauth_config.token = nil
oauth_config.secret = nil
oauth_config.callback = callback
oauth_config.verifier = nil
res = request(oauth_config.http_method, uri, param)
filter_response(res)
res
end
# Get access token.
# uri:: URI for request token.
# request_token:: a request token String. See get_access_token.
# request_token_secret:: a request secret String. See get_access_token.
# verifier:: a verifier tag String.
#
# When the request succeeds and the server returns a pair of access token
# and secret, oauth_config.token and oauth_token.secret are updated with
# the access token. Then you can call OAuthClient#get, #post, #delete, etc.
# All requests are signed.
def get_access_token(uri, request_token, request_token_secret, verifier = nil)
oauth_config.token = request_token
oauth_config.secret = request_token_secret
oauth_config.callback = nil
oauth_config.verifier = verifier
res = request(oauth_config.http_method, uri)
filter_response(res)
oauth_config.verifier = nil
res
end
# Parse response and returns a Hash.
def get_oauth_response(res)
enc = res.header['content-encoding']
body = nil
if enc and enc[0] and enc[0].downcase == 'gzip'
body = Zlib::GzipReader.wrap(StringIO.new(res.content)) { |gz| gz.read }
else
body = res.content
end
body.split('&').inject({}) { |r, e|
key, value = e.split('=', 2)
r[unescape(key)] = unescape(value)
r
}
end
private
def unescape(escaped)
escaped ? ::HTTP::Message.unescape(escaped) : nil
end
def filter_response(res)
if res.status == 200
if res.oauth_params = get_oauth_response(res)
oauth_config.token = res.oauth_params['oauth_token']
oauth_config.secret = res.oauth_params['oauth_token_secret']
end
end
end
end
httpclient-2.3.3/lib/httpclient/ 0000755 0000041 0000041 00000000000 12155433207 016653 5 ustar www-data www-data httpclient-2.3.3/lib/httpclient/session.rb 0000644 0000041 0000041 00000061660 12155433207 020674 0 ustar www-data www-data # HTTPClient - HTTP client library.
# Copyright (C) 2000-2009 NAKAMURA, Hiroshi .
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
# either the dual license version in 2003, or any later version.
#
# httpclient/session.rb is based on http-access.rb in http-access/0.0.4. Some
# part of it is copyrighted by Maebashi-san who made and published
# http-access/0.0.4. http-access/0.0.4 did not include license notice but when
# I asked Maebashi-san he agreed that I can redistribute it under the same terms
# of Ruby. Many thanks to Maebashi-san.
require 'socket'
require 'thread'
require 'stringio'
require 'zlib'
require 'httpclient/timeout'
require 'httpclient/ssl_config'
require 'httpclient/http'
class HTTPClient
# Represents a Site: protocol scheme, host String and port Number.
class Site
# Protocol scheme.
attr_accessor :scheme
# Host String.
attr_accessor :host
alias hostname host
# Port number.
attr_accessor :port
# Creates a new Site based on the given URI.
def initialize(uri = nil)
if uri
@scheme = uri.scheme
@host = uri.hostname
@port = uri.port.to_i
else
@scheme = 'tcp'
@host = '0.0.0.0'
@port = 0
end
end
# Returns address String.
def addr
"#{@scheme}://#{@host}:#{@port.to_s}"
end
# Returns true is scheme, host and port are '=='
def ==(rhs)
(@scheme == rhs.scheme) and (@host == rhs.host) and (@port == rhs.port)
end
# Same as ==.
def eql?(rhs)
self == rhs
end
def hash # :nodoc:
[@scheme, @host, @port].hash
end
def to_s # :nodoc:
addr
end
# Returns true if scheme, host and port of the given URI matches with this.
def match(uri)
(@scheme == uri.scheme) and (@host == uri.host) and (@port == uri.port.to_i)
end
def inspect # :nodoc:
sprintf("#<%s:0x%x %s>", self.class.name, __id__, addr)
end
EMPTY = Site.new.freeze
end
# Manages sessions for a HTTPClient instance.
class SessionManager
# Name of this client. Used for 'User-Agent' header in HTTP request.
attr_accessor :agent_name
# Owner of this client. Used for 'From' header in HTTP request.
attr_accessor :from
# Requested protocol version
attr_accessor :protocol_version
# Chunk size for chunked request
attr_accessor :chunk_size
# Device for dumping log for debugging
attr_accessor :debug_dev
# Boolean value for Socket#sync
attr_accessor :socket_sync
attr_accessor :connect_timeout
# Maximum retry count. 0 for infinite.
attr_accessor :connect_retry
attr_accessor :send_timeout
attr_accessor :receive_timeout
attr_accessor :keep_alive_timeout
attr_accessor :read_block_size
attr_accessor :protocol_retry_count
# Local address to bind local side of the socket to
attr_accessor :socket_local
attr_accessor :ssl_config
attr_reader :test_loopback_http_response
attr_accessor :transparent_gzip_decompression
def initialize(client)
@client = client
@proxy = client.proxy
@agent_name = nil
@from = nil
@protocol_version = nil
@debug_dev = client.debug_dev
@socket_sync = true
@chunk_size = ::HTTP::Message::Body::DEFAULT_CHUNK_SIZE
@connect_timeout = 60
@connect_retry = 1
@send_timeout = 120
@receive_timeout = 60 # For each read_block_size bytes
@keep_alive_timeout = 15 # '15' is from Apache 2 default
@read_block_size = 1024 * 16 # follows net/http change in 1.8.7
@protocol_retry_count = 5
@ssl_config = nil
@test_loopback_http_response = []
@transparent_gzip_decompression = false
@socket_local = Site.new
@sess_pool = {}
@sess_pool_mutex = Mutex.new
@sess_pool_last_checked = Time.now
end
def proxy=(proxy)
if proxy.nil?
@proxy = nil
else
@proxy = Site.new(proxy)
end
end
def query(req, via_proxy)
req.http_body.chunk_size = @chunk_size
sess = open(req.header.request_uri, via_proxy)
begin
sess.query(req)
rescue
sess.close
raise
end
sess
end
def reset(uri)
site = Site.new(uri)
close(site)
end
def reset_all
close_all
end
# assert: sess.last_used must not be nil
def keep(sess)
add_cached_session(sess)
end
def invalidate(site)
@sess_pool_mutex.synchronize do
if pool = @sess_pool[site]
pool.each do |sess|
sess.invalidate
end
end
end
end
private
def open(uri, via_proxy = false)
site = Site.new(uri)
sess = nil
if cached = get_cached_session(site)
sess = cached
else
sess = Session.new(@client, site, @agent_name, @from)
sess.proxy = via_proxy ? @proxy : nil
sess.socket_sync = @socket_sync
sess.requested_version = @protocol_version if @protocol_version
sess.connect_timeout = @connect_timeout
sess.connect_retry = @connect_retry
sess.send_timeout = @send_timeout
sess.receive_timeout = @receive_timeout
sess.read_block_size = @read_block_size
sess.protocol_retry_count = @protocol_retry_count
sess.ssl_config = @ssl_config
sess.debug_dev = @debug_dev
sess.socket_local = @socket_local
sess.test_loopback_http_response = @test_loopback_http_response
sess.transparent_gzip_decompression = @transparent_gzip_decompression
end
sess
end
def close_all
@sess_pool_mutex.synchronize do
@sess_pool.each do |site, pool|
pool.each do |sess|
sess.close
end
end
end
@sess_pool.clear
end
# This method might not work as you expected...
def close(dest)
if cached = get_cached_session(Site.new(dest))
cached.close
true
else
false
end
end
def get_cached_session(site)
@sess_pool_mutex.synchronize do
now = Time.now
if now > @sess_pool_last_checked + @keep_alive_timeout
scrub_cached_session(now)
@sess_pool_last_checked = now
end
if pool = @sess_pool[site]
pool.each_with_index do |sess, idx|
if valid_session?(sess, now)
return pool.slice!(idx)
end
end
end
end
nil
end
def scrub_cached_session(now)
@sess_pool.each do |site, pool|
pool.replace(pool.select { |sess|
if valid_session?(sess, now)
true
else
sess.close # close & remove from the pool
false
end
})
end
end
def valid_session?(sess, now)
!sess.invalidated? and (now <= sess.last_used + @keep_alive_timeout)
end
def add_cached_session(sess)
@sess_pool_mutex.synchronize do
(@sess_pool[sess.dest] ||= []).unshift(sess)
end
end
end
# Wraps up OpenSSL::SSL::SSLSocket and offers debugging features.
class SSLSocketWrap
def initialize(socket, context, debug_dev = nil)
unless SSLEnabled
raise ConfigurationError.new('Ruby/OpenSSL module is required')
end
@context = context
@socket = socket
@ssl_socket = create_openssl_socket(@socket)
@debug_dev = debug_dev
end
def ssl_connect(hostname = nil)
if hostname && @ssl_socket.respond_to?(:hostname=)
@ssl_socket.hostname = hostname
end
@ssl_socket.connect
end
def post_connection_check(host)
verify_mode = @context.verify_mode || OpenSSL::SSL::VERIFY_NONE
if verify_mode == OpenSSL::SSL::VERIFY_NONE
return
elsif @ssl_socket.peer_cert.nil? and
check_mask(verify_mode, OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT)
raise OpenSSL::SSL::SSLError.new('no peer cert')
end
hostname = host.host
if @ssl_socket.respond_to?(:post_connection_check) and RUBY_VERSION > "1.8.4"
@ssl_socket.post_connection_check(hostname)
else
@context.post_connection_check(@ssl_socket.peer_cert, hostname)
end
end
def ssl_version
@ssl_socket.ssl_version if @ssl_socket.respond_to?(:ssl_version)
end
def ssl_cipher
@ssl_socket.cipher
end
def ssl_state
@ssl_socket.state
end
def peer_cert
@ssl_socket.peer_cert
end
def close
@ssl_socket.close
@socket.close
end
def closed?
@socket.closed?
end
def eof?
@ssl_socket.eof?
end
def gets(*args)
str = @ssl_socket.gets(*args)
debug(str)
str
end
def read(*args)
str = @ssl_socket.read(*args)
debug(str)
str
end
def readpartial(*args)
str = @ssl_socket.readpartial(*args)
debug(str)
str
end
def <<(str)
rv = @ssl_socket.write(str)
debug(str)
rv
end
def flush
@ssl_socket.flush
end
def sync
@ssl_socket.sync
end
def sync=(sync)
@ssl_socket.sync = sync
end
private
def check_mask(value, mask)
value & mask == mask
end
def create_openssl_socket(socket)
ssl_socket = nil
if OpenSSL::SSL.const_defined?("SSLContext")
ctx = OpenSSL::SSL::SSLContext.new
@context.set_context(ctx)
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ctx)
else
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
@context.set_context(ssl_socket)
end
ssl_socket
end
def debug(str)
@debug_dev << str if @debug_dev && str
end
end
# Wraps up a Socket for method interception.
module SocketWrap
def initialize(socket, *args)
super(*args)
@socket = socket
end
def close
@socket.close
end
def closed?
@socket.closed?
end
def eof?
@socket.eof?
end
def gets(*args)
@socket.gets(*args)
end
def read(*args)
@socket.read(*args)
end
def readpartial(*args)
# StringIO doesn't support :readpartial
if @socket.respond_to?(:readpartial)
@socket.readpartial(*args)
else
@socket.read(*args)
end
end
def <<(str)
@socket << str
end
def flush
@socket.flush
end
def sync
@socket.sync
end
def sync=(sync)
@socket.sync = sync
end
end
# Module for intercepting Socket methods and dumps in/out to given debugging
# device. debug_dev must respond to <<.
module DebugSocket
extend SocketWrap
def debug_dev=(debug_dev)
@debug_dev = debug_dev
end
def close
super
debug("! CONNECTION CLOSED\n")
end
def gets(*args)
str = super
debug(str)
str
end
def read(*args)
str = super
debug(str)
str
end
def readpartial(*args)
str = super
debug(str)
str
end
def <<(str)
super
debug(str)
end
private
def debug(str)
if str && @debug_dev
if str.index("\0")
require 'hexdump'
str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
@debug_dev << HexDump.encode(str).join("\n")
else
@debug_dev << str
end
end
end
end
# Dummy Socket for emulating loopback test.
class LoopBackSocket
include SocketWrap
def initialize(host, port, response)
super(response.is_a?(StringIO) ? response : StringIO.new(response))
@host = host
@port = port
end
def <<(str)
# ignored
end
end
# Manages a HTTP session with a Site.
class Session
include HTTPClient::Timeout
include Util
# Destination site
attr_reader :dest
# Proxy site
attr_accessor :proxy
# Boolean value for Socket#sync
attr_accessor :socket_sync
# Requested protocol version
attr_accessor :requested_version
# Device for dumping log for debugging
attr_accessor :debug_dev
attr_accessor :connect_timeout
attr_accessor :connect_retry
attr_accessor :send_timeout
attr_accessor :receive_timeout
attr_accessor :read_block_size
attr_accessor :protocol_retry_count
attr_accessor :socket_local
attr_accessor :ssl_config
attr_reader :ssl_peer_cert
attr_accessor :test_loopback_http_response
attr_accessor :transparent_gzip_decompression
attr_reader :last_used
def initialize(client, dest, agent_name, from)
@client = client
@dest = dest
@invalidated = false
@proxy = nil
@socket_sync = true
@requested_version = nil
@debug_dev = nil
@connect_timeout = nil
@connect_retry = 1
@send_timeout = nil
@receive_timeout = nil
@read_block_size = nil
@protocol_retry_count = 5
@ssl_config = nil
@ssl_peer_cert = nil
@test_loopback_http_response = nil
@socket_local = Site::EMPTY
@agent_name = agent_name
@from = from
@state = :INIT
@requests = []
@status = nil
@reason = nil
@headers = []
@socket = nil
@readbuf = nil
@transparent_gzip_decompression = false
@last_used = nil
end
# Send a request to the server
def query(req)
connect if @state == :INIT
# Use absolute URI (not absolute path) iif via proxy AND not HTTPS.
req.header.request_absolute_uri = !@proxy.nil? and !https?(@dest)
begin
timeout(@send_timeout, SendTimeoutError) do
set_header(req)
req.dump(@socket)
# flush the IO stream as IO::sync mode is false
@socket.flush unless @socket_sync
end
rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, IOError
# JRuby can raise IOError instead of ECONNRESET for now
close
raise KeepAliveDisconnected.new(self)
rescue HTTPClient::TimeoutError
close
raise
rescue
close
if SSLEnabled and $!.is_a?(OpenSSL::SSL::SSLError)
raise KeepAliveDisconnected.new(self)
else
raise
end
end
@state = :META if @state == :WAIT
@next_connection = nil
@requests.push(req)
@last_used = Time.now
end
def close
if !@socket.nil? and !@socket.closed?
# @socket.flush may block when it the socket is already closed by
# foreign host and the client runs under MT-condition.
@socket.close
end
@state = :INIT
end
def closed?
@state == :INIT
end
def invalidate
@invalidated = true
end
def invalidated?
@invalidated
end
def get_header
begin
if @state != :META
raise RuntimeError.new("get_status must be called at the beginning of a session")
end
read_header
rescue
close
raise
end
[@version, @status, @reason, @headers]
end
def eof?
if !@content_length.nil?
@content_length == 0
else
@socket.closed? or @socket.eof?
end
end
def get_body(&block)
begin
read_header if @state == :META
return nil if @state != :DATA
if @gzipped and @transparent_gzip_decompression
# zlib itself has a functionality to decompress gzip stream.
# - zlib 1.2.5 Manual
# http://www.zlib.net/manual.html#Advanced
# > windowBits can also be greater than 15 for optional gzip decoding. Add 32 to
# > windowBits to enable zlib and gzip decoding with automatic header detection,
# > or add 16 to decode only the gzip format
inflate_stream = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
original_block = block
block = Proc.new { |buf|
original_block.call(inflate_stream.inflate(buf))
}
end
if @chunked
read_body_chunked(&block)
elsif @content_length
read_body_length(&block)
else
read_body_rest(&block)
end
rescue
close
raise
end
if eof?
if @next_connection
@state = :WAIT
else
close
end
end
nil
end
private
def set_header(req)
if @requested_version
if /^(?:HTTP\/|)(\d+.\d+)$/ =~ @requested_version
req.http_version = $1
end
end
if @agent_name && req.header.get('User-Agent').empty?
req.header.set('User-Agent', "#{@agent_name} #{LIB_NAME}")
end
if @from && req.header.get('From').empty?
req.header.set('From', @from)
end
if req.header.get('Accept').empty?
req.header.set('Accept', '*/*')
end
if @transparent_gzip_decompression
req.header.set('Accept-Encoding', 'gzip,deflate')
end
if req.header.get('Date').empty?
req.header.set_date_header
end
end
# Connect to the server
def connect
site = @proxy || @dest
retry_number = 0
begin
timeout(@connect_timeout, ConnectTimeoutError) do
@socket = create_socket(site)
if https?(@dest)
if @socket.is_a?(LoopBackSocket)
connect_ssl_proxy(@socket, urify(@dest.to_s)) if @proxy
else
@socket = create_ssl_socket(@socket)
connect_ssl_proxy(@socket, urify(@dest.to_s)) if @proxy
begin
@socket.ssl_connect(@dest.host)
ensure
if $DEBUG
warn("Protocol version: #{@socket.ssl_version}")
warn("Cipher: #{@socket.ssl_cipher.inspect}")
warn("State: #{@socket.ssl_state}")
end
end
@socket.post_connection_check(@dest)
@ssl_peer_cert = @socket.peer_cert
end
end
# Use Ruby internal buffering instead of passing data immediately
# to the underlying layer
# => we need to to call explicitly flush on the socket
@socket.sync = @socket_sync
end
rescue RetryableResponse
retry_number += 1
if retry_number < @protocol_retry_count
retry
end
raise BadResponseError.new("connect to the server failed with status #{@status} #{@reason}")
rescue TimeoutError
if @connect_retry == 0
retry
else
retry_number += 1
retry if retry_number < @connect_retry
end
close
raise
end
@state = :WAIT
end
def create_socket(site)
socket = nil
begin
@debug_dev << "! CONNECT TO #{site.host}:#{site.port}\n" if @debug_dev
if str = @test_loopback_http_response.shift
socket = LoopBackSocket.new(site.host, site.port, str)
elsif @socket_local == Site::EMPTY
socket = TCPSocket.new(site.host, site.port)
else
socket = TCPSocket.new(site.host, site.port, @socket_local.host, @socket_local.port)
end
if @debug_dev
@debug_dev << "! CONNECTION ESTABLISHED\n"
socket.extend(DebugSocket)
socket.debug_dev = @debug_dev
end
rescue SystemCallError => e
e.message << " (#{site})"
raise
rescue SocketError => e
e.message << " (#{site})"
raise
end
socket
end
# wrap socket with OpenSSL.
def create_ssl_socket(raw_socket)
SSLSocketWrap.new(raw_socket, @ssl_config, @debug_dev)
end
def connect_ssl_proxy(socket, uri)
req = HTTP::Message.new_connect_request(uri)
@client.request_filter.each do |filter|
filter.filter_request(req)
end
set_header(req)
req.dump(@socket)
@socket.flush unless @socket_sync
res = HTTP::Message.new_response('')
parse_header
res.http_version, res.status, res.reason = @version, @status, @reason
@headers.each do |key, value|
res.header.set(key.to_s, value)
end
commands = @client.request_filter.collect { |filter|
filter.filter_response(req, res)
}
if commands.find { |command| command == :retry }
raise RetryableResponse.new
end
unless @status == 200
raise BadResponseError.new("connect to ssl proxy failed with status #{@status} #{@reason}", res)
end
end
# Read status block.
def read_header
@content_length = nil
@chunked = false
@gzipped = false
@chunk_length = 0
parse_header
# Header of the request has been parsed.
@state = :DATA
req = @requests.shift
if req.header.request_method == 'HEAD' or no_message_body?(@status)
@content_length = 0
if @next_connection
@state = :WAIT
else
close
end
end
@next_connection = false if !@content_length and !@chunked
end
StatusParseRegexp = %r(\AHTTP/(\d+\.\d+)\s+(\d\d\d)\s*([^\r\n]+)?\r?\n\z)
def parse_header
timeout(@receive_timeout, ReceiveTimeoutError) do
initial_line = nil
begin
begin
initial_line = @socket.gets("\n")
if initial_line.nil?
close
raise KeepAliveDisconnected.new(self)
end
rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE, IOError
# JRuby can raise IOError instead of ECONNRESET for now
close
raise KeepAliveDisconnected.new(self)
end
if StatusParseRegexp !~ initial_line
@version = '0.9'
@status = nil
@reason = nil
@next_connection = false
@content_length = nil
@readbuf = initial_line
break
end
@version, @status, @reason = $1, $2.to_i, $3
@next_connection = HTTP::Message.keep_alive_enabled?(@version)
@headers = []
while true
line = @socket.gets("\n")
unless line
raise BadResponseError.new('unexpected EOF')
end
line.chomp!
break if line.empty?
if line[0] == ?\ or line[0] == ?\t
last = @headers.last[1]
last << ' ' unless last.empty?
last << line.strip
else
key, value = line.strip.split(/\s*:\s*/, 2)
parse_keepalive_header(key, value)
@headers << [key, value]
end
end
end while (@version == '1.1' && @status == 100)
end
end
def no_message_body?(status)
!status.nil? && # HTTP/0.9
((status >= 100 && status < 200) || status == 204 || status == 304)
end
def parse_keepalive_header(key, value)
key = key.downcase
if key == 'content-length'
@content_length = value.to_i
elsif key == 'content-encoding' and ( value.downcase == 'gzip' or
value.downcase == 'x-gzip' or value.downcase == 'deflate' )
@gzipped = true
elsif key == 'transfer-encoding' and value.downcase == 'chunked'
@chunked = true
@chunk_length = 0
@content_length = nil
elsif key == 'connection' or key == 'proxy-connection'
if value.downcase == 'keep-alive'
@next_connection = true
else
@next_connection = false
end
end
end
def read_body_length(&block)
return nil if @content_length == 0
while true
buf = ''
maxbytes = @read_block_size
maxbytes = @content_length if maxbytes > @content_length
timeout(@receive_timeout, ReceiveTimeoutError) do
begin
@socket.readpartial(maxbytes, buf)
rescue EOFError
close
buf = nil
end
end
if buf && buf.bytesize > 0
@content_length -= buf.bytesize
yield buf
else
@content_length = 0
end
return if @content_length == 0
end
end
RS = "\r\n"
def read_body_chunked(&block)
buf = ''
while true
len = @socket.gets(RS)
if len.nil? # EOF
close
return
end
@chunk_length = len.hex
if @chunk_length == 0
@content_length = 0
@socket.gets(RS)
return
end
timeout(@receive_timeout, ReceiveTimeoutError) do
@socket.read(@chunk_length + 2, buf)
end
unless buf.empty?
yield buf.slice(0, @chunk_length)
end
end
end
def read_body_rest
if @readbuf and @readbuf.bytesize > 0
yield @readbuf
@readbuf = nil
end
while true
buf = ''
timeout(@receive_timeout, ReceiveTimeoutError) do
begin
@socket.readpartial(@read_block_size, buf)
rescue EOFError
buf = nil
end
end
if buf && buf.bytesize > 0
yield buf
else
return
end
end
end
end
end
httpclient-2.3.3/lib/httpclient/cacert.p7s 0000644 0000041 0000041 00000345372 12155433207 020565 0 ustar www-data www-data MIME-Version: 1.0
Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha1"; boundary="----5FCD663C81182946FE75D58C22A8C67A"
This is an S/MIME signed message
------5FCD663C81182946FE75D58C22A8C67A
-----BEGIN CERTIFICATE-----
MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw
MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD
VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul
CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n
tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl
dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch
PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC
+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O
BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E
BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl
MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk
ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB
IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X
7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz
43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl
pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA
WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1
MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK
EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh
BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq
xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G
87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i
2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U
WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1
0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G
A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T
AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr
pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL
ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm
aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv
hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm
hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3
P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y
iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no
xqE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2
MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft
ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk
hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym
1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW
OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb
2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko
O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU
AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF
Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb
LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir
oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C
MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds
sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP
bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2
MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft
ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC
206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci
KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2
JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9
BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e
Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B
PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67
Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq
Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ
o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3
+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj
YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj
FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE
AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn
xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2
LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc
obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8
CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe
IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA
DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F
AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX
Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb
AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl
Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw
RY8mkaKO/qk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDpjCCAo6gAwIBAgIEAgAAvzANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJJ
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MS8wLQYD
VQQDEyZCYWx0aW1vcmUgQ3liZXJUcnVzdCBDb2RlIFNpZ25pbmcgUm9vdDAeFw0w
MDA1MTcxNDAxMDBaFw0yNTA1MTcyMzU5MDBaMGcxCzAJBgNVBAYTAklFMRIwEAYD
VQQKEwlCYWx0aW1vcmUxEzARBgNVBAsTCkN5YmVyVHJ1c3QxLzAtBgNVBAMTJkJh
bHRpbW9yZSBDeWJlclRydXN0IENvZGUgU2lnbmluZyBSb290MIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyHGaGBKOetv5mvxBr9jy9AmOrT/+Zzc82skm
ULGxPsvoTnMA8rLc88VG+wnvGJbOp+CchF0gDnqgqjaL+ii2eC6z7OhH8wTwkCO0
6q/lU7gF90ddK4bxp6TGOzW20g1SQdf0knXhogpQVoe+lwt7M4UQuSgY7jPqSBHX
W5FHdiLU7s9d56hOHJ2Wkd2cvXQJqHJhqrAhOvE9LANWCdLB3MO1x1Q3q+YmorJG
cXPKEYjuvOdk99ARGnNAWshJLA+375B/aIAEOAsbDzvU9aCzwo7hNLSAmW2edtSS
KUCxldI3pGcSf+Biu641xZk2gkS45ngYM2Fxk1stjZ94lYLrbQIDAQABo1owWDAT
BgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUyEE0XBUVBOVA8tGrmm8kknqH
QlowEgYDVR0TAQH/BAgwBgEB/wIBAzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcN
AQEFBQADggEBAFJ0qpVLIozHPZak/l36L7W86/AL6VY4HdFtDaG8aIvwxYClJDT9
8pYYEYahNvU351RA1WQfw19wQmstOceeUgXO52py0o1yP0dQg6vHjSXJsOOnUxaV
pmpT6hidj3ipd3ca+bSXR1mIJyi1yuEu1z4Oog24IkQD49FjsEE6ofWkLfd2HgRU
mXgyQNcrfE26ppyweW4Hvozs7tc4aVvBDFZon/7r0eHIiPnyzX++hbREZwBQPvQm
A2Tqd33oXj4cN0fI1uqk8zY8l8I5cgWUGSXD1zdBD8Efh4r9qr7psWRX5NuSoc/h
Seg7H5ETWsOP2SVYSYBHD8YDrqzjv7fAqio=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD
VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz
IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz
MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj
dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw
EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp
MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9
28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq
VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q
DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR
5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL
ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a
Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl
UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s
+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5
Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx
hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV
HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1
+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN
YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t
L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy
ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt
IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV
HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w
DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW
PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF
5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1
glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH
FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2
pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD
xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG
tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq
jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De
fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ
d0jQ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn
MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg
b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa
MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB
ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw
IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B
AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb
unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d
BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq
7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3
0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX
roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG
A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j
aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p
26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA
BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud
EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN
BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB
AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd
p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi
1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc
XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0
eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu
tGWaIZDgqtCYvDi1czyL+Nw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD
VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx
MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy
cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG
A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl
BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed
KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7
G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2
zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4
ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG
HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2
Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V
yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e
beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r
6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog
zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW
BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr
ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp
ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk
cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt
YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC
CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow
KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI
hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ
UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz
X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x
fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz
a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd
Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd
SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O
AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso
M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge
v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw
PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz
cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9
MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz
IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ
ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR
VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL
kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd
EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas
H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0
HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud
DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4
QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu
Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/
AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8
yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR
FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA
ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB
kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
l7+ijrRU
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDlTCCAn2gAwIBAgIRAL9c27byHG7ATet6Ajs26HkwDQYJKoZIhvcNAQEFBQAw
PjELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRwwGgYDVQQDExNDbGFz
cyAzUCBQcmltYXJ5IENBMB4XDTk5MDcwNzE3MTAwMFoXDTE5MDcwNjIzNTk1OVow
PjELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRwwGgYDVQQDExNDbGFz
cyAzUCBQcmltYXJ5IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
qzf/62CbQXhp9UlYsN4fcWmmK+OuUMapvJPpIL7kxBOCVu/wQzIJypt1A498T+Hg
T3aeC61kehQ6mp2/LxYLZRyp7py84xply0+F6pJWdWbWVUDv+8zWOD+rHO9CjRmJ
9reVhsKnHen3KfEq2WV5/Cv1jsoad36e6Kz5Zr9F++gTnV+2c+V9e477EnRdHwZe
hRumXhhEALq8027RUg4GrevutbTBu7zrOA9IIpHHb9K4cju6f8CNbLe8R3MhKoX/
rNYoohnVl2o6uaxtRezmTcPbqF3FXYKYrEpaquYrCAwQdLxi9jpJBGbYURwmpth1
n5y/rmBRPVy8ok97iWfNUwIDAQABo4GNMIGKMA8GA1UdEwQIMAYBAf8CAQowCwYD
VR0PBAQDAgEGMB0GA1UdDgQWBBSG4eGBcb9qEvEK8gHkyPtAzmiAiTARBglghkgB
hvhCAQEEBAMCAAEwOAYDVR0fBDEwLzAtoCugKYYnaHR0cDovL3d3dy5jZXJ0cGx1
cy5jb20vQ1JML2NsYXNzM1AuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQAlquEiQMKk
gDy3ol2ZjR96QjU4ZhcR372v/BURmBkz5gVChFSoS+uwnds32hZSQBF0aL/pybIQ
hLcdRAB5Jxz1WAYXGDI1tjCXY8amORvI7kYXYsUu5wqjmoowY3OqFKVNCqhyk/BJ
ERCQfBh9qCAFxMJ6NbocWgrgLnjIiLHPVwHsPeIGEzTAqNz6gIAF7gV2vZ0ryJ1Q
b2vFQFCE/V0d5pCcENOkxrkoGt61+Apwqs7eUD0DgNvYiMVIBuQDc90WzjbW5Zvq
d9qylrVlpwRdI673k7JeilFkX9rPjD1BW975o+kqfEcQH/YyPH5w6d+h1S4NsRpF
tLwS7SgX6R4C
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM
MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM
MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E
jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo
ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI
ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu
Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg
AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7
HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA
uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa
TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg
xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q
CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x
O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs
6GAqm4VKQPNriiTsBhYscw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM
MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D
ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU
cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3
WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg
Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw
IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH
UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM
TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU
BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM
kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x
AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV
HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y
sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL
I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8
J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY
VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc
MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj
IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB
IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE
RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl
U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290
IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU
ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC
QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr
rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S
NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc
QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH
txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP
BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp
tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa
IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl
6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+
xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
Cm26OWMohpLzGITY+9HPBVZkVw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3
MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub
j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo
U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b
u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+
bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
fF6adulZkMV8gzURZVE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
0vdXcDazv/wor3ElhVsT/h5/WrQ8
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50
cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs
IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz
dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy
NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu
dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt
dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0
aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T
RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN
cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW
wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1
U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0
jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN
BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/
jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v
1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R
nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH
VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT
ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw
MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j
LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ
KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo
RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu
WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw
Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD
AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK
eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM
zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+
WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN
/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL
MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj
KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2
MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV
BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw
NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV
BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL
So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal
tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG
CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT
qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz
rD6ogRLQy7rQkgu2npaqBA+K
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB
mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT
MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ
BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0
BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz
+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm
hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn
5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W
JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL
DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC
huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB
AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB
zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN
kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH
SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G
spki4cErx5z481+oghLrGREt
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY
MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9
AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA
ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0
7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W
kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI
mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1
6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl
4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K
oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj
UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU
AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW
MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy
c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE
BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0
IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV
VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8
cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT
QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh
F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v
c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w
mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd
VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX
teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ
f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe
Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+
nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB
/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY
MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX
IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn
ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z
uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN
Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja
QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW
koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9
ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt
DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm
bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
WD9f
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
ReYNnyicsbkqWletNw+vHX/bvZ8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDtjCCAp6gAwIBAgICAbYwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVVMx
GDAWBgNVBAoTD0dURSBDb3Jwb3JhdGlvbjEnMCUGA1UECxMeR1RFIEN5YmVyVHJ1
c3QgU29sdXRpb25zLCBJbmMuMR4wHAYDVQQDExVHVEUgQ3liZXJUcnVzdCBSb290
IDUwHhcNOTgwODE0MTQ1MDAwWhcNMTMwODE0MjM1OTAwWjBwMQswCQYDVQQGEwJV
UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
cnVzdCBTb2x1dGlvbnMsIEluYy4xHjAcBgNVBAMTFUdURSBDeWJlclRydXN0IFJv
b3QgNTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALwSbj+KfHqXAewe
uzlaAvR4RKJIG457SVJ6uHtHs6+Um2+7lvoramVcuByUc76/iQoigO5X/IwFu3Cf
lzkE2qOHXKjlyq/AM5rVN1xLrOSA0KYjYPv9ci6UncfOwgQy73hgXe2thw9FZR48
mgqavl0dmezn8tHGehfZrZtUln/EfGC/haoVNR1A2hG87FQhKC0joajwzy3N3fx+
D17hZQdWywe00lboXjHMGGPEhtIthc+Tkqtt/mg5+95zvYb45EZ66p8My/QZ/mO8
0Sx7iDM29uThnAxTgWAc2i6rlqkWiBNQmbK9Vd8VMH7o5Zj7cH5stQf8/Ea30O03
ln4y/iECAwEAAaNaMFgwEgYDVR0TAQH/BAgwBgEB/wIBBTAOBgNVHQ8BAf8EBAMC
AQYwFwYDVR0gBBAwDjAMBgoqhkiG+GMBAgEDMBkGA1UdDgQSBBB2CkkhOEyf3vjE
ScdxcZGdMA0GCSqGSIb3DQEBBQUAA4IBAQBBOtQYW9q43iEc4Y4J5fFoNP/elvQH
9ac886xKsZv6kvqb7eYyIapKdsXcTzjl39WG5NXIdn2Y17HNj021kSNsi4rr6nzv
FJTExvAfSi0ycWMrY5EmAgm2gB3t4sy4f9uHY8jh0GwmsTUdQGYQG82VVBgzYewT
T9oT95mvPtDPjqZyorPDBZrJJ32SzH5SjbOrcG2eiZ9N6xp1wpiq1QIW1wyKvyXk
6y28mOlYOBl8uTf+2+KZCHMGx5eDan0QAS8yuRcFSmXmL86+XlOmgumaUwqEdC2D
ysiUFnZflGEo8IWnObvXi9moshMdVAk0JH0ggX1mfqKQdFwQxr3sqxvC
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID5TCCAs2gAwIBAgISESG8J2xVR69YTu/UztYpsqKFMA0GCSqGSIb3DQEBCwUA
MEwxCzAJBgNVBAYTAkZSMRIwEAYDVQQKEwlLRVlORUNUSVMxDTALBgNVBAsTBFJP
T1QxGjAYBgNVBAMTEUtFWU5FQ1RJUyBST09UIENBMB4XDTA5MDUyNjAwMDAwMFoX
DTIwMDUyNjAwMDAwMFowTDELMAkGA1UEBhMCRlIxEjAQBgNVBAoTCUtFWU5FQ1RJ
UzENMAsGA1UECxMEUk9PVDEaMBgGA1UEAxMRS0VZTkVDVElTIFJPT1QgQ0EwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDG/bMXhaGtJhuVaTUhPaSI+t7b
YDZAF2nCFGP7uNnCdBU3LpzQIM1pjYQyooVMFLSb8iWzVCqDPy2+D/M7ZNH/oFDv
d087TuE/C2SFmrpYftLDYtNkJaLUspc8d11jKjOS/M2CDZtUlYf1teuMzVvRyjAv
yYhGtc0NEbQYj+7RoT5dFegoz9/DkJtszNEMRXezOuuKkB3pr2RqiXupPUN0+uRn
IqH73E3E9WLJyiW0yYBgM6nde6ACv5YlCl7JXyl7tBeBi22BGdDZg1wFj0FpGmlD
gJ+or+DpjJGLJyuiJmDND/KkowKDjhiBwheKQxX5bfMdEKRanERhIyF62PvRAgMB
AAGjgcAwgb0wEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8EBAMCAQYwVwYD
VR0fBFAwTjBMoEqgSIZGaHR0cDovL3RydXN0Y2VudGVyLWNybC5jZXJ0aWZpY2F0
Mi5jb20vS2V5bmVjdGlzL0tFWU5FQ1RJU19ST09UX0NBLmNybDAdBgNVHQ4EFgQU
77cjl9CokX+mz6YhwDSfzHdB4dAwHwYDVR0jBBgwFoAU77cjl9CokX+mz6YhwDSf
zHdB4dAwDQYJKoZIhvcNAQELBQADggEBABoxaZlCwuVAhaKfksNj1I8hOagZIf56
/MNNQPMr6EusW0xZk8bcfguvfF+VhWu9x2+6wb74xjpnS5PGBWk+JC3wG5HGPj/s
QhiTbAMkim75IGcrfG2rNMkqIjMN132P7tI2ZELINZpuGWHLjWfwaKfQJAXmwxe6
Ra58Q7WAeANNIHMF/EMQnTVpQnWUJYIrpjuQGN7Bqa/zLZW/lafPGJfhWeKirxoW
YQ33E3FTkzf9PK8AHWyLFK9Gloy2UnzMLU7N4elLCu6a/nqY5ym6G9ocutxrzQQO
JkCp63M8/lCoESdVvduOS+9PGO0V/72GmGbumiVxNGxQ8bJRy2adTSk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV
BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa
GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg
Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J
WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB
rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp
+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1
ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i
Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz
PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og
/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH
oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI
yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud
EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2
A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL
MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f
BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn
g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl
fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K
WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha
B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc
hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR
TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD
mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z
ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y
4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza
8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV
BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM
V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB
4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr
H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd
8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv
vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT
mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe
btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc
T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt
WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ
c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A
4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD
VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG
CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0
aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu
dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw
czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G
A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC
TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg
Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0
7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem
d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd
+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B
4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN
t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x
DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57
k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s
zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j
Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT
mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK
4SVhM7JZG+Ju1zdXtg2pEto=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC
TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0
aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0
aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz
MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw
IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR
dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp
li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D
rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ
WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug
F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU
xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC
Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv
dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw
ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl
IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh
c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy
ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI
KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T
KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq
y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p
dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD
VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL
MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk
fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8
7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R
cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y
mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW
xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK
SnQ2+Q==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl
MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh
U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz
MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N
IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11
bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE
RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO
zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5
bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF
MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1
VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC
OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW
tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ
q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb
EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+
Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O
VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY
MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t
dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5
WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD
VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8
9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ
DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9
Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N
QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ
xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G
A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T
AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG
kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr
Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5
Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU
JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot
RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl
MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe
U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX
DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy
dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj
YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV
OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr
zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM
VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ
hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO
ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw
awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs
OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF
coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc
okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8
t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy
1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/
SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy
NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y
LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+
TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y
TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0
LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW
I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw
nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDIDCCAgigAwIBAgIBJDANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MxIENBMB4XDTAx
MDQwNjEwNDkxM1oXDTIxMDQwNjEwNDkxM1owOTELMAkGA1UEBhMCRkkxDzANBgNV
BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMSBDQTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBALWJHytPZwp5/8Ue+H887dF+2rDNbS82rDTG
29lkFwhjMDMiikzujrsPDUJVyZ0upe/3p4zDq7mXy47vPxVnqIJyY1MPQYx9EJUk
oVqlBvqSV536pQHydekfvFYmUk54GWVYVQNYwBSujHxVX3BbdyMGNpfzJLWaRpXk
3w0LBUXl0fIdgrvGE+D+qnr9aTCU89JFhfzyMlsy3uhsXR/LpCJ0sICOXZT3BgBL
qdReLjVQCfOAl/QMF6452F/NM8EcyonCIvdFEu1eEpOdY6uCLrnrQkFEy0oaAIIN
nvmLVz5MxxftLItyM19yejhW1ebZrgUaHXVFsculJRwSVzb9IjcCAwEAAaMzMDEw
DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQIR+IMi/ZTiFIwCwYDVR0PBAQDAgEG
MA0GCSqGSIb3DQEBBQUAA4IBAQCLGrLJXWG04bkruVPRsoWdd44W7hE928Jj2VuX
ZfsSZ9gqXLar5V7DtxYvyOirHYr9qxp81V9jz9yw3Xe5qObSIjiHBxTZ/75Wtf0H
DjxVyhbMp6Z3N/vbXB9OWQaHowND9Rart4S9Tu+fMTfwRvFAttEMpWT4Y14h21VO
TzF2nBBhjrZTOqMRvq9tfB69ri3iDGnHhVNoomG6xT60eVR4ngrHAr5i0RGCS2Uv
kVrCqIexVmiUefkl98HVrhq4uz2PqYo4Ffdz0Fpg0YCw8NzVUM1O7pJIae2yIx4w
zMiUyLb1O4Z/P6Yun/Y+LLWSlj7fLJOK/4GMDw9ZIRlXvVWa
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx
MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV
BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o
Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt
5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s
3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej
vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu
8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw
DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG
MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil
zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/
3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD
FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6
Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2
ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln
biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF
MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT
d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8
76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+
bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c
6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE
emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd
MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt
MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y
MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y
FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi
aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM
gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB
qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7
lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn
8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6
45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO
UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5
O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC
bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv
GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a
77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC
hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3
92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp
Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w
ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt
Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFwTCCA6mgAwIBAgIITrIAZwwDXU8wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEjMCEGA1UEAxMaU3dpc3NTaWdu
IFBsYXRpbnVtIENBIC0gRzIwHhcNMDYxMDI1MDgzNjAwWhcNMzYxMDI1MDgzNjAw
WjBJMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMSMwIQYDVQQD
ExpTd2lzc1NpZ24gUGxhdGludW0gQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAMrfogLi2vj8Bxax3mCq3pZcZB/HL37PZ/pEQtZ2Y5Wu669y
IIpFR4ZieIbWIDkm9K6j/SPnpZy1IiEZtzeTIsBQnIJ71NUERFzLtMKfkr4k2Htn
IuJpX+UFeNSH2XFwMyVTtIc7KZAoNppVRDBopIOXfw0enHb/FZ1glwCNioUD7IC+
6ixuEFGSzH7VozPY1kneWCqv9hbrS3uQMpe5up1Y8fhXSQQeol0GcN1x2/ndi5ob
jM89o03Oy3z2u5yg+gnOI2Ky6Q0f4nIoj5+saCB9bzuohTEJfwvH6GXp43gOCWcw
izSC+13gzJ2BbWLuCB4ELE6b7P6pT1/9aXjvCR+htL/68++QHkwFix7qepF6w9fl
+zC8bBsQWJj3Gl/QKTIDE0ZNYWqFTFJ0LwYfexHihJfGmfNtf9dng34TaNhxKFrY
zt3oEBSa/m0jh26OWnA81Y0JAKeqvLAxN23IhBQeW71FYyBrS3SMvds6DsHPWhaP
pZjydomyExI7C3d3rLvlPClKknLKYRorXkzig3R3+jVIeoVNjZpTxN94ypeRSCtF
KwH3HBqi7Ri6Cr2D+m+8jVeTO9TUps4e8aCxzqv9KyiaTxvXw3LbpMS/XUz13XuW
ae5ogObnmLo2t/5u7Su9IPhlGdpVCX4l3P5hYnL5fhgC72O00Puv5TtjjGePAgMB
AAGjgawwgakwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFFCvzAeHFUdvOMW0ZdHelarp35zMMB8GA1UdIwQYMBaAFFCvzAeHFUdvOMW0
ZdHelarp35zMMEYGA1UdIAQ/MD0wOwYJYIV0AVkBAQEBMC4wLAYIKwYBBQUHAgEW
IGh0dHA6Ly9yZXBvc2l0b3J5LnN3aXNzc2lnbi5jb20vMA0GCSqGSIb3DQEBBQUA
A4ICAQAIhab1Fgz8RBrBY+D5VUYI/HAcQiiWjrfFwUF1TglxeeVtlspLpYhg0DB0
uMoI3LQwnkAHFmtllXcBrqS3NQuB2nEVqXQXOHtYyvkv+8Bldo1bAbl93oI9ZLi+
FHSjClTTLJUYFzX1UWs/j6KWYTl4a0vlpqD4U99REJNi54Av4tHgvI42Rncz7Lj7
jposiU0xEQ8mngS7twSNC/K5/FqdOxa3L8iYq/6KUFkuozv8KV2LwUvJ4ooTHbG/
u0IdUt1O2BReEMYxB+9xJ/cbOQncguqLs5WGXv312l0xpuAxtpTmREl0xRbl9x8D
YSjFyMsSoEJL+WuICI20MhjzdZ/EfwBPBZWcoxcCw7NTm6ogOSkrZvqdr16zktK1
puEa+S1BaYEUtLS17Yk9zvupnTVCRLEcFHOBzyoBNZox1S2PbYTfgE1X4z/FhHXa
icYwu+uPyyIIoK6q8QNsOktNCaUOcsZWayFCTiMlFGiudgp8DAdwZPmaL/YFOSbG
DI8Zf0NebvRbFS/bYV3mZy8/CJT5YLSYMdp08YSTcU1f+2BY0fvEwW2JorsgH51x
kcsymxM9Pn2SUjWskpSi0xjCfMfqr3YFFt1nJ8J+HAciIfNAChs0B0QTwoRqjt8Z
Wr9/6x3iGjjRXK9HkmuAtTClyY3YqzGBH9/CZjfTk6mFhnll0g==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE
BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu
IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow
RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY
U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv
Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br
YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF
nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH
6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt
eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/
c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ
MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH
HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf
jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6
5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB
rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c
wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB
AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp
WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9
xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ
2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ
IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8
aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X
em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR
dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/
OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+
hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy
tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDPDCCAqWgAwIBAgIQEj3w59oqIkekOIngiu7JZzANBgkqhkiG9w0BAQUFADCB
0TELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
Q2FwZSBUb3duMRowGAYDVQQKExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMf
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEkMCIGA1UEAxMbVGhhd3Rl
IFBlcnNvbmFsIEZyZWVtYWlsIENBMSswKQYJKoZIhvcNAQkBFhxwZXJzb25hbC1m
cmVlbWFpbEB0aGF3dGUuY29tMB4XDTk2MDEwMTAwMDAwMFoXDTIxMDEwMTIzNTk1
OVowgdExCzAJBgNVBAYTAlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNV
BAcTCUNhcGUgVG93bjEaMBgGA1UEChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNV
BAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24xJDAiBgNVBAMTG1Ro
YXd0ZSBQZXJzb25hbCBGcmVlbWFpbCBDQTErMCkGCSqGSIb3DQEJARYccGVyc29u
YWwtZnJlZW1haWxAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
gYEA1GnX1LCUZFtx6UfYDFG26nKRsIRefS0Nj3sS34UldSh0OkIsYyeflXtL734Z
hx2G6qPduc6WZBrCFG5ErHzmj+hND3EfQDimAKOHePb5lIZererAXnbr2RSjXW56
fAylS1V/Bhkpf56aJtVquzgkCGqYx7Hao5iR/Xnb5VrEHLkCAwEAAaMTMBEwDwYD
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQAemGDU5fJUYLA9GoFkR/db
o9lvwykLp9KpgUn2w22FFChFRAH0cVyVLhQPGivRqWvBX2c9FvFyIK++FsoOMF/J
y6WTLMNnVB5yIoojdmyUHVFSbJ3E4EcC18y/8IB7GG4l3GJh1qb+wR1/2bP9jVxF
EFrGZWSa6yz1A0/WSGL7Lg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDNjCCAp+gAwIBAgIQNhIilsXjOKUgodJfTNcJVDANBgkqhkiG9w0BAQUFADCB
zjELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhh
d3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
cnZlckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIxMDEwMTIzNTk1OVow
gc4xCzAJBgNVBAYTAlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcT
CUNhcGUgVG93bjEdMBsGA1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNV
BAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRo
YXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1z
ZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2
aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560
ZXUCTe/LCaIhUdib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j
+ao6hnO2RlNYyIkFvYMRuHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/
BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBlkKyID1bZ5jA01CbH0FDxkt5r1DmI
CSLGpmODA/eZd9iy5Ri4XWPz1HP7bJyZePFLeH0ZJMMrAoT4vCLZiiLXoPxx7JGH
IPG47LHlVYCsPVLIOQ7C8MAFT9aCdYy9X9LcdpoFEsmvcsPcJX6kTY4XpeCHf+Ga
WuFg3GQjPEIuTQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp
IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi
BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw
MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig
YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v
dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/
BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6
papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K
DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3
KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox
XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB
rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV
BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa
Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl
LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u
MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl
ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm
gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8
YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf
b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9
9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S
zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk
OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA
2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW
oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c
KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM
m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu
MdRAGmI0Nj81Aa6sY6A=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB
qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw
NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG
A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs
W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta
3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk
6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6
Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J
NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP
r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU
DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz
YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2
/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/
LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7
jVaMaA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDIjCCAougAwIBAgIQNKT/9jCvTKU8MxdCoZRmdTANBgkqhkiG9w0BAQUFADCB
xDELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhh
d3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0
ZS5jb20wHhcNOTYwODAxMDAwMDAwWhcNMjEwMTAxMjM1OTU5WjCBxDELMAkGA1UE
BhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3du
MR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlm
aWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZl
ciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8w
DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl
/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF
/rFrKbYvScg71CcEJRCXL+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982
OsK1ZiIS1ocNAgMBAAGjEzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEF
BQADgYEAvkBpQW/G28GnvwfAReTQtUMeTJUzNelewj4o9qgNUNX/4gwP/FACjq6R
ua00io2fJ3GqGcxL6ATK1BdrEhrWxl/WzV7/iXa/2EjYWb0IiokdV81FHlK6EpqE
+hiJX+j5MDVqAWC5mYCDhQpu2vTJj15zLTFKY6B08h+LItIpPus=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL
MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1
OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc
VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf
tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg
uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J
XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK
8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99
5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3
kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6
Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS
GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt
ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8
au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV
hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI
dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDtjCCAp6gAwIBAgIOBcAAAQACQdAGCk3OdRAwDQYJKoZIhvcNAQEFBQAwdjEL
MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDQgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
Q2VudGVyIENsYXNzIDQgQ0EgSUkwHhcNMDYwMzIzMTQxMDIzWhcNMjUxMjMxMjI1
OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgNCBDQTElMCMGA1UEAxMc
VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgNCBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBALXNTJytrlG7fEjFDSmGehSt2VA9CXIgDRS2Y8b+WJ7gIV7z
jyIZ3E6RIM1viCmis8GsKnK6i1S4QF/yqvhDhsIwXMynXX/GCEnkDjkvjhjWkd0j
FnmA22xIHbzB3ygQY9GB493fL3l1oht48pQB5hBiecugfQLANIJ7x8CtHUzXapZ2
W78mhEj9h/aECqqSB5lIPGG8ToVYx5ct/YFKocabEvVCUNFkPologiJw3fX64yhC
L04y87OjNopq1mJcrPoBbbTgci6VaLTxkwzGioLSHVPqfOA/QrcSWrjN2qUGZ8uh
d32llvCSHmcOHUJG5vnt+0dTf1cERh9GX8eu4I8CAwEAAaNCMEAwDwYDVR0TAQH/
BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFB/quz4lGwa9pd1iBX7G
TFq/6A9DMA0GCSqGSIb3DQEBBQUAA4IBAQBYpCubTPfkpJKknGWYGWIi/HIy6QRd
xMRwLVpG3kxHiiW5ot3u6hKvSI3vK2fbO8w0mCr3CEf/Iq978fTr4jgCMxh1KBue
dmWsiANy8jhHHYz1nwqIUxAUu4DlDLNdjRfuHhkcho0UZ3iMksseIUn3f9MYv5x5
+F0IebWqak2SNmy8eesOPXmK2PajVnBd3ttPedJ60pVchidlvqDTB4FAVd0Qy+BL
iILAkH0457+W4Ze6mqtCD9Of2J4VMxHL94J59bXAQVaS4d9VA61Iz9PyLrHHLVZM
ZHQqMc7cdalUR6SnQnIJ5+ECpkeyBM1CE+FhDOB4OiIgohxgQoaH96Xm
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL
MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV
BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1
c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx
MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg
R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD
VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR
JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T
fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu
jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z
wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ
fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD
VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G
CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1
7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn
8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs
ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT
ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/
2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1
OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd
AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC
FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi
1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq
jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ
wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/
WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy
NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC
uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw
IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6
g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP
BSeOE6Fuwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1
OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN
8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/
RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4
hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5
ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM
EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1
A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy
WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ
1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30
6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT
91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p
TpPDpFQUWw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB
kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw
IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG
EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD
VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu
dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6
E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ
D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK
4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq
lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW
bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB
o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT
MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js
LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr
BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB
AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj
j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH
KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv
2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3
mfnGV/TJVTl4uix5yaaIK/QI
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEojCCA4qgAwIBAgIQRL4Mi1AAJLQR0zYlJWfJiTANBgkqhkiG9w0BAQUFADCB
rjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xNjA0BgNVBAMTLVVUTi1VU0VSRmlyc3Qt
Q2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBFbWFpbDAeFw05OTA3MDkxNzI4NTBa
Fw0xOTA3MDkxNzM2NThaMIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAV
BgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5l
dHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UE
AxMtVVROLVVTRVJGaXJzdC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWls
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsjmFpPJ9q0E7YkY3rs3B
YHW8OWX5ShpHornMSMxqmNVNNRm5pELlzkniii8efNIxB8dOtINknS4p1aJkxIW9
hVE1eaROaJB7HHqkkqgX8pgV8pPMyaQylbsMTzC9mKALi+VuG6JG+ni8om+rWV6l
L8/K2m2qL+usobNqqrcuZzWLeeEeaYji5kbNoKXqvgvOdjp6Dpvq/NonWz1zHyLm
SGHGTPNpsaguG7bUMSAsvIKKjqQOpdeJQ/wWWq8dcdcRWdq6hw2v+vPhwvCkxWeM
1tZUOt4KpLoDd7NlyP0e03RiqhjKaJMeoYV+9Udly/hNVyh00jT/MLbu9mIwFIws
6wIDAQABo4G5MIG2MAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBSJgmd9xJ0mcABLtFBIfN49rgRufTBYBgNVHR8EUTBPME2gS6BJhkdodHRw
Oi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLVVTRVJGaXJzdC1DbGllbnRBdXRoZW50
aWNhdGlvbmFuZEVtYWlsLmNybDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH
AwQwDQYJKoZIhvcNAQEFBQADggEBALFtYV2mGn98q0rkMPxTbyUkxsrt4jFcKw7u
7mFVbwQ+zznexRtJlOTrIEy05p5QLnLZjfWqo7NK2lYcYJeA3IKirUq9iiv/Cwm0
xtcgBEXkzYABurorbs6q15L+5K/r9CYdFip/bDCVNy8zEqx/3cfREYxRmLLQo5HQ
rfafnoOTHh1CuEava2bwm3/q4wMC5QJRwarVNZ1yQAOJujEdxRBoUp7fooXFXAim
eOZTT7Hot9MUnpOmw2TjrH5xzbyf6QMbzPvprDHBr3wVdAKZw7JHpsIyYdfHb0gk
USeh1YdV8nuPmD0Wnu51tvjQjvLzxq4oW6fw8zYX/MMF08oDSlQ=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEZjCCA06gAwIBAgIQRL4Mi1AAJLQR0zYt4LNfGzANBgkqhkiG9w0BAQUFADCB
lTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHTAbBgNVBAMTFFVUTi1VU0VSRmlyc3Qt
T2JqZWN0MB4XDTk5MDcwOTE4MzEyMFoXDTE5MDcwOTE4NDAzNlowgZUxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAc
BgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3
dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdDCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6qgT+jo2F4qjEAVZURnicP
HxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x2AogZ8f02b+U60cEPgLO
KqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQw5ujm9M89RKZd7G3CeBo
5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbHd2pBnqcP1/vulBe3/IW+
pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh2JU022R5KP+6LhHC5ehb
kkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzTbafc8H9vg2XiaquHhnUC
AwEAAaOBrzCBrDALBgNVHQ8EBAMCAcYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov
L2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmlyc3QtT2JqZWN0LmNybDApBgNV
HSUEIjAgBggrBgEFBQcDAwYIKwYBBQUHAwgGCisGAQQBgjcKAwQwDQYJKoZIhvcN
AQEFBQADggEBAAgfUrE3RHjb/c652pWWmKpVZIC1WkDdIaXFwfNfLEzIR1pp6ujw
NTX00CXzyKakh0q9G7FzCL3Uw8q2NbtZhncxzaeAFK4T7/yxSPlrJSUtUbYsbUXB
mMiKVl0+7kNOPmsnjtA6S4ULX9Ptaqd1y9Fahy85dRNacrACgZ++8A+EVCBibGnU
4U3GDZlDAQ0Slox4nb9QorFEqmrPF3rPbw/U+CRVX/A0FklmPlBGyWNxODFiuGK5
81OtbLUrohKqGU8J2l7nk8aOFAj+8DCAGKCGhU3IfdeLA/5u1fedFqySLKAj5ZyR
Uh+U3xeUc8OzwcFxBSAAeL0TUh2oPs0AH8g=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICPDCCAaUCED9pHoGc8JpK83P/uUii5N0wDQYJKoZIhvcNAQEFBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDlGb9to1ZhLZlIcfZn3rmN67eehoAKkQ76OCWvRoiC5XOooJskXQ0f
zGVuDLDQVoQYh5oGmxChc9+0WDlrbsH2FdWoqD+qEgaNMax/sDTXjzRniAnNFBHi
TkVWaR94AoDa3EeRKbs2yWNcxeDXLYd7obcysHswuiovMaruo2fa2wIDAQABMA0G
CSqGSIb3DQEBBQUAA4GBAFgVKTk8d6PaXCUDfGD67gmZPCcQcMgMCeazh88K4hiW
NWLMv5sneYlfycQJ9M61Hd8qveXbhpxoJeUwfLaJFf5n0a3hUKw8fGJLj7qE1xIV
Gx/KXQ/BUpQqEZnae88MNhPVNdwQGVnqlMEAv3WP2fr9dgTbYruQagPZRjXZ+Hxb
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDAjCCAmsCEEzH6qqYPnHTkxD4PTqJkZIwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMg
UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQCq0Lq+Fi24g9TK0g+8djHKlNgdk4xWArzZbxpvUjZudVYK
VdPfQ4chEWWKfo+9Id5rMj8bhDSVBZ1BNeuS65bdqlk/AVNtmU/t5eIqWpDBucSm
Fc/IReumXY6cPvBkJHalzasab7bYe1FhbqZ/h8jit+U03EGI6glAvnOSPWvndQID
AQABMA0GCSqGSIb3DQEBBQUAA4GBAKlPww3HZ74sy9mozS11534Vnjty637rXC0J
h9ZrbWB85a7FkCMMXErQr7Fd88e2CtvgFZMN3QO8x3aKtd1Pw5sTdbgBwObJW2ul
uIncrKTdcu1OofdPvAbT6shkdHvClUGcZXNY8ZCaPGqxmMnEh7zPRW1F4m4iP/68
DzFc6PLZ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN2E1Lm0+afY8wR4
nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/EbRrsC+MO
8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjV
ojYJrKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjb
PG7PoBMAGrgnoeS+Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP2
6KbqxzcSXKMpHgLZ2x87tNcPVkeBFQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vr
n5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAq2aN17O6x5q25lXQBfGfMY1a
qtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/Ny9Sn2WCVhDr4
wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3
ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrs
pSCAaWihT37ha88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4
E1Z5T21Q6huwtVexN2ZYI/PcD98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0Ns
YXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
MjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y
aXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazAe
Fw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJVUzEX
MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGlj
IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMx
KGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
eTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazCBnzANBgkqhkiG9w0B
AQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjxnNuX6Zr8wgQGE75fUsjM
HiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRCwiNPStjw
DqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cC
AwEAATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9ji
nb3/7aHmZuovCfTK1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAX
rXfMSTWqz9iP0b63GJZHc2pUIjRkLbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnIn
jBJ7xUS0rg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJ
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVy
aVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24s
IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNp
Z24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJBgNV
BAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNp
Z24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIElu
Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24g
Q2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt
IEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwoNwtUs22e5LeWU
J92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6tW8UvxDO
JxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUY
wZF7C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9o
koqQHgiBVrKtaaNS0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjN
qWm6o+sdDZykIKbBoMXRRkwXbdKsZj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/E
Srg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0JhU8wI1NQ0kdvekhktdmnLfe
xbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf0xwLRtxyID+u
7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU
sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RI
sH/7NiXaldDxJBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTP
cjnhsUPgKM+351psE2tJs//jGHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
oJ2daZH9
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp
U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg
SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln
biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm
GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve
fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ
aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj
aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW
kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC
4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga
FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICoTCCAgqgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCWkEx
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzAN
BgNVBAoTBlRoYXd0ZTEdMBsGA1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAd
BgNVBAMTFlRoYXd0ZSBUaW1lc3RhbXBpbmcgQ0EwHhcNOTcwMTAxMDAwMDAwWhcN
MjAxMjMxMjM1OTU5WjCBizELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4g
Q2FwZTEUMBIGA1UEBxMLRHVyYmFudmlsbGUxDzANBgNVBAoTBlRoYXd0ZTEdMBsG
A1UECxMUVGhhd3RlIENlcnRpZmljYXRpb24xHzAdBgNVBAMTFlRoYXd0ZSBUaW1l
c3RhbXBpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANYrWHhhRYZT
6jR7UZztsOYuGA7+4F+oJ9O0yeB8WU4WDnNUYMF/9p8u6TqFJBU820cEY8OexJQa
Wt9MevPZQx08EHp5JduQ/vBR5zDWQQD9nyjfeb6Uu522FOMjhdepQeBMpHmwKxqL
8vg7ij5FrHGSALSQQZj7X+36ty6K+Ig3AgMBAAGjEzARMA8GA1UdEwEB/wQFMAMB
Af8wDQYJKoZIhvcNAQEEBQADgYEAZ9viwuaHPUCDhjc1fR/OmsMMZiCouqoEiYbC
9RAIDb/LogWK0E02PvTX72nGXuSwlG9KuefeW4i2e9vjJ+V2w/A1wcu1J5szedyQ
pgCed/r8zSeUQhac0xxo7L9c3eWpexAKMnRUEzGLhQOEkbdYATAUOK8oyvyxUBkZ
CayJSdM=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB
vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX
MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0
IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y
IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh
bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF
9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH
H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H
LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN
/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT
rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw
WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs
exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4
sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+
seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz
4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+
BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR
lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
7M2CYfE45k+XmCpajQ==
-----END CERTIFICATE-----
------5FCD663C81182946FE75D58C22A8C67A
Content-Type: application/x-pkcs7-signature; name="smime.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
MIIGiwYJKoZIhvcNAQcCoIIGfDCCBngCAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3
DQEHAaCCBAEwggP9MIIC5aADAgECAgEBMA0GCSqGSIb3DQEBDQUAMEsxCzAJBgNV
BAYTAkpQMREwDwYDVQQKDAhjdG9yLm9yZzEUMBIGA1UECwwLRGV2ZWxvcG1lbnQx
EzARBgNVBAMMCmh0dHBjbGllbnQwHhcNMDkwNTIxMTIzOTA1WhcNMzcxMjMxMjM1
OTU5WjBLMQswCQYDVQQGEwJKUDERMA8GA1UECgwIY3Rvci5vcmcxFDASBgNVBAsM
C0RldmVsb3BtZW50MRMwEQYDVQQDDApodHRwY2xpZW50MIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAzY+WR1Mf3vO8geg8gyPzvCc2+mohBQPsv+GXKw7R
cGyZH9dlR5RkoHXQlCoxe+JalOdT0pTrEZPHcQjjB93Z2kvAf93h3srgueBbWRju
QmVl4mFqSegbNDjRFIj1aRDRH0o4Tzzx/s6RMFp9wvxTJYVUgyFAHDZ32I2Xglzr
1CiNrIIOhJddtsQ/SfzKLsfAhTdEW5foO0yk7Hj/UrtnTTZxw4Yncn2QeB4NBJC0
XjZxBgLuqPbrsj85CCcHvJzFMI/QTtt1dCD7OqyeCuFqg2f/4j4Kg7ucPi3i5D4H
hqAAuHg8/GxtAP0wIv5W6s0Yti/QdV35mUtuWl10L08tOwIDAQABo4HrMIHoMA8G
A1UdEwEB/wQFMAMBAf8wMQYJYIZIAYb4QgENBCQWIlJ1YnkvT3BlblNTTCBHZW5l
cmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFECcHpeUyg5ybsdUDD4lbFjbTk8d
MA4GA1UdDwEB/wQEAwIBBjBzBgNVHSMEbDBqgBRAnB6XlMoOcm7HVAw+JWxY205P
HaFPpE0wSzELMAkGA1UEBhMCSlAxETAPBgNVBAoMCGN0b3Iub3JnMRQwEgYDVQQL
DAtEZXZlbG9wbWVudDETMBEGA1UEAwwKaHR0cGNsaWVudIIBATANBgkqhkiG9w0B
AQ0FAAOCAQEAFUV6nJsPleqwGc6f+gMe8rTEA8wXgCnge3XIxAs5uDylpWEA1J2o
h68+UKv+Y8sNGNzL4nV8KcTqUJ+x8t1OACOKuq3ymb3Y1xzGYnyFOYx3ctYLWf55
hZqiWpMJVEwGl7xRj16fJesWi50eWv16mvXwToCzpWsJJ+lcVmQP1YdVJanOQHeo
BO7mpwMLMIdsjTvnkx2b/WEokIPiXr2Hcnc6FEgRQ8l8ec+8znC2LILZ1wT2K3AT
/B1XMEl4gFclCb5CP4rypdxFPNlkYgGPDafjrEmBbpidw0IdT9ZZ80JjB2SeAdL9
o3whsSOELB++JcWKxWsM3/6llkYx/rtlpjGCAlIwggJOAgEBMFAwSzELMAkGA1UE
BhMCSlAxETAPBgNVBAoMCGN0b3Iub3JnMRQwEgYDVQQLDAtEZXZlbG9wbWVudDET
MBEGA1UEAwwKaHR0cGNsaWVudAIBATAJBgUrDgMCGgUAoIHYMBgGCSqGSIb3DQEJ
AzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTEzMDEwMTAyNDUyNlowIwYJ
KoZIhvcNAQkEMRYEFJckqjbw92vWqUryx9P8VUwbej47MHkGCSqGSIb3DQEJDzFs
MGowCwYJYIZIAWUDBAEqMAsGCWCGSAFlAwQBFjALBglghkgBZQMEAQIwCgYIKoZI
hvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsOAwIHMA0G
CCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABIIBAMm/55qbk5tKufy4fxd9po/B
1Fv45SvkTCWo0C2l6t1ZrqfkJNLLjSVP8CKAt4NVIo34RjEiQpfLtYbKu1yQlW9N
tcfode1vgS8ECRvFzNQAlBe5Ei9ypLdH+0sz6tEWX70W+fOSYqtJjghrbVvxcxjx
B1FdzJkFFV+woKVtS/oFGzv+daL49JqZe1I+LB9hsNf6Z8rcVu2NI4DCnT8yD6HT
i3jvUxGQSJx4w3oZQW0wXEZB89c0tKCACrYZkNVYIerRkmz5oH/F6NcRicDR6aSg
4fskz2tsbOp2tB4oVcQIWsheDeoXJQMpcejrh+9+LmP5hO4iZ3SzWghylLcb6u4=
------5FCD663C81182946FE75D58C22A8C67A--
httpclient-2.3.3/lib/httpclient/version.rb 0000644 0000041 0000041 00000000051 12155433207 020661 0 ustar www-data www-data class HTTPClient
VERSION = '2.3.3'
end
httpclient-2.3.3/lib/httpclient/connection.rb 0000644 0000041 0000041 00000004220 12155433207 021335 0 ustar www-data www-data # HTTPClient - HTTP client library.
# Copyright (C) 2000-2009 NAKAMURA, Hiroshi .
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
# either the dual license version in 2003, or any later version.
class HTTPClient
# Represents a HTTP response to an asynchronous request. Async methods of
# HTTPClient such as get_async, post_async, etc. returns an instance of
# Connection.
#
# == How to use
#
# 1. Invoke HTTP method asynchronously and check if it's been finished
# periodically.
#
# connection = clnt.post_async(url, body)
# print 'posting.'
# while true
# break if connection.finished?
# print '.'
# sleep 1
# end
# puts '.'
# res = connection.pop
# p res.status
#
# 2. Read the response as an IO.
#
# connection = clnt.get_async('http://dev.ctor.org/')
# io = connection.pop.content
# while str = io.read(40)
# p str
# end
class Connection
attr_accessor :async_thread
def initialize(header_queue = [], body_queue = []) # :nodoc:
@headers = header_queue
@body = body_queue
@async_thread = nil
@queue = Queue.new
end
# Checks if the asynchronous invocation has been finished or not.
def finished?
if !@async_thread
# Not in async mode.
true
elsif @async_thread.alive?
# Working...
false
else
# Async thread have been finished.
join
true
end
end
# Retrieves a HTTP::Message instance of HTTP response. Do not invoke this
# method twice for now. The second invocation will be blocked.
def pop
response_or_exception = @queue.pop
if response_or_exception.is_a? Exception
raise response_or_exception
end
response_or_exception
end
def push(result) # :nodoc:
@queue.push(result)
end
# Waits the completion of the asynchronous invocation.
def join
if @async_thread
@async_thread.join
end
nil
end
end
end
httpclient-2.3.3/lib/httpclient/timeout.rb 0000644 0000041 0000041 00000006645 12155433207 020701 0 ustar www-data www-data # HTTPClient - HTTP client library.
# Copyright (C) 2000-2009 NAKAMURA, Hiroshi .
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
# either the dual license version in 2003, or any later version.
require 'timeout'
require 'thread'
class HTTPClient
# Replaces timeout.rb to avoid Thread creation and scheduling overhead.
#
# You should check another timeout replace in WEBrick.
# See lib/webrick/utils.rb in ruby/1.9.
#
# About this implementation:
# * Do not create Thread for each timeout() call. Just create 1 Thread for
# timeout scheduler.
# * Do not wakeup the scheduler thread so often. Let scheduler thread sleep
# until the nearest period.
if !defined?(JRUBY_VERSION) and RUBY_VERSION < '1.9'
class TimeoutScheduler
# Represents timeout period.
class Period
attr_reader :thread, :time
# Creates new Period.
def initialize(thread, time, ex)
@thread, @time, @ex = thread, time, ex
@lock = Mutex.new
end
# Raises if thread exists and alive.
def raise(message)
@lock.synchronize do
if @thread and @thread.alive?
@thread.raise(@ex, message)
end
end
end
# Cancel this Period. Mutex is needed to avoid too-late exception.
def cancel
@lock.synchronize do
@thread = nil
end
end
end
# Creates new TimeoutScheduler.
def initialize
@pool = {}
@next = nil
@thread = start_timer_thread
end
# Registers new timeout period.
def register(thread, sec, ex)
period = Period.new(thread, Time.now + sec, ex || ::Timeout::Error)
@pool[period] = true
if @next.nil? or period.time < @next
begin
@thread.wakeup
rescue ThreadError
# Thread may be dead by fork.
@thread = start_timer_thread
end
end
period
end
# Cancels the given period.
def cancel(period)
@pool.delete(period)
period.cancel
end
private
def start_timer_thread
thread = Thread.new {
while true
if @pool.empty?
@next = nil
sleep
else
min, = @pool.min { |a, b| a[0].time <=> b[0].time }
@next = min.time
sec = @next - Time.now
if sec > 0
sleep(sec)
end
end
now = Time.now
@pool.keys.each do |period|
if period.time < now
period.raise('execution expired')
cancel(period)
end
end
end
}
Thread.pass while thread.status != 'sleep'
thread
end
end
class << self
# CAUTION: caller must aware of race condition.
def timeout_scheduler
@timeout_scheduler ||= TimeoutScheduler.new
end
end
timeout_scheduler # initialize at first time.
end
module Timeout
if !defined?(JRUBY_VERSION) and RUBY_VERSION < '1.9'
def timeout(sec, ex = nil, &block)
return yield if sec == nil or sec.zero?
scheduler = nil
begin
scheduler = HTTPClient.timeout_scheduler
period = scheduler.register(Thread.current, sec, ex)
yield(sec)
ensure
scheduler.cancel(period) if scheduler and period
end
end
end
end
end
httpclient-2.3.3/lib/httpclient/http.rb 0000644 0000041 0000041 00000073161 12155433207 020167 0 ustar www-data www-data # HTTPClient - HTTP client library.
# Copyright (C) 2000-2009 NAKAMURA, Hiroshi .
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
# either the dual license version in 2003, or any later version.
require 'time'
if defined?(Encoding::ASCII_8BIT)
require 'open-uri' # for encoding
end
# A namespace module for HTTP Message definitions used by HTTPClient.
module HTTP
# Represents HTTP response status code. Defines constants for HTTP response
# and some conditional methods.
module Status
OK = 200
CREATED = 201
ACCEPTED = 202
NON_AUTHORITATIVE_INFORMATION = 203
NO_CONTENT = 204
RESET_CONTENT = 205
PARTIAL_CONTENT = 206
MOVED_PERMANENTLY = 301
FOUND = 302
SEE_OTHER = 303
TEMPORARY_REDIRECT = MOVED_TEMPORARILY = 307
BAD_REQUEST = 400
UNAUTHORIZED = 401
PROXY_AUTHENTICATE_REQUIRED = 407
INTERNAL = 500
# Status codes for successful HTTP response.
SUCCESSFUL_STATUS = [
OK, CREATED, ACCEPTED,
NON_AUTHORITATIVE_INFORMATION, NO_CONTENT,
RESET_CONTENT, PARTIAL_CONTENT
]
# Status codes which is a redirect.
REDIRECT_STATUS = [
MOVED_PERMANENTLY, FOUND, SEE_OTHER,
TEMPORARY_REDIRECT, MOVED_TEMPORARILY
]
# Returns true if the given status represents successful HTTP response.
# See also SUCCESSFUL_STATUS.
def self.successful?(status)
SUCCESSFUL_STATUS.include?(status)
end
# Returns true if the given status is thought to be redirect.
# See also REDIRECT_STATUS.
def self.redirect?(status)
REDIRECT_STATUS.include?(status)
end
end
# Represents a HTTP message. A message is for a request or a response.
#
# Request message is generated from given parameters internally so users
# don't need to care about it. Response message is the instance that
# methods of HTTPClient returns so users need to know how to extract
# HTTP response data from Message.
#
# Some attributes are only for a request or a response, not both.
#
# == How to use HTTP response message
#
# 1. Gets response message body.
#
# res = clnt.get(url)
# p res.content #=> String
#
# 2. Gets response status code.
#
# res = clnt.get(url)
# p res.status #=> 200, 501, etc. (Integer)
#
# 3. Gets response header.
#
# res = clnt.get(url)
# res.header['set-cookie'].each do |value|
# p value
# end
# assert_equal(1, res.header['last-modified'].size)
# p res.header['last-modified'].first
#
class Message
CRLF = "\r\n"
# Represents HTTP message header.
class Headers
# HTTP version in a HTTP header. String.
attr_accessor :http_version
# Size of body. nil when size is unknown (e.g. chunked response).
attr_reader :body_size
# Request/Response is chunked or not.
attr_accessor :chunked
# Request only. Requested method.
attr_reader :request_method
# Request only. Requested URI.
attr_accessor :request_uri
# Request only. Requested query.
attr_accessor :request_query
# Request only. Requested via proxy or not.
attr_accessor :request_absolute_uri
# Response only. HTTP status
attr_reader :status_code
# Response only. HTTP status reason phrase.
attr_accessor :reason_phrase
# Used for dumping response.
attr_accessor :body_type # :nodoc:
# Used for dumping response.
attr_accessor :body_charset # :nodoc:
# Used for dumping response.
attr_accessor :body_date # :nodoc:
# Used for keeping content encoding.
attr_reader :body_encoding # :nodoc:
# HTTP response status code to reason phrase mapping definition.
STATUS_CODE_MAP = {
Status::OK => 'OK',
Status::CREATED => "Created",
Status::NON_AUTHORITATIVE_INFORMATION => "Non-Authoritative Information",
Status::NO_CONTENT => "No Content",
Status::RESET_CONTENT => "Reset Content",
Status::PARTIAL_CONTENT => "Partial Content",
Status::MOVED_PERMANENTLY => 'Moved Permanently',
Status::FOUND => 'Found',
Status::SEE_OTHER => 'See Other',
Status::TEMPORARY_REDIRECT => 'Temporary Redirect',
Status::MOVED_TEMPORARILY => 'Temporary Redirect',
Status::BAD_REQUEST => 'Bad Request',
Status::INTERNAL => 'Internal Server Error',
}
# $KCODE to charset mapping definition.
CHARSET_MAP = {
'NONE' => 'us-ascii',
'EUC' => 'euc-jp',
'SJIS' => 'shift_jis',
'UTF8' => 'utf-8',
}
# Creates a Message::Headers. Use init_request, init_response, or
# init_connect_request for acutual initialize.
def initialize
@http_version = '1.1'
@body_size = nil
@chunked = false
@request_method = nil
@request_uri = nil
@request_query = nil
@request_absolute_uri = nil
@status_code = nil
@reason_phrase = nil
@body_type = nil
@body_charset = nil
@body_date = nil
@body_encoding = nil
@is_request = nil
@header_item = []
@dumped = false
end
# Initialize this instance as a CONNECT request.
def init_connect_request(uri)
@is_request = true
@request_method = 'CONNECT'
@request_uri = uri
@request_query = nil
@http_version = '1.0'
end
# Placeholder URI object for nil uri.
NIL_URI = HTTPClient::Util.urify('http://nil-uri-given/')
# Initialize this instance as a general request.
def init_request(method, uri, query = nil)
@is_request = true
@request_method = method
@request_uri = uri || NIL_URI
@request_query = query
@request_absolute_uri = false
end
# Initialize this instance as a response.
def init_response(status_code, req = nil)
@is_request = false
self.status_code = status_code
if req
@request_method = req.request_method
@request_uri = req.request_uri
@request_query = req.request_query
end
end
# Sets status code and reason phrase.
def status_code=(status_code)
@status_code = status_code
@reason_phrase = STATUS_CODE_MAP[@status_code]
end
# Returns 'Content-Type' header value.
def content_type
self['Content-Type'][0]
end
# Sets 'Content-Type' header value. Overrides if already exists.
def content_type=(content_type)
delete('Content-Type')
self['Content-Type'] = content_type
end
alias contenttype content_type
alias contenttype= content_type=
if defined?(Encoding::ASCII_8BIT)
def set_body_encoding
if type = self.content_type
OpenURI::Meta.init(o = '')
o.meta_add_field('content-type', type)
@body_encoding = o.encoding
end
end
else
def set_body_encoding
@body_encoding = nil
end
end
# Sets byte size of message body.
# body_size == nil means that the body is_a? IO
def body_size=(body_size)
@body_size = body_size
end
# Dumps message header part and returns a dumped String.
def dump
set_header
str = nil
if @is_request
str = request_line
else
str = response_status_line
end
str + @header_item.collect { |key, value|
"#{ key }: #{ value }#{ CRLF }"
}.join
end
# Set Date header
def set_date_header
set('Date', Time.now.httpdate)
end
# Adds a header. Addition order is preserved.
def add(key, value)
if value.is_a?(Array)
value.each do |v|
@header_item.push([key, v])
end
else
@header_item.push([key, value])
end
end
# Sets a header.
def set(key, value)
delete(key)
add(key, value)
end
# Returns an Array of headers for the given key. Each element is a pair
# of key and value. It returns an single element Array even if the only
# one header exists. If nil key given, it returns all headers.
def get(key = nil)
if key.nil?
all
else
key = key.upcase
@header_item.find_all { |k, v| k.upcase == key }
end
end
# Returns an Array of all headers.
def all
@header_item
end
# Deletes headers of the given key.
def delete(key)
key = key.upcase
@header_item.delete_if { |k, v| k.upcase == key }
end
# Adds a header. See set.
def []=(key, value)
set(key, value)
end
# Returns an Array of header values for the given key.
def [](key)
get(key).collect { |item| item[1] }
end
def set_headers(headers)
headers.each do |key, value|
add(key, value)
end
set_body_encoding
end
def create_query_uri()
if @request_method == 'CONNECT'
return "#{@request_uri.host}:#{@request_uri.port}"
end
path = @request_uri.path
path = '/' if path.nil? or path.empty?
if query_str = create_query_part()
path += "?#{query_str}"
end
path
end
def create_query_part()
query_str = nil
if @request_uri.query
query_str = @request_uri.query
end
if @request_query
if query_str
query_str += "{Message.create_query_part_str(@request_query)}"
else
query_str = Message.create_query_part_str(@request_query)
end
end
query_str
end
private
def request_line
path = create_query_uri()
if @request_absolute_uri
path = "#{ @request_uri.scheme }://#{ @request_uri.host }:#{ @request_uri.port }#{ path }"
end
"#{ @request_method } #{ path } HTTP/#{ @http_version }#{ CRLF }"
end
def response_status_line
if defined?(Apache)
"HTTP/#{ @http_version } #{ @status_code } #{ @reason_phrase }#{ CRLF }"
else
"Status: #{ @status_code } #{ @reason_phrase }#{ CRLF }"
end
end
def set_header
if @is_request
set_request_header
else
set_response_header
end
end
def set_request_header
return if @dumped
@dumped = true
keep_alive = Message.keep_alive_enabled?(@http_version)
if !keep_alive and @request_method != 'CONNECT'
set('Connection', 'close')
end
if @chunked
set('Transfer-Encoding', 'chunked')
elsif @body_size and (keep_alive or @body_size != 0)
set('Content-Length', @body_size.to_s)
end
if @http_version >= '1.1' and get('Host').empty?
if @request_uri.port == @request_uri.default_port
# GFE/1.3 dislikes default port number (returns 404)
set('Host', "#{@request_uri.hostname}")
else
set('Host', "#{@request_uri.hostname}:#{@request_uri.port}")
end
end
end
def set_response_header
return if @dumped
@dumped = true
if defined?(Apache) && self['Date'].empty?
set_date_header
end
keep_alive = Message.keep_alive_enabled?(@http_version)
if @chunked
set('Transfer-Encoding', 'chunked')
else
if keep_alive or @body_size != 0
set('Content-Length', @body_size.to_s)
end
end
if @body_date
set('Last-Modified', @body_date.httpdate)
end
if self['Content-Type'].empty?
set('Content-Type', "#{ @body_type || 'text/html' }; charset=#{ charset_label }")
end
end
def charset_label
# TODO: should handle response encoding for 1.9 correctly.
if RUBY_VERSION > "1.9"
CHARSET_MAP[@body_charset] || 'us-ascii'
else
CHARSET_MAP[@body_charset || $KCODE] || 'us-ascii'
end
end
end
# Represents HTTP message body.
class Body
# Size of body. nil when size is unknown (e.g. chunked response).
attr_reader :size
# maxbytes of IO#read for streaming request. See DEFAULT_CHUNK_SIZE.
attr_accessor :chunk_size
# Default value for chunk_size
DEFAULT_CHUNK_SIZE = 1024 * 16
# Creates a Message::Body. Use init_request or init_response
# for acutual initialize.
def initialize
@body = nil
@size = nil
@positions = nil
@chunk_size = nil
end
# Initialize this instance as a request.
def init_request(body = nil, boundary = nil)
@boundary = boundary
@positions = {}
set_content(body, boundary)
@chunk_size = DEFAULT_CHUNK_SIZE
end
# Initialize this instance as a response.
def init_response(body = nil)
@body = body
if @body.respond_to?(:bytesize)
@size = @body.bytesize
elsif @body.respond_to?(:size)
@size = @body.size
else
@size = nil
end
end
# Dumps message body to given dev.
# dev needs to respond to <<.
#
# Message header must be given as the first argument for performance
# reason. (header is dumped to dev, too)
# If no dev (the second argument) given, this method returns a dumped
# String.
def dump(header = '', dev = '')
if @body.is_a?(Parts)
dev << header
@body.parts.each do |part|
if Message.file?(part)
reset_pos(part)
dump_file(part, dev)
else
dev << part
end
end
elsif Message.file?(@body)
dev << header
reset_pos(@body)
dump_file(@body, dev)
elsif @body
dev << header + @body
else
dev << header
end
dev
end
# Dumps message body with chunked encoding to given dev.
# dev needs to respond to <<.
#
# Message header must be given as the first argument for performance
# reason. (header is dumped to dev, too)
# If no dev (the second argument) given, this method returns a dumped
# String.
def dump_chunked(header = '', dev = '')
dev << header
if @body.is_a?(Parts)
@body.parts.each do |part|
if Message.file?(part)
reset_pos(part)
dump_chunks(part, dev)
else
dev << dump_chunk(part)
end
end
dev << (dump_last_chunk + CRLF)
elsif @body
reset_pos(@body)
dump_chunks(@body, dev)
dev << (dump_last_chunk + CRLF)
end
dev
end
# Returns a message body itself.
def content
@body
end
private
def set_content(body, boundary = nil)
if Message.file?(body)
# uses Transfer-Encoding: chunked if body does not respond to :size.
# bear in mind that server may not support it. at least ruby's CGI doesn't.
@body = body
remember_pos(@body)
@size = body.respond_to?(:size) ? body.size - body.pos : nil
elsif boundary and Message.multiparam_query?(body)
@body = build_query_multipart_str(body, boundary)
@size = @body.size
else
@body = Message.create_query_part_str(body)
@size = @body.bytesize
end
end
def remember_pos(io)
# IO may not support it (ex. IO.pipe)
@positions[io] = io.pos if io.respond_to?(:pos)
end
def reset_pos(io)
io.pos = @positions[io] if @positions.key?(io)
end
def dump_file(io, dev)
buf = ''
while !io.read(@chunk_size, buf).nil?
dev << buf
end
end
def dump_chunks(io, dev)
buf = ''
while !io.read(@chunk_size, buf).nil?
dev << dump_chunk(buf)
end
end
def dump_chunk(str)
dump_chunk_size(str.bytesize) + (str + CRLF)
end
def dump_last_chunk
dump_chunk_size(0)
end
def dump_chunk_size(size)
sprintf("%x", size) + CRLF
end
class Parts
attr_reader :size
def initialize
@body = []
@size = 0
@as_stream = false
end
def add(part)
if Message.file?(part)
@as_stream = true
@body << part
if part.respond_to?(:lstat)
@size += part.lstat.size
elsif part.respond_to?(:size)
if sz = part.size
@size += sz
else
@size = nil
end
else
# use chunked upload
@size = nil
end
elsif @body[-1].is_a?(String)
@body[-1] += part.to_s
@size += part.to_s.bytesize if @size
else
@body << part.to_s
@size += part.to_s.bytesize if @size
end
end
def parts
if @as_stream
@body
else
[@body.join]
end
end
end
def build_query_multipart_str(query, boundary)
parts = Parts.new
query.each do |attr, value|
headers = ["--#{boundary}"]
if Message.file?(value)
remember_pos(value)
param_str = params_from_file(value).collect { |k, v|
"#{k}=\"#{v}\""
}.join("; ")
if value.respond_to?(:mime_type)
content_type = value.mime_type
elsif value.respond_to?(:content_type)
content_type = value.content_type
else
path = value.respond_to?(:path) ? value.path : nil
content_type = Message.mime_type(path)
end
headers << %{Content-Disposition: form-data; name="#{attr}"; #{param_str}}
headers << %{Content-Type: #{content_type}}
elsif attr.is_a?(Hash)
h = attr
value = h[:content]
h.each do |h_key, h_val|
headers << %{#{h_key}: #{h_val}} if h_key != :content
end
remember_pos(value) if Message.file?(value)
else
headers << %{Content-Disposition: form-data; name="#{attr}"}
value = value.to_s
end
parts.add(headers.join(CRLF) + CRLF + CRLF)
parts.add(value)
parts.add(CRLF)
end
parts.add("--#{boundary}--" + CRLF + CRLF) # empty epilogue
parts
end
def params_from_file(value)
params = {}
path = value.respond_to?(:path) ? value.path : nil
params['filename'] = File.basename(path || '')
# Creation time is not available from File::Stat
if value.respond_to?(:mtime)
params['modification-date'] = value.mtime.rfc822
end
if value.respond_to?(:atime)
params['read-date'] = value.atime.rfc822
end
params
end
end
class << self
private :new
# Creates a Message instance of 'CONNECT' request.
# 'CONNECT' request does not have Body.
# uri:: an URI that need to connect. Only uri.host and uri.port are used.
def new_connect_request(uri)
m = new
m.http_header.init_connect_request(uri)
m.http_header.body_size = nil
m
end
# Creates a Message instance of general request.
# method:: HTTP method String.
# uri:: an URI object which represents an URL of web resource.
# query:: a Hash or an Array of query part of URL.
# e.g. { "a" => "b" } => 'http://host/part?a=b'
# Give an array to pass multiple value like
# [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'
# body:: a Hash or an Array of body part.
# e.g. { "a" => "b" } => 'a=b'.
# Give an array to pass multiple value like
# [["a", "b"], ["a", "c"]] => 'a=b&a=c'.
# boundary:: When the boundary given, it is sent as
# a multipart/form-data using this boundary String.
def new_request(method, uri, query = nil, body = nil, boundary = nil)
m = new
m.http_header.init_request(method, uri, query)
m.http_body = Body.new
m.http_body.init_request(body || '', boundary)
if body
m.http_header.body_size = m.http_body.size
m.http_header.chunked = true if m.http_body.size.nil?
else
m.http_header.body_size = nil
end
m
end
# Creates a Message instance of response.
# body:: a String or an IO of response message body.
def new_response(body, req = nil)
m = new
m.http_header.init_response(Status::OK, req)
m.http_body = Body.new
m.http_body.init_response(body)
m.http_header.body_size = m.http_body.size || 0
m
end
@@mime_type_handler = nil
# Sets MIME type handler.
#
# handler must respond to :call with a single argument :path and returns
# a MIME type String e.g. 'text/html'.
# When the handler returns nil or an empty String,
# 'application/octet-stream' is used.
#
# When you set nil to the handler, internal_mime_type is used instead.
# The handler is nil by default.
def mime_type_handler=(handler)
@@mime_type_handler = handler
end
# Returns MIME type handler.
def mime_type_handler
@@mime_type_handler
end
# For backward compatibility.
alias set_mime_type_func mime_type_handler=
alias get_mime_type_func mime_type_handler
def mime_type(path) # :nodoc:
if @@mime_type_handler
res = @@mime_type_handler.call(path)
if !res || res.to_s == ''
return 'application/octet-stream'
else
return res
end
else
internal_mime_type(path)
end
end
# Default MIME type handler.
# See mime_type_handler=.
def internal_mime_type(path)
case path
when /\.txt$/i
'text/plain'
when /\.(htm|html)$/i
'text/html'
when /\.doc$/i
'application/msword'
when /\.png$/i
'image/png'
when /\.gif$/i
'image/gif'
when /\.(jpg|jpeg)$/i
'image/jpeg'
else
'application/octet-stream'
end
end
# Returns true if the given HTTP version allows keep alive connection.
# version:: String
def keep_alive_enabled?(version)
version >= '1.1'
end
# Returns true if the given query (or body) has a multiple parameter.
def multiparam_query?(query)
query.is_a?(Array) or query.is_a?(Hash)
end
# Returns true if the given object is a File. In HTTPClient, a file is;
# * must respond to :read for retrieving String chunks.
# * must respond to :pos and :pos= to rewind for reading.
# Rewinding is only needed for following HTTP redirect. Some IO impl
# defines :pos= but raises an Exception for pos= such as StringIO
# but there's no problem as far as using it for non-following methods
# (get/post/etc.)
def file?(obj)
obj.respond_to?(:read) and obj.respond_to?(:pos) and
obj.respond_to?(:pos=)
end
def create_query_part_str(query) # :nodoc:
if multiparam_query?(query)
escape_query(query)
elsif query.respond_to?(:read)
query = query.read
else
query.to_s
end
end
def Array.try_convert(value)
return value if value.instance_of?(Array)
return nil if !value.respond_to?(:to_ary)
converted = value.to_ary
return converted if converted.instance_of?(Array)
cname = value.class.name
raise TypeError, "can't convert %s to %s (%s#%s gives %s)" %
[cname, Array.name, cname, :to_ary, converted.class.name]
end unless Array.respond_to?(:try_convert)
def escape_query(query) # :nodoc:
pairs = []
query.each { |attr, value|
left = escape(attr.to_s) << '='
if values = Array.try_convert(value)
values.each { |value|
if value.respond_to?(:read)
value = value.read
end
pairs.push(left + escape(value.to_s))
}
else
if value.respond_to?(:read)
value = value.read
end
pairs.push(left << escape(value.to_s))
end
}
pairs.join('&')
end
# from CGI.escape
if defined?(Encoding::ASCII_8BIT)
def escape(str) # :nodoc:
str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(/([^ a-zA-Z0-9_.-]+)/) {
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
}.tr(' ', '+')
end
else
def escape(str) # :nodoc:
str.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
}.tr(' ', '+')
end
end
# from CGI.parse
def parse(query)
params = Hash.new([].freeze)
query.split(/[&;]/n).each do |pairs|
key, value = pairs.split('=',2).collect{|v| unescape(v) }
if params.has_key?(key)
params[key].push(value)
else
params[key] = [value]
end
end
params
end
# from CGI.unescape
def unescape(string)
string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do
[$1.delete('%')].pack('H*')
end
end
end
# HTTP::Message::Headers:: message header.
attr_accessor :http_header
# HTTP::Message::Body:: message body.
attr_reader :http_body
# OpenSSL::X509::Certificate:: response only. server certificate which is
# used for retrieving the response.
attr_accessor :peer_cert
# Creates a Message. This method should be used internally.
# Use Message.new_connect_request, Message.new_request or
# Message.new_response instead.
def initialize # :nodoc:
@http_header = Headers.new
@http_body = @peer_cert = nil
end
# Dumps message (header and body) to given dev.
# dev needs to respond to <<.
def dump(dev = '')
str = @http_header.dump + CRLF
if @http_header.chunked
dev = @http_body.dump_chunked(str, dev)
elsif @http_body
dev = @http_body.dump(str, dev)
else
dev << str
end
dev
end
# Sets a new body. header.body_size is updated with new body.size.
def http_body=(body)
@http_body = body
@http_header.body_size = @http_body.size if @http_header
end
alias body= http_body=
# Returns HTTP version in a HTTP header. String.
def http_version
@http_header.http_version
end
# Sets HTTP version in a HTTP header. String.
def http_version=(http_version)
@http_header.http_version = http_version
end
VERSION_WARNING = 'Message#version (Float) is deprecated. Use Message#http_version (String) instead.'
def version
warn(VERSION_WARNING)
@http_header.http_version.to_f
end
def version=(version)
warn(VERSION_WARNING)
@http_header.http_version = version
end
# Returns HTTP status code in response. Integer.
def status
@http_header.status_code
end
alias code status
alias status_code status
# Sets HTTP status code of response. Integer.
# Reason phrase is updated, too.
def status=(status)
@http_header.status_code = status
end
# Returns HTTP status reason phrase in response. String.
def reason
@http_header.reason_phrase
end
# Sets HTTP status reason phrase of response. String.
def reason=(reason)
@http_header.reason_phrase = reason
end
# Returns 'Content-Type' header value.
def content_type
@http_header.content_type
end
# Sets 'Content-Type' header value. Overrides if already exists.
def content_type=(content_type)
@http_header.content_type = content_type
end
alias contenttype content_type
alias contenttype= content_type=
# Returns content encoding
def body_encoding
@http_header.body_encoding
end
# Returns a content of message body. A String or an IO.
def content
@http_body.content
end
alias header http_header
alias body content
# Returns Hash of header. key and value are both String. Each key has a
# single value so you can't extract exact value when a message has multiple
# headers like 'Set-Cookie'. Use header['Set-Cookie'] for that purpose.
# (It returns an Array always)
def headers
Hash[*http_header.all.flatten]
end
# Extracts cookies from 'Set-Cookie' header.
# Supports 'Set-Cookie' in response header only.
# Do we need 'Cookie' support in request header?
def cookies
set_cookies = http_header['set-cookie']
unless set_cookies.empty?
uri = http_header.request_uri
set_cookies.map { |str|
cookie = WebAgent::Cookie.new
cookie.parse(str, uri)
cookie
}
end
end
# Convenience method to return boolean of whether we had a successful request
def ok?
HTTP::Status.successful?(status)
end
def redirect?
HTTP::Status.redirect?(status)
end
# SEE_OTHER is a redirect, but it should sent as GET
def see_other?
status == HTTP::Status::SEE_OTHER
end
end
end
httpclient-2.3.3/lib/httpclient/util.rb 0000644 0000041 0000041 00000010670 12155433207 020161 0 ustar www-data www-data # HTTPClient - HTTP client library.
# Copyright (C) 2000-2012 NAKAMURA, Hiroshi .
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
# either the dual license version in 2003, or any later version.
unless ''.respond_to?(:bytesize)
class String
alias bytesize size
end
end
if RUBY_VERSION < "1.9.3"
require 'uri'
module URI
class Generic
def hostname
v = self.host
/\A\[(.*)\]\z/ =~ v ? $1 : v
end
end
end
end
class HTTPClient
# A module for common function.
module Util
# URI abstraction; Addressable::URI or URI
require 'uri'
begin
require 'addressable/uri'
# Older versions doesn't have #default_port
unless Addressable::URI.instance_methods.include?(:default_port) # 1.9 only
raise LoadError
end
class AddressableURI < Addressable::URI
# Overwrites the original definition just for one line...
def authority
self.host && @authority ||= (begin
authority = ""
if self.userinfo != nil
authority << "#{self.userinfo}@"
end
authority << self.host
if self.port != self.default_port # ...HERE! Compares with default_port because self.port is not nil in this wrapper.
authority << ":#{self.port}"
end
authority
end)
end
# HTTPClient expects urify("http://foo/").port to be not nil but 80 like URI.
def port
super || default_port
end
# Captured from uri/generic.rb
def hostname
v = self.host
/\A\[(.*)\]\z/ =~ v ? $1 : v
end
end
AddressableEnabled = true
rescue LoadError
AddressableEnabled = false
end
# Keyword argument helper.
# args:: given arguments.
# *field:: a list of arguments to be extracted.
#
# You can extract 3 arguments (a, b, c) with:
#
# include Util
# def my_method(*args)
# a, b, c = keyword_argument(args, :a, :b, :c)
# ...
# end
# my_method(1, 2, 3)
# my_method(:b => 2, :a = 1)
#
# instead of;
#
# def my_method(a, b, c)
# ...
# end
#
def keyword_argument(args, *field)
if args.size == 1 and Hash === args[0]
h = args[0]
if field.any? { |f| h.key?(f) }
return h.values_at(*field)
end
end
args
end
# Keyword argument to hash helper.
# args:: given arguments.
# *field:: a list of arguments to be extracted.
#
# Returns hash which has defined keys. When a Hash given, returns it
# including undefined keys. When an Array given, returns a Hash which only
# includes defined keys.
def argument_to_hash(args, *field)
return nil if args.empty?
if args.size == 1 and Hash === args[0]
h = args[0]
if field.any? { |f| h.key?(f) }
return h
end
end
h = {}
field.each_with_index do |e, idx|
h[e] = args[idx]
end
h
end
# Gets an URI instance.
def urify(uri)
if uri.nil?
nil
elsif uri.is_a?(URI)
uri
elsif AddressableEnabled
AddressableURI.parse(uri.to_s)
else
URI.parse(uri.to_s)
end
end
module_function :urify
# Returns true if the given 2 URIs have a part_of relationship.
# * the same scheme
# * the same host String (no host resolution or IP-addr conversion)
# * the same port number
# * target URI's path starts with base URI's path.
def uri_part_of(uri, part)
((uri.scheme == part.scheme) and
(uri.host == part.host) and
(uri.port == part.port) and
uri.path.upcase.index(part.path.upcase) == 0)
end
module_function :uri_part_of
# Returns parent directory URI of the given URI.
def uri_dirname(uri)
uri = uri.clone
uri.path = uri.path.sub(/\/[^\/]*\z/, '/')
uri
end
module_function :uri_dirname
# Finds a value of a Hash.
def hash_find_value(hash, &block)
v = hash.find(&block)
v ? v[1] : nil
end
module_function :hash_find_value
# Checks if the given URI is https.
def https?(uri)
uri.scheme && uri.scheme.downcase == 'https'
end
def http?(uri)
uri.scheme && uri.scheme.downcase == 'http'
end
end
end
httpclient-2.3.3/lib/httpclient/auth.rb 0000644 0000041 0000041 00000064675 12155433207 020163 0 ustar www-data www-data # HTTPClient - HTTP client library.
# Copyright (C) 2000-2009 NAKAMURA, Hiroshi .
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
# either the dual license version in 2003, or any later version.
require 'digest/md5'
require 'httpclient/session'
class HTTPClient
begin
require 'net/ntlm'
NTLMEnabled = true
rescue LoadError
NTLMEnabled = false
end
begin
require 'win32/sspi'
SSPIEnabled = true
rescue LoadError
SSPIEnabled = false
end
begin
require 'gssapi'
GSSAPIEnabled = true
rescue LoadError
GSSAPIEnabled = false
end
# Common abstract class for authentication filter.
#
# There are 2 authentication filters.
# WWWAuth:: Authentication filter for handling authentication negotiation
# between Web server. Parses 'WWW-Authentication' header in
# response and generates 'Authorization' header in request.
# ProxyAuth:: Authentication filter for handling authentication negotiation
# between Proxy server. Parses 'Proxy-Authentication' header in
# response and generates 'Proxy-Authorization' header in request.
class AuthFilterBase
private
def parse_authentication_header(res, tag)
challenge = res.header[tag]
return nil unless challenge
challenge.collect { |c| parse_challenge_header(c) }.compact
end
def parse_challenge_header(challenge)
scheme, param_str = challenge.scan(/\A(\S+)(?:\s+(.*))?\z/)[0]
return nil if scheme.nil?
return scheme, param_str
end
end
# Authentication filter for handling authentication negotiation between
# Web server. Parses 'WWW-Authentication' header in response and
# generates 'Authorization' header in request.
#
# Authentication filter is implemented using request filter of HTTPClient.
# It traps HTTP response header and maintains authentication state, and
# traps HTTP request header for inserting necessary authentication header.
#
# WWWAuth has sub filters (BasicAuth, DigestAuth, NegotiateAuth and
# SSPINegotiateAuth) and delegates some operations to it.
# NegotiateAuth requires 'ruby/ntlm' module (rubyntlm gem).
# SSPINegotiateAuth requires 'win32/sspi' module (rubysspi gem).
class WWWAuth < AuthFilterBase
attr_reader :basic_auth
attr_reader :digest_auth
attr_reader :negotiate_auth
attr_reader :sspi_negotiate_auth
attr_reader :oauth
# Creates new WWWAuth.
def initialize
@basic_auth = BasicAuth.new
@digest_auth = DigestAuth.new
@negotiate_auth = NegotiateAuth.new
@ntlm_auth = NegotiateAuth.new('NTLM')
@sspi_negotiate_auth = SSPINegotiateAuth.new
@oauth = OAuth.new
# sort authenticators by priority
@authenticator = [@oauth, @negotiate_auth, @ntlm_auth, @sspi_negotiate_auth, @digest_auth, @basic_auth]
end
# Resets challenge state. See sub filters for more details.
def reset_challenge
@authenticator.each do |auth|
auth.reset_challenge
end
end
# Set authentication credential. See sub filters for more details.
def set_auth(uri, user, passwd)
@authenticator.each do |auth|
auth.set(uri, user, passwd)
end
reset_challenge
end
# Filter API implementation. Traps HTTP request and insert
# 'Authorization' header if needed.
def filter_request(req)
@authenticator.each do |auth|
next unless auth.set? # hasn't be set, don't use it
if cred = auth.get(req)
req.header.set('Authorization', auth.scheme + " " + cred)
return
end
end
end
# Filter API implementation. Traps HTTP response and parses
# 'WWW-Authenticate' header.
#
# This remembers the challenges for all authentication methods
# available to the client. On the subsequent retry of the request,
# filter_request will select the strongest method.
def filter_response(req, res)
command = nil
if res.status == HTTP::Status::UNAUTHORIZED
if challenge = parse_authentication_header(res, 'www-authenticate')
uri = req.header.request_uri
challenge.each do |scheme, param_str|
@authenticator.each do |auth|
next unless auth.set? # hasn't be set, don't use it
if scheme.downcase == auth.scheme.downcase
challengeable = auth.challenge(uri, param_str)
command = :retry if challengeable
end
end
end
# ignore unknown authentication scheme
end
end
command
end
end
# Authentication filter for handling authentication negotiation between
# Proxy server. Parses 'Proxy-Authentication' header in response and
# generates 'Proxy-Authorization' header in request.
#
# Authentication filter is implemented using request filter of HTTPClient.
# It traps HTTP response header and maintains authentication state, and
# traps HTTP request header for inserting necessary authentication header.
#
# ProxyAuth has sub filters (BasicAuth, NegotiateAuth, and SSPINegotiateAuth)
# and delegates some operations to it.
# NegotiateAuth requires 'ruby/ntlm' module.
# SSPINegotiateAuth requires 'win32/sspi' module.
class ProxyAuth < AuthFilterBase
attr_reader :basic_auth
attr_reader :digest_auth
attr_reader :negotiate_auth
attr_reader :sspi_negotiate_auth
# Creates new ProxyAuth.
def initialize
@basic_auth = ProxyBasicAuth.new
@negotiate_auth = NegotiateAuth.new
@ntlm_auth = NegotiateAuth.new('NTLM')
@sspi_negotiate_auth = SSPINegotiateAuth.new
@digest_auth = ProxyDigestAuth.new
# sort authenticators by priority
@authenticator = [@negotiate_auth, @ntlm_auth, @sspi_negotiate_auth, @digest_auth, @basic_auth]
end
# Resets challenge state. See sub filters for more details.
def reset_challenge
@authenticator.each do |auth|
auth.reset_challenge
end
end
# Set authentication credential. See sub filters for more details.
def set_auth(user, passwd)
@authenticator.each do |auth|
auth.set(nil, user, passwd)
end
reset_challenge
end
# Filter API implementation. Traps HTTP request and insert
# 'Proxy-Authorization' header if needed.
def filter_request(req)
@authenticator.each do |auth|
next unless auth.set? # hasn't be set, don't use it
if cred = auth.get(req)
req.header.set('Proxy-Authorization', auth.scheme + " " + cred)
return
end
end
end
# Filter API implementation. Traps HTTP response and parses
# 'Proxy-Authenticate' header.
def filter_response(req, res)
command = nil
if res.status == HTTP::Status::PROXY_AUTHENTICATE_REQUIRED
if challenge = parse_authentication_header(res, 'proxy-authenticate')
uri = req.header.request_uri
challenge.each do |scheme, param_str|
@authenticator.each do |auth|
next unless auth.set? # hasn't be set, don't use it
if scheme.downcase == auth.scheme.downcase
challengeable = auth.challenge(uri, param_str)
command = :retry if challengeable
end
end
end
# ignore unknown authentication scheme
end
end
command
end
end
# Authentication filter for handling BasicAuth negotiation.
# Used in WWWAuth and ProxyAuth.
class BasicAuth
include HTTPClient::Util
# Authentication scheme.
attr_reader :scheme
# Creates new BasicAuth filter.
def initialize
@cred = nil
@set = false
@auth = {}
@challengeable = {}
@scheme = "Basic"
end
# Resets challenge state. Do not send '*Authorization' header until the
# server sends '*Authentication' again.
def reset_challenge
@challengeable.clear
end
# Set authentication credential.
# uri == nil for generic purpose (allow to use user/password for any URL).
def set(uri, user, passwd)
@set = true
if uri.nil?
@cred = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
else
uri = Util.uri_dirname(uri)
@auth[uri] = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
end
end
# have we marked this as set - ie that it's valid to use in this context?
def set?
@set == true
end
# Response handler: returns credential.
# It sends cred only when a given uri is;
# * child page of challengeable(got *Authenticate before) uri and,
# * child page of defined credential
def get(req)
target_uri = req.header.request_uri
return nil unless @challengeable.find { |uri, ok|
Util.uri_part_of(target_uri, uri) and ok
}
return @cred if @cred
Util.hash_find_value(@auth) { |uri, cred|
Util.uri_part_of(target_uri, uri)
}
end
# Challenge handler: remember URL for response.
def challenge(uri, param_str = nil)
@challengeable[urify(uri)] = true
true
end
end
class ProxyBasicAuth < BasicAuth
def set(uri, user, passwd)
@set = true
@cred = ["#{user}:#{passwd}"].pack('m').tr("\n", '')
end
def get(req)
target_uri = req.header.request_uri
return nil unless @challengeable['challenged']
@cred
end
# Challenge handler: remember URL for response.
def challenge(uri, param_str = nil)
@challengeable['challenged'] = true
true
end
end
# Authentication filter for handling DigestAuth negotiation.
# Used in WWWAuth.
class DigestAuth
# Authentication scheme.
attr_reader :scheme
# Creates new DigestAuth filter.
def initialize
@auth = {}
@challenge = {}
@set = false
@nonce_count = 0
@scheme = "Digest"
end
# Resets challenge state. Do not send '*Authorization' header until the
# server sends '*Authentication' again.
def reset_challenge
@challenge.clear
end
# Set authentication credential.
# uri == nil is ignored.
def set(uri, user, passwd)
@set = true
if uri
uri = Util.uri_dirname(uri)
@auth[uri] = [user, passwd]
end
end
# have we marked this as set - ie that it's valid to use in this context?
def set?
@set == true
end
# Response handler: returns credential.
# It sends cred only when a given uri is;
# * child page of challengeable(got *Authenticate before) uri and,
# * child page of defined credential
def get(req)
target_uri = req.header.request_uri
param = Util.hash_find_value(@challenge) { |uri, v|
Util.uri_part_of(target_uri, uri)
}
return nil unless param
user, passwd = Util.hash_find_value(@auth) { |uri, auth_data|
Util.uri_part_of(target_uri, uri)
}
return nil unless user
calc_cred(req, user, passwd, param)
end
# Challenge handler: remember URL and challenge token for response.
def challenge(uri, param_str)
@challenge[uri] = parse_challenge_param(param_str)
true
end
private
# this method is implemented by sromano and posted to
# http://tools.assembla.com/breakout/wiki/DigestForSoap
# Thanks!
# supported algorithms: MD5, MD5-sess
def calc_cred(req, user, passwd, param)
method = req.header.request_method
path = req.header.create_query_uri
a_1 = "#{user}:#{param['realm']}:#{passwd}"
a_2 = "#{method}:#{path}"
qop = param['qop']
nonce = param['nonce']
cnonce = nil
if qop || param['algorithm'] =~ /MD5-sess/
cnonce = generate_cnonce()
end
a_1_md5sum = Digest::MD5.hexdigest(a_1)
if param['algorithm'] =~ /MD5-sess/
a_1_md5sum = Digest::MD5.hexdigest("#{a_1_md5sum}:#{nonce}:#{cnonce}")
algorithm = "MD5-sess"
else
algorithm = "MD5"
end
message_digest = []
message_digest << a_1_md5sum
message_digest << nonce
if qop
@nonce_count += 1
message_digest << ('%08x' % @nonce_count)
message_digest << cnonce
message_digest << param['qop']
end
message_digest << Digest::MD5.hexdigest(a_2)
header = []
header << "username=\"#{user}\""
header << "realm=\"#{param['realm']}\""
header << "nonce=\"#{nonce}\""
header << "uri=\"#{path}\""
if cnonce
header << "cnonce=\"#{cnonce}\""
end
if qop
header << "nc=#{'%08x' % @nonce_count}"
header << "qop=#{param['qop']}"
end
header << "response=\"#{Digest::MD5.hexdigest(message_digest.join(":"))}\""
header << "algorithm=#{algorithm}"
header << "opaque=\"#{param['opaque']}\"" if param.key?('opaque')
header.join(", ")
end
# cf. WEBrick::HTTPAuth::DigestAuth#generate_next_nonce(aTime)
def generate_cnonce
now = "%012d" % Time.now.to_i
pk = Digest::MD5.hexdigest([now, self.__id__, Process.pid, rand(65535)].join)[0, 32]
[now + ':' + pk].pack('m*').chop
end
def parse_challenge_param(param_str)
param = {}
param_str.scan(/\s*([^\,]+(?:\\.[^\,]*)*)/).each do |str|
key, value = str[0].scan(/\A([^=]+)=(.*)\z/)[0]
if /\A"(.*)"\z/ =~ value
value = $1.gsub(/\\(.)/, '\1')
end
param[key] = value
end
param
end
end
# Authentication filter for handling DigestAuth negotiation.
# Ignores uri argument. Used in ProxyAuth.
class ProxyDigestAuth < DigestAuth
# overrides DigestAuth#set. sets default user name and password. uri is not used.
def set(uri, user, passwd)
@set = true
@auth = [user, passwd]
end
# overrides DigestAuth#get. Uses default user name and password
# regardless of target uri if the proxy has required authentication
# before
def get(req)
target_uri = req.header.request_uri
param = @challenge
return nil unless param
user, passwd = @auth
return nil unless user
calc_cred(req, user, passwd, param)
end
def reset_challenge
@challenge = nil
end
def challenge(uri, param_str)
@challenge = parse_challenge_param(param_str)
true
end
end
# Authentication filter for handling Negotiate/NTLM negotiation.
# Used in WWWAuth and ProxyAuth.
#
# NegotiateAuth depends on 'ruby/ntlm' module.
class NegotiateAuth
# Authentication scheme.
attr_reader :scheme
# NTLM opt for ruby/ntlm. {:ntlmv2 => true} by default.
attr_reader :ntlm_opt
# Creates new NegotiateAuth filter.
def initialize(scheme = "Negotiate")
@auth = {}
@auth_default = nil
@challenge = {}
@scheme = scheme
@set = false
@ntlm_opt = {
:ntlmv2 => true
}
end
# Resets challenge state. Do not send '*Authorization' header until the
# server sends '*Authentication' again.
def reset_challenge
@challenge.clear
end
# Set authentication credential.
# uri == nil for generic purpose (allow to use user/password for any URL).
def set(uri, user, passwd)
@set = true
if uri
uri = Util.uri_dirname(uri)
@auth[uri] = [user, passwd]
else
@auth_default = [user, passwd]
end
end
# have we marked this as set - ie that it's valid to use in this context?
def set?
@set == true
end
# Response handler: returns credential.
# See ruby/ntlm for negotiation state transition.
def get(req)
return nil unless NTLMEnabled
target_uri = req.header.request_uri
domain_uri, param = @challenge.find { |uri, v|
Util.uri_part_of(target_uri, uri)
}
return nil unless param
user, passwd = Util.hash_find_value(@auth) { |uri, auth_data|
Util.uri_part_of(target_uri, uri)
}
unless user
user, passwd = @auth_default
end
return nil unless user
domain = nil
domain, user = user.split("\\") if user.index("\\")
state = param[:state]
authphrase = param[:authphrase]
case state
when :init
t1 = Net::NTLM::Message::Type1.new
t1.domain = domain if domain
return t1.encode64
when :response
t2 = Net::NTLM::Message.decode64(authphrase)
param = {:user => user, :password => passwd}
param[:domain] = domain if domain
t3 = t2.response(param, @ntlm_opt.dup)
@challenge.delete(domain_uri)
return t3.encode64
end
nil
end
# Challenge handler: remember URL and challenge token for response.
def challenge(uri, param_str)
return false unless NTLMEnabled
if param_str.nil? or @challenge[uri].nil?
c = @challenge[uri] = {}
c[:state] = :init
c[:authphrase] = ""
else
c = @challenge[uri]
c[:state] = :response
c[:authphrase] = param_str
end
true
end
end
# Authentication filter for handling Negotiate/NTLM negotiation.
# Used in ProxyAuth.
#
# SSPINegotiateAuth depends on 'win32/sspi' module.
class SSPINegotiateAuth
# Authentication scheme.
attr_reader :scheme
# Creates new SSPINegotiateAuth filter.
def initialize
@challenge = {}
@scheme = "Negotiate"
end
# Resets challenge state. Do not send '*Authorization' header until the
# server sends '*Authentication' again.
def reset_challenge
@challenge.clear
end
# Set authentication credential.
# NOT SUPPORTED: username and necessary data is retrieved by win32/sspi.
# See win32/sspi for more details.
def set(*args)
# not supported
end
# have we marked this as set - ie that it's valid to use in this context?
def set?
SSPIEnabled || GSSAPIEnabled
end
# Response handler: returns credential.
# See win32/sspi for negotiation state transition.
def get(req)
return nil unless SSPIEnabled || GSSAPIEnabled
target_uri = req.header.request_uri
domain_uri, param = @challenge.find { |uri, v|
Util.uri_part_of(target_uri, uri)
}
return nil unless param
state = param[:state]
authenticator = param[:authenticator]
authphrase = param[:authphrase]
case state
when :init
if SSPIEnabled
authenticator = param[:authenticator] = Win32::SSPI::NegotiateAuth.new
return authenticator.get_initial_token(@scheme)
else # use GSSAPI
authenticator = param[:authenticator] = GSSAPI::Simple.new(domain_uri.host, 'HTTP')
# Base64 encode the context token
return [authenticator.init_context].pack('m').gsub(/\n/,'')
end
when :response
@challenge.delete(domain_uri)
if SSPIEnabled
return authenticator.complete_authentication(authphrase)
else # use GSSAPI
return authenticator.init_context(authphrase.unpack('m').pop)
end
end
nil
end
# Challenge handler: remember URL and challenge token for response.
def challenge(uri, param_str)
return false unless SSPIEnabled || GSSAPIEnabled
if param_str.nil? or @challenge[uri].nil?
c = @challenge[uri] = {}
c[:state] = :init
c[:authenticator] = nil
c[:authphrase] = ""
else
c = @challenge[uri]
c[:state] = :response
c[:authphrase] = param_str
end
true
end
end
# Authentication filter for handling OAuth negotiation.
# Used in WWWAuth.
#
# CAUTION: This impl only support '#7 Accessing Protected Resources' in OAuth
# Core 1.0 spec for now. You need to obtain Access token and Access secret by
# yourself.
#
# CAUTION: This impl does NOT support OAuth Request Body Hash spec for now.
# http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
#
class OAuth
include HTTPClient::Util
# Authentication scheme.
attr_reader :scheme
class Config
include HTTPClient::Util
attr_accessor :http_method
attr_accessor :realm
attr_accessor :consumer_key
attr_accessor :consumer_secret
attr_accessor :token
attr_accessor :secret
attr_accessor :signature_method
attr_accessor :version
attr_accessor :callback
attr_accessor :verifier
# for OAuth Session 1.0 (draft)
attr_accessor :session_handle
attr_reader :signature_handler
attr_accessor :debug_timestamp
attr_accessor :debug_nonce
def initialize(*args)
@http_method,
@realm,
@consumer_key,
@consumer_secret,
@token,
@secret,
@signature_method,
@version,
@callback,
@verifier =
keyword_argument(args,
:http_method,
:realm,
:consumer_key,
:consumer_secret,
:token,
:secret,
:signature_method,
:version,
:callback,
:verifier
)
@http_method ||= :post
@session_handle = nil
@signature_handler = {}
end
end
def self.escape(str) # :nodoc:
if str.respond_to?(:force_encoding)
str.dup.force_encoding('BINARY').gsub(/([^a-zA-Z0-9_.~-]+)/) {
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
}
else
str.gsub(/([^a-zA-Z0-9_.~-]+)/n) {
'%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
}
end
end
def escape(str)
self.class.escape(str)
end
# Creates new DigestAuth filter.
def initialize
@config = nil # common config
@auth = {} # configs for each site
@challengeable = {}
@nonce_count = 0
@signature_handler = {
'HMAC-SHA1' => method(:sign_hmac_sha1)
}
@scheme = "OAuth"
end
# Resets challenge state. Do not send '*Authorization' header until the
# server sends '*Authentication' again.
def reset_challenge
@challengeable.clear
end
# Set authentication credential.
# You cannot set OAuth config via WWWAuth#set_auth. Use OAuth#config=
def set(*args)
# not supported
end
# have we marked this as set - ie that it's valid to use in this context?
def set?
true
end
# Set authentication credential.
def set_config(uri, config)
if uri.nil?
@config = config
else
uri = Util.uri_dirname(urify(uri))
@auth[uri] = config
end
end
# Get authentication credential.
def get_config(uri = nil)
if uri.nil?
@config
else
uri = urify(uri)
Util.hash_find_value(@auth) { |cand_uri, cred|
Util.uri_part_of(uri, cand_uri)
}
end
end
# Response handler: returns credential.
# It sends cred only when a given uri is;
# * child page of challengeable(got *Authenticate before) uri and,
# * child page of defined credential
def get(req)
target_uri = req.header.request_uri
return nil unless @challengeable[nil] or @challengeable.find { |uri, ok|
Util.uri_part_of(target_uri, uri) and ok
}
config = get_config(target_uri) || @config
return nil unless config
calc_cred(req, config)
end
# Challenge handler: remember URL for response.
def challenge(uri, param_str = nil)
if uri.nil?
@challengeable[nil] = true
else
@challengeable[urify(uri)] = true
end
true
end
private
def calc_cred(req, config)
header = {}
header['oauth_consumer_key'] = config.consumer_key
header['oauth_token'] = config.token
header['oauth_signature_method'] = config.signature_method
header['oauth_timestamp'] = config.debug_timestamp || Time.now.to_i.to_s
header['oauth_nonce'] = config.debug_nonce || generate_nonce()
header['oauth_version'] = config.version if config.version
header['oauth_callback'] = config.callback if config.callback
header['oauth_verifier'] = config.verifier if config.verifier
header['oauth_session_handle'] = config.session_handle if config.session_handle
signature = sign(config, header, req)
header['oauth_signature'] = signature
# no need to do but we should sort for easier to test.
str = header.sort_by { |k, v| k }.map { |k, v| encode_header(k, v) }.join(', ')
if config.realm
str = %Q(realm="#{config.realm}", ) + str
end
str
end
def generate_nonce
@nonce_count += 1
now = "%012d" % Time.now.to_i
pk = Digest::MD5.hexdigest([@nonce_count.to_s, now, self.__id__, Process.pid, rand(65535)].join)[0, 32]
[now + ':' + pk].pack('m*').chop
end
def encode_header(k, v)
%Q(#{escape(k.to_s)}="#{escape(v.to_s)}")
end
def encode_param(params)
params.map { |k, v|
[v].flatten.map { |vv|
%Q(#{escape(k.to_s)}=#{escape(vv.to_s)})
}
}.flatten
end
def sign(config, header, req)
base_string = create_base_string(config, header, req)
if handler = config.signature_handler[config.signature_method] || @signature_handler[config.signature_method.to_s]
handler.call(config, base_string)
else
raise ConfigurationError.new("Unknown OAuth signature method: #{config.signature_method}")
end
end
def create_base_string(config, header, req)
params = encode_param(header)
query = req.header.request_query
if query and HTTP::Message.multiparam_query?(query)
params += encode_param(query)
end
# captures HTTP Message body only for 'application/x-www-form-urlencoded'
if req.header.contenttype == 'application/x-www-form-urlencoded' and req.http_body.size
params += encode_param(HTTP::Message.parse(req.http_body.content))
end
uri = req.header.request_uri
if uri.query
params += encode_param(HTTP::Message.parse(uri.query))
end
if uri.port == uri.default_port
request_url = "#{uri.scheme.downcase}://#{uri.host}#{uri.path}"
else
request_url = "#{uri.scheme.downcase}://#{uri.host}:#{uri.port}#{uri.path}"
end
[req.header.request_method.upcase, request_url, params.sort.join('&')].map { |e|
escape(e)
}.join('&')
end
def sign_hmac_sha1(config, base_string)
unless SSLEnabled
raise ConfigurationError.new("openssl required for OAuth implementation")
end
key = [escape(config.consumer_secret.to_s), escape(config.secret.to_s)].join('&')
digester = OpenSSL::Digest::SHA1.new
[OpenSSL::HMAC.digest(digester, key, base_string)].pack('m*').chomp
end
end
end
httpclient-2.3.3/lib/httpclient/include_client.rb 0000644 0000041 0000041 00000006305 12155433207 022165 0 ustar www-data www-data # It is useful to re-use a HTTPClient instance for multiple requests, to
# re-use HTTP 1.1 persistent connections.
#
# To do that, you sometimes want to store an HTTPClient instance in a global/
# class variable location, so it can be accessed and re-used.
#
# This mix-in makes it easy to create class-level access to one or more
# HTTPClient instances. The HTTPClient instances are lazily initialized
# on first use (to, for instance, avoid interfering with WebMock/VCR),
# and are initialized in a thread-safe manner. Note that a
# HTTPClient, once initialized, is safe for use in multiple threads.
#
# Note that you `extend` HTTPClient::IncludeClient, not `include.
#
# require 'httpclient/include_client'
# class Widget
# extend HTTPClient::IncludeClient
#
# include_http_client
# # and/or, specify more stuff
# include_http_client('http://myproxy:8080', :method_name => :my_client) do |client|
# # any init you want
# client.set_cookie_store nil
# client.
# end
# end
#
# That creates two HTTPClient instances available at the class level.
# The first will be available from Widget.http_client (default method
# name for `include_http_client`), with default initialization.
#
# The second will be available at Widget.my_client, with the init arguments
# provided, further initialized by the block provided.
#
# In addition to a class-level method, for convenience instance-level methods
# are also provided. Widget.http_client is identical to Widget.new.http_client
#
#
class HTTPClient
module IncludeClient
def include_http_client(*args, &block)
# We're going to dynamically define a class
# to hold our state, namespaced, as well as possibly dynamic
# name of cover method.
method_name = (args.last.delete(:method_name) if args.last.kind_of? Hash) || :http_client
args.pop if args.last == {} # if last arg was named methods now empty, remove it.
# By the amazingness of closures, we can create these things
# in local vars here and use em in our method, we don't even
# need iVars for state.
client_instance = nil
client_mutex = Mutex.new
client_args = args
client_block = block
# to define a _class method_ on the specific class that's currently
# `self`, we have to use this bit of metaprogramming, sorry.
(class << self; self ; end).instance_eval do
define_method(method_name) do
# implementation copied from ruby stdlib singleton
# to create this global obj thread-safely.
return client_instance if client_instance
client_mutex.synchronize do
return client_instance if client_instance
# init HTTPClient with specified args/block
client_instance = HTTPClient.new(*client_args)
client_block.call(client_instance) if client_block
end
return client_instance
end
end
# And for convenience, an _instance method_ on the class that just
# delegates to the class method.
define_method(method_name) do
self.class.send(method_name)
end
end
end
end
httpclient-2.3.3/lib/httpclient/ssl_config.rb 0000644 0000041 0000041 00000032550 12155433207 021333 0 ustar www-data www-data # HTTPClient - HTTP client library.
# Copyright (C) 2000-2009 NAKAMURA, Hiroshi .
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
# either the dual license version in 2003, or any later version.
class HTTPClient
begin
require 'openssl'
SSLEnabled = true
rescue LoadError
SSLEnabled = false
end
# Represents SSL configuration for HTTPClient instance.
# The implementation depends on OpenSSL.
#
# == Trust Anchor Control
#
# SSLConfig loads 'httpclient/cacert.p7s' as a trust anchor
# (trusted certificate(s)) with add_trust_ca in initialization time.
# This means that HTTPClient instance trusts some CA certificates by default,
# like Web browsers. 'httpclient/cacert.p7s' is created by the author and
# included in released package.
#
# 'cacert.p7s' is automatically generated from JDK 1.6. Regardless its
# filename extension (p7s), HTTPClient doesn't verify the signature in it.
#
# You may want to change trust anchor by yourself. Call clear_cert_store
# then add_trust_ca for that purpose.
class SSLConfig
include OpenSSL if SSLEnabled
# String name of OpenSSL's SSL version method name: SSLv2, SSLv23 or SSLv3
attr_reader :ssl_version
# OpenSSL::X509::Certificate:: certificate for SSL client authenticateion.
# nil by default. (no client authenticateion)
attr_reader :client_cert
# OpenSSL::PKey::PKey:: private key for SSL client authentication.
# nil by default. (no client authenticateion)
attr_reader :client_key
# A number which represents OpenSSL's verify mode. Default value is
# OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT.
attr_reader :verify_mode
# A number of verify depth. Certification path which length is longer than
# this depth is not allowed.
attr_reader :verify_depth
# A callback handler for custom certificate verification. nil by default.
# If the handler is set, handler.call is invoked just after general
# OpenSSL's verification. handler.call is invoked with 2 arguments,
# ok and ctx; ok is a result of general OpenSSL's verification. ctx is a
# OpenSSL::X509::StoreContext.
attr_reader :verify_callback
# SSL timeout in sec. nil by default.
attr_reader :timeout
# A number of OpenSSL's SSL options. Default value is
# OpenSSL::SSL::OP_ALL | OpenSSL::SSL::OP_NO_SSLv2
attr_reader :options
# A String of OpenSSL's cipher configuration. Default value is
# ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH
# See ciphers(1) man in OpenSSL for more detail.
attr_reader :ciphers
# OpenSSL::X509::X509::Store used for verification. You can reset the
# store with clear_cert_store and set the new store with cert_store=.
attr_reader :cert_store # don't use if you don't know what it is.
# For server side configuration. Ignore this.
attr_reader :client_ca # :nodoc:
# Creates a SSLConfig.
def initialize(client)
return unless SSLEnabled
@client = client
@cert_store = X509::Store.new
@client_cert = @client_key = @client_ca = nil
@verify_mode = SSL::VERIFY_PEER | SSL::VERIFY_FAIL_IF_NO_PEER_CERT
@verify_depth = nil
@verify_callback = nil
@dest = nil
@timeout = nil
@ssl_version = "SSLv3"
@options = defined?(SSL::OP_ALL) ? SSL::OP_ALL | SSL::OP_NO_SSLv2 : nil
# OpenSSL 0.9.8 default: "ALL:!ADH:!LOW:!EXP:!MD5:+SSLv2:@STRENGTH"
@ciphers = "ALL:!aNULL:!eNULL:!SSLv2" # OpenSSL >1.0.0 default
@cacerts_loaded = false
end
# Sets SSL version method String. Possible values: "SSLv2" for SSL2,
# "SSLv3" for SSL3 and TLS1.x, "SSLv23" for SSL3 with fallback to SSL2.
def ssl_version=(ssl_version)
@ssl_version = ssl_version
change_notify
end
# Sets certificate (OpenSSL::X509::Certificate) for SSL client
# authentication.
# client_key and client_cert must be a pair.
#
# Calling this method resets all existing sessions.
def client_cert=(client_cert)
@client_cert = client_cert
change_notify
end
# Sets private key (OpenSSL::PKey::PKey) for SSL client authentication.
# client_key and client_cert must be a pair.
#
# Calling this method resets all existing sessions.
def client_key=(client_key)
@client_key = client_key
change_notify
end
# Sets certificate and private key for SSL client authentication.
# cert_file:: must be a filename of PEM/DER formatted file.
# key_file:: must be a filename of PEM/DER formatted file. Key must be an
# RSA key. If you want to use other PKey algorithm,
# use client_key=.
#
# Calling this method resets all existing sessions.
def set_client_cert_file(cert_file, key_file)
@client_cert = X509::Certificate.new(File.open(cert_file) { |f| f.read })
@client_key = PKey::RSA.new(File.open(key_file) { |f| f.read })
change_notify
end
# Sets OpenSSL's default trusted CA certificates. Generally, OpenSSL is
# configured to use OS's trusted CA certificates located at
# /etc/pki/certs or /etc/ssl/certs. Unfortunately OpenSSL's Windows build
# does not work with Windows Certificate Storage.
#
# On Windows or when you build OpenSSL manually, you can set the
# CA certificates directory by SSL_CERT_DIR env variable at runtime.
#
# SSL_CERT_DIR=/etc/ssl/certs ruby -rhttpclient -e "..."
#
# Calling this method resets all existing sessions.
def set_default_paths
@cacerts_loaded = true # avoid lazy override
@cert_store = X509::Store.new
@cert_store.set_default_paths
change_notify
end
# Drops current certificate store (OpenSSL::X509::Store) for SSL and create
# new one for the next session.
#
# Calling this method resets all existing sessions.
def clear_cert_store
@cacerts_loaded = true # avoid lazy override
@cert_store = X509::Store.new
change_notify
end
# Sets new certificate store (OpenSSL::X509::Store).
# don't use if you don't know what it is.
#
# Calling this method resets all existing sessions.
def cert_store=(cert_store)
@cacerts_loaded = true # avoid lazy override
@cert_store = cert_store
change_notify
end
# Sets trust anchor certificate(s) for verification.
# trust_ca_file_or_hashed_dir:: a filename of a PEM/DER formatted
# OpenSSL::X509::Certificate or
# a 'c-rehash'eddirectory name which stores
# trusted certificate files.
#
# Calling this method resets all existing sessions.
def add_trust_ca(trust_ca_file_or_hashed_dir)
@cacerts_loaded = true # avoid lazy override
add_trust_ca_to_store(@cert_store, trust_ca_file_or_hashed_dir)
change_notify
end
alias set_trust_ca add_trust_ca
def add_trust_ca_to_store(cert_store, trust_ca_file_or_hashed_dir)
if FileTest.directory?(trust_ca_file_or_hashed_dir)
cert_store.add_path(trust_ca_file_or_hashed_dir)
else
cert_store.add_file(trust_ca_file_or_hashed_dir)
end
end
# Loads default trust anchors.
# Calling this method resets all existing sessions.
def load_trust_ca
load_cacerts(@cert_store)
change_notify
end
# Adds CRL for verification.
# crl:: a OpenSSL::X509::CRL or a filename of a PEM/DER formatted
# OpenSSL::X509::CRL.
#
# Calling this method resets all existing sessions.
def add_crl(crl)
unless crl.is_a?(X509::CRL)
crl = X509::CRL.new(File.open(crl) { |f| f.read })
end
@cert_store.add_crl(crl)
@cert_store.flags = X509::V_FLAG_CRL_CHECK | X509::V_FLAG_CRL_CHECK_ALL
change_notify
end
alias set_crl add_crl
# Sets verify mode of OpenSSL. New value must be a combination of
# constants OpenSSL::SSL::VERIFY_*
#
# Calling this method resets all existing sessions.
def verify_mode=(verify_mode)
@verify_mode = verify_mode
change_notify
end
# Sets verify depth. New value must be a number.
#
# Calling this method resets all existing sessions.
def verify_depth=(verify_depth)
@verify_depth = verify_depth
change_notify
end
# Sets callback handler for custom certificate verification.
# See verify_callback.
#
# Calling this method resets all existing sessions.
def verify_callback=(verify_callback)
@verify_callback = verify_callback
change_notify
end
# Sets SSL timeout in sec.
#
# Calling this method resets all existing sessions.
def timeout=(timeout)
@timeout = timeout
change_notify
end
# Sets SSL options. New value must be a combination of # constants
# OpenSSL::SSL::OP_*
#
# Calling this method resets all existing sessions.
def options=(options)
@options = options
change_notify
end
# Sets cipher configuration. New value must be a String.
#
# Calling this method resets all existing sessions.
def ciphers=(ciphers)
@ciphers = ciphers
change_notify
end
def client_ca=(client_ca) # :nodoc:
@client_ca = client_ca
change_notify
end
# interfaces for SSLSocketWrap.
def set_context(ctx) # :nodoc:
load_trust_ca unless @cacerts_loaded
@cacerts_loaded = true
# Verification: Use Store#verify_callback instead of SSLContext#verify*?
ctx.cert_store = @cert_store
ctx.verify_mode = @verify_mode
ctx.verify_depth = @verify_depth if @verify_depth
ctx.verify_callback = @verify_callback || method(:default_verify_callback)
# SSL config
ctx.cert = @client_cert
ctx.key = @client_key
ctx.client_ca = @client_ca
ctx.timeout = @timeout
ctx.options = @options
ctx.ciphers = @ciphers
ctx.ssl_version = @ssl_version
end
# post connection check proc for ruby < 1.8.5.
# this definition must match with the one in ext/openssl/lib/openssl/ssl.rb
def post_connection_check(peer_cert, hostname) # :nodoc:
check_common_name = true
cert = peer_cert
cert.extensions.each{|ext|
next if ext.oid != "subjectAltName"
ext.value.split(/,\s+/).each{|general_name|
if /\ADNS:(.*)/ =~ general_name
check_common_name = false
reg = Regexp.escape($1).gsub(/\\\*/, "[^.]+")
return true if /\A#{reg}\z/i =~ hostname
elsif /\AIP Address:(.*)/ =~ general_name
check_common_name = false
return true if $1 == hostname
end
}
}
if check_common_name
cert.subject.to_a.each{|oid, value|
if oid == "CN"
reg = Regexp.escape(value).gsub(/\\\*/, "[^.]+")
return true if /\A#{reg}\z/i =~ hostname
end
}
end
raise SSL::SSLError, "hostname was not match with the server certificate"
end
# Default callback for verification: only dumps error.
def default_verify_callback(is_ok, ctx)
if $DEBUG
if is_ok
warn("ok: #{ctx.current_cert.subject.to_s.dump}")
else
warn("ng: #{ctx.current_cert.subject.to_s.dump} at depth #{ctx.error_depth} - #{ctx.error}: #{ctx.error_string} in #{ctx.chain.inspect}")
end
warn(ctx.current_cert.to_text)
warn(ctx.current_cert.to_pem)
end
if !is_ok
depth = ctx.error_depth
code = ctx.error
msg = ctx.error_string
warn("at depth #{depth} - #{code}: #{msg}")
end
is_ok
end
# Sample callback method: CAUTION: does not check CRL/ARL.
def sample_verify_callback(is_ok, ctx)
unless is_ok
depth = ctx.error_depth
code = ctx.error
msg = ctx.error_string
warn("at depth #{depth} - #{code}: #{msg}") if $DEBUG
return false
end
cert = ctx.current_cert
self_signed = false
ca = false
pathlen = nil
server_auth = true
self_signed = (cert.subject.cmp(cert.issuer) == 0)
# Check extensions whatever its criticality is. (sample)
cert.extensions.each do |ex|
case ex.oid
when 'basicConstraints'
/CA:(TRUE|FALSE), pathlen:(\d+)/ =~ ex.value
ca = ($1 == 'TRUE')
pathlen = $2.to_i
when 'keyUsage'
usage = ex.value.split(/\s*,\s*/)
ca = usage.include?('Certificate Sign')
server_auth = usage.include?('Key Encipherment')
when 'extendedKeyUsage'
usage = ex.value.split(/\s*,\s*/)
server_auth = usage.include?('Netscape Server Gated Crypto')
when 'nsCertType'
usage = ex.value.split(/\s*,\s*/)
ca = usage.include?('SSL CA')
server_auth = usage.include?('SSL Server')
end
end
if self_signed
warn('self signing CA') if $DEBUG
return true
elsif ca
warn('middle level CA') if $DEBUG
return true
elsif server_auth
warn('for server authentication') if $DEBUG
return true
end
return false
end
private
def change_notify
@client.reset_all
end
def load_cacerts(cert_store)
file = File.join(File.dirname(__FILE__), 'cacert.p7s')
add_trust_ca_to_store(cert_store, file)
end
end
end
httpclient-2.3.3/lib/httpclient/cookie.rb 0000644 0000041 0000041 00000025013 12155433207 020452 0 ustar www-data www-data # cookie.rb is redistributed file which is originally included in Webagent
# version 0.6.2 by TAKAHASHI `Maki' Masayoshi. And it contains some bug fixes.
# You can download the entire package of Webagent from
# http://www.rubycolor.org/arc/.
# Cookie class
#
# I refered to w3m's source to make these classes. Some comments
# are quoted from it. I'm thanksful for author(s) of it.
#
# w3m homepage: http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/eng/
require 'time'
require 'monitor'
require 'httpclient/util'
class WebAgent
module CookieUtils
def head_match?(str1, str2)
str1 == str2[0, str1.length]
end
def tail_match?(str1, str2)
if str1.length > 0
str1 == str2[-str1.length..-1].to_s
else
true
end
end
def domain_match(host, domain)
domainname = domain.sub(/\.\z/, '').downcase
hostname = host.sub(/\.\z/, '').downcase
case domain
when /\d+\.\d+\.\d+\.\d+/
return (hostname == domainname)
when '.'
return true
when /^\./
# allows; host == rubyforge.org, domain == .rubyforge.org
return tail_match?(domainname, '.' + hostname)
else
return (hostname == domainname)
end
end
end
class Cookie
include CookieUtils
attr_accessor :name, :value
attr_accessor :domain, :path
attr_accessor :expires ## for Netscape Cookie
attr_accessor :url
attr_writer :use, :secure, :http_only, :discard, :domain_orig, :path_orig, :override
USE = 1
SECURE = 2
DOMAIN = 4
PATH = 8
DISCARD = 16
OVERRIDE = 32
OVERRIDE_OK = 32
HTTP_ONLY = 64
def initialize
@name = @value = @domain = @path = nil
@expires = nil
@url = nil
@use = @secure = @http_only = @discard = @domain_orig = @path_orig = @override = nil
end
def discard?
@discard
end
def use?
@use
end
def secure?
@secure
end
def http_only?
@http_only
end
def domain_orig?
@domain_orig
end
def path_orig?
@path_orig
end
def override?
@override
end
def flag
flg = 0
flg += USE if @use
flg += SECURE if @secure
flg += HTTP_ONLY if @http_only
flg += DOMAIN if @domain_orig
flg += PATH if @path_orig
flg += DISCARD if @discard
flg += OVERRIDE if @override
flg
end
def set_flag(flag)
flag = flag.to_i
@use = true if flag & USE > 0
@secure = true if flag & SECURE > 0
@http_only = true if flag & HTTP_ONLY > 0
@domain_orig = true if flag & DOMAIN > 0
@path_orig = true if flag & PATH > 0
@discard = true if flag & DISCARD > 0
@override = true if flag & OVERRIDE > 0
end
def match?(url)
domainname = url.host
if (!domainname ||
!domain_match(domainname, @domain) ||
(@path && !head_match?(@path, url.path.empty? ? '/' : url.path)) ||
(@secure && (url.scheme != 'https')) )
return false
else
return true
end
end
def join_quotedstr(array, sep)
ret = Array.new
old_elem = nil
array.each{|elem|
if (elem.scan(/"/).length % 2) == 0
if old_elem
old_elem << sep << elem
else
ret << elem
old_elem = nil
end
else
if old_elem
old_elem << sep << elem
ret << old_elem
old_elem = nil
else
old_elem = elem.dup
end
end
}
ret
end
def parse(str, url)
@url = url
# TODO: should not depend on join_quotedstr. scan with escape like CSV.
cookie_elem = str.split(/;/)
cookie_elem = join_quotedstr(cookie_elem, ';')
cookie_elem -= [""] # del empty elements, a cookie might included ";;"
first_elem = cookie_elem.shift
if first_elem !~ /([^=]*)(\=(.*))?/
return
## raise ArgumentError 'invalid cookie value'
end
@name = $1.strip
@value = normalize_cookie_value($3)
cookie_elem.each{|pair|
key, value = pair.split(/=/, 2) ## value may nil
key.strip!
value = normalize_cookie_value(value)
case key.downcase
when 'domain'
@domain = value
when 'expires'
@expires = nil
begin
@expires = Time.parse(value).gmtime if value
rescue ArgumentError
end
when 'path'
@path = value
when 'secure'
@secure = true ## value may nil, but must 'true'.
when 'httponly'
@http_only = true ## value may nil, but must 'true'.
else
## ignore
end
}
end
private
def normalize_cookie_value(value)
if value
value = value.strip.sub(/\A"(.*)"\z/) { $1 }
value = nil if value.empty?
end
value
end
end
class CookieManager
include CookieUtils
### errors
class Error < StandardError; end
class ErrorOverrideOK < Error; end
class SpecialError < Error; end
attr_reader :cookies
attr_accessor :cookies_file
attr_accessor :accept_domains, :reject_domains
def initialize(file=nil)
@cookies = Array.new
@cookies.extend(MonitorMixin)
@cookies_file = file
@is_saved = true
@reject_domains = Array.new
@accept_domains = Array.new
@netscape_rule = false
end
def cookies=(cookies)
@cookies = cookies
@cookies.extend(MonitorMixin)
end
def save_all_cookies(force = nil, save_unused = true, save_discarded = true)
@cookies.synchronize do
check_expired_cookies
if @is_saved and !force
return
end
File.open(@cookies_file, 'w') do |f|
@cookies.each do |cookie|
if (cookie.use? or save_unused) and
(!cookie.discard? or save_discarded)
f.print(cookie.url.to_s,"\t",
cookie.name,"\t",
cookie.value,"\t",
cookie.expires.to_i,"\t",
cookie.domain,"\t",
cookie.path,"\t",
cookie.flag,"\n")
end
end
end
end
@is_saved = true
end
def save_cookies(force = nil)
save_all_cookies(force, false, false)
end
def check_expired_cookies
@cookies.reject!{|cookie|
is_expired = (cookie.expires && (cookie.expires < Time.now.gmtime))
if is_expired && !cookie.discard?
@is_saved = false
end
is_expired
}
end
def parse(str, url)
cookie = WebAgent::Cookie.new
cookie.parse(str, url)
add(cookie)
end
def find(url)
return nil if @cookies.empty?
cookie_list = Array.new
@cookies.each{|cookie|
is_expired = (cookie.expires && (cookie.expires < Time.now.gmtime))
if cookie.use? && !is_expired && cookie.match?(url)
if cookie_list.select{|c1| c1.name == cookie.name}.empty?
cookie_list << cookie
end
end
}
return make_cookie_str(cookie_list)
end
def add(given)
check_domain(given.domain, given.url.host, given.override?)
domain = given.domain || given.url.host
path = given.path || given.url.path.sub(%r|/[^/]*\z|, '')
cookie = nil
@cookies.synchronize do
cookie = @cookies.find { |c|
c.domain == domain && c.path == path && c.name == given.name
}
if !cookie
cookie = WebAgent::Cookie.new
cookie.use = true
@cookies << cookie
end
check_expired_cookies
end
cookie.domain = domain
cookie.path = path
cookie.url = given.url
cookie.name = given.name
cookie.value = given.value
cookie.expires = given.expires
cookie.secure = given.secure?
cookie.http_only = given.http_only?
cookie.domain_orig = given.domain
cookie.path_orig = given.path
if cookie.discard? || cookie.expires == nil
cookie.discard = true
else
cookie.discard = false
@is_saved = false
end
end
def load_cookies
return if !File.readable?(@cookies_file)
@cookies.synchronize do
@cookies.clear
File.open(@cookies_file,'r'){|f|
while line = f.gets
cookie = WebAgent::Cookie.new
@cookies << cookie
col = line.chomp.split(/\t/)
cookie.url = HTTPClient::Util.urify(col[0])
cookie.name = col[1]
cookie.value = col[2]
if col[3].empty? or col[3] == '0'
cookie.expires = nil
else
cookie.expires = Time.at(col[3].to_i).gmtime
end
cookie.domain = col[4]
cookie.path = col[5]
cookie.set_flag(col[6])
end
}
end
end
# Who use it?
def check_cookie_accept_domain(domain)
unless domain
return false
end
@accept_domains.each{|dom|
if domain_match(domain, dom)
return true
end
}
@reject_domains.each{|dom|
if domain_match(domain, dom)
return false
end
}
return true
end
private
def make_cookie_str(cookie_list)
if cookie_list.empty?
return nil
end
ret = ''
c = cookie_list.shift
ret += "#{c.name}=#{c.value}"
cookie_list.each{|cookie|
ret += "; #{cookie.name}=#{cookie.value}"
}
return ret
end
# for conformance to http://wp.netscape.com/newsref/std/cookie_spec.html
attr_accessor :netscape_rule
SPECIAL_DOMAIN = [".com",".edu",".gov",".mil",".net",".org",".int"]
def check_domain(domain, hostname, override)
return unless domain
# [DRAFT 12] s. 4.2.2 (does not apply in the case that
# host name is the same as domain attribute for version 0
# cookie)
# I think that this rule has almost the same effect as the
# tail match of [NETSCAPE].
if domain !~ /^\./ && hostname != domain
domain = '.'+domain
end
# [NETSCAPE] rule
if @netscape_rule
n = domain.scan(/\./).length
if n < 2
cookie_error(SpecialError.new, override)
elsif n == 2
## [NETSCAPE] rule
ok = SPECIAL_DOMAIN.select{|sdomain|
sdomain == domain[-(sdomain.length)..-1]
}
if ok.empty?
cookie_error(SpecialError.new, override)
end
end
end
# this implementation does not check RFC2109 4.3.2 case 2;
# the portion of host not in domain does not contain a dot.
# according to nsCookieService.cpp in Firefox 3.0.4, Firefox 3.0.4
# and IE does not check, too.
end
# not tested well; used only netscape_rule = true.
def cookie_error(err, override)
if !err.kind_of?(ErrorOverrideOK) || !override
raise err
end
end
end
end
httpclient-2.3.3/lib/http-access2.rb 0000644 0000041 0000041 00000003300 12155433207 017316 0 ustar www-data www-data # HTTPAccess2 - HTTP accessing library.
# Copyright (C) 2000-2007 NAKAMURA, Hiroshi .
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
# either the dual license version in 2003, or any later version.
# http-access2.rb is based on http-access.rb in http-access/0.0.4. Some
# part of it is copyrighted by Maebashi-san who made and published
# http-access/0.0.4. http-access/0.0.4 did not include license notice but
# when I asked Maebashi-san he agreed that I can redistribute it under the
# same terms of Ruby. Many thanks to Maebashi-san.
require 'httpclient'
module HTTPAccess2
VERSION = ::HTTPClient::VERSION
RUBY_VERSION_STRING = ::HTTPClient::RUBY_VERSION_STRING
SSLEnabled = ::HTTPClient::SSLEnabled
SSPIEnabled = ::HTTPClient::SSPIEnabled
DEBUG_SSL = true
Util = ::HTTPClient::Util
class Client < ::HTTPClient
class RetryableResponse < StandardError
end
end
SSLConfig = ::HTTPClient::SSLConfig
BasicAuth = ::HTTPClient::BasicAuth
DigestAuth = ::HTTPClient::DigestAuth
NegotiateAuth = ::HTTPClient::NegotiateAuth
AuthFilterBase = ::HTTPClient::AuthFilterBase
WWWAuth = ::HTTPClient::WWWAuth
ProxyAuth = ::HTTPClient::ProxyAuth
Site = ::HTTPClient::Site
Connection = ::HTTPClient::Connection
SessionManager = ::HTTPClient::SessionManager
SSLSocketWrap = ::HTTPClient::SSLSocketWrap
DebugSocket = ::HTTPClient::DebugSocket
class Session < ::HTTPClient::Session
class Error < StandardError
end
class InvalidState < Error
end
class BadResponse < Error
end
class KeepAliveDisconnected < Error
end
end
end
httpclient-2.3.3/lib/hexdump.rb 0000644 0000041 0000041 00000002252 12155433207 016475 0 ustar www-data www-data # encoding: binary
# This was written by Arai-san and published at
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/31987
module HexDump
# str must be in BINARY encoding in 1.9
def encode(str)
offset = 0
result = []
while raw = str.slice(offset, 16) and raw.length > 0
# data field
data = ''
for v in raw.unpack('N* a*')
if v.kind_of? Integer
data << sprintf("%08x ", v)
else
v.each_byte {|c| data << sprintf("%02x", c) }
end
end
# text field
text = raw.tr("\000-\037\177-\377", ".")
result << sprintf("%08x %-36s %s", offset, data, text)
offset += 16
# omit duplicate line
if /^(#{regex_quote_n(raw)})+/n =~ str[offset .. -1]
result << sprintf("%08x ...", offset)
offset += $&.length
# should print at the end
if offset == str.length
result << sprintf("%08x %-36s %s", offset-16, data, text)
end
end
end
result
end
module_function :encode
if RUBY_VERSION >= "1.9"
# raw must be in BINARY encoding in 1.9
def self.regex_quote_n(raw)
Regexp.quote(raw)
end
else
def self.regex_quote_n(raw)
Regexp.quote(raw, 'n')
end
end
end
httpclient-2.3.3/lib/httpclient.rb 0000644 0000041 0000041 00000113127 12155433207 017205 0 ustar www-data www-data # HTTPClient - HTTP client library.
# Copyright (C) 2000-2009 NAKAMURA, Hiroshi .
#
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
# redistribute it and/or modify it under the same terms of Ruby's license;
# either the dual license version in 2003, or any later version.
require 'stringio'
require 'digest/sha1'
# Extra library
require 'httpclient/version'
require 'httpclient/util'
require 'httpclient/ssl_config'
require 'httpclient/connection'
require 'httpclient/session'
require 'httpclient/http'
require 'httpclient/auth'
require 'httpclient/cookie'
# :main:HTTPClient
# The HTTPClient class provides several methods for accessing Web resources
# via HTTP.
#
# HTTPClient instance is designed to be MT-safe. You can call a HTTPClient
# instance from several threads without synchronization after setting up an
# instance.
#
# clnt = HTTPClient.new
# clnt.set_cookie_store('/home/nahi/cookie.dat')
# urls.each do |url|
# Thread.new(url) do |u|
# p clnt.head(u).status
# end
# end
#
# == How to use
#
# At first, how to create your client. See initialize for more detail.
#
# 1. Create simple client.
#
# clnt = HTTPClient.new
#
# 2. Accessing resources through HTTP proxy. You can use environment
# variable 'http_proxy' or 'HTTP_PROXY' instead.
#
# clnt = HTTPClient.new('http://myproxy:8080')
#
# === How to retrieve web resources
#
# See get and get_content.
#
# 1. Get content of specified URL. It returns HTTP::Message object and
# calling 'body' method of it returns a content String.
#
# puts clnt.get('http://dev.ctor.org/').body
#
# 2. For getting content directly, use get_content. It follows redirect
# response and returns a String of whole result.
#
# puts clnt.get_content('http://dev.ctor.org/')
#
# 3. You can pass :follow_redirect option to follow redirect response in get.
#
# puts clnt.get('http://dev.ctor.org/', :follow_redirect => true)
#
# 4. Get content as chunks of String. It yields chunks of String.
#
# clnt.get_content('http://dev.ctor.org/') do |chunk|
# puts chunk
# end
#
# === Invoking other HTTP methods
#
# See head, get, post, put, delete, options, propfind, proppatch and trace.
# It returns a HTTP::Message instance as a response.
#
# 1. Do HEAD request.
#
# res = clnt.head(uri)
# p res.header['Last-Modified'][0]
#
# 2. Do GET request with query.
#
# query = { 'keyword' => 'ruby', 'lang' => 'en' }
# res = clnt.get(uri, query)
# p res.status
# p res.contenttype
# p res.header['X-Custom']
# puts res.body
#
# You can also use keyword argument style.
#
# res = clnt.get(uri, :query => { :keyword => 'ruby', :lang => 'en' })
#
# === How to POST
#
# See post.
#
# 1. Do POST a form data.
#
# body = { 'keyword' => 'ruby', 'lang' => 'en' }
# res = clnt.post(uri, body)
#
# Keyword argument style.
#
# res = clnt.post(uri, :body => ...)
#
# 2. Do multipart file upload with POST. No need to set extra header by
# yourself from httpclient/2.1.4.
#
# File.open('/tmp/post_data') do |file|
# body = { 'upload' => file, 'user' => 'nahi' }
# res = clnt.post(uri, body)
# end
#
# 3. Do multipart wth custom body.
#
# File.open('/tmp/post_data') do |file|
# body = [{ 'Content-Type' => 'application/atom+xml; charset=UTF-8',
# :content => '...' },
# { 'Content-Type' => 'video/mp4',
# 'Content-Transfer-Encoding' => 'binary',
# :content => file }]
# res = clnt.post(uri, body)
# end
#
# === Accessing via SSL
#
# Ruby needs to be compiled with OpenSSL.
#
# 1. Get content of specified URL via SSL.
# Just pass an URL which starts with 'https://'.
#
# https_url = 'https://www.rsa.com'
# clnt.get(https_url)
#
# 2. Getting peer certificate from response.
#
# res = clnt.get(https_url)
# p res.peer_cert #=> returns OpenSSL::X509::Certificate
#
# 3. Configuring OpenSSL options. See HTTPClient::SSLConfig for more details.
#
# user_cert_file = 'cert.pem'
# user_key_file = 'privkey.pem'
# clnt.ssl_config.set_client_cert_file(user_cert_file, user_key_file)
# clnt.get(https_url)
#
# === Handling Cookies
#
# 1. Using volatile Cookies. Nothing to do. HTTPClient handles Cookies.
#
# clnt = HTTPClient.new
# res = clnt.get(url1) # receives Cookies.
# res = clnt.get(url2) # sends Cookies if needed.
# p res.cookies
#
# 2. Saving non volatile Cookies to a specified file. Need to set a file at
# first and invoke save method at last.
#
# clnt = HTTPClient.new
# clnt.set_cookie_store('/home/nahi/cookie.dat')
# clnt.get(url)
# ...
# clnt.save_cookie_store
#
# 3. Disabling Cookies.
#
# clnt = HTTPClient.new
# clnt.cookie_manager = nil
#
# === Configuring authentication credentials
#
# 1. Authentication with Web server. Supports BasicAuth, DigestAuth, and
# Negotiate/NTLM (requires ruby/ntlm module).
#
# clnt = HTTPClient.new
# domain = 'http://dev.ctor.org/http-access2/'
# user = 'user'
# password = 'user'
# clnt.set_auth(domain, user, password)
# p clnt.get('http://dev.ctor.org/http-access2/login').status
#
# 2. Authentication with Proxy server. Supports BasicAuth and NTLM
# (requires win32/sspi)
#
# clnt = HTTPClient.new(proxy)
# user = 'proxy'
# password = 'proxy'
# clnt.set_proxy_auth(user, password)
# p clnt.get(url)
#
# === Invoking HTTP methods with custom header
#
# Pass a Hash or an Array for header argument.
#
# header = { 'Accept' => 'text/html' }
# clnt.get(uri, query, header)
#
# header = [['Accept', 'image/jpeg'], ['Accept', 'image/png']]
# clnt.get_content(uri, query, header)
#
# === Invoking HTTP methods asynchronously
#
# See head_async, get_async, post_async, put_async, delete_async,
# options_async, propfind_async, proppatch_async, and trace_async.
# It immediately returns a HTTPClient::Connection instance as a returning value.
#
# connection = clnt.post_async(url, body)
# print 'posting.'
# while true
# break if connection.finished?
# print '.'
# sleep 1
# end
# puts '.'
# res = connection.pop
# p res.status
# p res.body.read # res.body is an IO for the res of async method.
#
# === Shortcut methods
#
# You can invoke get_content, get, etc. without creating HTTPClient instance.
#
# ruby -rhttpclient -e 'puts HTTPClient.get_content(ARGV.shift)' http://dev.ctor.org/
# ruby -rhttpclient -e 'p HTTPClient.head(ARGV.shift).header["last-modified"]' http://dev.ctor.org/
#
class HTTPClient
RUBY_VERSION_STRING = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
LIB_NAME = "(#{VERSION}, #{RUBY_VERSION_STRING})"
include Util
# Raised for indicating running environment configuration error for example
# accessing via SSL under the ruby which is not compiled with OpenSSL.
class ConfigurationError < StandardError
end
# Raised for indicating HTTP response error.
class BadResponseError < RuntimeError
# HTTP::Message:: a response
attr_reader :res
def initialize(msg, res = nil) # :nodoc:
super(msg)
@res = res
end
end
# Raised for indicating a timeout error.
class TimeoutError < RuntimeError
end
# Raised for indicating a connection timeout error.
# You can configure connection timeout via HTTPClient#connect_timeout=.
class ConnectTimeoutError < TimeoutError
end
# Raised for indicating a request sending timeout error.
# You can configure request sending timeout via HTTPClient#send_timeout=.
class SendTimeoutError < TimeoutError
end
# Raised for indicating a response receiving timeout error.
# You can configure response receiving timeout via
# HTTPClient#receive_timeout=.
class ReceiveTimeoutError < TimeoutError
end
# Deprecated. just for backward compatibility
class Session
BadResponse = ::HTTPClient::BadResponseError
end
class << self
%w(get_content post_content head get post put delete options propfind proppatch trace).each do |name|
eval <<-EOD
def #{name}(*arg, &block)
clnt = new
begin
clnt.#{name}(*arg, &block)
ensure
clnt.reset_all
end
end
EOD
end
private
def attr_proxy(symbol, assignable = false)
name = symbol.to_s
define_method(name) {
@session_manager.__send__(name)
}
if assignable
aname = name + '='
define_method(aname) { |rhs|
reset_all
@session_manager.__send__(aname, rhs)
}
end
end
end
# HTTPClient::SSLConfig:: SSL configurator.
attr_reader :ssl_config
# WebAgent::CookieManager:: Cookies configurator.
attr_accessor :cookie_manager
# An array of response HTTP message body String which is used for loop-back
# test. See test/* to see how to use it. If you want to do loop-back test
# of HTTP header, use test_loopback_http_response instead.
attr_reader :test_loopback_response
# An array of request filter which can trap HTTP request/response.
# See HTTPClient::WWWAuth to see how to use it.
attr_reader :request_filter
# HTTPClient::ProxyAuth:: Proxy authentication handler.
attr_reader :proxy_auth
# HTTPClient::WWWAuth:: WWW authentication handler.
attr_reader :www_auth
# How many times get_content and post_content follows HTTP redirect.
# 10 by default.
attr_accessor :follow_redirect_count
# Set HTTP version as a String:: 'HTTP/1.0' or 'HTTP/1.1'
attr_proxy(:protocol_version, true)
# Connect timeout in sec.
attr_proxy(:connect_timeout, true)
# Request sending timeout in sec.
attr_proxy(:send_timeout, true)
# Response receiving timeout in sec.
attr_proxy(:receive_timeout, true)
# Reuse the same connection within this timeout in sec. from last used.
attr_proxy(:keep_alive_timeout, true)
# Size of reading block for non-chunked response.
attr_proxy(:read_block_size, true)
# Negotiation retry count for authentication. 5 by default.
attr_proxy(:protocol_retry_count, true)
# if your ruby is older than 2005-09-06, do not set socket_sync = false to
# avoid an SSL socket blocking bug in openssl/buffering.rb.
attr_proxy(:socket_sync, true)
# User-Agent header in HTTP request.
attr_proxy(:agent_name, true)
# From header in HTTP request.
attr_proxy(:from, true)
# An array of response HTTP String (not a HTTP message body) which is used
# for loopback test. See test/* to see how to use it.
attr_proxy(:test_loopback_http_response)
# Decompress a compressed (with gzip or deflate) content body transparently. false by default.
attr_proxy(:transparent_gzip_decompression, true)
# Local socket address. Set HTTPClient#socket_local.host and HTTPClient#socket_local.port to specify local binding hostname and port of TCP socket.
attr_proxy(:socket_local, true)
# Default header for PROPFIND request.
PROPFIND_DEFAULT_EXTHEADER = { 'Depth' => '0' }
# Default User-Agent header
DEFAULT_AGENT_NAME = 'HTTPClient/1.0'
# Creates a HTTPClient instance which manages sessions, cookies, etc.
#
# HTTPClient.new takes 3 optional arguments for proxy url string,
# User-Agent String and From header String. User-Agent and From are embedded
# in HTTP request Header if given. No User-Agent and From header added
# without setting it explicitly.
#
# proxy = 'http://myproxy:8080'
# agent_name = 'MyAgent/0.1'
# from = 'from@example.com'
# HTTPClient.new(proxy, agent_name, from)
#
# You can use a keyword argument style Hash. Keys are :proxy, :agent_name
# and :from.
#
# HTTPClient.new(:agent_name => 'MyAgent/0.1')
def initialize(*args)
proxy, agent_name, from = keyword_argument(args, :proxy, :agent_name, :from)
@proxy = nil # assigned later.
@no_proxy = nil
@no_proxy_regexps = []
@www_auth = WWWAuth.new
@proxy_auth = ProxyAuth.new
@request_filter = [@proxy_auth, @www_auth]
@debug_dev = nil
@redirect_uri_callback = method(:default_redirect_uri_callback)
@test_loopback_response = []
@session_manager = SessionManager.new(self)
@session_manager.agent_name = agent_name || DEFAULT_AGENT_NAME
@session_manager.from = from
@session_manager.ssl_config = @ssl_config = SSLConfig.new(self)
@cookie_manager = WebAgent::CookieManager.new
@follow_redirect_count = 10
load_environment
self.proxy = proxy if proxy
keep_webmock_compat
end
# webmock 1.6.2 depends on HTTP::Message#body.content to work.
# let's keep it work iif webmock is loaded for a while.
def keep_webmock_compat
if respond_to?(:do_get_block_with_webmock)
::HTTP::Message.module_eval do
def body
def (o = self.content).content
self
end
o
end
end
end
end
# Returns debug device if exists. See debug_dev=.
def debug_dev
@debug_dev
end
# Sets debug device. Once debug device is set, all HTTP requests and
# responses are dumped to given device. dev must respond to << for dump.
#
# Calling this method resets all existing sessions.
def debug_dev=(dev)
@debug_dev = dev
reset_all
@session_manager.debug_dev = dev
end
# Returns URI object of HTTP proxy if exists.
def proxy
@proxy
end
# Sets HTTP proxy used for HTTP connection. Given proxy can be an URI,
# a String or nil. You can set user/password for proxy authentication like
# HTTPClient#proxy = 'http://user:passwd@myproxy:8080'
#
# You can use environment variable 'http_proxy' or 'HTTP_PROXY' for it.
# You need to use 'cgi_http_proxy' or 'CGI_HTTP_PROXY' instead if you run
# HTTPClient from CGI environment from security reason. (HTTPClient checks
# 'REQUEST_METHOD' environment variable whether it's CGI or not)
#
# Calling this method resets all existing sessions.
def proxy=(proxy)
if proxy.nil? || proxy.to_s.empty?
@proxy = nil
@proxy_auth.reset_challenge
else
@proxy = urify(proxy)
if @proxy.scheme == nil or @proxy.scheme.downcase != 'http' or
@proxy.host == nil or @proxy.port == nil
raise ArgumentError.new("unsupported proxy #{proxy}")
end
@proxy_auth.reset_challenge
if @proxy.user || @proxy.password
@proxy_auth.set_auth(@proxy.user, @proxy.password)
end
end
reset_all
@session_manager.proxy = @proxy
@proxy
end
# Returns NO_PROXY setting String if given.
def no_proxy
@no_proxy
end
# Sets NO_PROXY setting String. no_proxy must be a comma separated String.
# Each entry must be 'host' or 'host:port' such as;
# HTTPClient#no_proxy = 'example.com,example.co.jp:443'
#
# 'localhost' is treated as a no_proxy site regardless of explicitly listed.
# HTTPClient checks given URI objects before accessing it.
# 'host' is tail string match. No IP-addr conversion.
#
# You can use environment variable 'no_proxy' or 'NO_PROXY' for it.
#
# Calling this method resets all existing sessions.
def no_proxy=(no_proxy)
@no_proxy = no_proxy
@no_proxy_regexps.clear
if @no_proxy
@no_proxy.scan(/([^:,]+)(?::(\d+))?/) do |host, port|
if host[0] == ?.
regexp = /#{Regexp.quote(host)}\z/i
else
regexp = /(\A|\.)#{Regexp.quote(host)}\z/i
end
@no_proxy_regexps << [regexp, port]
end
end
reset_all
end
# Sets credential for Web server authentication.
# domain:: a String or an URI to specify where HTTPClient should use this
# credential. If you set uri to nil, HTTPClient uses this credential
# wherever a server requires it.
# user:: username String.
# passwd:: password String.
#
# You can set multiple credentials for each uri.
#
# clnt.set_auth('http://www.example.com/foo/', 'foo_user', 'passwd')
# clnt.set_auth('http://www.example.com/bar/', 'bar_user', 'passwd')
#
# Calling this method resets all existing sessions.
def set_auth(domain, user, passwd)
uri = urify(domain)
@www_auth.set_auth(uri, user, passwd)
reset_all
end
# Deprecated. Use set_auth instead.
def set_basic_auth(domain, user, passwd)
uri = urify(domain)
@www_auth.basic_auth.set(uri, user, passwd)
reset_all
end
# Sets credential for Proxy authentication.
# user:: username String.
# passwd:: password String.
#
# Calling this method resets all existing sessions.
def set_proxy_auth(user, passwd)
@proxy_auth.set_auth(user, passwd)
reset_all
end
# Sets the filename where non-volatile Cookies be saved by calling
# save_cookie_store.
# This method tries to load and managing Cookies from the specified file.
#
# Calling this method resets all existing sessions.
def set_cookie_store(filename)
@cookie_manager.cookies_file = filename
@cookie_manager.load_cookies if filename
reset_all
end
# Try to save Cookies to the file specified in set_cookie_store. Unexpected
# error will be raised if you don't call set_cookie_store first.
# (interface mismatch between WebAgent::CookieManager implementation)
def save_cookie_store
@cookie_manager.save_cookies
end
# Returns stored cookies.
def cookies
if @cookie_manager
@cookie_manager.cookies
end
end
# Sets callback proc when HTTP redirect status is returned for get_content
# and post_content. default_redirect_uri_callback is used by default.
#
# If you need strict implementation which does not allow relative URI
# redirection, set strict_redirect_uri_callback instead.
#
# clnt.redirect_uri_callback = clnt.method(:strict_redirect_uri_callback)
#
def redirect_uri_callback=(redirect_uri_callback)
@redirect_uri_callback = redirect_uri_callback
end
# Retrieves a web resource.
#
# uri:: a String or an URI object which represents an URL of web resource.
# query:: a Hash or an Array of query part of URL.
# e.g. { "a" => "b" } => 'http://host/part?a=b'.
# Give an array to pass multiple value like
# [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'.
# header:: a Hash or an Array of extra headers. e.g.
# { 'Accept' => 'text/html' } or
# [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
# &block:: Give a block to get chunked message-body of response like
# get_content(uri) { |chunked_body| ... }.
# Size of each chunk may not be the same.
#
# get_content follows HTTP redirect status (see HTTP::Status.redirect?)
# internally and try to retrieve content from redirected URL. See
# redirect_uri_callback= how HTTP redirection is handled.
#
# If you need to get full HTTP response including HTTP status and headers,
# use get method. get returns HTTP::Message as a response and you need to
# follow HTTP redirect by yourself if you need.
def get_content(uri, *args, &block)
query, header = keyword_argument(args, :query, :header)
success_content(follow_redirect(:get, uri, query, nil, header || {}, &block))
end
# Posts a content.
#
# uri:: a String or an URI object which represents an URL of web resource.
# body:: a Hash or an Array of body part. e.g.
# { "a" => "b" } => 'a=b'
# Give an array to pass multiple value like
# [["a", "b"], ["a", "c"]] => 'a=b&a=c'
# When you pass a File as a value, it will be posted as a
# multipart/form-data. e.g.
# { 'upload' => file }
# You can also send custom multipart by passing an array of hashes.
# Each part must have a :content attribute which can be a file, all
# other keys will become headers.
# [{ 'Content-Type' => 'text/plain', :content => "some text" },
# { 'Content-Type' => 'video/mp4', :content => File.new('video.mp4') }]
# =>
# header:: a Hash or an Array of extra headers. e.g.
# { 'Accept' => 'text/html' }
# or
# [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
# &block:: Give a block to get chunked message-body of response like
# post_content(uri) { |chunked_body| ... }.
# Size of each chunk may not be the same.
#
# post_content follows HTTP redirect status (see HTTP::Status.redirect?)
# internally and try to post the content to redirected URL. See
# redirect_uri_callback= how HTTP redirection is handled.
# Bear in mind that you should not depend on post_content because it sends
# the same POST method to the new location which is prohibited in HTTP spec.
#
# If you need to get full HTTP response including HTTP status and headers,
# use post method.
def post_content(uri, *args, &block)
body, header = keyword_argument(args, :body, :header)
success_content(follow_redirect(:post, uri, nil, body, header || {}, &block))
end
# A method for redirect uri callback. How to use:
# clnt.redirect_uri_callback = clnt.method(:strict_redirect_uri_callback)
# This callback does not allow relative redirect such as
# Location: ../foo/
# in HTTP header. (raises BadResponseError instead)
def strict_redirect_uri_callback(uri, res)
newuri = urify(res.header['location'][0])
if https?(uri) && !https?(newuri)
raise BadResponseError.new("redirecting to non-https resource")
end
if !http?(newuri) && !https?(newuri)
raise BadResponseError.new("unexpected location: #{newuri}", res)
end
puts "redirect to: #{newuri}" if $DEBUG
newuri
end
# A default method for redirect uri callback. This method is used by
# HTTPClient instance by default.
# This callback allows relative redirect such as
# Location: ../foo/
# in HTTP header.
def default_redirect_uri_callback(uri, res)
newuri = urify(res.header['location'][0])
if !http?(newuri) && !https?(newuri)
newuri = uri + newuri
warn("could be a relative URI in location header which is not recommended")
warn("'The field value consists of a single absolute URI' in HTTP spec")
end
if https?(uri) && !https?(newuri)
raise BadResponseError.new("redirecting to non-https resource")
end
puts "redirect to: #{newuri}" if $DEBUG
newuri
end
# Sends HEAD request to the specified URL. See request for arguments.
def head(uri, *args)
request(:head, uri, argument_to_hash(args, :query, :header, :follow_redirect))
end
# Sends GET request to the specified URL. See request for arguments.
def get(uri, *args, &block)
request(:get, uri, argument_to_hash(args, :query, :header, :follow_redirect), &block)
end
# Sends POST request to the specified URL. See request for arguments.
# You should not depend on :follow_redirect => true for POST method. It
# sends the same POST method to the new location which is prohibited in HTTP spec.
def post(uri, *args, &block)
request(:post, uri, argument_to_hash(args, :body, :header, :follow_redirect), &block)
end
# Sends PUT request to the specified URL. See request for arguments.
def put(uri, *args, &block)
request(:put, uri, argument_to_hash(args, :body, :header), &block)
end
# Sends DELETE request to the specified URL. See request for arguments.
def delete(uri, *args, &block)
request(:delete, uri, argument_to_hash(args, :body, :header), &block)
end
# Sends OPTIONS request to the specified URL. See request for arguments.
def options(uri, *args, &block)
request(:options, uri, argument_to_hash(args, :header), &block)
end
# Sends PROPFIND request to the specified URL. See request for arguments.
def propfind(uri, *args, &block)
request(:propfind, uri, argument_to_hash(args, :header), &block)
end
# Sends PROPPATCH request to the specified URL. See request for arguments.
def proppatch(uri, *args, &block)
request(:proppatch, uri, argument_to_hash(args, :body, :header), &block)
end
# Sends TRACE request to the specified URL. See request for arguments.
def trace(uri, *args, &block)
request('TRACE', uri, argument_to_hash(args, :query, :header), &block)
end
# Sends a request to the specified URL.
#
# method:: HTTP method to be sent. method.to_s.upcase is used.
# uri:: a String or an URI object which represents an URL of web resource.
# query:: a Hash or an Array of query part of URL.
# e.g. { "a" => "b" } => 'http://host/part?a=b'
# Give an array to pass multiple value like
# [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'
# body:: a Hash or an Array of body part. e.g.
# { "a" => "b" }
# => 'a=b'
# Give an array to pass multiple value like
# [["a", "b"], ["a", "c"]]
# => 'a=b&a=c'.
# When the given method is 'POST' and the given body contains a file
# as a value, it will be posted as a multipart/form-data. e.g.
# { 'upload' => file }
# You can also send custom multipart by passing an array of hashes.
# Each part must have a :content attribute which can be a file, all
# other keys will become headers.
# [{ 'Content-Type' => 'text/plain', :content => "some text" },
# { 'Content-Type' => 'video/mp4', :content => File.new('video.mp4') }]
# =>
# See HTTP::Message.file? for actual condition of 'a file'.
# header:: a Hash or an Array of extra headers. e.g.
# { 'Accept' => 'text/html' } or
# [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
# &block:: Give a block to get chunked message-body of response like
# get(uri) { |chunked_body| ... }.
# Size of each chunk may not be the same.
#
# You can also pass a String as a body. HTTPClient just sends a String as
# a HTTP request message body.
#
# When you pass an IO as a body, HTTPClient sends it as a HTTP request with
# chunked encoding (Transfer-Encoding: chunked in HTTP header) if IO does not
# respond to :read. Bear in mind that some server application does not support
# chunked request. At least cgi.rb does not support it.
def request(method, uri, *args, &block)
query, body, header, follow_redirect = keyword_argument(args, :query, :body, :header, :follow_redirect)
if [:post, :put].include?(method)
body ||= ''
end
if method == :propfind
header ||= PROPFIND_DEFAULT_EXTHEADER
else
header ||= {}
end
uri = urify(uri)
if block
filtered_block = proc { |res, str|
block.call(str)
}
end
if follow_redirect
follow_redirect(method, uri, query, body, header, &block)
else
do_request(method, uri, query, body, header, &filtered_block)
end
end
# Sends HEAD request in async style. See request_async for arguments.
# It immediately returns a HTTPClient::Connection instance as a result.
def head_async(uri, *args)
query, header = keyword_argument(args, :query, :header)
request_async(:head, uri, query, nil, header || {})
end
# Sends GET request in async style. See request_async for arguments.
# It immediately returns a HTTPClient::Connection instance as a result.
def get_async(uri, *args)
query, header = keyword_argument(args, :query, :header)
request_async(:get, uri, query, nil, header || {})
end
# Sends POST request in async style. See request_async for arguments.
# It immediately returns a HTTPClient::Connection instance as a result.
def post_async(uri, *args)
body, header = keyword_argument(args, :body, :header)
request_async(:post, uri, nil, body || '', header || {})
end
# Sends PUT request in async style. See request_async for arguments.
# It immediately returns a HTTPClient::Connection instance as a result.
def put_async(uri, *args)
body, header = keyword_argument(args, :body, :header)
request_async(:put, uri, nil, body || '', header || {})
end
# Sends DELETE request in async style. See request_async for arguments.
# It immediately returns a HTTPClient::Connection instance as a result.
def delete_async(uri, *args)
header = keyword_argument(args, :header)
request_async(:delete, uri, nil, nil, header || {})
end
# Sends OPTIONS request in async style. See request_async for arguments.
# It immediately returns a HTTPClient::Connection instance as a result.
def options_async(uri, *args)
header = keyword_argument(args, :header)
request_async(:options, uri, nil, nil, header || {})
end
# Sends PROPFIND request in async style. See request_async for arguments.
# It immediately returns a HTTPClient::Connection instance as a result.
def propfind_async(uri, *args)
header = keyword_argument(args, :header)
request_async(:propfind, uri, nil, nil, header || PROPFIND_DEFAULT_EXTHEADER)
end
# Sends PROPPATCH request in async style. See request_async for arguments.
# It immediately returns a HTTPClient::Connection instance as a result.
def proppatch_async(uri, *args)
body, header = keyword_argument(args, :body, :header)
request_async(:proppatch, uri, nil, body, header || {})
end
# Sends TRACE request in async style. See request_async for arguments.
# It immediately returns a HTTPClient::Connection instance as a result.
def trace_async(uri, *args)
query, body, header = keyword_argument(args, :query, :body, :header)
request_async(:trace, uri, query, body, header || {})
end
# Sends a request in async style. request method creates new Thread for
# HTTP connection and returns a HTTPClient::Connection instance immediately.
#
# Arguments definition is the same as request.
def request_async(method, uri, query = nil, body = nil, header = {})
uri = urify(uri)
do_request_async(method, uri, query, body, header)
end
# Resets internal session for the given URL. Keep-alive connection for the
# site (host-port pair) is disconnected if exists.
def reset(uri)
uri = urify(uri)
@session_manager.reset(uri)
end
# Resets all of internal sessions. Keep-alive connections are disconnected.
def reset_all
@session_manager.reset_all
end
private
class RetryableResponse < StandardError # :nodoc:
end
class KeepAliveDisconnected < StandardError # :nodoc:
attr_reader :sess
def initialize(sess = nil)
@sess = sess
end
end
def do_request(method, uri, query, body, header, &block)
conn = Connection.new
res = nil
if HTTP::Message.file?(body)
pos = body.pos rescue nil
end
retry_count = @session_manager.protocol_retry_count
proxy = no_proxy?(uri) ? nil : @proxy
while retry_count > 0
body.pos = pos if pos
req = create_request(method, uri, query, body, header)
begin
protect_keep_alive_disconnected do
do_get_block(req, proxy, conn, &block)
end
res = conn.pop
break
rescue RetryableResponse
res = conn.pop
retry_count -= 1
end
end
res
end
def do_request_async(method, uri, query, body, header)
conn = Connection.new
t = Thread.new(conn) { |tconn|
begin
if HTTP::Message.file?(body)
pos = body.pos rescue nil
end
retry_count = @session_manager.protocol_retry_count
proxy = no_proxy?(uri) ? nil : @proxy
while retry_count > 0
body.pos = pos if pos
req = create_request(method, uri, query, body, header)
begin
protect_keep_alive_disconnected do
do_get_stream(req, proxy, tconn)
end
break
rescue RetryableResponse
retry_count -= 1
end
end
rescue Exception
conn.push $!
end
}
conn.async_thread = t
conn
end
def load_environment
# http_proxy
if getenv('REQUEST_METHOD')
# HTTP_PROXY conflicts with the environment variable usage in CGI where
# HTTP_* is used for HTTP header information. Unlike open-uri, we
# simply ignore http_proxy in CGI env and use cgi_http_proxy instead.
self.proxy = getenv('cgi_http_proxy')
else
self.proxy = getenv('http_proxy')
end
# no_proxy
self.no_proxy = getenv('no_proxy')
end
def getenv(name)
ENV[name.downcase] || ENV[name.upcase]
end
def follow_redirect(method, uri, query, body, header, &block)
uri = urify(uri)
if block
filtered_block = proc { |r, str|
block.call(str) if r.ok?
}
end
if HTTP::Message.file?(body)
pos = body.pos rescue nil
end
retry_number = 0
while retry_number < @follow_redirect_count
body.pos = pos if pos
res = do_request(method, uri, query, body, header, &filtered_block)
if res.redirect?
method = :get if res.see_other? # See RFC2616 10.3.4
uri = urify(@redirect_uri_callback.call(uri, res))
retry_number += 1
else
return res
end
end
raise BadResponseError.new("retry count exceeded", res)
end
def success_content(res)
if res.ok?
return res.content
else
raise BadResponseError.new("unexpected response: #{res.header.inspect}", res)
end
end
def protect_keep_alive_disconnected
begin
yield
rescue KeepAliveDisconnected => e
if e.sess
@session_manager.invalidate(e.sess.dest)
end
yield
end
end
def create_request(method, uri, query, body, header)
method = method.to_s.upcase
if header.is_a?(Hash)
header = header.to_a
else
header = header.dup
end
boundary = nil
if body
_, content_type = header.find { |key, value|
key.downcase == 'content-type'
}
if content_type
if /\Amultipart/ =~ content_type
if content_type =~ /boundary=(.+)\z/
boundary = $1
else
boundary = create_boundary
content_type = "#{content_type}; boundary=#{boundary}"
header = override_header(header, 'Content-Type', content_type)
end
end
else
if file_in_form_data?(body)
boundary = create_boundary
content_type = "multipart/form-data; boundary=#{boundary}"
else
content_type = 'application/x-www-form-urlencoded'
end
header << ['Content-Type', content_type]
end
end
req = HTTP::Message.new_request(method, uri, query, body, boundary)
header.each do |key, value|
req.header.add(key.to_s, value)
end
if @cookie_manager && cookie = @cookie_manager.find(uri)
req.header.add('Cookie', cookie)
end
req
end
def create_boundary
Digest::SHA1.hexdigest(Time.now.to_s)
end
def file_in_form_data?(body)
HTTP::Message.multiparam_query?(body) &&
body.any? { |k, v| HTTP::Message.file?(v) }
end
def override_header(header, key, value)
result = []
header.each do |k, v|
if k.downcase == key.downcase
result << [key, value]
else
result << [k, v]
end
end
result
end
NO_PROXY_HOSTS = ['localhost']
def no_proxy?(uri)
if !@proxy or NO_PROXY_HOSTS.include?(uri.host)
return true
end
@no_proxy_regexps.each do |regexp, port|
if !port || uri.port == port.to_i
if regexp =~ uri.host
return true
end
end
end
false
end
# !! CAUTION !!
# Method 'do_get*' runs under MT conditon. Be careful to change.
def do_get_block(req, proxy, conn, &block)
@request_filter.each do |filter|
filter.filter_request(req)
end
if str = @test_loopback_response.shift
dump_dummy_request_response(req.http_body.dump, str) if @debug_dev
conn.push(HTTP::Message.new_response(str, req.header))
return
end
content = block ? nil : ''
res = HTTP::Message.new_response(content, req.header)
@debug_dev << "= Request\n\n" if @debug_dev
sess = @session_manager.query(req, proxy)
res.peer_cert = sess.ssl_peer_cert
@debug_dev << "\n\n= Response\n\n" if @debug_dev
do_get_header(req, res, sess)
conn.push(res)
sess.get_body do |part|
set_encoding(part, res.body_encoding)
if block
block.call(res, part)
else
content << part
end
end
# there could be a race condition but it's OK to cache unreusable
# connection because we do retry for that case.
@session_manager.keep(sess) unless sess.closed?
commands = @request_filter.collect { |filter|
filter.filter_response(req, res)
}
if commands.find { |command| command == :retry }
raise RetryableResponse.new
end
end
def do_get_stream(req, proxy, conn)
@request_filter.each do |filter|
filter.filter_request(req)
end
if str = @test_loopback_response.shift
dump_dummy_request_response(req.http_body.dump, str) if @debug_dev
conn.push(HTTP::Message.new_response(StringIO.new(str), req.header))
return
end
piper, pipew = IO.pipe
res = HTTP::Message.new_response(piper, req.header)
@debug_dev << "= Request\n\n" if @debug_dev
sess = @session_manager.query(req, proxy)
res.peer_cert = sess.ssl_peer_cert
@debug_dev << "\n\n= Response\n\n" if @debug_dev
do_get_header(req, res, sess)
conn.push(res)
sess.get_body do |part|
set_encoding(part, res.body_encoding)
pipew.write(part)
end
pipew.close
@session_manager.keep(sess) unless sess.closed?
_ = @request_filter.collect { |filter|
filter.filter_response(req, res)
}
# ignore commands (not retryable in async mode)
end
def do_get_header(req, res, sess)
res.http_version, res.status, res.reason, headers = sess.get_header
res.header.set_headers(headers)
if @cookie_manager
res.header['set-cookie'].each do |cookie|
@cookie_manager.parse(cookie, req.header.request_uri)
end
end
end
def dump_dummy_request_response(req, res)
@debug_dev << "= Dummy Request\n\n"
@debug_dev << req
@debug_dev << "\n\n= Dummy Response\n\n"
@debug_dev << res
end
def set_encoding(str, encoding)
str.force_encoding(encoding) if encoding
end
end
httpclient-2.3.3/lib/http-access2/ 0000755 0000041 0000041 00000000000 12155433207 016775 5 ustar www-data www-data httpclient-2.3.3/lib/http-access2/http.rb 0000644 0000041 0000041 00000000032 12155433207 020274 0 ustar www-data www-data require 'httpclient/http'
httpclient-2.3.3/lib/http-access2/cookie.rb 0000644 0000041 0000041 00000000034 12155433207 020570 0 ustar www-data www-data require 'httpclient/cookie'