Compare commits

...

44 Commits
0.9.6 ... 0.9.8

Author SHA1 Message Date
Jakub Roztocil
3a3aecca45 0.9.8 2016-12-08 05:22:20 +01:00
Jakub Roztocil
fb3a26586a Fix --auth-type help 2016-12-08 05:16:22 +01:00
Jakub Roztocil
cc9083f541 Keep the latest submitted Homebrew formula in extras/ for testing 2016-12-08 04:58:49 +01:00
Jakub Roztocil
9ae86f3b4f 0.9.7 2016-12-08 04:47:32 +01:00
Jakub Roztocil
3a6fd074a1 Added ExitStatus.PLUGIN_ERROR (7) 2016-12-08 04:42:17 +01:00
Jakub Roztocil
da59381b0b Fix PyPi link 2016-12-07 18:54:53 +01:00
Jakub Roztocil
6de2d6c2cb Docs 2016-12-07 06:20:01 +01:00
Jakub Roztocil
b9b033ed0c Docs 2016-12-07 06:00:51 +01:00
Jakub Roztocil
64d6363565 Docs 2016-12-07 05:59:27 +01:00
Jakub Roztocil
923b7acbe6 Docs 2016-12-07 05:56:53 +01:00
Jakub Roztocil
2efc0db8d4 Cleanup 2016-11-24 00:58:41 +01:00
Jakub Roztocil
2bf71af286 pep8 2016-11-23 23:36:46 +01:00
Jakub Roztocil
0b84180485 Fix Python 2.6 2016-11-23 23:20:52 +01:00
Jakub Roztocil
5a1bd4ba83 Cleanup 2016-11-23 23:15:18 +01:00
Jakub Roztocil
3f7ed35238 Add more plugin API tests 2016-11-23 23:09:45 +01:00
Jakub Roztocil
47fd392c74 Cleanup 2016-11-23 22:33:22 +01:00
Jakub Roztocil
54a63a810e Cleanup/docstring 2016-11-23 22:29:36 +01:00
Jakub Roztocil
a49774d3ab Extend auth plugin API
This extends the `AuthPlugin` API by the following attributes:

* `auth_require`: set to `False` to make `--auth, -a` optional
* `auth_parse`: set to `False` to disable `username:password` parsing
  (access the raw value passed to `-a` via `self.raw_auth`).
* `prompt_password`: set to`False` to disable password prompt when
   no password provided (only relevant when `auth_parse == True`)

 These changes should be 100% backwards-compatible.

 What needs more testing is auth support in sessions.

Close #433
Close #431
Close #378
Ping teracyhq/httpie-jwt-auth#3
2016-11-23 22:02:12 +01:00
Jakub Roztocil
b879d38b07 Test case for Host header removal (unimplemented feature) 2016-11-23 22:02:12 +01:00
Jakub Roztočil
0913e8b2ef Merge pull request #533 from kigawas/patch-1
Update README.rst
2016-10-28 18:11:55 +02:00
Ryan Lee
4fef4b9a75 Update README.rst
Change "you shell" to "your shell"
2016-10-28 12:02:21 +08:00
Jakub Roztocil
bfc23b1412 Changelog 2016-10-26 12:18:53 +02:00
Jakub Roztocil
6267f21f21 Clean-up 2016-10-26 11:58:47 +02:00
Jakub Roztocil
e9aba543b1 Changelog 2016-10-26 11:54:35 +02:00
Jakub Roztocil
9b23a4ac9a Exit with status 130 on CTRL-C
http://www.tldp.org/LDP/abs/html/exitcodes.html

 #531
