mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-11-13 06:54:54 +03:00
Output curl command for each request at runtime
This commit is contained in:
parent
8e1fdafca6
commit
f9f3f55baf
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
@ -74,6 +74,7 @@ jobs:
|
||||
export PATH="$PWD/target/debug:$PATH"
|
||||
cd integration
|
||||
./integration.py
|
||||
./test_curl_commands.sh tests/*.curl
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
if: ${{ always() }}
|
||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -342,6 +342,7 @@ dependencies = [
|
||||
"hurl_core",
|
||||
"libflate",
|
||||
"libxml",
|
||||
"percent-encoding",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
14
integration/test_curl_commands.sh
Executable file
14
integration/test_curl_commands.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
for f in $*; do
|
||||
echo "** $f"
|
||||
cat "$f" | grep -v '^$' | grep -v '^#' | while read -r line; do
|
||||
echo "$line"
|
||||
cmd="$line --no-progress-meter --output /dev/null --fail"
|
||||
echo "$cmd" | bash || (echo ">>> Error <<<<" && exit 1)
|
||||
done
|
||||
echo
|
||||
done
|
||||
|
||||
echo "all curl commands have been run with success!"
|
@ -33,9 +33,13 @@ def get_os():
|
||||
def test(hurl_file):
|
||||
|
||||
options_file = hurl_file.replace('.hurl','.options')
|
||||
curl_file = hurl_file.replace('.hurl','.curl')
|
||||
|
||||
options = []
|
||||
if os.path.exists(options_file):
|
||||
options = open(options_file).read().strip().split(' ')
|
||||
if os.path.exists(curl_file):
|
||||
options.append('--verbose')
|
||||
|
||||
cmd = ['hurl', hurl_file] + options
|
||||
print(' '.join(cmd))
|
||||
@ -78,6 +82,31 @@ def test(hurl_file):
|
||||
print(f'actual: <{actual}>\nexpected: <{expected}>')
|
||||
sys.exit(1)
|
||||
|
||||
# curl output
|
||||
if os.path.exists(curl_file):
|
||||
expected_commands = []
|
||||
for line in open(curl_file, 'r').readlines():
|
||||
line = line.strip()
|
||||
if line == "" or line.startswith("#"):
|
||||
continue
|
||||
expected_commands.append(line)
|
||||
|
||||
actual = decode_string(result.stderr).strip()
|
||||
actual_commands= [line[2:] for line in actual.split('\n') if line.startswith('* curl')]
|
||||
|
||||
if len(actual_commands) != len(expected_commands):
|
||||
print('Assert error at %s' % (f))
|
||||
print('expected: %d commands' % len(expected_commands))
|
||||
print('actual: %d commands' % len(actual_commands))
|
||||
sys.exit(1)
|
||||
|
||||
for i in range(len(expected_commands)):
|
||||
if actual_commands[i] != expected_commands[i]:
|
||||
print('Assert error at %s:%i' % (curl_file, i+1))
|
||||
print('expected: %s' % expected_commands[i])
|
||||
print('actual: %s' % actual_commands[i])
|
||||
sys.exit(1)
|
||||
|
||||
def main():
|
||||
for hurl_file in sys.argv[1:]:
|
||||
test(hurl_file)
|
||||
|
1
integration/tests/assert_base64.curl
Normal file
1
integration/tests/assert_base64.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/assert-base64'
|
1
integration/tests/assert_header.curl
Normal file
1
integration/tests/assert_header.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/assert-header'
|
4
integration/tests/assert_json.curl
Normal file
4
integration/tests/assert_json.curl
Normal file
@ -0,0 +1,4 @@
|
||||
curl 'http://localhost:8000/assert-json'
|
||||
curl 'http://localhost:8000/assert-json/index'
|
||||
curl 'http://localhost:8000/assert-json'
|
||||
curl 'http://localhost:8000/assert-json/list'
|
1
integration/tests/assert_match.curl
Normal file
1
integration/tests/assert_match.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/assert-match'
|
1
integration/tests/assert_regex.curl
Normal file
1
integration/tests/assert_regex.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/assert-regex'
|
3
integration/tests/assert_status_code.curl
Normal file
3
integration/tests/assert_status_code.curl
Normal file
@ -0,0 +1,3 @@
|
||||
curl 'http://localhost:8000/assert-status-code'
|
||||
curl 'http://localhost:8000/assert-status-code'
|
||||
curl 'http://localhost:8000/assert-status-code'
|
1
integration/tests/assert_xpath.curl
Normal file
1
integration/tests/assert_xpath.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/assert-xpath'
|
1
integration/tests/basic_authentication.curl
Normal file
1
integration/tests/basic_authentication.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/basic-authentication' --user 'bob:secret'
|
2
integration/tests/bom.curl
Normal file
2
integration/tests/bom.curl
Normal file
@ -0,0 +1,2 @@
|
||||
curl 'http://localhost:8000/utf8_bom'
|
||||
|
2
integration/tests/bytes.curl
Normal file
2
integration/tests/bytes.curl
Normal file
@ -0,0 +1,2 @@
|
||||
curl 'http://localhost:8000/bytes'
|
||||
|
1
integration/tests/capture_and_assert.curl
Normal file
1
integration/tests/capture_and_assert.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/capture-and-assert'
|
3
integration/tests/captures.curl
Normal file
3
integration/tests/captures.curl
Normal file
@ -0,0 +1,3 @@
|
||||
curl 'http://localhost:8000/captures'
|
||||
curl 'http://localhost:8000/captures-check?param1=value1¶m2=Bob'
|
||||
curl 'http://localhost:8000/captures-json'
|
0
integration/tests/color.curl
Normal file
0
integration/tests/color.curl
Normal file
@ -1 +1,5 @@
|
||||
* fail fast: true
|
||||
* insecure: false
|
||||
* follow redirect: false
|
||||
* max redirect: 50
|
||||
[1;33mwarning[0m: no entry have been executed for file tests/color.hurl
|
||||
|
7
integration/tests/compressed.curl
Normal file
7
integration/tests/compressed.curl
Normal file
@ -0,0 +1,7 @@
|
||||
curl 'http://localhost:8000/compressed/none' --compressed
|
||||
curl 'http://localhost:8000/compressed/gzip' --compressed
|
||||
curl 'http://localhost:8000/compressed/zlib' --compressed
|
||||
# curl needs to be built with brotli support
|
||||
curl 'http://localhost:8000/compressed/brotli' --compressed
|
||||
curl 'http://localhost:8000/compressed/brotli_identity' --compressed
|
||||
|
1
integration/tests/cookie_file.curl
Normal file
1
integration/tests/cookie_file.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/cookie_file' --cookie tests/cookie_file.cookies
|
@ -1 +1 @@
|
||||
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="comment"># curl --cookie tests/cookie_file.cookies http://localhost:8000/cookie_file</span></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/cookie_file</span></span></div><div class="response"><span class="line"><span class="version">HTTP/*</span> <span class="status">200</span></span></div></div></div>
|
||||
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/cookie_file</span></span></div><div class="response"><span class="line"><span class="version">HTTP/*</span> <span class="status">200</span></span></div></div></div>
|
@ -1,3 +1,2 @@
|
||||
# curl --cookie tests/cookie_file.cookies http://localhost:8000/cookie_file
|
||||
GET http://localhost:8000/cookie_file
|
||||
HTTP/* 200
|
||||
|
4
integration/tests/cookie_storage.curl
Normal file
4
integration/tests/cookie_storage.curl
Normal file
@ -0,0 +1,4 @@
|
||||
curl 'http://localhost:8000/cookie-storage/assert-that-cookie1-is-valueA' --cookie 'cookie1=valueA'
|
||||
curl 'http://localhost:8000/cookie-storage/assert-that-cookie1-is-not-in-session'
|
||||
|
||||
|
9
integration/tests/cookies.curl
Normal file
9
integration/tests/cookies.curl
Normal file
@ -0,0 +1,9 @@
|
||||
curl 'http://localhost:8000/cookies/set-request-cookie1-valueA' --cookie 'cookie1=valueA'
|
||||
curl 'http://localhost:8000/cookies/assert-that-cookie1-is-not-in-session'
|
||||
curl 'http://localhost:8000/cookies/set-multiple-request-cookies' --cookie 'user1=Bob; user2=Bill'
|
||||
curl 'http://localhost:8000/cookies/set-session-cookie2-valueA'
|
||||
curl 'http://localhost:8000/cookies/assert-that-cookie2-is-valueA' --cookie 'cookie2=valueA'
|
||||
curl 'http://localhost:8000/cookies/assert-that-cookie2-is-valueA-and-valueB' --cookie 'cookie2=valueB; cookie2=valueA'
|
||||
curl 'http://localhost:8000/cookies/delete-cookie2' --cookie 'cookie2=valueA'
|
||||
curl 'http://localhost:8000/cookies/assert-that-cookie2-is-not-in-session'
|
||||
curl 'http://localhost:8000/cookies/set'
|
File diff suppressed because one or more lines are too long
@ -28,7 +28,7 @@ HTTP/1.0 200
|
||||
|
||||
GET http://localhost:8000/cookies/assert-that-cookie2-is-valueA-and-valueB
|
||||
[Cookies]
|
||||
cookie2: ValueB
|
||||
cookie2: valueB
|
||||
HTTP/1.0 200
|
||||
|
||||
|
||||
|
@ -1 +1 @@
|
||||
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/cookies/set-request-cookie1-valueA","cookies":[{"name":"cookie1","value":"valueA"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/assert-that-cookie1-is-not-in-session"},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/set-multiple-request-cookies","cookies":[{"name":"user1","value":"Bob"},{"name":"user2","value":"Bill"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/set-session-cookie2-valueA"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"cookie","expr":"cookie2"},"predicate":{"type":"equal","value":"valueA"}}]}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/assert-that-cookie2-is-valueA"},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/assert-that-cookie2-is-valueA-and-valueB","cookies":[{"name":"cookie2","value":"ValueB"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/delete-cookie2"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"cookie","expr":"cookie2"},"predicate":{"type":"equal","value":""}},{"query":{"type":"cookie","expr":"cookie2[Max-Age]"},"predicate":{"type":"equal","value":0}}]}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/assert-that-cookie2-is-not-in-session"},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/set"},"response":{"version":"HTTP/1.0","status":200,"headers":[{"name":"Set-Cookie","value":"LSID=DQAAAKEaem_vYg; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly; Path=/accounts"},{"name":"Set-Cookie","value":"HSID=AYQEVnDKrdst; Domain=.localhost; Expires=Wed, 13 Jan 2021 22:23:01 GMT; HttpOnly; Path=/"},{"name":"Set-Cookie","value":"SSID=Ap4PGTEq; Domain=.localhost; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly; Path=/"}],"asserts":[{"query":{"type":"header","name":"Set-Cookie"},"predicate":{"type":"count","value":3}},{"query":{"type":"cookie","expr":"LSID"},"predicate":{"type":"equal","value":"DQAAAKEaem_vYg"}},{"query":{"type":"cookie","expr":"LSID[Value]"},"predicate":{"type":"equal","value":"DQAAAKEaem_vYg"}},{"query":{"type":"cookie","expr":"LSID[Expires]"},"predicate":{"type":"exist"}},{"query":{"type":"cookie","expr":"LSID[Expires]"},"predicate":{"type":"equal","value":"Wed, 13 Jan 2021 22:23:01 GMT"}},{"query":{"type":"cookie","expr":"LSID[Max-Age]"},"predicate":{"not":true,"type":"exist"}},{"query":{"type":"cookie","expr":"LSID[Domain]"},"predicate":{"not":true,"type":"exist"}},{"query":{"type":"cookie","expr":"LSID[Path]"},"predicate":{"type":"equal","value":"/accounts"}},{"query":{"type":"cookie","expr":"LSID[Secure]"},"predicate":{"type":"exist"}},{"query":{"type":"cookie","expr":"LSID[HttpOnly]"},"predicate":{"type":"exist"}},{"query":{"type":"cookie","expr":"LSID[SameSite]"},"predicate":{"not":true,"type":"exist"}}]}}]}
|
||||
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/cookies/set-request-cookie1-valueA","cookies":[{"name":"cookie1","value":"valueA"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/assert-that-cookie1-is-not-in-session"},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/set-multiple-request-cookies","cookies":[{"name":"user1","value":"Bob"},{"name":"user2","value":"Bill"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/set-session-cookie2-valueA"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"cookie","expr":"cookie2"},"predicate":{"type":"equal","value":"valueA"}}]}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/assert-that-cookie2-is-valueA"},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/assert-that-cookie2-is-valueA-and-valueB","cookies":[{"name":"cookie2","value":"valueB"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/delete-cookie2"},"response":{"version":"HTTP/1.0","status":200,"asserts":[{"query":{"type":"cookie","expr":"cookie2"},"predicate":{"type":"equal","value":""}},{"query":{"type":"cookie","expr":"cookie2[Max-Age]"},"predicate":{"type":"equal","value":0}}]}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/assert-that-cookie2-is-not-in-session"},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/cookies/set"},"response":{"version":"HTTP/1.0","status":200,"headers":[{"name":"Set-Cookie","value":"LSID=DQAAAKEaem_vYg; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly; Path=/accounts"},{"name":"Set-Cookie","value":"HSID=AYQEVnDKrdst; Domain=.localhost; Expires=Wed, 13 Jan 2021 22:23:01 GMT; HttpOnly; Path=/"},{"name":"Set-Cookie","value":"SSID=Ap4PGTEq; Domain=.localhost; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly; Path=/"}],"asserts":[{"query":{"type":"header","name":"Set-Cookie"},"predicate":{"type":"count","value":3}},{"query":{"type":"cookie","expr":"LSID"},"predicate":{"type":"equal","value":"DQAAAKEaem_vYg"}},{"query":{"type":"cookie","expr":"LSID[Value]"},"predicate":{"type":"equal","value":"DQAAAKEaem_vYg"}},{"query":{"type":"cookie","expr":"LSID[Expires]"},"predicate":{"type":"exist"}},{"query":{"type":"cookie","expr":"LSID[Expires]"},"predicate":{"type":"equal","value":"Wed, 13 Jan 2021 22:23:01 GMT"}},{"query":{"type":"cookie","expr":"LSID[Max-Age]"},"predicate":{"not":true,"type":"exist"}},{"query":{"type":"cookie","expr":"LSID[Domain]"},"predicate":{"not":true,"type":"exist"}},{"query":{"type":"cookie","expr":"LSID[Path]"},"predicate":{"type":"equal","value":"/accounts"}},{"query":{"type":"cookie","expr":"LSID[Secure]"},"predicate":{"type":"exist"}},{"query":{"type":"cookie","expr":"LSID[HttpOnly]"},"predicate":{"type":"exist"}},{"query":{"type":"cookie","expr":"LSID[SameSite]"},"predicate":{"not":true,"type":"exist"}}]}}]}
|
||||
|
@ -74,7 +74,8 @@ def assert_that_cookie2_is_valueb():
|
||||
|
||||
@app.route("/cookies/assert-that-cookie2-is-valueA-and-valueB")
|
||||
def assert_that_cookie2_is_valuea_and_valueb():
|
||||
assert request.headers['Cookie'] == 'cookie2=valueA; cookie2=ValueB'
|
||||
assert 'cookie2=valueA' in request.headers['Cookie']
|
||||
assert 'cookie2=valueB' in request.headers['Cookie']
|
||||
return ''
|
||||
|
||||
@app.route("/cookies/set-session-cookie2-valueA-subdomain")
|
||||
|
2
integration/tests/delete.curl
Normal file
2
integration/tests/delete.curl
Normal file
@ -0,0 +1,2 @@
|
||||
curl 'http://localhost:8000/delete' -X DELETE
|
||||
|
0
integration/tests/empty.curl
Normal file
0
integration/tests/empty.curl
Normal file
2
integration/tests/encoding.curl
Normal file
2
integration/tests/encoding.curl
Normal file
@ -0,0 +1,2 @@
|
||||
curl 'http://localhost:8000/encoding/utf8'
|
||||
curl 'http://localhost:8000/encoding/latin1'
|
1
integration/tests/expect.curl
Normal file
1
integration/tests/expect.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/expect' -H 'Expect: 100-continue' -H 'Content-Type:' --data 'data'
|
1
integration/tests/follow_redirect.curl
Normal file
1
integration/tests/follow_redirect.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/follow-redirect' -L
|
2
integration/tests/form_params.curl
Normal file
2
integration/tests/form_params.curl
Normal file
@ -0,0 +1,2 @@
|
||||
curl 'http://localhost:8000/form-params' --data 'param1=value1' --data 'param2=' --data 'param3=a%3Db' --data 'param4=a%253db'
|
||||
curl 'http://localhost:8000/form-params' -H 'Content-Type: application/x-www-form-urlencoded' --data 'param1=value1¶m2=¶m3=a%3db¶m4=a%253db'
|
7
integration/tests/headers.curl.ignore
Normal file
7
integration/tests/headers.curl.ignore
Normal file
@ -0,0 +1,7 @@
|
||||
curl 'http://localhost:8000/default-headers'
|
||||
curl 'http://localhost:8000/default-headers' -H 'User-Agent: hurl/1.0' -H 'Host: localhost:8000'
|
||||
curl 'http://localhost:8000/default-headers' -H 'User-Agent: hurl/1.0' -H 'Host: localhost:8000'
|
||||
curl 'http://localhost:8000/custom-headers' -H 'Fruit: Raspberry' -H 'Fruit: Apple' -H 'Fruit: Banana' -H 'Fruit: Grape' -H 'Color: Green'
|
||||
curl 'http://localhost:8000/custom-headers-utf8' -H 'Beverage: café'
|
||||
curl 'http://localhost:8000/custom-headers-quote' -H $'Header1: \''
|
||||
curl 'http://localhost:8000/response-headers'
|
@ -1 +1 @@
|
||||
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/default-headers</span></span></div><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/default-headers</span></span></div><span class="line"><span class="string">User-Agent</span><span>:</span> <span class="string">hurl/1.0</span></span><span class="line"><span class="string">Host</span><span>:</span> <span class="string">localhost:8000</span> <span class="comment"># comment</span></span><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/default-headers</span></span></div><span class="line"><span class="string">User-Agent</span><span>:</span> <span class="string">hurl/1.0</span></span><span class="line"><span class="string">Host</span><span>:</span> <span class="string">localhost:8000</span> <span class="comment"># comment</span></span><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/custom-headers</span></span></div><span class="line"><span class="string">Fruit</span><span>:</span> <span class="string">Raspberry</span></span><span class="line"><span class="string">Fruit</span><span>:</span> <span class="string">Apple</span></span><span class="line"><span class="string">Fruit</span><span>:</span> <span class="string">Banana</span></span><span class="line"><span class="string">Fruit</span><span>:</span> <span class="string">Grape</span></span><span class="line"><span class="string">Color</span><span>:</span> <span class="string">Green</span></span><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/custom-headers-utf8</span></span></div><span class="line"><span class="string">Beverage</span><span>:</span> <span class="string">café</span> <span class="comment"># send the utf8 string - expected to be decoded as ascii in the server side</span></span><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/response-headers</span></span></div><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span><span class="line"><span class="string">Beverage</span><span>:</span> <span class="string">cafe</span> <span class="comment"># TBC send utf8</span></span></div></div></div>
|
||||
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/default-headers</span></span></div><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/default-headers</span></span></div><span class="line"><span class="string">User-Agent</span><span>:</span> <span class="string">hurl/1.0</span></span><span class="line"><span class="string">Host</span><span>:</span> <span class="string">localhost:8000</span> <span class="comment"># comment</span></span><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/default-headers</span></span></div><span class="line"><span class="string">User-Agent</span><span>:</span> <span class="string">hurl/1.0</span></span><span class="line"><span class="string">Host</span><span>:</span> <span class="string">localhost:8000</span> <span class="comment"># comment</span></span><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/custom-headers</span></span></div><span class="line"><span class="string">Fruit</span><span>:</span> <span class="string">Raspberry</span></span><span class="line"><span class="string">Fruit</span><span>:</span> <span class="string">Apple</span></span><span class="line"><span class="string">Fruit</span><span>:</span> <span class="string">Banana</span></span><span class="line"><span class="string">Fruit</span><span>:</span> <span class="string">Grape</span></span><span class="line"><span class="string">Color</span><span>:</span> <span class="string">Green</span></span><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/custom-headers-utf8</span></span></div><span class="line"><span class="string">Beverage</span><span>:</span> <span class="string">café</span> <span class="comment"># send the utf8 string - expected to be decoded as ascii in the server side</span></span><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/custom-headers-quote</span></span></div><span class="line"><span class="string">Header1</span><span>:</span> <span class="string">'</span></span><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><div class="hurl-entry"><div class="request"><span class="line"></span><span class="line"><span class="method">GET</span> <span class="url">http://localhost:8000/response-headers</span></span></div><div class="response"><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span><span class="line"><span class="string">Beverage</span><span>:</span> <span class="string">cafe</span> <span class="comment"># TBC send utf8</span></span></div></div></div>
|
||||
|
@ -24,6 +24,10 @@ GET http://localhost:8000/custom-headers-utf8
|
||||
Beverage: café # send the utf8 string - expected to be decoded as ascii in the server side
|
||||
HTTP/1.0 200
|
||||
|
||||
GET http://localhost:8000/custom-headers-quote
|
||||
Header1: '
|
||||
HTTP/1.0 200
|
||||
|
||||
GET http://localhost:8000/response-headers
|
||||
HTTP/1.0 200
|
||||
Beverage: cafe # TBC send utf8
|
||||
Beverage: cafe # TBC send utf8
|
||||
|
@ -1 +1 @@
|
||||
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/default-headers"},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/default-headers","headers":[{"name":"User-Agent","value":"hurl/1.0"},{"name":"Host","value":"localhost:8000"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/default-headers","headers":[{"name":"User-Agent","value":"hurl/1.0"},{"name":"Host","value":"localhost:8000"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/custom-headers","headers":[{"name":"Fruit","value":"Raspberry"},{"name":"Fruit","value":"Apple"},{"name":"Fruit","value":"Banana"},{"name":"Fruit","value":"Grape"},{"name":"Color","value":"Green"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/custom-headers-utf8","headers":[{"name":"Beverage","value":"café"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/response-headers"},"response":{"version":"HTTP/1.0","status":200,"headers":[{"name":"Beverage","value":"cafe"}]}}]}
|
||||
{"entries":[{"request":{"method":"GET","url":"http://localhost:8000/default-headers"},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/default-headers","headers":[{"name":"User-Agent","value":"hurl/1.0"},{"name":"Host","value":"localhost:8000"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/default-headers","headers":[{"name":"User-Agent","value":"hurl/1.0"},{"name":"Host","value":"localhost:8000"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/custom-headers","headers":[{"name":"Fruit","value":"Raspberry"},{"name":"Fruit","value":"Apple"},{"name":"Fruit","value":"Banana"},{"name":"Fruit","value":"Grape"},{"name":"Color","value":"Green"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/custom-headers-utf8","headers":[{"name":"Beverage","value":"café"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/custom-headers-quote","headers":[{"name":"Header1","value":"'"}]},"response":{"version":"HTTP/1.0","status":200}},{"request":{"method":"GET","url":"http://localhost:8000/response-headers"},"response":{"version":"HTTP/1.0","status":200,"headers":[{"name":"Beverage","value":"cafe"}]}}]}
|
||||
|
@ -4,7 +4,7 @@ from tests import app
|
||||
|
||||
@app.route("/default-headers")
|
||||
def default_headers():
|
||||
assert 'hurl' in request.headers['User-Agent']
|
||||
assert 'hurl' in request.headers['User-Agent'] or 'curl' in request.headers['User-Agent']
|
||||
assert request.headers['Host'] == 'localhost:8000'
|
||||
assert 'Content-Length' not in request.headers
|
||||
return ''
|
||||
@ -25,6 +25,12 @@ def custom_headers_utf8():
|
||||
return ''
|
||||
|
||||
|
||||
@app.route("/custom-headers-quote")
|
||||
def custom_headers_quotes():
|
||||
assert request.headers['Header1'] == "'"
|
||||
return ''
|
||||
|
||||
|
||||
@app.route("/response-headers")
|
||||
def response_headers():
|
||||
resp = make_response()
|
||||
|
2
integration/tests/hello.curl
Normal file
2
integration/tests/hello.curl
Normal file
@ -0,0 +1,2 @@
|
||||
curl 'http://localhost:8000/hello'
|
||||
curl 'http://localhost:8000/hello'
|
2
integration/tests/include.curl
Normal file
2
integration/tests/include.curl
Normal file
@ -0,0 +1,2 @@
|
||||
curl 'http://localhost:8000/include'
|
||||
|
1
integration/tests/large.curl
Normal file
1
integration/tests/large.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/large'
|
1
integration/tests/multipart_form_data.curl
Normal file
1
integration/tests/multipart_form_data.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/multipart-form-data' -F 'key1=value1' -F 'upload1=@tests/data.txt;type=text/plain' -F 'upload2=@tests/data.html;type=text/html' -F 'upload3=@tests/data.txt;type=text/html'
|
@ -1 +1 @@
|
||||
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="comment"># curl -v -F key1=value1 -F upload1=@tests/data.txt -Fupload2=@tests/data.html -Fupload3="@tests/data.txt;type=text/html" http://localhost:8000/multipart-form-data</span></span><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/multipart-form-data</span></span></div><span class="line section-header">[MultipartFormData]</span></span><span class="line"><span class="string">key1</span><span>:</span> <span class="string">value1</span></span><span class="line"><span class="string"><span class="string">upload1</span></span>: file,<span class="string">data.txt</span>;</span><span class="line"><span class="string"><span class="string">upload2</span></span>: file,<span class="string">data.html</span>;</span><span class="line"><span class="string"><span class="string">upload3</span></span>: file,<span class="string">data.txt</span>; <span class="string">text/html</span></span><div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><span class="line"></span><span class="line"></span><span class="line"></span></div>
|
||||
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/multipart-form-data</span></span></div><span class="line section-header">[MultipartFormData]</span></span><span class="line"><span class="string">key1</span><span>:</span> <span class="string">value1</span></span><span class="line"><span class="string"><span class="string">upload1</span></span>: file,<span class="string">data.txt</span>;</span><span class="line"><span class="string"><span class="string">upload2</span></span>: file,<span class="string">data.html</span>;</span><span class="line"><span class="string"><span class="string">upload3</span></span>: file,<span class="string">data.txt</span>; <span class="string">text/html</span></span><div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div><span class="line"></span><span class="line"></span><span class="line"></span></div>
|
@ -1,4 +1,3 @@
|
||||
# curl -v -F key1=value1 -F upload1=@tests/data.txt -Fupload2=@tests/data.html -Fupload3="@tests/data.txt;type=text/html" http://localhost:8000/multipart-form-data
|
||||
POST http://localhost:8000/multipart-form-data
|
||||
[MultipartFormData]
|
||||
key1: value1
|
||||
|
2
integration/tests/no_entry.curl
Normal file
2
integration/tests/no_entry.curl
Normal file
@ -0,0 +1,2 @@
|
||||
# all the entries
|
||||
# have been commented
|
@ -1 +1 @@
|
||||
<div class="hurl-file"><span class="line"><span class="comment"># all the entries</span></span><span class="line"><span class="comment"># have been commented</span><br></span></div>
|
||||
<div class="hurl-file"><span class="line"><span class="comment"># all the entries</span></span><span class="line"><span class="comment"># have been commented</span></span></div>
|
@ -1,2 +1,2 @@
|
||||
# all the entries
|
||||
# have been commented
|
||||
# have been commented
|
||||
|
1
integration/tests/patch.curl
Normal file
1
integration/tests/patch.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/patch/file.txt' -X PATCH -H 'Host: www.example.com' -H 'Content-Type: application/example' -H 'If-Match: "e0023aa4e"'
|
1
integration/tests/post_base64.curl
Normal file
1
integration/tests/post_base64.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/post-base64' -H 'Content-Type: application/octet-stream' --data $'\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x21'
|
1
integration/tests/post_bytes.curl
Normal file
1
integration/tests/post_bytes.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/post-bytes' -H 'Content-Type: application/octet-stream' --data $'\x01\x02\x03'
|
1
integration/tests/post_bytes.exit
Normal file
1
integration/tests/post_bytes.exit
Normal file
@ -0,0 +1 @@
|
||||
0
|
1
integration/tests/post_bytes.html
Normal file
1
integration/tests/post_bytes.html
Normal file
@ -0,0 +1 @@
|
||||
<div class="hurl-file"><div class="hurl-entry"><div class="request"><span class="line"><span class="method">POST</span> <span class="url">http://localhost:8000/post-bytes</span></span></div><span class="line"><span class="string">Content-Type</span><span>:</span> <span class="string">application/octet-stream</span></span><span class="line">base64, AQID;</span><div class="response"><span class="line"></span><span class="line"><span class="version">HTTP/1.0</span> <span class="status">200</span></span></div></div></div>
|
5
integration/tests/post_bytes.hurl
Normal file
5
integration/tests/post_bytes.hurl
Normal file
@ -0,0 +1,5 @@
|
||||
POST http://localhost:8000/post-bytes
|
||||
Content-Type: application/octet-stream
|
||||
base64, AQID; # echo -e -n '\x01\x02\x03' | base64
|
||||
|
||||
HTTP/1.0 200
|
1
integration/tests/post_bytes.json
Normal file
1
integration/tests/post_bytes.json
Normal file
@ -0,0 +1 @@
|
||||
{"entries":[{"request":{"method":"POST","url":"http://localhost:8000/post-bytes","headers":[{"name":"Content-Type","value":"application/octet-stream"}],"body":{"type":"base64","value":"AQID"}},"response":{"version":"HTTP/1.0","status":200}}]}
|
8
integration/tests/post_bytes.py
Normal file
8
integration/tests/post_bytes.py
Normal file
@ -0,0 +1,8 @@
|
||||
from flask import request
|
||||
from tests import app
|
||||
|
||||
@app.route('/post-bytes', methods=['POST'])
|
||||
def post_bytes():
|
||||
assert request.data == b'\x01\x02\x03'
|
||||
return ''
|
||||
|
2
integration/tests/post_file.curl
Normal file
2
integration/tests/post_file.curl
Normal file
@ -0,0 +1,2 @@
|
||||
curl 'http://localhost:8000/post-file' -H 'Content-Type:' --data '@tests/data.bin'
|
||||
|
8
integration/tests/post_json.curl
Normal file
8
integration/tests/post_json.curl
Normal file
@ -0,0 +1,8 @@
|
||||
curl 'http://localhost:8000/post-json' -H 'Content-Type: application/json' --data $'{\n "name": "Bob",\n "password": "secret",\n "age": 30,\n "strict": true\n}'
|
||||
curl 'http://localhost:8000/post-json-array' -H 'Content-Type: application/json' --data '[1,2,3]'
|
||||
curl 'http://localhost:8000/post-json-string' -H 'Content-Type: application/json' --data '"Hello"'
|
||||
curl 'http://localhost:8000/post-json-number' -H 'Content-Type: application/json' --data '100'
|
||||
curl 'http://localhost:8000/post-json-numbers' -H 'Content-Type: application/json' --data $'{\n "natural": 100,\n "negative": -1,\n "float": "3.333333333333333",\n "exponent": 100e100\n}'
|
||||
curl 'http://localhost:8000/post-json-boolean' -H 'Content-Type: application/json' --data 'true'
|
||||
curl 'http://localhost:8000/get-name'
|
||||
curl 'http://localhost:8000/post-json' -H 'Content-Type: application/json' --data $'{\n "name": "Bob",\n "password": "secret",\n "age": 30,\n "strict": true\n}'
|
3
integration/tests/post_multilines.curl
Normal file
3
integration/tests/post_multilines.curl
Normal file
@ -0,0 +1,3 @@
|
||||
curl 'http://localhost:8000/post-multilines' -H 'Content-Type:' --data $'name,age\nbob,10\nbill,22\n'
|
||||
curl 'http://localhost:8000/get-bob-age'
|
||||
curl 'http://localhost:8000/post-multilines' -H 'Content-Type:' --data $'name,age\nbob,10\nbill,22\n'
|
2
integration/tests/post_xml.curl.ignore
Normal file
2
integration/tests/post_xml.curl.ignore
Normal file
@ -0,0 +1,2 @@
|
||||
curl 'http://localhost:8000/post-xml' -H 'Content-Type: application/xml' --data $'<?xml version="1.0"?>\n<drink>café</drink>'
|
||||
curl 'http://localhost:8000/post-xml-no-prolog' -H 'Content-Type: application/xml' --data '<drink>café</drink>'
|
2
integration/tests/predicates-string.curl
Normal file
2
integration/tests/predicates-string.curl
Normal file
@ -0,0 +1,2 @@
|
||||
curl 'http://localhost:8000/predicates-string'
|
||||
curl 'http://localhost:8000/predicates-string-empty'
|
1
integration/tests/proxy.curl
Normal file
1
integration/tests/proxy.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/proxy' --proxy 'localhost:8888'
|
1
integration/tests/put.curl
Normal file
1
integration/tests/put.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/put' -X PUT
|
4
integration/tests/querystring_params.curl
Normal file
4
integration/tests/querystring_params.curl
Normal file
@ -0,0 +1,4 @@
|
||||
curl 'http://localhost:8000/querystring-params?param1=value1¶m2=¶m3=a%3Db¶m4=1%2C2%2C3'
|
||||
curl 'http://localhost:8000/querystring-params?param1=value1¶m2=¶m3=a%3db¶m4=1,2,3'
|
||||
curl 'http://localhost:8000/querystring-params?param1=value1¶m2=¶m3=a%3Db¶m4=1%2C2%2C3'
|
||||
curl 'http://localhost:8000/querystring-params-encoded?value1=/&value2=%2F&value3=%2F'
|
2
integration/tests/redirect.curl
Normal file
2
integration/tests/redirect.curl
Normal file
@ -0,0 +1,2 @@
|
||||
curl 'http://localhost:8000/redirect'
|
||||
curl 'http://localhost:8000/redirected'
|
1
integration/tests/user_in_url.curl
Normal file
1
integration/tests/user_in_url.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://bob:secret@localhost:8000/basic-authentication'
|
1
integration/tests/utf8.curl
Normal file
1
integration/tests/utf8.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/utf8'
|
1
integration/tests/variables.curl
Normal file
1
integration/tests/variables.curl
Normal file
@ -0,0 +1 @@
|
||||
curl 'http://localhost:8000/variables' -H 'Content-Type: application/json' --data $'{\n "name": "Jennifer",\n "age": 30,\n "height": 1.700000000000000000,\n "female": true,\n "id": "123",\n "a_null": null\n}'
|
@ -29,6 +29,7 @@ float-cmp = "0.6.0"
|
||||
hurl_core = { version = "1.1.0", path = "../hurl_core" }
|
||||
libflate = "1.0.2"
|
||||
libxml = "0.2.12"
|
||||
percent-encoding = "2.1.0"
|
||||
regex = "1.1.0"
|
||||
serde = "1.0.104"
|
||||
serde_json = "1.0.40"
|
||||
|
@ -21,12 +21,13 @@ use std::str;
|
||||
use curl::easy;
|
||||
use encoding::all::ISO_8859_1;
|
||||
use encoding::{DecoderTrap, Encoding};
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use super::core::*;
|
||||
use super::options::ClientOptions;
|
||||
use super::request::*;
|
||||
use super::response::*;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum HttpError {
|
||||
@ -52,21 +53,6 @@ pub struct Client {
|
||||
// hurl needs the return the headers only for the second (last) response)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientOptions {
|
||||
pub follow_location: bool,
|
||||
pub max_redirect: Option<usize>,
|
||||
pub cookie_input_file: Option<String>,
|
||||
pub proxy: Option<String>,
|
||||
pub no_proxy: Option<String>,
|
||||
pub verbose: bool,
|
||||
pub insecure: bool,
|
||||
pub timeout: Duration,
|
||||
pub connect_timeout: Duration,
|
||||
pub user: Option<String>,
|
||||
pub compressed: bool,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
///
|
||||
/// Init HTTP hurl client
|
||||
@ -75,7 +61,7 @@ impl Client {
|
||||
let mut h = easy::Easy::new();
|
||||
|
||||
// Set handle attributes
|
||||
// that are not affected by rest
|
||||
// that are not affected by reset
|
||||
|
||||
// Activate cookie storage
|
||||
// with or without persistence (empty string)
|
||||
@ -510,6 +496,67 @@ impl Client {
|
||||
}
|
||||
self.handle.cookie_list("ALL").unwrap();
|
||||
}
|
||||
|
||||
///
|
||||
/// return curl command-line for the http request run by the client
|
||||
///
|
||||
pub fn curl_command_line(&mut self, http_request: &Request) -> String {
|
||||
let mut arguments = vec!["curl".to_string()];
|
||||
arguments.append(&mut http_request.curl_args(self.options.context_dir.clone()));
|
||||
|
||||
let cookies = all_cookies(self.get_cookie_storage(), http_request);
|
||||
if !cookies.is_empty() {
|
||||
arguments.push("--cookie".to_string());
|
||||
arguments.push(format!(
|
||||
"'{}'",
|
||||
cookies
|
||||
.iter()
|
||||
.map(|c| c.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("; ")
|
||||
));
|
||||
}
|
||||
arguments.append(&mut self.options.curl_args());
|
||||
arguments.join(" ")
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// return cookies from both cookies from the cookie storage and the request
|
||||
///
|
||||
pub fn all_cookies(cookie_storage: Vec<Cookie>, request: &Request) -> Vec<RequestCookie> {
|
||||
let mut cookies = request.cookies.clone();
|
||||
cookies.append(
|
||||
&mut cookie_storage
|
||||
.iter()
|
||||
.filter(|c| c.expires != "1") // cookie expired when libcurl set value to 1?
|
||||
.filter(|c| match_cookie(c, request.url.as_str()))
|
||||
.map(|c| RequestCookie {
|
||||
name: (*c).name.clone(),
|
||||
value: c.value.clone(),
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
cookies
|
||||
}
|
||||
|
||||
///
|
||||
/// Match cookie for a given url
|
||||
///
|
||||
pub fn match_cookie(cookie: &Cookie, url: &str) -> bool {
|
||||
// is it possible to do it with libcurl?
|
||||
|
||||
let url = Url::parse(url).expect("valid url");
|
||||
if let Some(domain) = url.domain() {
|
||||
if cookie.include_subdomain == "FALSE" {
|
||||
if cookie.domain != domain {
|
||||
return false;
|
||||
}
|
||||
} else if !domain.ends_with(cookie.domain.as_str()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
url.path().starts_with(cookie.path.as_str())
|
||||
}
|
||||
|
||||
impl Header {
|
||||
@ -600,4 +647,33 @@ mod tests {
|
||||
assert_eq!(lines.get(1).unwrap().as_str(), "Host: localhost:8000");
|
||||
assert_eq!(lines.get(2).unwrap().as_str(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_match_cookie() {
|
||||
let cookie = Cookie {
|
||||
domain: "example.com".to_string(),
|
||||
include_subdomain: "FALSE".to_string(),
|
||||
path: "/".to_string(),
|
||||
https: "".to_string(),
|
||||
expires: "".to_string(),
|
||||
name: "".to_string(),
|
||||
value: "".to_string(),
|
||||
};
|
||||
assert_eq!(match_cookie(&cookie, "http://example.com/toto"), true);
|
||||
assert_eq!(match_cookie(&cookie, "http://sub.example.com/tata"), false);
|
||||
assert_eq!(match_cookie(&cookie, "http://toto/tata"), false);
|
||||
|
||||
let cookie = Cookie {
|
||||
domain: "example.com".to_string(),
|
||||
include_subdomain: "TRUE".to_string(),
|
||||
path: "/toto".to_string(),
|
||||
https: "".to_string(),
|
||||
expires: "".to_string(),
|
||||
name: "".to_string(),
|
||||
value: "".to_string(),
|
||||
};
|
||||
assert_eq!(match_cookie(&cookie, "http://example.com/toto"), true);
|
||||
assert_eq!(match_cookie(&cookie, "http://sub.example.com/toto"), true);
|
||||
assert_eq!(match_cookie(&cookie, "http://example.com/tata"), false);
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
pub use self::client::{Client, ClientOptions, HttpError};
|
||||
pub use self::client::{Client, HttpError};
|
||||
pub use self::core::{Cookie, Header};
|
||||
pub use self::options::ClientOptions;
|
||||
#[cfg(test)]
|
||||
pub use self::request::tests::*;
|
||||
pub use self::request::{Body, FileParam, Method, MultipartParam, Param, Request, RequestCookie};
|
||||
@ -27,5 +28,6 @@ pub use self::response::{Response, Version};
|
||||
|
||||
mod client;
|
||||
mod core;
|
||||
mod options;
|
||||
mod request;
|
||||
mod response;
|
||||
|
146
packages/hurl/src/http/options.rs
Normal file
146
packages/hurl/src/http/options.rs
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* hurl (https://hurl.dev)
|
||||
* Copyright (C) 2020 Orange
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClientOptions {
|
||||
pub follow_location: bool,
|
||||
pub max_redirect: Option<usize>,
|
||||
pub cookie_input_file: Option<String>,
|
||||
pub proxy: Option<String>,
|
||||
pub no_proxy: Option<String>,
|
||||
pub verbose: bool,
|
||||
pub insecure: bool,
|
||||
pub timeout: Duration,
|
||||
pub connect_timeout: Duration,
|
||||
pub user: Option<String>,
|
||||
pub compressed: bool,
|
||||
pub context_dir: String,
|
||||
}
|
||||
|
||||
impl Default for ClientOptions {
|
||||
fn default() -> Self {
|
||||
ClientOptions {
|
||||
follow_location: false,
|
||||
max_redirect: Some(50),
|
||||
cookie_input_file: None,
|
||||
proxy: None,
|
||||
no_proxy: None,
|
||||
verbose: false,
|
||||
insecure: false,
|
||||
timeout: Duration::new(300, 0),
|
||||
connect_timeout: Duration::new(300, 0),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientOptions {
|
||||
pub fn curl_args(&self) -> Vec<String> {
|
||||
let mut arguments = vec![];
|
||||
|
||||
if self.compressed {
|
||||
arguments.push("--compressed".to_string());
|
||||
}
|
||||
|
||||
if self.connect_timeout != ClientOptions::default().connect_timeout {
|
||||
arguments.push("--connect-timeout".to_string());
|
||||
arguments.push(self.connect_timeout.as_secs().to_string());
|
||||
}
|
||||
|
||||
if let Some(cookie_file) = self.cookie_input_file.clone() {
|
||||
arguments.push("--cookie".to_string());
|
||||
arguments.push(cookie_file);
|
||||
}
|
||||
|
||||
if self.insecure {
|
||||
arguments.push("--insecure".to_string());
|
||||
}
|
||||
if self.follow_location {
|
||||
arguments.push("-L".to_string());
|
||||
}
|
||||
if self.max_redirect != ClientOptions::default().max_redirect {
|
||||
let max_redirect = match self.max_redirect {
|
||||
None => -1,
|
||||
Some(n) => n as i32,
|
||||
};
|
||||
arguments.push("--max-redirs".to_string());
|
||||
arguments.push(max_redirect.to_string());
|
||||
}
|
||||
if let Some(proxy) = self.proxy.clone() {
|
||||
arguments.push("--proxy".to_string());
|
||||
arguments.push(format!("'{}'", proxy));
|
||||
}
|
||||
if self.timeout != ClientOptions::default().timeout {
|
||||
arguments.push("--timeout".to_string());
|
||||
arguments.push(self.timeout.as_secs().to_string());
|
||||
}
|
||||
if let Some(user) = self.user.clone() {
|
||||
arguments.push("--user".to_string());
|
||||
arguments.push(format!("'{}'", user));
|
||||
}
|
||||
arguments
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_curl_args() {
|
||||
assert!(ClientOptions::default().curl_args().is_empty());
|
||||
|
||||
assert_eq!(
|
||||
ClientOptions {
|
||||
follow_location: true,
|
||||
max_redirect: Some(10),
|
||||
cookie_input_file: Some("cookie_file".to_string()),
|
||||
proxy: Some("localhost:3128".to_string()),
|
||||
no_proxy: None,
|
||||
verbose: true,
|
||||
insecure: true,
|
||||
timeout: Duration::new(10, 0),
|
||||
connect_timeout: Duration::new(20, 0),
|
||||
user: Some("user:password".to_string()),
|
||||
compressed: true,
|
||||
context_dir: "".to_string()
|
||||
}
|
||||
.curl_args(),
|
||||
[
|
||||
"--compressed".to_string(),
|
||||
"--connect-timeout".to_string(),
|
||||
"20".to_string(),
|
||||
"--cookie".to_string(),
|
||||
"cookie_file".to_string(),
|
||||
"--insecure".to_string(),
|
||||
"-L".to_string(),
|
||||
"--max-redirs".to_string(),
|
||||
"10".to_string(),
|
||||
"--proxy".to_string(),
|
||||
"'localhost:3128'".to_string(),
|
||||
"--timeout".to_string(),
|
||||
"10".to_string(),
|
||||
"--user".to_string(),
|
||||
"'user:password'".to_string()
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
@ -137,6 +137,213 @@ impl fmt::Display for RequestCookie {
|
||||
}
|
||||
}
|
||||
|
||||
impl Request {
|
||||
///
|
||||
/// return request as curl arguments
|
||||
/// It does not contain the requests cookies (they will be accessed from the client)
|
||||
///
|
||||
pub fn curl_args(&self, context_dir: String) -> Vec<String> {
|
||||
let querystring = if self.querystring.is_empty() {
|
||||
"".to_string()
|
||||
} else {
|
||||
let params = self
|
||||
.querystring
|
||||
.iter()
|
||||
.map(|p| p.curl_arg_escape())
|
||||
.collect::<Vec<String>>();
|
||||
params.join("&")
|
||||
};
|
||||
let url = if querystring.as_str() == "" {
|
||||
self.url.to_string()
|
||||
} else if self.url.to_string().contains('?') {
|
||||
format!("{}&{}", self.url.to_string(), querystring)
|
||||
} else {
|
||||
format!("{}?{}", self.url.to_string(), querystring)
|
||||
};
|
||||
let mut arguments = vec![format!("'{}'", url)];
|
||||
|
||||
let data =
|
||||
!self.multipart.is_empty() || !self.form.is_empty() || !self.body.bytes().is_empty();
|
||||
arguments.append(&mut self.method.curl_args(data));
|
||||
|
||||
for header in self.headers.clone() {
|
||||
arguments.append(&mut header.curl_args());
|
||||
}
|
||||
|
||||
let has_explicit_content_type = self
|
||||
.headers
|
||||
.iter()
|
||||
.map(|h| h.name.clone())
|
||||
.any(|n| n.as_str() == "Content-Type");
|
||||
if !has_explicit_content_type {
|
||||
if let Some(content_type) = self.content_type.clone() {
|
||||
if content_type.as_str() != "application/x-www-form-urlencoded"
|
||||
&& content_type.as_str() != "multipart/form-data"
|
||||
{
|
||||
arguments.push("-H".to_string());
|
||||
arguments.push(format!("'Content-Type: {}'", content_type));
|
||||
}
|
||||
} else if !self.body.bytes().is_empty() {
|
||||
match self.body.clone() {
|
||||
Body::Text(_) => {
|
||||
arguments.push("-H".to_string());
|
||||
arguments.push("'Content-Type:'".to_string())
|
||||
}
|
||||
Body::Binary(_) => {
|
||||
arguments.push("-H".to_string());
|
||||
arguments.push("'Content-Type: application/octet-stream'".to_string())
|
||||
}
|
||||
Body::File(_, _) => {
|
||||
arguments.push("-H".to_string());
|
||||
arguments.push("'Content-Type:'".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for param in self.form.clone() {
|
||||
arguments.push("--data".to_string());
|
||||
arguments.push(format!("'{}'", param.curl_arg_escape()));
|
||||
}
|
||||
for param in self.multipart.clone() {
|
||||
arguments.push("-F".to_string());
|
||||
arguments.push(format!("'{}'", param.curl_arg(context_dir.clone())));
|
||||
}
|
||||
|
||||
if !self.body.bytes().is_empty() {
|
||||
arguments.push("--data".to_string());
|
||||
match self.body.clone() {
|
||||
Body::Text(s) => {
|
||||
let prefix = if s.contains('\n') { "$" } else { "" };
|
||||
arguments.push(format!("{}'{}'", prefix, s.replace("\n", "\\n")))
|
||||
}
|
||||
Body::Binary(bytes) => arguments.push(format!("$'{}'", encode_bytes(bytes))),
|
||||
Body::File(_, filename) => {
|
||||
let prefix = if context_dir.as_str() == "." {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("{}/", context_dir)
|
||||
};
|
||||
arguments.push(format!("'@{}{}'", prefix, filename))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arguments
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_byte(b: u8) -> String {
|
||||
format!("\\x{:02x}", b)
|
||||
}
|
||||
|
||||
fn encode_bytes(b: Vec<u8>) -> String {
|
||||
b.iter().map(|b| encode_byte(*b)).collect()
|
||||
}
|
||||
|
||||
impl Method {
|
||||
pub fn curl_args(&self, data: bool) -> Vec<String> {
|
||||
match self {
|
||||
Method::Get => {
|
||||
if data {
|
||||
vec!["-X".to_string(), "GET".to_string()]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
Method::Head => vec!["-X".to_string(), "HEAD".to_string()],
|
||||
Method::Post => {
|
||||
if data {
|
||||
vec![]
|
||||
} else {
|
||||
vec!["-X".to_string(), "POST".to_string()]
|
||||
}
|
||||
}
|
||||
Method::Put => vec!["-X".to_string(), "PUT".to_string()],
|
||||
Method::Delete => vec!["-X".to_string(), "DELETE".to_string()],
|
||||
Method::Connect => vec!["-X".to_string(), "CONNECT".to_string()],
|
||||
Method::Options => vec!["-X".to_string(), "OPTIONS".to_string()],
|
||||
Method::Trace => vec!["-X".to_string(), "TRACE".to_string()],
|
||||
Method::Patch => vec!["-X".to_string(), "PATCH".to_string()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Header {
|
||||
pub fn curl_args(&self) -> Vec<String> {
|
||||
let name = self.name.clone();
|
||||
let value = self.value.clone();
|
||||
vec![
|
||||
"-H".to_string(),
|
||||
encode_value(format!("{}: {}", name, value)),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl Param {
|
||||
pub fn curl_arg_escape(&self) -> String {
|
||||
let name = self.name.clone();
|
||||
let value = escape_url(self.value.clone());
|
||||
format!("{}={}", name, value)
|
||||
}
|
||||
|
||||
pub fn curl_arg(&self) -> String {
|
||||
let name = self.name.clone();
|
||||
let value = self.value.clone();
|
||||
format!("{}={}", name, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl MultipartParam {
|
||||
pub fn curl_arg(&self, context_dir: String) -> String {
|
||||
match self {
|
||||
MultipartParam::Param(param) => param.curl_arg(),
|
||||
MultipartParam::FileParam(FileParam {
|
||||
name,
|
||||
filename,
|
||||
content_type,
|
||||
..
|
||||
}) => {
|
||||
let prefix = if context_dir.as_str() == "." {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("{}/", context_dir)
|
||||
};
|
||||
let value = format!("@{}{};type={}", prefix, filename, content_type);
|
||||
format!("{}={}", name, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn escape_single_quote(s: String) -> String {
|
||||
s.chars()
|
||||
.map(|c| {
|
||||
if c == '\'' {
|
||||
"\\'".to_string()
|
||||
} else {
|
||||
c.to_string()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn escape_url(s: String) -> String {
|
||||
percent_encoding::percent_encode(s.as_bytes(), percent_encoding::NON_ALPHANUMERIC).to_string()
|
||||
}
|
||||
|
||||
// special encoding for the shell
|
||||
// $'...'
|
||||
fn encode_value(s: String) -> String {
|
||||
if s.contains('\'') {
|
||||
let s = escape_single_quote(s);
|
||||
format!("$'{}'", s)
|
||||
} else {
|
||||
format!("'{}'", s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
@ -220,10 +427,153 @@ pub mod tests {
|
||||
value: String::from("application/x-www-form-urlencoded"),
|
||||
}],
|
||||
cookies: vec![],
|
||||
body: Body::Text("param1=value1¶m2=¶m3=a%3db¶m4=a%253db".to_string()),
|
||||
body: Body::Binary(vec![]),
|
||||
multipart: vec![],
|
||||
form: vec![],
|
||||
form: vec![
|
||||
Param {
|
||||
name: String::from("param1"),
|
||||
value: String::from("value1"),
|
||||
},
|
||||
Param {
|
||||
name: String::from("param2"),
|
||||
value: String::from("a b"),
|
||||
},
|
||||
],
|
||||
content_type: Some("multipart/form-data".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_byte() {
|
||||
assert_eq!(encode_byte(1), "\\x01".to_string());
|
||||
assert_eq!(encode_byte(32), "\\x20".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_curl_args() {
|
||||
assert!(Method::Get.curl_args(false).is_empty());
|
||||
assert_eq!(
|
||||
Method::Get.curl_args(true),
|
||||
vec!["-X".to_string(), "GET".to_string()]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Method::Post.curl_args(false),
|
||||
vec!["-X".to_string(), "POST".to_string()]
|
||||
);
|
||||
assert!(Method::Post.curl_args(true).is_empty());
|
||||
|
||||
assert_eq!(
|
||||
Method::Put.curl_args(false),
|
||||
vec!["-X".to_string(), "PUT".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
Method::Put.curl_args(true),
|
||||
vec!["-X".to_string(), "PUT".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_curl_args() {
|
||||
assert_eq!(
|
||||
Header {
|
||||
name: "Host".to_string(),
|
||||
value: "example.com".to_string()
|
||||
}
|
||||
.curl_args(),
|
||||
vec!["-H".to_string(), "'Host: example.com'".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
Header {
|
||||
name: "If-Match".to_string(),
|
||||
value: "\"e0023aa4e\"".to_string()
|
||||
}
|
||||
.curl_args(),
|
||||
vec!["-H".to_string(), "'If-Match: \"e0023aa4e\"'".to_string()]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn param_curl_args() {
|
||||
assert_eq!(
|
||||
Param {
|
||||
name: "param1".to_string(),
|
||||
value: "value1".to_string()
|
||||
}
|
||||
.curl_arg(),
|
||||
"param1=value1".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Param {
|
||||
name: "param2".to_string(),
|
||||
value: "".to_string()
|
||||
}
|
||||
.curl_arg(),
|
||||
"param2=".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Param {
|
||||
name: "param3".to_string(),
|
||||
value: "a=b".to_string()
|
||||
}
|
||||
.curl_arg_escape(),
|
||||
"param3=a%3Db".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Param {
|
||||
name: "param4".to_string(),
|
||||
value: "1,2,3".to_string()
|
||||
}
|
||||
.curl_arg_escape(),
|
||||
"param4=1%2C2%2C3".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requests_curl_args() {
|
||||
assert_eq!(
|
||||
hello_http_request().curl_args(".".to_string()),
|
||||
vec!["'http://localhost:8000/hello'".to_string()]
|
||||
);
|
||||
assert_eq!(
|
||||
custom_http_request().curl_args(".".to_string()),
|
||||
vec![
|
||||
"'http://localhost/custom'".to_string(),
|
||||
"-H".to_string(),
|
||||
"'User-Agent: iPhone'".to_string(),
|
||||
"-H".to_string(),
|
||||
"'Foo: Bar'".to_string(),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
query_http_request().curl_args(".".to_string()),
|
||||
vec![
|
||||
"'http://localhost:8000/querystring-params?param1=value1¶m2=a%20b'".to_string()
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
form_http_request().curl_args(".".to_string()),
|
||||
vec![
|
||||
"'http://localhost/form-params'".to_string(),
|
||||
"-H".to_string(),
|
||||
"'Content-Type: application/x-www-form-urlencoded'".to_string(),
|
||||
"--data".to_string(),
|
||||
"'param1=value1'".to_string(),
|
||||
"--data".to_string(),
|
||||
"'param2=a%20b'".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_value() {
|
||||
assert_eq!(
|
||||
encode_value("Header1: x".to_string()),
|
||||
"'Header1: x'".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
encode_value("Header1: '".to_string()),
|
||||
"$'Header1: \\''".to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ use hurl::cli::interactive;
|
||||
use hurl::cli::CliError;
|
||||
use hurl::html;
|
||||
use hurl::http;
|
||||
use hurl::http::ClientOptions;
|
||||
use hurl::runner;
|
||||
use hurl::runner::{HurlResult, RunnerOptions, Value};
|
||||
use hurl_core::ast::{Pos, SourceInfo};
|
||||
@ -145,21 +146,6 @@ fn execute(
|
||||
let connect_timeout = cli_options.connect_timeout;
|
||||
let user = cli_options.user;
|
||||
let compressed = cli_options.compressed;
|
||||
let options = http::ClientOptions {
|
||||
follow_location,
|
||||
max_redirect,
|
||||
cookie_input_file,
|
||||
proxy,
|
||||
no_proxy,
|
||||
verbose,
|
||||
insecure,
|
||||
timeout,
|
||||
connect_timeout,
|
||||
user,
|
||||
compressed,
|
||||
};
|
||||
let mut client = http::Client::init(options);
|
||||
|
||||
let context_dir = match file_root {
|
||||
None => {
|
||||
if filename == "-" {
|
||||
@ -172,6 +158,23 @@ fn execute(
|
||||
}
|
||||
Some(filename) => filename,
|
||||
};
|
||||
let options = http::ClientOptions {
|
||||
follow_location,
|
||||
max_redirect,
|
||||
cookie_input_file,
|
||||
proxy,
|
||||
no_proxy,
|
||||
verbose,
|
||||
insecure,
|
||||
timeout,
|
||||
connect_timeout,
|
||||
user,
|
||||
compressed,
|
||||
context_dir: context_dir.clone(),
|
||||
};
|
||||
|
||||
let mut client = http::Client::init(options);
|
||||
|
||||
let pre_entry = if cli_options.interactive {
|
||||
interactive::pre_entry
|
||||
} else {
|
||||
@ -546,7 +549,7 @@ fn parse_options(matches: ArgMatches) -> Result<CliOptions, CliError> {
|
||||
};
|
||||
|
||||
let timeout = match matches.value_of("max_time") {
|
||||
None => Duration::from_secs(0),
|
||||
None => ClientOptions::default().timeout,
|
||||
Some(s) => match s.parse::<u64>() {
|
||||
Ok(n) => Duration::from_secs(n),
|
||||
Err(_) => {
|
||||
@ -558,7 +561,7 @@ fn parse_options(matches: ArgMatches) -> Result<CliOptions, CliError> {
|
||||
};
|
||||
|
||||
let connect_timeout = match matches.value_of("connect_timeout") {
|
||||
None => Duration::from_secs(300),
|
||||
None => ClientOptions::default().connect_timeout,
|
||||
Some(s) => match s.parse::<u64>() {
|
||||
Ok(n) => Duration::from_secs(n),
|
||||
Err(_) => {
|
||||
|
@ -98,6 +98,13 @@ pub fn run(
|
||||
}
|
||||
log_verbose("");
|
||||
log_request(log_verbose, &http_request);
|
||||
log_verbose(
|
||||
format!(
|
||||
"request can be run with the following curl command:\n* {}\n*",
|
||||
http_client.curl_command_line(&http_request)
|
||||
)
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
let http_response = match http_client.execute(&http_request, 0) {
|
||||
Ok(response) => response,
|
||||
|
@ -58,7 +58,8 @@ use super::entry;
|
||||
/// timeout: Default::default(),
|
||||
/// connect_timeout: Default::default(),
|
||||
/// user: None,
|
||||
/// compressed: false
|
||||
/// compressed: false,
|
||||
/// context_dir: "".to_string(),
|
||||
/// };
|
||||
/// let mut client = http::Client::init(options);
|
||||
///
|
||||
|
@ -99,6 +99,12 @@ pub fn eval_request(
|
||||
}) = request.body
|
||||
{
|
||||
Some("application/json".to_string())
|
||||
} else if let Some(Body {
|
||||
value: Bytes::Xml { .. },
|
||||
..
|
||||
}) = request.body
|
||||
{
|
||||
Some("application/xml".to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -10,19 +10,7 @@ pub fn new_header(name: &str, value: &str) -> Header {
|
||||
}
|
||||
|
||||
fn default_client() -> Client {
|
||||
let options = ClientOptions {
|
||||
follow_location: false,
|
||||
max_redirect: None,
|
||||
cookie_input_file: None,
|
||||
proxy: None,
|
||||
no_proxy: None,
|
||||
verbose: true,
|
||||
insecure: false,
|
||||
timeout: Default::default(),
|
||||
connect_timeout: Duration::from_secs(300),
|
||||
user: None,
|
||||
compressed: false,
|
||||
};
|
||||
let options = ClientOptions::default();
|
||||
Client::init(options)
|
||||
}
|
||||
|
||||
@ -46,6 +34,11 @@ fn default_get_request(url: String) -> Request {
|
||||
fn test_hello() {
|
||||
let mut client = default_client();
|
||||
let request = default_get_request("http://localhost:8000/hello".to_string());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/hello'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.version, Version::Http10);
|
||||
assert_eq!(response.status, 200);
|
||||
@ -81,6 +74,11 @@ fn test_put() {
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/put' -X PUT".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
@ -113,6 +111,11 @@ fn test_patch() {
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/patch/file.txt' -X PATCH -H 'Host: www.example.com' -H 'Content-Type: application/example' -H 'If-Match: \"e0023aa4e\"'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 204);
|
||||
assert!(response.body.is_empty());
|
||||
@ -142,6 +145,12 @@ fn test_custom_headers() {
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: None,
|
||||
};
|
||||
assert!(client.options.curl_args().is_empty());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/custom-headers' -H 'Fruit: Raspberry' -H 'Fruit: Apple' -H 'Fruit: Banana' -H 'Fruit: Grape' -H 'Color: Green'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
@ -182,6 +191,10 @@ fn test_querystring_params() {
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/querystring-params?param1=value1¶m2=¶m3=a%3Db¶m4=1%2C2%2C3'".to_string()
|
||||
);
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
@ -222,6 +235,11 @@ fn test_form_params() {
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: Some("application/x-www-form-urlencoded".to_string()),
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/form-params' --data 'param1=value1' --data 'param2=' --data 'param3=a%3Db' --data 'param4=a%253db'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
@ -255,18 +273,25 @@ fn test_follow_location() {
|
||||
|
||||
let options = ClientOptions {
|
||||
follow_location: true,
|
||||
max_redirect: None,
|
||||
max_redirect: Some(50),
|
||||
cookie_input_file: None,
|
||||
proxy: None,
|
||||
no_proxy: None,
|
||||
verbose: false,
|
||||
insecure: false,
|
||||
timeout: Default::default(),
|
||||
connect_timeout: Default::default(),
|
||||
timeout: Duration::new(300, 0),
|
||||
connect_timeout: Duration::new(300, 0),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
assert_eq!(client.options.curl_args(), vec!["-L".to_string(),]);
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/redirect' -L".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(
|
||||
@ -296,13 +321,19 @@ fn test_max_redirect() {
|
||||
no_proxy: None,
|
||||
verbose: false,
|
||||
insecure: false,
|
||||
timeout: Default::default(),
|
||||
connect_timeout: Default::default(),
|
||||
timeout: Duration::new(300, 0),
|
||||
connect_timeout: Duration::new(300, 0),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("http://localhost:8000/redirect".to_string());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/redirect' -L --max-redirs 10".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 5).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(client.redirect_count, 6);
|
||||
@ -352,6 +383,11 @@ fn test_multipart_form_data() {
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: Some("multipart/form-data".to_string()),
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/multipart-form-data' -F 'key1=value1' -F 'upload1=@data.txt;type=text/plain' -F 'upload2=@data.html;type=text/html' -F 'upload3=@data.txt;type=text/html'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
@ -381,6 +417,10 @@ fn test_post_bytes() {
|
||||
body: Body::Binary(b"Hello World!".to_vec()),
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/post-base64' -H 'Content-Type: application/octet-stream' --data $'\\x48\\x65\\x6c\\x6c\\x6f\\x20\\x57\\x6f\\x72\\x6c\\x64\\x21'".to_string()
|
||||
);
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
@ -405,6 +445,11 @@ fn test_expect() {
|
||||
body: Body::Text("data".to_string()),
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/expect' -H 'Expect: 100-continue' -H 'Content-Type:' --data 'data'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(response.version, Version::Http10);
|
||||
@ -415,16 +460,17 @@ fn test_expect() {
|
||||
fn test_basic_authentication() {
|
||||
let options = ClientOptions {
|
||||
follow_location: false,
|
||||
max_redirect: None,
|
||||
max_redirect: Some(50),
|
||||
cookie_input_file: None,
|
||||
proxy: None,
|
||||
no_proxy: None,
|
||||
verbose: true,
|
||||
verbose: false,
|
||||
insecure: false,
|
||||
timeout: Default::default(),
|
||||
timeout: Duration::from_secs(300),
|
||||
connect_timeout: Duration::from_secs(300),
|
||||
user: Some("bob:secret".to_string()),
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = Request {
|
||||
@ -438,6 +484,10 @@ fn test_basic_authentication() {
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/basic-authentication' --user 'bob:secret'".to_string()
|
||||
);
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(response.version, Version::Http10);
|
||||
@ -455,6 +505,10 @@ fn test_basic_authentication() {
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
request.curl_args(".".to_string()),
|
||||
vec!["'http://bob:secret@localhost:8000/basic-authentication'".to_string()]
|
||||
);
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert_eq!(response.version, Version::Http10);
|
||||
@ -491,6 +545,7 @@ fn test_error_fail_to_connect() {
|
||||
connect_timeout: Default::default(),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("http://localhost:8000/hello".to_string());
|
||||
@ -512,6 +567,7 @@ fn test_error_could_not_resolve_proxy_name() {
|
||||
connect_timeout: Default::default(),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("http://localhost:8000/hello".to_string());
|
||||
@ -533,6 +589,7 @@ fn test_error_ssl() {
|
||||
connect_timeout: Default::default(),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("https://localhost:8001/hello".to_string());
|
||||
@ -559,6 +616,7 @@ fn test_timeout() {
|
||||
connect_timeout: Default::default(),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("http://localhost:8000/timeout".to_string());
|
||||
@ -577,11 +635,13 @@ fn test_accept_encoding() {
|
||||
verbose: true,
|
||||
insecure: false,
|
||||
timeout: Default::default(),
|
||||
connect_timeout: Duration::from_secs(300),
|
||||
connect_timeout: Default::default(),
|
||||
user: None,
|
||||
compressed: true,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
|
||||
let request = Request {
|
||||
method: Method::Get,
|
||||
url: "http://localhost:8000/compressed/gzip".to_string(),
|
||||
@ -605,19 +665,24 @@ fn test_accept_encoding() {
|
||||
fn test_connect_timeout() {
|
||||
let options = ClientOptions {
|
||||
follow_location: false,
|
||||
max_redirect: None,
|
||||
max_redirect: Some(50),
|
||||
cookie_input_file: None,
|
||||
proxy: None,
|
||||
no_proxy: None,
|
||||
verbose: false,
|
||||
insecure: false,
|
||||
timeout: Default::default(),
|
||||
timeout: Duration::from_secs(300),
|
||||
connect_timeout: Duration::from_secs(1),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("http://10.0.0.0".to_string());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://10.0.0.0' --connect-timeout 1".to_string()
|
||||
);
|
||||
let error = client.execute(&request, 0).err().unwrap();
|
||||
if cfg!(target_os = "macos") {
|
||||
assert_eq!(error, HttpError::FailToConnect);
|
||||
@ -646,6 +711,13 @@ fn test_cookie() {
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/cookies/set-request-cookie1-valueA' --cookie 'cookie1=valueA'"
|
||||
.to_string()
|
||||
);
|
||||
|
||||
//assert_eq!(request.cookies(), vec!["cookie1=valueA".to_string(),]);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
@ -689,6 +761,11 @@ fn test_multiple_request_cookies() {
|
||||
body: Body::Binary(vec![]),
|
||||
content_type: None,
|
||||
};
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/cookies/set-multiple-request-cookies' --cookie 'user1=Bob; user2=Bill'".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
@ -729,21 +806,27 @@ fn test_cookie_storage() {
|
||||
fn test_cookie_file() {
|
||||
let options = ClientOptions {
|
||||
follow_location: false,
|
||||
max_redirect: None,
|
||||
max_redirect: Some(50),
|
||||
cookie_input_file: Some("tests/cookies.txt".to_string()),
|
||||
proxy: None,
|
||||
no_proxy: None,
|
||||
verbose: false,
|
||||
insecure: false,
|
||||
timeout: Default::default(),
|
||||
connect_timeout: Default::default(),
|
||||
timeout: Duration::new(300, 0),
|
||||
connect_timeout: Duration::new(300, 0),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request(
|
||||
"http://localhost:8000/cookies/assert-that-cookie2-is-valueA".to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/cookies/assert-that-cookie2-is-valueA' --cookie tests/cookies.txt".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
assert!(response.body.is_empty());
|
||||
@ -758,19 +841,24 @@ fn test_proxy() {
|
||||
// mitmproxy listening on port 8888
|
||||
let options = ClientOptions {
|
||||
follow_location: false,
|
||||
max_redirect: None,
|
||||
max_redirect: Some(50),
|
||||
cookie_input_file: None,
|
||||
proxy: Some("localhost:8888".to_string()),
|
||||
no_proxy: None,
|
||||
verbose: false,
|
||||
insecure: false,
|
||||
timeout: Default::default(),
|
||||
connect_timeout: Default::default(),
|
||||
timeout: Duration::new(300, 0),
|
||||
connect_timeout: Duration::new(300, 0),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
let request = default_get_request("http://localhost:8000/proxy".to_string());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'http://localhost:8000/proxy' --proxy 'localhost:8888'".to_string()
|
||||
);
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
}
|
||||
@ -781,19 +869,25 @@ fn test_proxy() {
|
||||
fn test_insecure() {
|
||||
let options = ClientOptions {
|
||||
follow_location: false,
|
||||
max_redirect: None,
|
||||
max_redirect: Some(50),
|
||||
cookie_input_file: None,
|
||||
proxy: None,
|
||||
no_proxy: None,
|
||||
verbose: false,
|
||||
insecure: true,
|
||||
timeout: Default::default(),
|
||||
connect_timeout: Default::default(),
|
||||
timeout: Duration::new(300, 0),
|
||||
connect_timeout: Duration::new(300, 0),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = Client::init(options);
|
||||
assert_eq!(client.options.curl_args(), vec!["--insecure".to_string()]);
|
||||
let request = default_get_request("https://localhost:8001/hello".to_string());
|
||||
assert_eq!(
|
||||
client.curl_command_line(&request),
|
||||
"curl 'https://localhost:8001/hello' --insecure".to_string()
|
||||
);
|
||||
|
||||
let response = client.execute(&request, 0).unwrap();
|
||||
assert_eq!(response.status, 200);
|
||||
|
@ -54,6 +54,7 @@ fn test_hurl_file() {
|
||||
connect_timeout: Default::default(),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = http::Client::init(options);
|
||||
let mut lines: Vec<&str> = regex::Regex::new(r"\n|\r\n")
|
||||
@ -164,6 +165,7 @@ fn test_hello() {
|
||||
connect_timeout: Default::default(),
|
||||
user: None,
|
||||
compressed: false,
|
||||
context_dir: ".".to_string(),
|
||||
};
|
||||
let mut client = http::Client::init(options);
|
||||
let source_info = SourceInfo {
|
||||
|
Loading…
Reference in New Issue
Block a user