def _guess_method(self): """Set `args.method` if not specified to either POST or GET based on whether the request has data or not. """ if self.args.method is None: # Invoked as `http URL'. assert not self.args.request_items if self.has_stdin_data: self.args.method = HTTP_POST else: self.args.method = HTTP_GET # FIXME: False positive, e.g., "localhost" matches but is a valid URL. elif not re.match('^[a-zA-Z]+$', self.args.method): # Invoked as `http URL item+'. The URL is now in `args.method` # and the first ITEM is now incorrectly in `args.url`. try: # Parse the URL as an ITEM and store it as the first ITEM arg. self.args.request_items.insert(0, KeyValueArgType( *SEPARATOR_GROUP_ALL_ITEMS).__call__(self.args.url)) except argparse.ArgumentTypeError as e: if self.args.traceback: raise self.error(e.args[0]) else: # Set the URL correctly self.args.url = self.args.method # Infer the method has_data = ( self.has_stdin_data or any( item.sep in SEPARATOR_GROUP_DATA_ITEMS for item in self.args.request_items) ) self.args.method = HTTP_POST if has_data else HTTP_GET
metavar='URL', help=""" The scheme defaults to 'http://' if the URL does not include one. (You can override this with: --default-scheme=https) You can also use a shorthand for localhost $ http :3000 # => http://localhost:3000 $ http :/foo # => http://localhost/foo """) positional.add_argument(dest='request_items', metavar='REQUEST_ITEM', nargs=ZERO_OR_MORE, default=None, type=KeyValueArgType(*SEPARATOR_GROUP_ALL_ITEMS), help=r""" Optional key-value pairs to be included in the request. The separator used determines the type: ':' HTTP headers: Referer:http://httpie.org Cookie:foo=bar User-Agent:bacon/1.0 '==' URL parameters to be appended to the request URI: search==httpie '=' Data fields to be serialized into a JSON object (with --json, -j) or form data (with --form, -f):
class TestItemParsing: key_value_arg = KeyValueArgType(*constants.SEPARATOR_GROUP_ALL_ITEMS) def test_invalid_items(self): items = ['no-separator'] for item in items: pytest.raises(argparse.ArgumentTypeError, self.key_value_arg, item) def test_escape_separator(self): items = RequestItems.from_args([ # headers self.key_value_arg(r'foo\:bar:baz'), self.key_value_arg(r'jack\@jill:hill'), # data self.key_value_arg(r'baz\=bar=foo'), # files self.key_value_arg(fr'bar\@baz@{FILE_PATH_ARG}'), ]) # `RequestHeadersDict` => `dict` headers = dict(items.headers) assert headers == { 'foo:bar': 'baz', 'jack@jill': 'hill', } assert items.data == {'baz=bar': 'foo'} assert 'bar@baz' in items.files @pytest.mark.parametrize('string, key, sep, value', [ ('path=c:\\windows', 'path', '=', 'c:\\windows'), ('path=c:\\windows\\', 'path', '=', 'c:\\windows\\'), ('path\\==c:\\windows', 'path=', '=', 'c:\\windows'), ]) def test_backslash_before_non_special_character_does_not_escape( self, string, key, sep, value): expected = KeyValueArg(orig=string, key=key, sep=sep, value=value) actual = self.key_value_arg(string) assert actual == expected def test_escape_longsep(self): items = RequestItems.from_args([ self.key_value_arg(r'bob\:==foo'), ]) assert items.params == {'bob:': 'foo'} def test_valid_items(self): items = RequestItems.from_args([ self.key_value_arg('string=value'), self.key_value_arg('Header:value'), self.key_value_arg('Unset-Header:'), self.key_value_arg('Empty-Header;'), self.key_value_arg('list:=["a", 1, {}, false]'), self.key_value_arg('obj:={"a": "b"}'), self.key_value_arg('ed='), self.key_value_arg('bool:=true'), self.key_value_arg('file@' + FILE_PATH_ARG), self.key_value_arg('query==value'), self.key_value_arg('string-embed=@' + FILE_PATH_ARG), self.key_value_arg('raw-json-embed:=@' + JSON_FILE_PATH_ARG), ]) # Parsed headers # `RequestHeadersDict` => `dict` headers = dict(items.headers) assert headers == { 'Header': 'value', 'Unset-Header': None, 'Empty-Header': '' } # Parsed data raw_json_embed = items.data.pop('raw-json-embed') assert raw_json_embed == load_json_preserve_order_and_dupe_keys( JSON_FILE_CONTENT) items.data['string-embed'] = items.data['string-embed'].strip() assert dict(items.data) == { 'ed': '', 'string': 'value', 'bool': True, 'list': ['a', 1, {}, False], 'obj': load_json_preserve_order_and_dupe_keys('{"a": "b"}'), 'string-embed': FILE_CONTENT, } # Parsed query string parameters assert items.params == {'query': 'value'} # Parsed file fields assert 'file' in items.files assert (items.files['file'][1].read().strip().decode() == FILE_CONTENT) def test_multiple_file_fields_with_same_field_name(self): items = RequestItems.from_args([ self.key_value_arg('file_field@' + FILE_PATH_ARG), self.key_value_arg('file_field@' + FILE_PATH_ARG), ]) assert len(items.files['file_field']) == 2 def test_multiple_text_fields_with_same_field_name(self): items = RequestItems.from_args( request_item_args=[ self.key_value_arg('text_field=a'), self.key_value_arg('text_field=b') ], as_form=True, ) assert items.data['text_field'] == ['a', 'b'] assert list(items.data.items()) == [ ('text_field', 'a'), ('text_field', 'b'), ]