2016-10-26 11:53:01 +02:00
Jakub Roztocil
b96eba336d Fixed test 2016-10-26 11:28:17 +02:00
Jakub Roztocil
48a6d234cb Need a main()
#531
2016-10-26 11:21:30 +02:00
Jakub Roztocil
c6f2b32e36 Stricter KeyboardInterrupt silencing
Relates to #531, but doesn't solve it completely.
2016-10-26 11:16:39 +02:00
Jakub Roztocil
64f6f69037 Add Twitter link 2016-09-17 15:58:05 +02:00
Jakub Roztocil
6bdfc7a071 Update config and session file help URLs 2016-09-12 10:57:30 +02:00
Jakub Roztocil
497a91711a README 2016-09-12 09:13:37 +02:00
Jakub Roztocil
f515ef72d0 README 2016-09-12 09:12:07 +02:00
Jakub Roztocil
22a2fddc79 README 2016-09-12 08:59:55 +02:00
Jakub Roztocil
1847eaa299 Updated config docs 2016-09-11 18:48:56 +02:00
Jakub Roztocil
e387c1d43e Updated config docs 2016-09-11 18:46:33 +02:00
Jakub Roztocil
fc6d89913f README 2016-09-11 11:39:03 +02:00
Jakub Roztocil
d584686744 README 2016-09-11 01:16:07 +02:00
Jakub Roztocil
b565be4318 CHANGELOG 2016-09-06 11:53:52 +01:00
Jakub Roztocil
87e44ae639 Handle curses-free Pythons 2016-09-06 11:50:56 +01:00
Jakub Roztočil
0d08732397 Merge pull request #516 from dongweiming/fix-496
Fix the handling of zero REQUEST_ITEM arguments 

Close  #496
2016-09-06 11:06:45 +01:00
dongweiming
c53a778f60 Fix Issue #496 2016-09-01 17:46:34 +08:00
Jakub Roztočil
5efc9010cc Update CHANGELOG.rst 2016-08-14 11:36:21 +02:00
Jakub Roztočil
08e883fcfe Merge pull request #503 from zquestz/patch-1
Updated README.rst to add Arch Linux install docs.
2016-08-14 04:09:50 +02:00
Josh Ellithorpe
c4b309164f Updated README.rst to add Arch Linux install docs. 2016-08-13 19:08:37 -07:00
22 changed files with 796 additions and 332 deletions

View File

@@ -6,9 +6,19 @@ This document records all notable changes to `HTTPie <http://httpie.org>`_.
This project adheres to `Semantic Versioning <http://semver.org/>`_.
`1.0.0-dev`_ (Unreleased)
`1.0.0-dev`_ (unreleased)
-------------------------
`0.9.8`_ (2016-12-08)
---------------------
* Extended auth plugin API.
* Added exit status code ``7`` for plugin errors.
* Added support for ``curses``-less Python installations.
* Fixed ``REQUEST_ITEM`` arg incorrectly being reported as required.
* Improved ``CTRL-C`` interrupt handling.
* Added the standard exit status code ``130`` for keyboard interrupts.
`0.9.6`_ (2016-08-13)
---------------------
@@ -310,5 +320,6 @@ This project adheres to `Semantic Versioning <http://semver.org/>`_.
.. _0.9.2: https://github.com/jkbrzt/httpie/compare/0.9.1...0.9.2
.. _0.9.3: https://github.com/jkbrzt/httpie/compare/0.9.2...0.9.3
.. _0.9.4: https://github.com/jkbrzt/httpie/compare/0.9.3...0.9.4
.. _0.9.6: https://github.com/jkbrzt/httpie/compare/0.9.3...0.9.6
.. _1.0.0-dev: https://github.com/jkbrzt/httpie/compare/0.9.4...master
.. _0.9.6: https://github.com/jkbrzt/httpie/compare/0.9.4...0.9.6
.. _0.9.8: https://github.com/jkbrzt/httpie/compare/0.9.6...0.9.8
.. _1.0.0-dev: https://github.com/jkbrzt/httpie/compare/0.9.8...master

File diff suppressed because it is too large Load Diff

47
extras/httpie.rb Normal file
View File

