commit
64bccad495
|
@ -16,3 +16,8 @@ development_dependencies:
|
|||
webmock:
|
||||
github: manastech/webmock.cr
|
||||
branch: master
|
||||
kemal:
|
||||
github: kemalcr/kemal
|
||||
version: ~> 1.0.0
|
||||
kemal-basic-auth:
|
||||
github: kemalcr/kemal-basic-auth
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe "Mechanize AuthChallenge test" do
|
||||
it "test_realm_basic" do
|
||||
uri = URI.parse "http://example.com/"
|
||||
challenge = Mechanize::HTTP::AuthChallenge.new "Digest", {"realm" => "r"}, "Digest realm=r"
|
||||
challenge.scheme = "Basic"
|
||||
|
||||
expected = Mechanize::HTTP::AuthRealm.new "Basic", uri, "r"
|
||||
uri_path = URI.parse("http://example.com/foo")
|
||||
|
||||
challenge.realm(uri_path).should eq expected
|
||||
end
|
||||
|
||||
it "test_realm_digest" do
|
||||
uri = URI.parse "http://example.com/"
|
||||
challenge = Mechanize::HTTP::AuthChallenge.new "Digest", {"realm" => "r"}, "Digest realm=r"
|
||||
|
||||
expected = Mechanize::HTTP::AuthRealm.new "Digest", uri, "r"
|
||||
uri_path = URI.parse("http://example.com/foo")
|
||||
|
||||
challenge.realm(uri_path).should eq expected
|
||||
end
|
||||
|
||||
it "test_realm_digest_case" do
|
||||
uri = URI.parse "http://example.com/"
|
||||
challenge = Mechanize::HTTP::AuthChallenge.new "Digest", {"realm" => "R"}, "Digest realm=R"
|
||||
|
||||
expected = Mechanize::HTTP::AuthRealm.new "Digest", uri, "R"
|
||||
uri_path = URI.parse("http://example.com/foo")
|
||||
|
||||
challenge.realm(uri_path).should eq expected
|
||||
end
|
||||
|
||||
it "test_realm_unknown" do
|
||||
challenge = Mechanize::HTTP::AuthChallenge.new "Digest", {"realm" => "R"}, "Digest realm=R"
|
||||
challenge.scheme = "Unknown"
|
||||
|
||||
uri_path = URI.parse("http://example.com/foo")
|
||||
expect_raises(Exception, "unknown HTTP authentication scheme #{challenge.scheme}") do
|
||||
challenge.realm(uri_path)
|
||||
end
|
||||
end
|
||||
|
||||
it "test_realm_name" do
|
||||
uri = URI.parse "http://example.com/"
|
||||
challenge = Mechanize::HTTP::AuthChallenge.new "Digest", {"realm" => "r"}, "Digest realm=r"
|
||||
challenge.realm_name.should eq "r"
|
||||
end
|
||||
|
||||
it "test_realm_name_case" do
|
||||
uri = URI.parse "http://example.com/"
|
||||
challenge = Mechanize::HTTP::AuthChallenge.new "Digest", {"realm" => "R"}, "Digest realm=R"
|
||||
challenge.realm_name.should eq "R"
|
||||
end
|
||||
|
||||
it "test_realm_name_ntlm" do
|
||||
challenge = Mechanize::HTTP::AuthChallenge.new "Negotiate, NTLM"
|
||||
challenge.realm_name.should eq nil
|
||||
end
|
||||
end
|
|
@ -0,0 +1,48 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe "Mechanize AuthRealm test" do
|
||||
it "test_initialize" do
|
||||
uri = URI.parse("http://example.com")
|
||||
realm = Mechanize::HTTP::AuthRealm.new "Digest", uri, "r"
|
||||
realm.realm.should eq "r"
|
||||
|
||||
realm = Mechanize::HTTP::AuthRealm.new "Digest", uri, "R"
|
||||
realm.realm.should_not eq "r"
|
||||
realm.realm.should eq "R"
|
||||
|
||||
realm = Mechanize::HTTP::AuthRealm.new "Digest", uri, nil
|
||||
realm.realm.should eq nil
|
||||
end
|
||||
|
||||
it "test_equals2" do
|
||||
uri = URI.parse("http://example.com")
|
||||
realm = Mechanize::HTTP::AuthRealm.new "Digest", uri, "r"
|
||||
other = realm.dup
|
||||
realm.should eq other
|
||||
|
||||
other = Mechanize::HTTP::AuthRealm.new "Basic", uri, "r"
|
||||
realm.should_not eq other
|
||||
|
||||
other = Mechanize::HTTP::AuthRealm.new "Digest", URI.parse("http://other.example/"), "r"
|
||||
realm.should_not eq other
|
||||
|
||||
other = Mechanize::HTTP::AuthRealm.new "Digest", uri, "R"
|
||||
realm.should_not eq other
|
||||
|
||||
other = Mechanize::HTTP::AuthRealm.new "Digest", uri, "s"
|
||||
realm.should_not eq other
|
||||
end
|
||||
|
||||
it "test_hash" do
|
||||
uri = URI.parse("http://example.com")
|
||||
realm = Mechanize::HTTP::AuthRealm.new "Digest", uri, "r"
|
||||
h = Hash(Mechanize::HTTP::AuthRealm, Int32).new(0)
|
||||
h[realm] = 1
|
||||
|
||||
other = realm.dup
|
||||
h[other].should eq 1
|
||||
|
||||
other = Mechanize::HTTP::AuthRealm.new "Basic", uri, "r"
|
||||
h[other].should eq 0
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
require "./spec_helper"
|
||||
require "./server.cr"
|
||||
|
||||
describe "Mechanize HTTP Authentication test" do
|
||||
WebMock.allow_net_connect = true
|
||||
it "should be unsuccessful without credentials " do
|
||||
agent = Mechanize.new
|
||||
page = agent.get("#{TEST_SERVER_URL}/secret")
|
||||
page.code.should eq 401
|
||||
# WebMock.allow_net_connect = false
|
||||
end
|
||||
|
||||
it "should be successful with credentials " do
|
||||
agent = Mechanize.new
|
||||
agent.add_auth("#{TEST_SERVER_URL}", "username", "password")
|
||||
page = agent.get("#{TEST_SERVER_URL}/secret")
|
||||
page.code.should eq 200
|
||||
end
|
||||
end
|
|
@ -0,0 +1,139 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe "Mechanize AuthStore test" do
|
||||
it "add_auth" do
|
||||
auth_store = Mechanize::HTTP::AuthStore.new
|
||||
url = URI.parse("http://example.com/")
|
||||
user = "kanezoh"
|
||||
password = "password"
|
||||
realm = ""
|
||||
auth_store.add_auth(url, user, password)
|
||||
auth_store.auth_accounts[url].size.should eq 1
|
||||
auth_store.auth_accounts[url][realm].should eq(["kanezoh", "password", ""])
|
||||
end
|
||||
|
||||
it "test_add_auth_domain" do
|
||||
auth_store = Mechanize::HTTP::AuthStore.new
|
||||
url = URI.parse("http://example.com/")
|
||||
user = "kanezoh"
|
||||
password = "password"
|
||||
domain = "domain"
|
||||
realm = ""
|
||||
auth_store.add_auth(url, user, password, nil, domain)
|
||||
auth_store.auth_accounts[url][realm].should eq(["kanezoh", "password", "domain"])
|
||||
end
|
||||
|
||||
it "test_add_auth_realm" do
|
||||
auth_store = Mechanize::HTTP::AuthStore.new
|
||||
url = URI.parse("http://example.com/")
|
||||
auth_store.add_auth url, "user1", "pass"
|
||||
auth_store.add_auth url, "user2", "pass", "realm"
|
||||
|
||||
expected = {
|
||||
url => {
|
||||
"" => ["user1", "pass", ""],
|
||||
"realm" => ["user2", "pass", ""],
|
||||
},
|
||||
}
|
||||
|
||||
auth_store.auth_accounts.should eq expected
|
||||
end
|
||||
|
||||
it "test_add_auth_realm_case" do
|
||||
auth_store = Mechanize::HTTP::AuthStore.new
|
||||
url = URI.parse("http://example.com/")
|
||||
auth_store.add_auth url, "user1", "pass", "realm"
|
||||
auth_store.add_auth url, "user2", "pass", "Realm"
|
||||
|
||||
expected = {
|
||||
url => {
|
||||
"realm" => ["user1", "pass", ""],
|
||||
"Realm" => ["user2", "pass", ""],
|
||||
},
|
||||
}
|
||||
|
||||
auth_store.auth_accounts.should eq expected
|
||||
end
|
||||
|
||||
it "test_add_auth_string" do
|
||||
auth_store = Mechanize::HTTP::AuthStore.new
|
||||
url = URI.parse("http://example.com/")
|
||||
auth_store.add_auth "#{url}/path", "user", "pass"
|
||||
|
||||
expected = {
|
||||
url => {
|
||||
"" => ["user", "pass", ""],
|
||||
},
|
||||
}
|
||||
|
||||
auth_store.auth_accounts.should eq expected
|
||||
end
|
||||
|
||||
it "test_credentials_eh" do
|
||||
auth_store = Mechanize::HTTP::AuthStore.new
|
||||
url = URI.parse("http://example.com/")
|
||||
|
||||
challenges = [
|
||||
Mechanize::HTTP::AuthChallenge.new("Basic", {"realm" => "r"}),
|
||||
Mechanize::HTTP::AuthChallenge.new("Digest", {"realm" => "r"}),
|
||||
]
|
||||
|
||||
auth_store.credentials?(url, challenges).should_not eq true
|
||||
|
||||
auth_store.add_auth url, "user", "pass"
|
||||
|
||||
auth_store.credentials?(url, challenges).should eq true
|
||||
auth_store.credentials?("#{url}/path", challenges).should eq true
|
||||
end
|
||||
|
||||
it "test_credentials_for" do
|
||||
auth_store = Mechanize::HTTP::AuthStore.new
|
||||
url = URI.parse("http://example.com/")
|
||||
auth_store.credentials_for(url, "realm").should eq nil
|
||||
|
||||
auth_store.add_auth url, "user", "pass", "realm"
|
||||
|
||||
auth_store.credentials_for(url, "realm").should eq ["user", "pass", ""]
|
||||
auth_store.credentials_for(url.to_s, "realm").should eq ["user", "pass", ""]
|
||||
|
||||
auth_store.credentials_for(url, "other").should eq nil
|
||||
end
|
||||
|
||||
it "test_credentials_for_no_realm" do
|
||||
auth_store = Mechanize::HTTP::AuthStore.new
|
||||
url = URI.parse("http://example.com/")
|
||||
auth_store.add_auth url, "user", "pass" # no realm set
|
||||
|
||||
auth_store.credentials_for(url, "realm").should eq ["user", "pass", ""]
|
||||
end
|
||||
|
||||
it "test_credentials_for_realm" do
|
||||
auth_store = Mechanize::HTTP::AuthStore.new
|
||||
url = URI.parse("http://example.com/")
|
||||
auth_store.add_auth url, "user1", "pass"
|
||||
auth_store.add_auth url, "user2", "pass", "realm"
|
||||
|
||||
auth_store.credentials_for(url, "realm").should eq ["user2", "pass", ""]
|
||||
auth_store.credentials_for(url, "other").should eq ["user1", "pass", ""]
|
||||
end
|
||||
|
||||
it "test_credentials_for_realm_case" do
|
||||
auth_store = Mechanize::HTTP::AuthStore.new
|
||||
url = URI.parse("http://example.com/")
|
||||
auth_store.add_auth url, "user1", "pass", "realm"
|
||||
auth_store.add_auth url, "user2", "pass", "Realm"
|
||||
|
||||
auth_store.credentials_for(url, "realm").should eq ["user1", "pass", ""]
|
||||
auth_store.credentials_for(url, "Realm").should eq ["user2", "pass", ""]
|
||||
end
|
||||
|
||||
it "test_credentials_for_path" do
|
||||
auth_store = Mechanize::HTTP::AuthStore.new
|
||||
url = URI.parse("http://example.com/")
|
||||
auth_store.add_auth url, "user", "pass", "realm"
|
||||
|
||||
url = url.to_s + "/path"
|
||||
|
||||
auth_store.credentials_for(url, "realm").should eq ["user", "pass", ""]
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
require "kemal"
|
||||
require "kemal-basic-auth"
|
||||
|
||||
TEST_SERVER_HOST = "0.0.0.0"
|
||||
TEST_SERVER_PORT = 4567
|
||||
TEST_SERVER_URL = "http://#{TEST_SERVER_HOST}:#{TEST_SERVER_PORT}"
|
||||
|
||||
class BasicAuthHandler < Kemal::BasicAuth::Handler
|
||||
only ["/secret"]
|
||||
|
||||
def call(env)
|
||||
return call_next(env) unless only_match?(env)
|
||||
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
add_handler BasicAuthHandler.new("username", "password")
|
||||
|
||||
get "/secret" do
|
||||
"Authorized"
|
||||
end
|
||||
|
||||
kemal_config = Kemal.config
|
||||
kemal_config.env = "development"
|
||||
kemal_config.logging = false
|
||||
|
||||
spawn do
|
||||
Kemal.run(port: TEST_SERVER_PORT)
|
||||
end
|
||||
spawn do
|
||||
sleep 300000
|
||||
end
|
||||
|
||||
until Kemal.config.running
|
||||
sleep 1.millisecond
|
||||
end
|
|
@ -0,0 +1,250 @@
|
|||
require "./spec_helper"
|
||||
|
||||
describe "Mechanize HTTP Authentication test" do
|
||||
it "auth_param" do
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
parser.scanner = StringScanner.new("realm=here")
|
||||
parser.auth_param.should eq ["realm", "here"]
|
||||
end
|
||||
|
||||
it "auth_param bad no value" do
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
parser.scanner = StringScanner.new("realm=")
|
||||
parser.auth_param.should eq nil
|
||||
end
|
||||
|
||||
it "auth_param bad token" do
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
parser.scanner = StringScanner.new("realm")
|
||||
parser.auth_param.should eq nil
|
||||
end
|
||||
|
||||
it "auth_param bad value" do
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
parser.scanner = StringScanner.new("realm=\"this ")
|
||||
parser.auth_param.should eq nil
|
||||
end
|
||||
|
||||
it "auth_param with quote" do
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
parser.scanner = StringScanner.new("realm=\"this site\"")
|
||||
parser.auth_param.should eq ["realm", "this site"]
|
||||
end
|
||||
|
||||
it "test parse" do
|
||||
expect = [challenge("Basic", {"realm" => "foo", "qop" => "auth,auth-int"}, "Basic realm=foo, qop=\"auth,auth-int\"")]
|
||||
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
result = parser.parse("Basic realm=foo, qop=\"auth,auth-int\"")
|
||||
result[0].scheme.should eq expect[0].scheme
|
||||
result[0].params.should eq expect[0].params
|
||||
result[0].raw.should eq expect[0].raw
|
||||
end
|
||||
|
||||
it "test_parse_without_comma_delimiter" do
|
||||
expect = [challenge("Basic", {"realm" => "foo", "qop" => "auth,auth-int"}, "Basic realm=foo qop=\"auth,auth-int\"")]
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
result = parser.parse("Basic realm=foo qop=\"auth,auth-int\"")
|
||||
result[0].scheme.should eq expect[0].scheme
|
||||
result[0].params.should eq expect[0].params
|
||||
end
|
||||
|
||||
it "test_parse_multiple" do
|
||||
expect = [
|
||||
challenge("Basic", {"realm" => "foo"}, "Basic realm=foo"),
|
||||
challenge("Digest", {"realm" => "bar"}, "Digest realm=bar"),
|
||||
]
|
||||
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
result = parser.parse("Basic realm=foo, Digest realm=bar")
|
||||
result[0].scheme.should eq expect[0].scheme
|
||||
result[0].params.should eq expect[0].params
|
||||
result[0].raw.should eq expect[0].raw
|
||||
result[1].scheme.should eq expect[1].scheme
|
||||
result[1].params.should eq expect[1].params
|
||||
result[1].raw.should eq expect[1].raw
|
||||
end
|
||||
|
||||
it "test_parse_multiple_without_comma_delimiter" do
|
||||
expect = [
|
||||
challenge("Basic", {"realm" => "foo"}, "Basic realm=foo"),
|
||||
challenge("Digest", {"realm" => "bar"}, "Digest realm=bar"),
|
||||
]
|
||||
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
result = parser.parse("Basic realm=foo, Digest realm=bar")
|
||||
result[0].scheme.should eq expect[0].scheme
|
||||
result[0].params.should eq expect[0].params
|
||||
result[0].raw.should eq expect[0].raw
|
||||
result[1].scheme.should eq expect[1].scheme
|
||||
result[1].params.should eq expect[1].params
|
||||
result[1].raw.should eq expect[1].raw
|
||||
end
|
||||
|
||||
it "test_parse_multiple_blank" do
|
||||
expect = [
|
||||
challenge("Basic", {"realm" => "foo"}, "Basic realm=foo"),
|
||||
challenge("Digest", {"realm" => "bar"}, "Digest realm=bar"),
|
||||
]
|
||||
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
result = parser.parse("Basic realm=foo, Digest realm=bar")
|
||||
result[0].scheme.should eq expect[0].scheme
|
||||
result[0].params.should eq expect[0].params
|
||||
result[0].raw.should eq expect[0].raw
|
||||
result[1].scheme.should eq expect[1].scheme
|
||||
result[1].params.should eq expect[1].params
|
||||
result[1].raw.should eq expect[1].raw
|
||||
end
|
||||
|
||||
it "test_parse_ntlm_init" do
|
||||
expect = [
|
||||
challenge("NTLM", nil, "NTLM"),
|
||||
]
|
||||
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
result = parser.parse("NTLM")
|
||||
result[0].scheme.should eq expect[0].scheme
|
||||
result[0].params.should eq expect[0].params
|
||||
result[0].raw.should eq expect[0].raw
|
||||
end
|
||||
|
||||
it "test_parse_ntlm_type_2_3" do
|
||||
expect = [
|
||||
challenge("NTLM", "foo=", "NTLM foo="),
|
||||
]
|
||||
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
result = parser.parse("NTLM foo=")
|
||||
result[0].scheme.should eq expect[0].scheme
|
||||
result[0].params.should eq expect[0].params
|
||||
result[0].raw.should eq expect[0].raw
|
||||
end
|
||||
|
||||
it "test_parse_realm_uppercase" do
|
||||
expect = [
|
||||
challenge("Basic", {"realm" => "foo"}, "Basic ReAlM=foo"),
|
||||
]
|
||||
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
result = parser.parse("Basic ReAlM=foo")
|
||||
result[0].scheme.should eq expect[0].scheme
|
||||
result[0].params.should eq expect[0].params
|
||||
result[0].raw.should eq expect[0].raw
|
||||
end
|
||||
|
||||
it "test_parse_realm_value_case" do
|
||||
expect = [
|
||||
challenge("Basic", {"realm" => "Foo"}, "Basic realm=Foo"),
|
||||
]
|
||||
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
result = parser.parse("Basic realm=Foo")
|
||||
result[0].scheme.should eq expect[0].scheme
|
||||
result[0].params.should eq expect[0].params
|
||||
result[0].raw.should eq expect[0].raw
|
||||
end
|
||||
|
||||
it "test_parse_scheme_uppercase" do
|
||||
expect = [
|
||||
challenge("Basic", {"realm" => "foo"}, "BaSiC realm=foo"),
|
||||
]
|
||||
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
result = parser.parse("BaSiC realm=foo")
|
||||
result[0].scheme.should eq expect[0].scheme
|
||||
result[0].params.should eq expect[0].params
|
||||
result[0].raw.should eq expect[0].raw
|
||||
end
|
||||
|
||||
it "test_parse_bad_whitespace_around_auth_param" do
|
||||
expect = [
|
||||
challenge("Basic", {"realm" => "foo"}, "Basic realm = \"foo\""),
|
||||
]
|
||||
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
result = parser.parse("Basic realm = \"foo\"")
|
||||
result[0].scheme.should eq expect[0].scheme
|
||||
result[0].params.should eq expect[0].params
|
||||
result[0].raw.should eq expect[0].raw
|
||||
end
|
||||
|
||||
it "test_parse_bad_single_quote" do
|
||||
expect = [
|
||||
challenge("Basic", {"realm" => "'foo"}, "Basic realm='foo"),
|
||||
]
|
||||
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
result = parser.parse("Basic realm='foo bar', qop='baz'")
|
||||
result[0].scheme.should eq expect[0].scheme
|
||||
result[0].params.should eq expect[0].params
|
||||
result[0].raw.should eq expect[0].raw
|
||||
end
|
||||
|
||||
it "test_quoted_string" do
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
parser.scanner = StringScanner.new "\"text\""
|
||||
|
||||
string = parser.quoted_string
|
||||
|
||||
string.should eq "text"
|
||||
end
|
||||
|
||||
it "test_quoted_string_bad" do
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
parser.scanner = StringScanner.new "\"text"
|
||||
|
||||
string = parser.quoted_string
|
||||
|
||||
string.should eq nil
|
||||
end
|
||||
|
||||
it "test_quoted_string_quote" do
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
parser.scanner = StringScanner.new "\"escaped \\\" here\""
|
||||
|
||||
string = parser.quoted_string
|
||||
|
||||
string.should eq "escaped \\\" here"
|
||||
end
|
||||
|
||||
it "test_quoted_string_quote_end" do
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
parser.scanner = StringScanner.new "\"end \\\"\""
|
||||
|
||||
string = parser.quoted_string
|
||||
|
||||
string.should eq "end \\\""
|
||||
end
|
||||
|
||||
it "test_token" do
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
parser.scanner = StringScanner.new "text"
|
||||
|
||||
string = parser.token
|
||||
|
||||
string.should eq "text"
|
||||
end
|
||||
|
||||
it "test_token_space" do
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
parser.scanner = StringScanner.new "t ext"
|
||||
|
||||
string = parser.token
|
||||
|
||||
string.should eq "t"
|
||||
end
|
||||
|
||||
it "test_token_special" do
|
||||
parser = Mechanize::HTTP::WWWAuthenticateParser.new
|
||||
parser.scanner = StringScanner.new "t\text"
|
||||
|
||||
string = parser.token
|
||||
|
||||
string.should eq "t"
|
||||
end
|
||||
end
|
||||
|
||||
private def challenge(scheme, params, raw)
|
||||
Mechanize::HTTP::AuthChallenge.new(scheme, params, raw)
|
||||
end
|
|
@ -296,6 +296,15 @@ class Mechanize
|
|||
end
|
||||
end
|
||||
|
||||
# set basic auth credentials.
|
||||
# ```
|
||||
# # make download.html whose content is http://example.com's html.
|
||||
# agent.add_auth("http://example.com", "username", "password")
|
||||
# ```
|
||||
def add_auth(uri : String, user : String, pass : String)
|
||||
@agent.add_auth(uri, user, pass)
|
||||
end
|
||||
|
||||
# Runs given block, then resets the page history as it was before.
|
||||
private def transact
|
||||
# save the previous history status.
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
require "../cookie"
|
||||
require "../history"
|
||||
require "./auth_store"
|
||||
require "./www_authenticate_parser"
|
||||
|
||||
class Mechanize
|
||||
module HTTP
|
||||
|
@ -10,6 +12,9 @@ class Mechanize
|
|||
property history : History
|
||||
property user_agent : String
|
||||
property request_cookies : ::HTTP::Cookies
|
||||
getter auth_store : AuthStore
|
||||
getter authenticate_methods : Hash(URI, Hash(String, Array(AuthRealm)))
|
||||
getter authenticate_parser : WWWAuthenticateParser
|
||||
|
||||
def initialize(@context : Mechanize? = nil)
|
||||
@history = History.new
|
||||
|
@ -17,6 +22,9 @@ class Mechanize
|
|||
@context = context
|
||||
@request_cookies = ::HTTP::Cookies.new
|
||||
@user_agent = ""
|
||||
@auth_store = AuthStore.new
|
||||
@authenticate_methods = Hash(URI, Hash(String, Array(AuthRealm))).new
|
||||
@authenticate_parser = WWWAuthenticateParser.new
|
||||
end
|
||||
|
||||
# send http request and return page.
|
||||
|
@ -33,10 +41,13 @@ class Mechanize
|
|||
set_user_agent
|
||||
set_request_referer(referer)
|
||||
uri, params = resolve_parameters(uri, method, params)
|
||||
response = http_request(uri, method, params, body)
|
||||
client = ::HTTP::Client.new(uri)
|
||||
request_auth client, uri
|
||||
response = http_request(client, uri, method, params, body)
|
||||
body = response.not_nil!.body
|
||||
page = response_parse(response, body, uri)
|
||||
response_log(response)
|
||||
|
||||
# save cookies
|
||||
save_response_cookies(response, uri, page)
|
||||
|
||||
|
@ -44,6 +55,10 @@ class Mechanize
|
|||
return follow_redirect(response, headers, page)
|
||||
end
|
||||
|
||||
if response && response.status.unauthorized?
|
||||
return response_authenticate(response, page, uri, params, referer)
|
||||
end
|
||||
|
||||
page
|
||||
end
|
||||
|
||||
|
@ -62,26 +77,57 @@ class Mechanize
|
|||
end
|
||||
|
||||
# send http request
|
||||
private def http_request(uri, method, params, body) : ::HTTP::Client::Response?
|
||||
private def http_request(client : ::HTTP::Client,
|
||||
uri : URI,
|
||||
method : Symbol,
|
||||
params : Hash(String, String)?,
|
||||
body : String?) : ::HTTP::Client::Response?
|
||||
request_log(uri, method)
|
||||
path = uri.path
|
||||
|
||||
case uri.scheme.not_nil!.downcase
|
||||
when "http", "https"
|
||||
case method
|
||||
when :get
|
||||
::HTTP::Client.get(uri, headers: request_headers)
|
||||
client.get(path, headers: request_headers)
|
||||
when :post
|
||||
::HTTP::Client.post(uri, headers: request_headers, form: params.not_nil!.fetch("value", ""))
|
||||
client.post(path, headers: request_headers, form: params.not_nil!.fetch("value", ""))
|
||||
when :put
|
||||
::HTTP::Client.put(uri, headers: request_headers, body: body)
|
||||
client.put(path, headers: request_headers, body: body)
|
||||
when :delete
|
||||
::HTTP::Client.delete(uri, headers: request_headers, body: body)
|
||||
client.delete(path, headers: request_headers, body: body)
|
||||
when :head
|
||||
::HTTP::Client.head(uri, headers: request_headers)
|
||||
client.head(path, headers: request_headers)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private def request_auth(client : ::HTTP::Client, uri : URI)
|
||||
base_uri = uri.dup
|
||||
base_uri.path = "/"
|
||||
base_uri.user &&= nil
|
||||
base_uri.password &&= nil
|
||||
schemes = @authenticate_methods.fetch(base_uri, nil)
|
||||
return if schemes.nil?
|
||||
|
||||
if realm = schemes["basic"].find { |r| r.uri == base_uri }
|
||||
res = @auth_store.credentials_for uri, realm.realm
|
||||
if res
|
||||
user, password = res
|
||||
client.basic_auth user, password
|
||||
end
|
||||
end
|
||||
|
||||
# if realm = schemes[:digest].find { |r| r.uri == base_uri } then
|
||||
# request_auth_digest request, uri, realm, base_uri, false
|
||||
# elsif realm = schemes[:iis_digest].find { |r| r.uri == base_uri } then
|
||||
# request_auth_digest request, uri, realm, base_uri, true
|
||||
# elsif realm = schemes[:basic].find { |r| r.uri == base_uri } then
|
||||
# user, password, = @auth_store.credentials_for uri, realm.realm
|
||||
# request.basic_auth user, password
|
||||
# end
|
||||
end
|
||||
|
||||
# returns the page now mechanize visiting.
|
||||
# ```
|
||||
# agent.current_page
|
||||
|
@ -114,6 +160,15 @@ class Mechanize
|
|||
@history.max_size = length
|
||||
end
|
||||
|
||||
# set basic auth credentials.
|
||||
# ```
|
||||
# # make download.html whose content is http://example.com's html.
|
||||
# agent.add_auth("http://example.com", "username", "password")
|
||||
# ```
|
||||
def add_auth(uri : String, user : String, pass : String)
|
||||
@auth_store.add_auth(uri, user, pass)
|
||||
end
|
||||
|
||||
private def set_request_headers(uri, headers)
|
||||
reset_request_header_cookies
|
||||
headers.each do |k, v|
|
||||
|
@ -129,7 +184,7 @@ class Mechanize
|
|||
end
|
||||
|
||||
# Sets a Referer header.
|
||||
def set_request_referer(referer : Page?)
|
||||
private def set_request_referer(referer : Page?)
|
||||
return unless referer
|
||||
|
||||
request_headers["Referer"] = referer.uri.to_s
|
||||
|
@ -138,7 +193,7 @@ class Mechanize
|
|||
private def resolve_parameters(uri, method, params)
|
||||
case method
|
||||
when :get
|
||||
return uri, nil if params.empty?
|
||||
return uri, nil if params.nil? || params.empty?
|
||||
query = URI::Params.encode(params)
|
||||
uri.query = query
|
||||
return uri, nil
|
||||
|
@ -213,6 +268,39 @@ class Mechanize
|
|||
target_url
|
||||
end
|
||||
|
||||
private def response_authenticate(response, page, uri, params, referer) : Page
|
||||
www_authenticate = response.headers["www-authenticate"]
|
||||
|
||||
unless www_authenticate = response.headers["www-authenticate"]
|
||||
# TODO: raise error
|
||||
end
|
||||
|
||||
challenges = @authenticate_parser.parse(www_authenticate)
|
||||
|
||||
unless @auth_store.credentials?(uri, challenges)
|
||||
# TODO: raise error
|
||||
return page
|
||||
end
|
||||
|
||||
if challenge = challenges.find { |c| c.scheme == "Basic" }
|
||||
realm = challenge.realm uri
|
||||
if realm
|
||||
@authenticate_methods[realm.uri] = Hash(String, Array(AuthRealm)).new([] of AuthRealm) unless @authenticate_methods.has_key?(realm.uri)
|
||||
existing_realms = @authenticate_methods[realm.uri]["basic"]
|
||||
|
||||
if existing_realms && existing_realms.includes? realm
|
||||
# TODO: raise error
|
||||
end
|
||||
|
||||
existing_realms << realm
|
||||
end
|
||||
fetch(uri, headers: request_headers, params: params, referer: referer)
|
||||
else
|
||||
# TODO: raise error
|
||||
raise Exception.new("error")
|
||||
end
|
||||
end
|
||||
|
||||
# reset request cookie before setting headers.
|
||||
private def reset_request_header_cookies
|
||||
request_headers.delete("Cookie")
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
require "./auth_realm"
|
||||
|
||||
class Mechanize
|
||||
module HTTP
|
||||
##
|
||||
# A parsed WWW-Authenticate header
|
||||
|
||||
class AuthChallenge
|
||||
property scheme : String?
|
||||
property params : String? | Hash(String, String)?
|
||||
property raw : String
|
||||
|
||||
def initialize(scheme = nil, params = nil, raw = "")
|
||||
@scheme = scheme
|
||||
@params = params
|
||||
@raw = raw
|
||||
end
|
||||
|
||||
def [](param) : String?
|
||||
params_value = params
|
||||
if params_value.is_a?(Hash)
|
||||
params_value[param] # NTLM has a string for params
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Constructs an AuthRealm for this challenge
|
||||
|
||||
def realm(uri) : AuthRealm
|
||||
target_uri = uri.dup
|
||||
target_uri.path = "/"
|
||||
case scheme
|
||||
when "Basic"
|
||||
# raise ArgumentError, "provide uri for Basic authentication" unless uri
|
||||
AuthRealm.new scheme, target_uri, self["realm"]
|
||||
when "Digest"
|
||||
AuthRealm.new scheme, target_uri, self["realm"]
|
||||
else
|
||||
# raise Mechanize::Error, "unknown HTTP authentication scheme #{scheme}"
|
||||
raise Exception.new("unknown HTTP authentication scheme #{scheme}")
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# The name of the realm for this challenge
|
||||
|
||||
def realm_name : String?
|
||||
params_value = params
|
||||
if params_value.is_a?(Hash)
|
||||
params_value["realm"] # NTLM has a string for params
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# The raw authentication challenge
|
||||
|
||||
# alias to_s raw
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
# This class represents realm attribute of www-authenticate header.
|
||||
class Mechanize::HTTP::AuthRealm
|
||||
getter scheme : String?
|
||||
getter uri : URI
|
||||
getter realm : String?
|
||||
|
||||
def initialize(scheme, uri, realm)
|
||||
@scheme = scheme
|
||||
@uri = uri
|
||||
@realm = realm if realm
|
||||
end
|
||||
|
||||
def ==(other) : Bool
|
||||
self.class === other &&
|
||||
@scheme == other.scheme &&
|
||||
@uri == other.uri &&
|
||||
@realm == other.realm
|
||||
end
|
||||
end
|
|
@ -0,0 +1,60 @@
|
|||
class Mechanize
|
||||
module HTTP
|
||||
# This class store info for HTTP Authentication.
|
||||
class AuthStore
|
||||
getter auth_accounts : Hash(URI, Hash(String, Array(String)))
|
||||
|
||||
def initialize
|
||||
@auth_accounts = Hash(URI, Hash(String, Array(String))).new
|
||||
end
|
||||
|
||||
def add_auth(uri : String | URI, user : String, pass : String, realm : String? = nil, domain : String? = nil)
|
||||
target_uri = uri.dup
|
||||
unless uri.is_a?(URI)
|
||||
target_uri = URI.parse(uri)
|
||||
end
|
||||
target_uri = target_uri.as(URI)
|
||||
target_uri.path = "/"
|
||||
target_uri.user = nil
|
||||
target_uri.password = nil
|
||||
realm = "" if realm.nil?
|
||||
domain = "" if domain.nil?
|
||||
|
||||
realm_hash = {realm => [user, pass, domain]}
|
||||
if auth_accounts.has_key?(target_uri)
|
||||
auth_accounts[target_uri].merge!(realm_hash)
|
||||
else
|
||||
auth_accounts[target_uri] = realm_hash
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns true if credentials exist for the +challenges+ from the server at
|
||||
# +uri+.
|
||||
|
||||
def credentials?(uri, challenges) : Bool
|
||||
challenges.any? do |challenge|
|
||||
credentials_for uri, challenge.realm_name
|
||||
end
|
||||
end
|
||||
|
||||
# Retrieves credentials for +realm+ on the server at +uri+.
|
||||
def credentials_for(uri : String | URI, realm : String?) : Array(String)?
|
||||
target_uri = uri.dup
|
||||
unless uri.is_a?(URI)
|
||||
target_uri = URI.parse(uri)
|
||||
end
|
||||
target_uri = target_uri.as(URI)
|
||||
target_uri.path = "/"
|
||||
target_uri.user = nil
|
||||
target_uri.password = nil
|
||||
realm = "" if realm.nil?
|
||||
|
||||
realms = auth_accounts.fetch(target_uri, nil)
|
||||
return nil if realms.nil?
|
||||
|
||||
realms.fetch(realm, nil) || realms.fetch("", nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,166 @@
|
|||
require "string_scanner"
|
||||
require "./auth_challenge"
|
||||
|
||||
##
|
||||
# Parses the WWW-Authenticate HTTP header into separate challenges.
|
||||
|
||||
class Mechanize
|
||||
module HTTP
|
||||
class WWWAuthenticateParser
|
||||
property scanner : StringScanner
|
||||
|
||||
# Creates a new header parser for WWW-Authenticate headers
|
||||
|
||||
def initialize
|
||||
@scanner = StringScanner.new("")
|
||||
end
|
||||
|
||||
# Parsers the header. Returns an Array of challenges as strings
|
||||
|
||||
def parse(www_authenticate : String) : Array(AuthChallenge)
|
||||
challenges = [] of AuthChallenge
|
||||
@scanner = StringScanner.new(www_authenticate)
|
||||
|
||||
loop do
|
||||
break if scanner.eos?
|
||||
start = scanner.offset
|
||||
challenge = AuthChallenge.new
|
||||
|
||||
scheme = auth_scheme
|
||||
|
||||
if scheme == "Negotiate"
|
||||
scan_comma_spaces
|
||||
end
|
||||
|
||||
break unless scheme
|
||||
challenge.scheme = scheme
|
||||
|
||||
space = spaces
|
||||
|
||||
if scheme == "NTLM"
|
||||
if space
|
||||
challenge.params = scanner.scan(/.*/)
|
||||
end
|
||||
|
||||
challenge.raw = www_authenticate[start, scanner.offset]
|
||||
challenges << challenge
|
||||
next
|
||||
else
|
||||
challenge.scheme = scheme.capitalize
|
||||
end
|
||||
|
||||
next unless space
|
||||
params = Hash(String, String).new
|
||||
|
||||
loop do
|
||||
offset = scanner.offset
|
||||
param = auth_param
|
||||
if param
|
||||
name, value = param
|
||||
name = name.downcase if name =~ /^realm$/i
|
||||
params[name] = value
|
||||
else
|
||||
challenge.params = params
|
||||
challenges << challenge
|
||||
|
||||
if scanner.eos?
|
||||
challenge.raw = www_authenticate[start, scanner.offset]
|
||||
break
|
||||
end
|
||||
|
||||
scanner.offset = offset # rewind
|
||||
challenge.raw = www_authenticate[start, scanner.offset].sub(/(,+)? *$/, "")
|
||||
challenge = nil # a token should be next, new challenge
|
||||
break
|
||||
end
|
||||
|
||||
spaces
|
||||
|
||||
scanner.scan(/(, *)+/)
|
||||
end
|
||||
end
|
||||
|
||||
challenges
|
||||
end
|
||||
|
||||
# scans a comma followed by spaces
|
||||
# needed for Negotiation, NTLM
|
||||
|
||||
private def scan_comma_spaces
|
||||
scanner.scan(/, +/)
|
||||
end
|
||||
|
||||
# token = 1*<any CHAR except CTLs or separators>
|
||||
#
|
||||
# Parses a token
|
||||
|
||||
def token
|
||||
scanner.scan(/[^\000-\037\177()<>@,;:\\"\/\[\]?={} ]+/)
|
||||
end
|
||||
|
||||
private def auth_scheme
|
||||
token
|
||||
end
|
||||
|
||||
##
|
||||
# 1*SP
|
||||
#
|
||||
# Parses spaces
|
||||
|
||||
private def spaces
|
||||
scanner.scan(/ +/)
|
||||
end
|
||||
|
||||
# auth-param = token "=" ( token | quoted-string )
|
||||
#
|
||||
# Parses an auth parameter
|
||||
|
||||
def auth_param : Array(String)?
|
||||
return nil unless name = token
|
||||
return nil unless scanner.scan(/ *= */)
|
||||
|
||||
value = if scanner.peek(1) == "\""
|
||||
quoted_string
|
||||
else
|
||||
token
|
||||
end
|
||||
|
||||
return nil unless value
|
||||
|
||||
return [name, value]
|
||||
end
|
||||
|
||||
##
|
||||
# quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
|
||||
# qdtext = <any TEXT except <">>
|
||||
# quoted-pair = "\" CHAR
|
||||
#
|
||||
# For TEXT, the rules of RFC 2047 are ignored.
|
||||
|
||||
def quoted_string : String?
|
||||
return nil unless @scanner.scan(/"/)
|
||||
|
||||
text = String.new
|
||||
|
||||
loop do
|
||||
chunk = scanner.scan(/[\r\n \t\x21\x23-\x7e\x{0080}-\x{00ff}]+/) # not " which is \x22
|
||||
|
||||
if chunk
|
||||
text += chunk
|
||||
|
||||
text += (scanner.scan(/./) || "") if chunk.ends_with?("\\") && "\"" == scanner.peek(1)
|
||||
else
|
||||
if "\"" == scanner.peek(1)
|
||||
scanner.scan(/./)
|
||||
break
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue