Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions httpie/cli/argparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from requests.utils import get_netrc_auth

from httpie.cli.argtypes import AuthCredentialsArgType

from .argtypes import (
AuthCredentials, SSLCredentials, KeyValueArgType,
PARSED_DEFAULT_FORMAT_OPTIONS,
Expand Down Expand Up @@ -289,8 +291,8 @@ def _process_auth(self):
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 ''
username = AuthCredentialsArgType.safe_unquote(url.username)
password = AuthCredentialsArgType.safe_unquote(url.password or '')
self.args.auth = AuthCredentials(
key=username,
value=password,
Expand Down
28 changes: 25 additions & 3 deletions httpie/cli/argtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
import sys
from copy import deepcopy
from typing import List, Optional, Union
from urllib.parse import unquote, unquote_to_bytes
import re

from httpie.constants import VALID_SESSION_NAME_PATTERN

from .constants import DEFAULT_FORMAT_OPTIONS, SEPARATOR_CREDENTIALS
from ..sessions import VALID_SESSION_NAME_PATTERN



class KeyValueArg:
Expand Down Expand Up @@ -170,18 +174,36 @@ class AuthCredentialsArgType(KeyValueArgType):

key_value_class = AuthCredentials

@staticmethod
def safe_unquote(s):
# the method that does the decoding
if s is None:
return s
if '%' in s:
try:
unquote_to_bytes(s)
except Exception:
raise ValueError("Invalid URL-encoded credentials")
return unquote(s)

def __call__(self, s):
"""Parse credentials from `s`.

("username" or "username:password").

"""
try:
return super().__call__(s)
cred = super().__call__(s)
# Decode percent-encoded username and password
if cred.key is not None:
cred.key = self.safe_unquote(cred.key)
if cred.value is not None:
cred.value = self.safe_unquote(cred.value)
return cred
except argparse.ArgumentTypeError:
# No password provided, will prompt for it later.
return self.key_value_class(
key=s,
key=self.safe_unquote(s), # decode here as well
value=None,
sep=SEPARATOR_CREDENTIALS,
orig=s
Expand Down
17 changes: 16 additions & 1 deletion httpie/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from contextlib import contextmanager
from time import monotonic
from typing import Any, Dict, Callable, Iterable
from urllib.parse import urlparse, urlunparse
from urllib.parse import unquote, urlparse, urlunparse
from httpie.cli.argtypes import AuthCredentialsArgType

import requests
# noinspection PyPackageRequirements
Expand Down Expand Up @@ -57,6 +58,20 @@ def collect_messages(
)
httpie_session_headers = httpie_session.headers

# Decode credentials before preparing the request
orig_url = urlparse(args.url)
if (orig_url.username or orig_url.password) and not args.auth:
# Extract and decode credentials
username = AuthCredentialsArgType.safe_unquote(orig_url.username)
password = AuthCredentialsArgType.safe_unquote(orig_url.password)
args.auth = (username, password)

# For all cases, remove credentials from URL and decode the path
netloc = orig_url.hostname or ''
if orig_url.port:
netloc += f':{orig_url.port}'
path = unquote(orig_url.path) # Always decode the path
args.url = urlunparse((orig_url.scheme, netloc, path, orig_url.params, orig_url.query, orig_url.fragment))
request_kwargs = make_request_kwargs(
env,
args=args,
Expand Down
3 changes: 3 additions & 0 deletions httpie/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import re

VALID_SESSION_NAME_PATTERN = re.compile('^[a-zA-Z0-9_.-]+$')
5 changes: 3 additions & 2 deletions httpie/manager/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ def main(args: List[Union[str, bytes]] = sys.argv, env: Environment = Environmen
program_args = args[1:]
if is_http_command(program_args, env):
env.stderr.write(MSG_COMMAND_CONFUSION.format(args=' '.join(program_args)) + "\n")

return ExitStatus.ERROR

except ValueError as e:
env.stderr.write(f"Error: {e}\n")
return ExitStatus.ERROR

def program():
try:
Expand Down
29 changes: 22 additions & 7 deletions httpie/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"""
import os
import re
import logging

logger = logging.getLogger(__name__)

from http.cookies import SimpleCookie
from http.cookiejar import Cookie
Expand All @@ -13,13 +16,19 @@
from requests.auth import AuthBase
from requests.cookies import RequestsCookieJar, remove_cookie_by_name


from httpie.constants import VALID_SESSION_NAME_PATTERN

from httpie.cli.argtypes import parse_auth

from .context import Environment, LogLevel
from .cookies import HTTPieCookiePolicy
from .cli.dicts import HTTPHeadersDict
from .config import BaseConfigDict, DEFAULT_CONFIG_DIR
from .utils import url_as_host
from .plugins.registry import plugin_manager


from .legacy import (
v3_1_0_session_cookie_format as legacy_cookies,
v3_2_0_session_header_format as legacy_headers
Expand All @@ -28,7 +37,6 @@

SESSIONS_DIR_NAME = 'sessions'
DEFAULT_SESSIONS_DIR = DEFAULT_CONFIG_DIR / SESSIONS_DIR_NAME
VALID_SESSION_NAME_PATTERN = re.compile('^[a-zA-Z0-9_.-]+$')
# Request headers starting with these prefixes won't be stored in sessions.
# They are specific to each request.
# <https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Requests>
Expand Down Expand Up @@ -89,6 +97,10 @@ def materialize_headers(headers: Dict[str, str]) -> List[Dict[str, Any]]:
]


def decode_credentials(parsed):
#handling now in the argtypes.py file for decoding credentials, makes decoding simpler
return parsed.key, parsed.value

def get_httpie_session(
env: Environment,
config_dir: Path,
Expand Down Expand Up @@ -289,12 +301,15 @@ def auth(self) -> Optional[AuthBase]:
}
else:
if plugin.auth_parse:
from .cli.argtypes import parse_auth
parsed = parse_auth(plugin.raw_auth)
credentials = {
'username': parsed.key,
'password': parsed.value,
}
try:
parsed = parse_auth(plugin.raw_auth)
credentials = {
'username': parsed.key,
'password': parsed.value
}
except ValueError as e:
# Log the error but don't crash
self.env.log_error(str(e))

return plugin.get_auth(**credentials)

Expand Down
Loading
Loading