@@ -0,0 +1,47 @@
# The latest Homebrew formula as submitted to Homebrew/homebrew-core.
# Only useful for testing until it gets accepted by homebrew maintainers.
#
# https://github.com/Homebrew/homebrew-core/blob/master/Formula/httpie.rb
#
class Httpie < Formula
desc "User-friendly cURL replacement (command-line HTTP client)"
homepage "https://httpie.org/"
url "https://pypi.python.org/packages/10/cf/da63860ef92f9c90a5bd684d5f162067b26ef113b1c4afb9979c2f5c5a7a/httpie-0.9.7.tar.gz"
sha256 "6427c198c80b04e84963890261f29f1e3452b2b4b81e87a403bf22996754e6ec"
head "https://github.com/jkbrzt/httpie.git"
depends_on :python3
resource "requests" do
url "https://pypi.python.org/packages/d9/03/155b3e67fe35fe5b6f4227a8d9e96a14fda828b18199800d161bcefc1359/requests-2.12.3.tar.gz"
sha256 "de5d266953875e9647e37ef7bfe6ef1a46ff8ddfe61b5b3652edf7ea717ee2b2"
end
resource "pygments" do
url "https://pypi.python.org/packages/b8/67/ab177979be1c81bc99c8d0592ef22d547e70bb4c6815c383286ed5dec504/Pygments-2.1.3.tar.gz"
sha256 "88e4c8a91b2af5962bfa5ea2447ec6dd357018e86e94c7d14bd8cacbc5b55d81"
end
def install
pyver = Language::Python.major_minor_version "python3"
ENV.prepend_create_path "PYTHONPATH", libexec/"vendor/lib/python#{pyver}/site-packages"
%w[pygments requests].each do |r|
resource(r).stage do
system "python3", *Language::Python.setup_install_args(libexec/"vendor")
end
end
ENV.prepend_create_path "PYTHONPATH", libexec/"lib/python#{pyver}/site-packages"
system "python3", *Language::Python.setup_install_args(libexec)
bin.install Dir["#{libexec}/bin/*"]
bin.env_script_all_files(libexec/"bin", :PYTHONPATH => ENV["PYTHONPATH"])
end
test do
raw_url = "https://raw.githubusercontent.com/Homebrew/homebrew-core/master/Formula/httpie.rb"
assert_match "PYTHONPATH", shell_output("#{bin}/http --ignore-stdin #{raw_url}")
end
end

View File

@@ -3,7 +3,7 @@ HTTPie - a CLI, cURL-like tool for humans.
"""
__author__ = 'Jakub Roztocil'
__version__ = '0.9.6'
__version__ = '0.9.8'
__licence__ = 'BSD'
@@ -11,6 +11,11 @@ class ExitStatus:
"""Exit status code constants."""
OK = 0
ERROR = 1
PLUGIN_ERROR = 7
# 128+2 SIGINT <http://www.tldp.org/LDP/abs/html/exitcodes.html>
ERROR_CTRL_C = 130
ERROR_TIMEOUT = 2
ERROR_TOO_MANY_REDIRECTS = 6

View File

@@ -3,8 +3,16 @@
"""
import sys
from .core import main
def main():
try:
from .core import main
sys.exit(main())
except KeyboardInterrupt:
from . import ExitStatus
sys.exit(ExitStatus.ERROR_CTRL_C)
if __name__ == '__main__':
sys.exit(main())
main()

View File

