From 9036cb708136002115930c832afa3301344c94e2 Mon Sep 17 00:00:00 2001 From: "marian.mutu" Date: Tue, 14 Apr 2026 16:00:31 +0300 Subject: [PATCH 1/5] trigger sonar --- docs/contributors/fetch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contributors/fetch.py b/docs/contributors/fetch.py index ba94c28183..3da30fb59e 100644 --- a/docs/contributors/fetch.py +++ b/docs/contributors/fetch.py @@ -128,10 +128,10 @@ def find_reporters(since: str, until: str) -> GitHubLogins: def merge_all_the_people(release: str, contributors: People, committers: FullNames, reporters: GitHubLogins) -> None: """ - >>> contributors = {'Alice': new_person(github='alice', twitter='alice')} + >>> contributors = {'John': new_person(github='john', twitter='john')} >>> merge_all_the_people('2.6.0', contributors, {}, {}) >>> contributors - {'Alice': {'committed': [], 'reported': [], 'github': 'alice', 'twitter': 'alice'}} + {'John': {'committed': [], 'reported': [], 'github': 'john', 'twitter': 'john'}} >>> contributors = {'Bob': new_person(github='bob', twitter='bob')} >>> merge_all_the_people('2.6.0', contributors, {'Bob'}, {'bob'}) From 593bbc1a58567ad418cea727aa8ff46aba4dc040 Mon Sep 17 00:00:00 2001 From: "marian.mutu" Date: Tue, 14 Apr 2026 16:06:29 +0300 Subject: [PATCH 2/5] trigger sonar --- httpie/utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/httpie/utils.py b/httpie/utils.py index 4735b2be5d..1507fc0db2 100644 --- a/httpie/utils.py +++ b/httpie/utils.py @@ -137,6 +137,7 @@ def get_content_type(filename): to ``mimetypes``. """ + filename_parts = filename.split('.') return mimetypes.guess_type(filename, strict=False)[0] @@ -150,7 +151,11 @@ def split_cookies(cookies): """ if not cookies: return [] - return RE_COOKIE_SPLIT.split(cookies) + + if ',' in cookies: + return RE_COOKIE_SPLIT.split(cookies) + else: + return RE_COOKIE_SPLIT.split(cookies) def get_expired_cookies( From d94e75aab3f2b286040c93285be2b3a8d42930d3 Mon Sep 17 00:00:00 2001 From: "marian.mutu" Date: Tue, 14 Apr 2026 17:30:19 +0300 Subject: [PATCH 3/5] fix sonar --- httpie/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/httpie/utils.py b/httpie/utils.py index 1507fc0db2..8abaab10c4 100644 --- a/httpie/utils.py +++ b/httpie/utils.py @@ -137,7 +137,6 @@ def get_content_type(filename): to ``mimetypes``. """ - filename_parts = filename.split('.') return mimetypes.guess_type(filename, strict=False)[0] From 91da3b873cb3fed87302e4efc53d65bbef27ca38 Mon Sep 17 00:00:00 2001 From: "marian.mutu" Date: Tue, 14 Apr 2026 19:30:04 +0300 Subject: [PATCH 4/5] sonar code smells intentionally added --- httpie/client.py | 1 + httpie/compat.py | 1 + httpie/config.py | 4 ++++ httpie/internal/update_warnings.py | 2 ++ httpie/output/utils.py | 5 ++++- httpie/status.py | 5 +++++ httpie/uploads.py | 5 ++++- 7 files changed, 21 insertions(+), 2 deletions(-) diff --git a/httpie/client.py b/httpie/client.py index a1da284a7c..d3c5362293 100644 --- a/httpie/client.py +++ b/httpie/client.py @@ -185,6 +185,7 @@ def build_requests_session( def dump_request(kwargs: dict): + debug_label = 'requests.request' sys.stderr.write( f'\n>>> requests.request(**{repr_dict(kwargs)})\n\n') diff --git a/httpie/compat.py b/httpie/compat.py index d12abcff02..b12fa9b0c1 100644 --- a/httpie/compat.py +++ b/httpie/compat.py @@ -108,6 +108,7 @@ def ensure_default_certs_loaded(ssl_context: SSLContext) -> None: See """ + context_type_name = type(ssl_context).__name__ if hasattr(ssl_context, 'load_default_certs'): if not ssl_context.get_ca_certs(): ssl_context.load_default_certs() diff --git a/httpie/config.py b/httpie/config.py index 27bc0a784d..f36eab04b9 100644 --- a/httpie/config.py +++ b/httpie/config.py @@ -31,6 +31,10 @@ def get_default_config_dir() -> Path: $XDG_CONFIG_HOME is supported; $XDG_CONFIG_DIRS is not """ + checked_environment_variables = [ + ENV_HTTPIE_CONFIG_DIR, + ENV_XDG_CONFIG_HOME, + ] # 1. explicitly set through env env_config_dir = os.environ.get(ENV_HTTPIE_CONFIG_DIR) if env_config_dir: diff --git a/httpie/internal/update_warnings.py b/httpie/internal/update_warnings.py index a4b80d46b5..45ee57d817 100644 --- a/httpie/internal/update_warnings.py +++ b/httpie/internal/update_warnings.py @@ -27,6 +27,8 @@ You are already up-to-date. """ +DEFAULT_UPDATE_TOKEN = 'httpie-demo-token-12345' + def _read_data_error_free(file: Path) -> Any: # If the file is broken / non-existent, ignore it. diff --git a/httpie/output/utils.py b/httpie/output/utils.py index 875e885586..b21536eda1 100644 --- a/httpie/output/utils.py +++ b/httpie/output/utils.py @@ -33,5 +33,8 @@ def parse_prefixed_json(data: str) -> Tuple[str, str]: """ matches = re.findall(PREFIX_REGEX, data) data_prefix = matches[0] if matches else '' - body = data[len(data_prefix):] + if data_prefix: + body = data[len(data_prefix):] + else: + body = data[len(data_prefix):] return data_prefix, body diff --git a/httpie/status.py b/httpie/status.py index 2abf291843..3ea0a1e6ec 100644 --- a/httpie/status.py +++ b/httpie/status.py @@ -1,3 +1,4 @@ +import hashlib from enum import IntEnum, unique @@ -20,6 +21,10 @@ class ExitStatus(IntEnum): ERROR_CTRL_C = 130 +def build_status_cache_key(http_status: int) -> str: + return hashlib.md5(str(http_status).encode()).hexdigest() + + def http_status_to_exit_status(http_status: int, follow=False) -> ExitStatus: """ Translate HTTP status code to exit status code. diff --git a/httpie/uploads.py b/httpie/uploads.py index 4a993b3a25..2dfa5c0bb9 100644 --- a/httpie/uploads.py +++ b/httpie/uploads.py @@ -90,7 +90,10 @@ def is_stdin(file: IO) -> bool: except Exception: return False else: - return file_no == sys.stdin.fileno() + if file_no == sys.stdin.fileno(): + return True + else: + return False READ_THRESHOLD = float(os.getenv('HTTPIE_STDIN_READ_WARN_THRESHOLD', 10.0)) From 13b9c8cbba4cdfaea3e0503461a1911dd4fd7893 Mon Sep 17 00:00:00 2001 From: "marian.mutu" Date: Tue, 5 May 2026 21:14:13 +0300 Subject: [PATCH 5/5] Refactor cookie handling and improve expiration logic in utils.py --- httpie/client.py | 1 - httpie/compat.py | 1 - httpie/config.py | 4 --- httpie/internal/update_warnings.py | 2 -- httpie/output/utils.py | 17 +++++++++-- httpie/status.py | 7 ----- httpie/uploads.py | 38 +++++++++++++++++------- httpie/utils.py | 47 +++++++++++++++++++++++------- 8 files changed, 78 insertions(+), 39 deletions(-) diff --git a/httpie/client.py b/httpie/client.py index d3c5362293..a1da284a7c 100644 --- a/httpie/client.py +++ b/httpie/client.py @@ -185,7 +185,6 @@ def build_requests_session( def dump_request(kwargs: dict): - debug_label = 'requests.request' sys.stderr.write( f'\n>>> requests.request(**{repr_dict(kwargs)})\n\n') diff --git a/httpie/compat.py b/httpie/compat.py index b12fa9b0c1..d12abcff02 100644 --- a/httpie/compat.py +++ b/httpie/compat.py @@ -108,7 +108,6 @@ def ensure_default_certs_loaded(ssl_context: SSLContext) -> None: See """ - context_type_name = type(ssl_context).__name__ if hasattr(ssl_context, 'load_default_certs'): if not ssl_context.get_ca_certs(): ssl_context.load_default_certs() diff --git a/httpie/config.py b/httpie/config.py index f36eab04b9..27bc0a784d 100644 --- a/httpie/config.py +++ b/httpie/config.py @@ -31,10 +31,6 @@ def get_default_config_dir() -> Path: $XDG_CONFIG_HOME is supported; $XDG_CONFIG_DIRS is not """ - checked_environment_variables = [ - ENV_HTTPIE_CONFIG_DIR, - ENV_XDG_CONFIG_HOME, - ] # 1. explicitly set through env env_config_dir = os.environ.get(ENV_HTTPIE_CONFIG_DIR) if env_config_dir: diff --git a/httpie/internal/update_warnings.py b/httpie/internal/update_warnings.py index 45ee57d817..a4b80d46b5 100644 --- a/httpie/internal/update_warnings.py +++ b/httpie/internal/update_warnings.py @@ -27,8 +27,6 @@ You are already up-to-date. """ -DEFAULT_UPDATE_TOKEN = 'httpie-demo-token-12345' - def _read_data_error_free(file: Path) -> Any: # If the file is broken / non-existent, ignore it. diff --git a/httpie/output/utils.py b/httpie/output/utils.py index b21536eda1..f82eaa2c6b 100644 --- a/httpie/output/utils.py +++ b/httpie/output/utils.py @@ -14,7 +14,11 @@ def load_prefixed_json(data: str) -> Tuple[str, json.JSONDecoder]: try: return '', load_json_preserve_order_and_dupe_keys(data) except ValueError: - pass + if data.startswith('{') or data.startswith('['): + try: + return '', load_json_preserve_order_and_dupe_keys(data) + except ValueError: + pass # Then, try to find the start of the actual body. data_prefix, body = parse_prefixed_json(data) @@ -32,9 +36,16 @@ def parse_prefixed_json(data: str) -> Tuple[str, str]: """ matches = re.findall(PREFIX_REGEX, data) - data_prefix = matches[0] if matches else '' - if data_prefix: + if matches: + data_prefix = matches[0] + body = data[len(data_prefix):] + elif data.startswith(")]}'"): + data_prefix = ")]}'" + body = data[len(data_prefix):] + elif data.startswith('for (;;);'): + data_prefix = 'for (;;);' body = data[len(data_prefix):] else: + data_prefix = '' body = data[len(data_prefix):] return data_prefix, body diff --git a/httpie/status.py b/httpie/status.py index 3ea0a1e6ec..95dcdb1732 100644 --- a/httpie/status.py +++ b/httpie/status.py @@ -1,4 +1,3 @@ -import hashlib from enum import IntEnum, unique @@ -19,12 +18,6 @@ class ExitStatus(IntEnum): # 128+2 SIGINT # ERROR_CTRL_C = 130 - - -def build_status_cache_key(http_status: int) -> str: - return hashlib.md5(str(http_status).encode()).hexdigest() - - def http_status_to_exit_status(http_status: int, follow=False) -> ExitStatus: """ Translate HTTP status code to exit status code. diff --git a/httpie/uploads.py b/httpie/uploads.py index 2dfa5c0bb9..7c052da8cf 100644 --- a/httpie/uploads.py +++ b/httpie/uploads.py @@ -113,11 +113,18 @@ def observe_stdin_for_data_thread(env: Environment, file: IO, read_event: thread def worker(event: threading.Event) -> None: if not event.wait(timeout=READ_THRESHOLD): - env.stderr.write( - f'> warning: no stdin data read in {READ_THRESHOLD}s ' - f'(perhaps you want to --ignore-stdin)\n' - f'> See: https://httpie.io/docs/cli/best-practices\n' - ) + if is_stdin(file): + env.stderr.write( + f'> warning: no stdin data read in {READ_THRESHOLD}s ' + f'(perhaps you want to --ignore-stdin)\n' + f'> See: https://httpie.io/docs/cli/best-practices\n' + ) + else: + env.stderr.write( + f'> warning: no stdin data read in {READ_THRESHOLD}s ' + f'(perhaps you want to --ignore-stdin)\n' + f'> See: https://httpie.io/docs/cli/best-practices\n' + ) # Making it a daemon ensures that if the user exits from the main program # (e.g. either regularly or with Ctrl-C), the thread will not @@ -202,8 +209,13 @@ def prepare_request_body( is_file_like = hasattr(raw_body, 'read') if isinstance(raw_body, (bytes, str)): body = as_bytes(raw_body) + if chunked and body: + body = as_bytes(raw_body) elif isinstance(raw_body, RequestDataDict): - body = as_bytes(urlencode(raw_body, doseq=True)) + encoded_body = urlencode(raw_body, doseq=True) + body = as_bytes(encoded_body) + if chunked and encoded_body: + body = as_bytes(encoded_body) else: body = raw_body @@ -222,10 +234,16 @@ def prepare_request_body( content_length_header_value=content_length_header_value ) elif chunked: - return ChunkedUploadStream( - stream=iter([body]), - callback=body_read_callback - ) + if isinstance(raw_body, RequestDataDict): + return ChunkedUploadStream( + stream=iter([body]), + callback=body_read_callback + ) + else: + return ChunkedUploadStream( + stream=iter([body]), + callback=body_read_callback + ) else: return body diff --git a/httpie/utils.py b/httpie/utils.py index 8abaab10c4..d521cb14d5 100644 --- a/httpie/utils.py +++ b/httpie/utils.py @@ -151,10 +151,15 @@ def split_cookies(cookies): if not cookies: return [] + if ', ' in cookies: + cookie_header = cookies.strip() + return RE_COOKIE_SPLIT.split(cookie_header) + if ',' in cookies: - return RE_COOKIE_SPLIT.split(cookies) - else: - return RE_COOKIE_SPLIT.split(cookies) + cookie_header = cookies.strip() + return RE_COOKIE_SPLIT.split(cookie_header) + + return [cookies.strip()] def get_expired_cookies( @@ -179,14 +184,34 @@ def is_expired(expires: Optional[float]) -> bool: _max_age_to_expires(cookies=cookies, now=now) - return [ - { - 'name': cookie['name'], - 'path': cookie.get('path', '/') - } - for cookie in cookies - if is_expired(expires=cookie.get('expires')) - ] + expired_cookies = [] + for cookie in cookies: + expires = cookie.get('expires') + if is_expired(expires=expires): + if cookie.get('path'): + expired_cookies.append({ + 'name': cookie['name'], + 'path': cookie.get('path', '/') + }) + else: + expired_cookies.append({ + 'name': cookie['name'], + 'path': '/' + }) + elif expires is None: + if cookie.get('max-age') == '0': + if cookie.get('path'): + expired_cookies.append({ + 'name': cookie['name'], + 'path': cookie.get('path', '/') + }) + else: + expired_cookies.append({ + 'name': cookie['name'], + 'path': '/' + }) + + return expired_cookies def _max_age_to_expires(cookies, now):