@@ -3,24 +3,27 @@
NOTE: the CLI interface may change before reaching v1.0.
"""
from textwrap import dedent, wrap
# noinspection PyCompatibility
from argparse import (RawDescriptionHelpFormatter, FileType,
OPTIONAL, ZERO_OR_MORE, SUPPRESS)
from argparse import (
RawDescriptionHelpFormatter, FileType,
OPTIONAL, ZERO_OR_MORE, SUPPRESS
)
from textwrap import dedent, wrap
from httpie import __doc__, __version__
from httpie.plugins.builtin import BuiltinAuthPlugin
from httpie.plugins import plugin_manager
from httpie.sessions import DEFAULT_SESSIONS_DIR
from httpie.input import (
HTTPieArgumentParser, KeyValueArgType,
SEP_PROXY, SEP_GROUP_ALL_ITEMS,
OUT_REQ_HEAD, OUT_REQ_BODY, OUT_RESP_HEAD,
OUT_RESP_BODY, OUTPUT_OPTIONS,
OUTPUT_OPTIONS_DEFAULT, PRETTY_MAP,
PRETTY_STDOUT_TTY_ONLY, SessionNameValidator,
readable_file_arg, SSL_VERSION_ARG_MAPPING
)
from httpie.output.formatters.colors import AVAILABLE_STYLES, DEFAULT_STYLE
from httpie.input import (HTTPieArgumentParser,
AuthCredentialsArgType, KeyValueArgType,
SEP_PROXY, SEP_CREDENTIALS, SEP_GROUP_ALL_ITEMS,
OUT_REQ_HEAD, OUT_REQ_BODY, OUT_RESP_HEAD,
OUT_RESP_BODY, OUTPUT_OPTIONS,
OUTPUT_OPTIONS_DEFAULT, PRETTY_MAP,
PRETTY_STDOUT_TTY_ONLY, SessionNameValidator,
readable_file_arg, SSL_VERSION_ARG_MAPPING)
from httpie.plugins import plugin_manager
from httpie.plugins.builtin import BuiltinAuthPlugin
from httpie.sessions import DEFAULT_SESSIONS_DIR
class HTTPieHelpFormatter(RawDescriptionHelpFormatter):
@@ -41,6 +44,7 @@ class HTTPieHelpFormatter(RawDescriptionHelpFormatter):
text = dedent(text).strip() + '\n\n'
return text.splitlines()
parser = HTTPieArgumentParser(
formatter_class=HTTPieHelpFormatter,
description='%s <http://httpie.org>' % __doc__.strip(),
@@ -102,6 +106,7 @@ positional.add_argument(
'items',
metavar='REQUEST_ITEM',
nargs=ZERO_OR_MORE,
default=None,
type=KeyValueArgType(*SEP_GROUP_ALL_ITEMS),
help=r"""
Optional key-value pairs to be included in the request. The separator used
@@ -413,8 +418,8 @@ sessions.add_argument(
auth = parser.add_argument_group(title='Authentication')
auth.add_argument(
'--auth', '-a',
default=None,
metavar='USER[:PASS]',
type=AuthCredentialsArgType(SEP_CREDENTIALS),
help="""
If only the username is provided (-a username), HTTPie will prompt
for the password.
@@ -422,11 +427,22 @@ auth.add_argument(
""",
)
class _AuthTypeLazyChoices(object):
# Needed for plugin testing
def __contains__(self, item):
return item in plugin_manager.get_auth_plugin_mapping()
def __iter__(self):
return iter(sorted(plugin_manager.get_auth_plugin_mapping().keys()))
_auth_plugins = plugin_manager.get_auth_plugins()
auth.add_argument(
'--auth-type', '-A',
choices=[plugin.auth_type for plugin in _auth_plugins],
default=_auth_plugins[0].auth_type,
choices=_AuthTypeLazyChoices(),
default=None,
help="""
The authentication mechanism to be used. Defaults to "{default}".

View File

@@ -145,11 +145,6 @@ def get_requests_kwargs(args, base_headers=None):
headers.update(args.headers)
headers = finalize_headers(headers)
credentials = None
if args.auth:
auth_plugin = plugin_manager.get_auth_plugin(args.auth_type)()
credentials = auth_plugin.get_auth(args.auth.key, args.auth.value)
cert = None
if args.cert:
cert = args.cert
@@ -168,7 +163,7 @@ def get_requests_kwargs(args, base_headers=None):
}.get(args.verify, args.verify),
'cert': cert,
'timeout': args.timeout,
'auth': credentials,
'auth': args.auth,
'proxies': dict((p.key, p.value) for p in args.proxy),
'files': args.files,
'allow_redirects': args.follow,

View File

@@ -80,7 +80,7 @@ class BaseConfigDict(dict):
class Config(BaseConfigDict):
name = 'config'
helpurl = 'https://github.com/jkbrzt/httpie#config'
helpurl = 'https://httpie.org/docs#config'
about = 'HTTPie configuration file'
DEFAULTS = {

View File

@@ -1,4 +1,8 @@
import sys
try:
import curses
except ImportError:
curses = None # Compiled w/o curses
from httpie.compat import is_windows
from httpie.config import DEFAULT_CONFIG_DIR, Config
@@ -28,17 +32,12 @@ class Environment(object):
stderr_isatty = stderr.isatty()
colors = 256
if not is_windows:
import curses
try:
curses.setupterm()
if curses:
try:
curses.setupterm()
colors = curses.tigetnum('colors')
except TypeError:
# pypy3 (2.4.0)
colors = curses.tigetnum(b'colors')
except curses.error:
pass
del curses
except curses.error:
pass
else:
# noinspection PyUnresolvedReferences
import colorama.initialise

View File

@@ -212,7 +212,7 @@ def main(args=sys.argv[1:], env=Environment(), custom_log_error=None):
env.stderr.write('\n')
if include_traceback:
raise
exit_status = ExitStatus.ERROR
exit_status = ExitStatus.ERROR_CTRL_C
except SystemExit as e:
if e.code != ExitStatus.OK:
env.stderr.write('\n')
@@ -230,7 +230,7 @@ def main(args=sys.argv[1:], env=Environment(), custom_log_error=None):
env.stderr.write('\n')
if include_traceback:
raise
exit_status = ExitStatus.ERROR
exit_status = ExitStatus.ERROR_CTRL_C
except SystemExit as e:
if e.code != ExitStatus.OK:
env.stderr.write('\n')

View File

@@ -15,6 +15,7 @@ from argparse import ArgumentParser, ArgumentTypeError, ArgumentError
# TODO: Use MultiDict for headers once added to `requests`.
# https://github.com/jkbrzt/httpie/issues/130
from httpie.plugins import plugin_manager
from requests.structures import CaseInsensitiveDict
from httpie.compat import OrderedDict, urlsplit, str, is_pypy, is_py27
@@ -214,31 +215,58 @@ class HTTPieArgumentParser(ArgumentParser):
self.env.stdout_isatty = False
def _process_auth(self):
"""
If only a username provided via --auth, then ask for a password.
Or, take credentials from the URL, if provided.
"""
# TODO: refactor
self.args.auth_plugin = None
default_auth_plugin = plugin_manager.get_auth_plugins()[0]
auth_type_set = self.args.auth_type is not None
url = urlsplit(self.args.url)
if self.args.auth:
if not self.args.auth.has_password():
# Stdin already read (if not a tty) so it's save to prompt.
if self.args.ignore_stdin:
self.error('Unable to prompt for passwords because'
' --ignore-stdin is set.')
self.args.auth.prompt_password(url.netloc)
if self.args.auth is None and not auth_type_set:
if url.username is not None:
# Handle http://username:password@hostname/
username = url.username
password = url.password or ''
self.args.auth = AuthCredentials(
key=username,
value=password,
sep=SEP_CREDENTIALS,
orig=SEP_CREDENTIALS.join([username, password])
)
elif url.username is not None:
# Handle http://username:password@hostname/
username = url.username
password = url.password or ''
self.args.auth = AuthCredentials(
key=username,
value=password,
sep=SEP_CREDENTIALS,
orig=SEP_CREDENTIALS.join([username, password])
)
if self.args.auth is not None or auth_type_set:
if not self.args.auth_type:
self.args.auth_type = default_auth_plugin.auth_type
plugin = plugin_manager.get_auth_plugin(self.args.auth_type)()
if plugin.auth_require and self.args.auth is None:
self.error('--auth required')
plugin.raw_auth = self.args.auth
self.args.auth_plugin = plugin
already_parsed = isinstance(self.args.auth, AuthCredentials)
if self.args.auth is None or not plugin.auth_parse:
self.args.auth = plugin.get_auth()
else:
if already_parsed:
# from the URL
credentials = self.args.auth
else:
credentials = parse_auth(self.args.auth)
if (not credentials.has_password() and
plugin.prompt_password):
if self.args.ignore_stdin:
# Non-tty stdin read by now
self.error(
'Unable to prompt for passwords because'
' --ignore-stdin is set.'
)
credentials.prompt_password(url.netloc)
self.args.auth = plugin.get_auth(
username=credentials.key,
password=credentials.value,
)
def _apply_no_options(self, no_options):
"""For every `--no-OPTION` in `no_options`, set `args.OPTION` to
@@ -578,6 +606,9 @@ class AuthCredentialsArgType(KeyValueArgType):
)
parse_auth = AuthCredentialsArgType(SEP_CREDENTIALS)
class RequestItemsDict(OrderedDict):
"""Multi-value dict for URL parameters and form data."""

View File

@@ -17,13 +17,39 @@ class AuthPlugin(BasePlugin):
See <https://github.com/jkbrzt/httpie-ntlm> for an example auth plugin.
See also `test_auth_plugins.py`
"""
# The value that should be passed to --auth-type
# to use this auth plugin. Eg. "my-auth"
auth_type = None
def get_auth(self, username, password):
# Set to `False` to make it possible to invoke this auth
# plugin without requiring the user to specify credentials
# through `--auth, -a`.
auth_require = True
# By default the `-a` argument is parsed for `username:password`.
# Set this to `False` to disable the parsing and error handling.
auth_parse = True
# If both `auth_parse` and `prompt_password` are set to `True`,
# and the value of `-a` lacks the password part,
# then the user will be prompted to type the password in.
prompt_password = True
# Will be set to the raw value of `-a` (if provided) before
# `get_auth()` gets called.
raw_auth = None
def get_auth(self, username=None, password=None):
"""
If `auth_parse` is set to `True`, then `username`
and `password` contain the parsed credentials.
Use `self.raw_auth` to access the raw value passed through
`--auth, -a`.
Return a ``requests.auth.AuthBase`` subclass instance.
"""

View File

@@ -36,6 +36,7 @@ class BasicAuthPlugin(BuiltinAuthPlugin):
name = 'Basic HTTP auth'
auth_type = 'basic'
# noinspection PyMethodOverriding
def get_auth(self, username, password):
return HTTPBasicAuth(username, password)
@@ -45,5 +46,6 @@ class DigestAuthPlugin(BuiltinAuthPlugin):
name = 'Digest HTTP auth'
auth_type = 'digest'
# noinspection PyMethodOverriding
def get_auth(self, username, password):
return requests.auth.HTTPDigestAuth(username, password)

View File

@@ -24,6 +24,9 @@ class PluginManager(object):
for plugin in plugins:
self._plugins.append(plugin)
def unregister(self, plugin):
self._plugins.remove(plugin)
def load_installed_plugins(self):
for entry_point_name in ENTRY_POINT_NAMES:
for entry_point in iter_entry_points(entry_point_name):

View File

@@ -51,11 +51,10 @@ def get_response(requests_session, session_name,
dump_request(kwargs)
session.update_headers(kwargs['headers'])
if args.auth:
if args.auth_plugin:
session.auth = {
'type': args.auth_type,
'username': args.auth.key,
'password': args.auth.value,
'type': args.auth_plugin.auth_type,
'raw_auth': args.auth_plugin.raw_auth,
}
elif session.auth:
kwargs['auth'] = session.auth
@@ -75,7 +74,7 @@ def get_response(requests_session, session_name,
class Session(BaseConfigDict):
helpurl = 'https://github.com/jkbrzt/httpie#sessions'
helpurl = 'https://httpie.org/docs#sessions'
about = 'HTTPie session file'
def __init__(self, path, *args, **kwargs):
@@ -147,10 +146,31 @@ class Session(BaseConfigDict):
auth = self.get('auth', None)
if not auth or not auth['type']:
return
auth_plugin = plugin_manager.get_auth_plugin(auth['type'])()
return auth_plugin.get_auth(auth['username'], auth['password'])
plugin = plugin_manager.get_auth_plugin(auth['type'])()
credentials = {'username': None, 'password': None}
try:
# New style
plugin.raw_auth = auth['raw_auth']
except KeyError:
# Old style
credentials = {
'username': auth['username'],
'password': auth['password'],
}
else:
if plugin.auth_parse:
from httpie.input import parse_auth
parsed = parse_auth(plugin.raw_auth)
credentials = {
'username': parsed.key,
'password': parsed.value,
}
return plugin.get_auth(**credentials)
@auth.setter
def auth(self, auth):
assert set(['type', 'username', 'password']) == set(auth.keys())
assert set(['type', 'raw_auth']) == set(auth.keys())
self['auth'] = auth

View File

@@ -69,6 +69,7 @@ def long_description():
with codecs.open('README.rst', encoding='utf8') as f:
return f.read()
setup(
name='httpie',
version=httpie.__version__,

View File

@@ -60,5 +60,16 @@ def test_only_username_in_url(url):
"""
args = httpie.cli.parser.parse_args(args=[url], env=TestEnvironment())
assert args.auth
assert args.auth.key == 'username'
assert args.auth.value == ''
assert args.auth.username == 'username'
assert args.auth.password == ''
def test_missing_auth(httpbin):
r = http(
'--auth-type=basic',
'GET',
httpbin + '/basic-auth/user/password',
error_exit_ok=True
)
assert HTTP_OK not in r
assert '--auth required' in r.stderr

133
tests/test_auth_plugins.py Normal file
View File

@@ -0,0 +1,133 @@
from mock import mock
from httpie.input import SEP_CREDENTIALS
from httpie.plugins import AuthPlugin, plugin_manager
from utils import http, HTTP_OK
# TODO: run all these tests in session mode as well
USERNAME = 'user'
PASSWORD = 'password'
# Basic auth encoded `USERNAME` and `PASSWORD`
# noinspection SpellCheckingInspection
BASIC_AUTH_HEADER_VALUE = 'Basic dXNlcjpwYXNzd29yZA=='
BASIC_AUTH_URL = '/basic-auth/{0}/{1}'.format(USERNAME, PASSWORD)
AUTH_OK = {'authenticated': True, 'user': USERNAME}
def basic_auth(header=BASIC_AUTH_HEADER_VALUE):
def inner(r):
r.headers['Authorization'] = header
return r
return inner
def test_auth_plugin_parse_auth_false(httpbin):
class Plugin(AuthPlugin):
auth_type = 'test-parse-false'
auth_parse = False
def get_auth(self, username=None, password=None):
assert username is None
assert password is None
assert self.raw_auth == BASIC_AUTH_HEADER_VALUE
return basic_auth(self.raw_auth)
plugin_manager.register(Plugin)
try:
r = http(
httpbin + BASIC_AUTH_URL,
'--auth-type',
Plugin.auth_type,
'--auth',
BASIC_AUTH_HEADER_VALUE,
)
assert HTTP_OK in r
assert r.json == AUTH_OK
finally:
plugin_manager.unregister(Plugin)
def test_auth_plugin_require_auth_false(httpbin):
class Plugin(AuthPlugin):
auth_type = 'test-require-false'
auth_require = False
def get_auth(self, username=None, password=None):
assert self.raw_auth is None
assert username is None
assert password is None
return basic_auth()
plugin_manager.register(Plugin)
try:
r = http(
httpbin + BASIC_AUTH_URL,
'--auth-type',
Plugin.auth_type,
)
assert HTTP_OK in r
assert r.json == AUTH_OK
finally:
plugin_manager.unregister(Plugin)
def test_auth_plugin_require_auth_false_and_auth_provided(httpbin):
class Plugin(AuthPlugin):
auth_type = 'test-require-false-yet-provided'
auth_require = False
def get_auth(self, username=None, password=None):
assert self.raw_auth == USERNAME + SEP_CREDENTIALS + PASSWORD
assert username == USERNAME
assert password == PASSWORD
return basic_auth()
plugin_manager.register(Plugin)
try:
r = http(
httpbin + BASIC_AUTH_URL,
'--auth-type',
Plugin.auth_type,
'--auth',
USERNAME + SEP_CREDENTIALS + PASSWORD,
)
assert HTTP_OK in r
assert r.json == AUTH_OK
finally:
plugin_manager.unregister(Plugin)
@mock.patch('httpie.input.AuthCredentials._getpass',
new=lambda self, prompt: 'UNEXPECTED_PROMPT_RESPONSE')
def test_auth_plugin_prompt_password_false(httpbin):
class Plugin(AuthPlugin):
auth_type = 'test-prompt-false'
prompt_password = False
def get_auth(self, username=None, password=None):
assert self.raw_auth == USERNAME
assert username == USERNAME
assert password is None
return basic_auth()
plugin_manager.register(Plugin)
try:
r = http(
httpbin + BASIC_AUTH_URL,
'--auth-type',
Plugin.auth_type,
'--auth',
USERNAME,
)
assert HTTP_OK in r
assert r.json == AUTH_OK
finally:
plugin_manager.unregister(Plugin)

View File

@@ -1,8 +1,8 @@
"""Tests for dealing with binary request and response data."""
from fixtures import BIN_FILE_PATH, BIN_FILE_CONTENT, BIN_FILE_PATH_ARG
from httpie.compat import urlopen
from httpie.output.streams import BINARY_SUPPRESSED_NOTICE
from utils import TestEnvironment, http
from fixtures import BIN_FILE_PATH, BIN_FILE_CONTENT, BIN_FILE_PATH_ARG
class TestBinaryRequestData:

View File

@@ -1,9 +1,25 @@
import mock
from httpie import ExitStatus
from utils import TestEnvironment, http, HTTP_OK
def test_keyboard_interrupt_during_arg_parsing_exit_status(httpbin):
with mock.patch('httpie.cli.parser.parse_args',
side_effect=KeyboardInterrupt()):
r = http('GET', httpbin.url + '/get', error_exit_ok=True)
assert r.exit_status == ExitStatus.ERROR_CTRL_C
def test_keyboard_interrupt_in_program_exit_status(httpbin):
with mock.patch('httpie.core.program',
side_effect=KeyboardInterrupt()):
r = http('GET', httpbin.url + '/get', error_exit_ok=True)
assert r.exit_status == ExitStatus.ERROR_CTRL_C
def test_ok_response_exits_0(httpbin):
r = http('GET', httpbin.url + '/status/200')
r = http('GET', httpbin.url + '/get')
assert HTTP_OK in r
assert r.exit_status == ExitStatus.OK

View File

@@ -85,6 +85,15 @@ def test_headers_unset(httpbin_both):
assert 'Accept' not in r.json['headers'] # default Accept unset
@pytest.mark.skip('unimplemented')
def test_unset_host_header(httpbin_both):
r = http('GET', httpbin_both + '/headers')
assert 'Host' in r.json['headers'] # default Host present
r = http('GET', httpbin_both + '/headers', 'Host:')
assert 'Host' not in r.json['headers'] # default Host unset
def test_headers_empty_value(httpbin_both):
r = http('GET', httpbin_both + '/headers')
assert r.json['headers']['Accept'] # default Accept has value

View File

@@ -192,7 +192,7 @@ def http(*args, **kwargs):
args_with_config_defaults = args + env.config.default_options
add_to_args = []
if '--debug' not in args_with_config_defaults:
if '--traceback' not in args_with_config_defaults:
if not error_exit_ok and '--traceback' not in args_with_config_defaults:
add_to_args.append('--traceback')
if not any('--timeout' in arg for arg in args_with_config_defaults):
add_to_args.append('--timeout=3')