diff mbox series

[1/4] uclient-fetch: --header option to pass additional raw HTTP headers

Message ID 20220509215923.36255-2-stokito@gmail.com
State Changes Requested
Delegated to: Petr Štetiar
Headers show
Series Make wget useful for API calls i.e. goodbye curl. | expand

Commit Message

Sergey Ponomarev May 9, 2022, 9:59 p.m. UTC
You can add a custom HTTP header(s) to request:

    wget --header='Authorization: Bearer TOKEN' \
        --header='If-Modified-Since: Wed, 9 May 2021 12:16:00 GMT' \
        https://example.com/

Some headers like Authorization or User-Agent may be already set by --password or --user-agent.
We may override them but it's a protection from user itself.
To keep code concise the logic omitted.

Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
---
 tests/cram/test-san_uclient-fetch.t |  1 +
 tests/cram/test_uclient-fetch.t     |  1 +
 uclient-fetch.c                     | 16 +++++++++++++++
 uclient-http.c                      | 31 +++++++++++++++++++++++++++++
 uclient.h                           |  1 +
 5 files changed, 50 insertions(+)

Comments

Jo-Philipp Wich May 10, 2022, 9:11 a.m. UTC | #1
Hi Sergey,

a minor nitpick inline below.

On 5/9/22 11:59 PM, Sergey Ponomarev wrote:
> You can add a custom HTTP header(s) to request:
> 
>     wget --header='Authorization: Bearer TOKEN' \
>         --header='If-Modified-Since: Wed, 9 May 2021 12:16:00 GMT' \
>         https://example.com/
> 
> Some headers like Authorization or User-Agent may be already set by --password or --user-agent.
> We may override them but it's a protection from user itself.
> To keep code concise the logic omitted.
> 
> Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
> ---
> [...]
> +			case L_HEADER:
> +				if (!raw_headers) {
> +					/* Max possible count of headers is the count of args (argc) - 2
> +					 Since the first arg is program and last is a URL.
> +					 But user may forget the URL and raw_headers is null terminated so allocate argc */
> +					raw_headers = calloc(argc, sizeof(char *));

Please handle a possible calloc() error here. Maybe consider introducing an
"xalloc()" or similar helper which wraps calloc() and invokes abort() on NULL
return.

> +				}
> +				raw_headers[raw_headers_count] = optarg;
> +				raw_headers_count++;
> +				break;
>  			case L_POST_DATA:
>  				post_data = optarg;
>  				break;
> [...]
Andre Heider June 22, 2022, 5:33 a.m. UTC | #2
Hi Sergey,

any update on this series? I'd be interested in the --header option.

Thanks,
Andre

On 10/05/2022 11:11, Jo-Philipp Wich wrote:
> Hi Sergey,
> 
> a minor nitpick inline below.
> 
> On 5/9/22 11:59 PM, Sergey Ponomarev wrote:
>> You can add a custom HTTP header(s) to request:
>>
>>      wget --header='Authorization: Bearer TOKEN' \
>>          --header='If-Modified-Since: Wed, 9 May 2021 12:16:00 GMT' \
>>          https://example.com/
>>
>> Some headers like Authorization or User-Agent may be already set by --password or --user-agent.
>> We may override them but it's a protection from user itself.
>> To keep code concise the logic omitted.
>>
>> Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
>> ---
>> [...]
>> +			case L_HEADER:
>> +				if (!raw_headers) {
>> +					/* Max possible count of headers is the count of args (argc) - 2
>> +					 Since the first arg is program and last is a URL.
>> +					 But user may forget the URL and raw_headers is null terminated so allocate argc */
>> +					raw_headers = calloc(argc, sizeof(char *));
> 
> Please handle a possible calloc() error here. Maybe consider introducing an
> "xalloc()" or similar helper which wraps calloc() and invokes abort() on NULL
> return.
> 
>> +				}
>> +				raw_headers[raw_headers_count] = optarg;
>> +				raw_headers_count++;
>> +				break;
>>   			case L_POST_DATA:
>>   				post_data = optarg;
>>   				break;
>> [...]
> 
> 
> _______________________________________________
> openwrt-devel mailing list
> openwrt-devel@lists.openwrt.org
> https://lists.openwrt.org/mailman/listinfo/openwrt-devel
Sergey Ponomarev June 22, 2022, 10:26 a.m. UTC | #3
Hi Andre and Jo-Philipp,

Please add the calloc() error handler yourself because I'm not
experienced in C and I haven't time. But for you this may be just 5
minutes.
The feature is very important from my point of view. The first version
I sent a year(s?) ago but still no progress.
If there are any problems please let me know

On Wed, 22 Jun 2022 at 08:33, Andre Heider <a.heider@gmail.com> wrote:
>
> Hi Sergey,
>
> any update on this series? I'd be interested in the --header option.
>
> Thanks,
> Andre
>
> On 10/05/2022 11:11, Jo-Philipp Wich wrote:
> > Hi Sergey,
> >
> > a minor nitpick inline below.
> >
> > On 5/9/22 11:59 PM, Sergey Ponomarev wrote:
> >> You can add a custom HTTP header(s) to request:
> >>
> >>      wget --header='Authorization: Bearer TOKEN' \
> >>          --header='If-Modified-Since: Wed, 9 May 2021 12:16:00 GMT' \
> >>          https://example.com/
> >>
> >> Some headers like Authorization or User-Agent may be already set by --password or --user-agent.
> >> We may override them but it's a protection from user itself.
> >> To keep code concise the logic omitted.
> >>
> >> Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
> >> ---
> >> [...]
> >> +                    case L_HEADER:
> >> +                            if (!raw_headers) {
> >> +                                    /* Max possible count of headers is the count of args (argc) - 2
> >> +                                     Since the first arg is program and last is a URL.
> >> +                                     But user may forget the URL and raw_headers is null terminated so allocate argc */
> >> +                                    raw_headers = calloc(argc, sizeof(char *));
> >
> > Please handle a possible calloc() error here. Maybe consider introducing an
> > "xalloc()" or similar helper which wraps calloc() and invokes abort() on NULL
> > return.
> >
> >> +                            }
> >> +                            raw_headers[raw_headers_count] = optarg;
> >> +                            raw_headers_count++;
> >> +                            break;
> >>                      case L_POST_DATA:
> >>                              post_data = optarg;
> >>                              break;
> >> [...]
> >
> >
> > _______________________________________________
> > openwrt-devel mailing list
> > openwrt-devel@lists.openwrt.org
> > https://lists.openwrt.org/mailman/listinfo/openwrt-devel
>
Andre Heider June 23, 2022, 6:20 a.m. UTC | #4
On 22/06/2022 12:26, Sergey Ponomarev wrote:
> Hi Andre and Jo-Philipp,
> 
> Please add the calloc() error handler yourself because I'm not
> experienced in C and I haven't time. But for you this may be just 5
> minutes.

Sure, I had a look, and while adding that wrapper isn't a problem, the 
patchset itself has an issue:

--header 'Content-type: foo/bar' doesn't work as intended because 
'Content-type: application/x-www-form-urlencoded' is still added too. So 
both headers are sent, which breaks my use case.

I may be looking at that, but I'm time constrained myself too at the moment.

Cheers,
Andre

> The feature is very important from my point of view. The first version
> I sent a year(s?) ago but still no progress.
> If there are any problems please let me know
> 
> On Wed, 22 Jun 2022 at 08:33, Andre Heider <a.heider@gmail.com> wrote:
>>
>> Hi Sergey,
>>
>> any update on this series? I'd be interested in the --header option.
>>
>> Thanks,
>> Andre
>>
>> On 10/05/2022 11:11, Jo-Philipp Wich wrote:
>>> Hi Sergey,
>>>
>>> a minor nitpick inline below.
>>>
>>> On 5/9/22 11:59 PM, Sergey Ponomarev wrote:
>>>> You can add a custom HTTP header(s) to request:
>>>>
>>>>       wget --header='Authorization: Bearer TOKEN' \
>>>>           --header='If-Modified-Since: Wed, 9 May 2021 12:16:00 GMT' \
>>>>           https://example.com/
>>>>
>>>> Some headers like Authorization or User-Agent may be already set by --password or --user-agent.
>>>> We may override them but it's a protection from user itself.
>>>> To keep code concise the logic omitted.
>>>>
>>>> Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
>>>> ---
>>>> [...]
>>>> +                    case L_HEADER:
>>>> +                            if (!raw_headers) {
>>>> +                                    /* Max possible count of headers is the count of args (argc) - 2
>>>> +                                     Since the first arg is program and last is a URL.
>>>> +                                     But user may forget the URL and raw_headers is null terminated so allocate argc */
>>>> +                                    raw_headers = calloc(argc, sizeof(char *));
>>>
>>> Please handle a possible calloc() error here. Maybe consider introducing an
>>> "xalloc()" or similar helper which wraps calloc() and invokes abort() on NULL
>>> return.
>>>
>>>> +                            }
>>>> +                            raw_headers[raw_headers_count] = optarg;
>>>> +                            raw_headers_count++;
>>>> +                            break;
>>>>                       case L_POST_DATA:
>>>>                               post_data = optarg;
>>>>                               break;
>>>> [...]
>>>
>>>
>>> _______________________________________________
>>> openwrt-devel mailing list
>>> openwrt-devel@lists.openwrt.org
>>> https://lists.openwrt.org/mailman/listinfo/openwrt-devel
>>
> 
>
Sergey Ponomarev June 24, 2022, 10:40 a.m. UTC | #5
Andre,

--header 'Content-type: foo/bar' doesn't work as intended because
'Content-type: application/x-www-form-urlencoded' is still added too. So
both headers are sent, which breaks my use case.

Yes, you are right and I described the behaviour in a comment. The
headers overriding doesn't work in this version: it makes code
slightly complicated and slower.
But you can achieve your goal by doing --method=POST
--post-data="OLOLO" --header 'Content-type: foo/bar'

On Thu, 23 Jun 2022 at 09:20, Andre Heider <a.heider@gmail.com> wrote:
>
> On 22/06/2022 12:26, Sergey Ponomarev wrote:
> > Hi Andre and Jo-Philipp,
> >
> > Please add the calloc() error handler yourself because I'm not
> > experienced in C and I haven't time. But for you this may be just 5
> > minutes.
>
> Sure, I had a look, and while adding that wrapper isn't a problem, the
> patchset itself has an issue:
>
> --header 'Content-type: foo/bar' doesn't work as intended because
> 'Content-type: application/x-www-form-urlencoded' is still added too. So
> both headers are sent, which breaks my use case.
>
> I may be looking at that, but I'm time constrained myself too at the moment.
>
> Cheers,
> Andre
>
> > The feature is very important from my point of view. The first version
> > I sent a year(s?) ago but still no progress.
> > If there are any problems please let me know
> >
> > On Wed, 22 Jun 2022 at 08:33, Andre Heider <a.heider@gmail.com> wrote:
> >>
> >> Hi Sergey,
> >>
> >> any update on this series? I'd be interested in the --header option.
> >>
> >> Thanks,
> >> Andre
> >>
> >> On 10/05/2022 11:11, Jo-Philipp Wich wrote:
> >>> Hi Sergey,
> >>>
> >>> a minor nitpick inline below.
> >>>
> >>> On 5/9/22 11:59 PM, Sergey Ponomarev wrote:
> >>>> You can add a custom HTTP header(s) to request:
> >>>>
> >>>>       wget --header='Authorization: Bearer TOKEN' \
> >>>>           --header='If-Modified-Since: Wed, 9 May 2021 12:16:00 GMT' \
> >>>>           https://example.com/
> >>>>
> >>>> Some headers like Authorization or User-Agent may be already set by --password or --user-agent.
> >>>> We may override them but it's a protection from user itself.
> >>>> To keep code concise the logic omitted.
> >>>>
> >>>> Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
> >>>> ---
> >>>> [...]
> >>>> +                    case L_HEADER:
> >>>> +                            if (!raw_headers) {
> >>>> +                                    /* Max possible count of headers is the count of args (argc) - 2
> >>>> +                                     Since the first arg is program and last is a URL.
> >>>> +                                     But user may forget the URL and raw_headers is null terminated so allocate argc */
> >>>> +                                    raw_headers = calloc(argc, sizeof(char *));
> >>>
> >>> Please handle a possible calloc() error here. Maybe consider introducing an
> >>> "xalloc()" or similar helper which wraps calloc() and invokes abort() on NULL
> >>> return.
> >>>
> >>>> +                            }
> >>>> +                            raw_headers[raw_headers_count] = optarg;
> >>>> +                            raw_headers_count++;
> >>>> +                            break;
> >>>>                       case L_POST_DATA:
> >>>>                               post_data = optarg;
> >>>>                               break;
> >>>> [...]
> >>>
> >>>
> >>> _______________________________________________
> >>> openwrt-devel mailing list
> >>> openwrt-devel@lists.openwrt.org
> >>> https://lists.openwrt.org/mailman/listinfo/openwrt-devel
> >>
> >
> >
>
diff mbox series

Patch

diff --git a/tests/cram/test-san_uclient-fetch.t b/tests/cram/test-san_uclient-fetch.t
index 435659b..047749a 100644
--- a/tests/cram/test-san_uclient-fetch.t
+++ b/tests/cram/test-san_uclient-fetch.t
@@ -15,6 +15,7 @@  check uclient-fetch usage:
   \t--user=<user>\t\t\tHTTP authentication username (esc)
   \t--password=<password>\t\tHTTP authentication password (esc)
   \t--user-agent | -U <str>\t\tSet HTTP user agent (esc)
+  \t--header='Header: value'\t\tAdd HTTP header. Multiple allowed (esc)
   \t--post-data=STRING\t\tuse the POST method; send STRING as the data (esc)
   \t--post-file=FILE\t\tuse the POST method; send FILE as the data (esc)
   \t--spider | -s\t\t\tSpider mode - only check file existence (esc)
diff --git a/tests/cram/test_uclient-fetch.t b/tests/cram/test_uclient-fetch.t
index e22aa40..cb70271 100644
--- a/tests/cram/test_uclient-fetch.t
+++ b/tests/cram/test_uclient-fetch.t
@@ -15,6 +15,7 @@  check uclient-fetch usage:
   \t--user=<user>\t\t\tHTTP authentication username (esc)
   \t--password=<password>\t\tHTTP authentication password (esc)
   \t--user-agent | -U <str>\t\tSet HTTP user agent (esc)
+  \t--header='Header: value'\t\tAdd HTTP header. Multiple allowed (esc)
   \t--post-data=STRING\t\tuse the POST method; send STRING as the data (esc)
   \t--post-file=FILE\t\tuse the POST method; send FILE as the data (esc)
   \t--spider | -s\t\t\tSpider mode - only check file existence (esc)
diff --git a/uclient-fetch.c b/uclient-fetch.c
index 282092e..4efc917 100644
--- a/uclient-fetch.c
+++ b/uclient-fetch.c
@@ -44,6 +44,7 @@ 
 static const char *user_agent = "uclient-fetch";
 static const char *post_data;
 static const char *post_file;
+static const char **raw_headers = NULL;
 static struct ustream_ssl_ctx *ssl_ctx;
 static const struct ustream_ssl_ops *ssl_ops;
 static int quiet = false;
@@ -342,6 +343,7 @@  static int init_request(struct uclient *cl)
 
 	uclient_http_reset_headers(cl);
 	uclient_http_set_header(cl, "User-Agent", user_agent);
+	uclient_http_set_raw_headers(cl, raw_headers);
 	if (cur_resume)
 		check_resume_offset(cl);
 
@@ -481,6 +483,7 @@  static int usage(const char *progname)
 		"	--continue | -c			Continue a partially-downloaded file\n"
 		"	--user=<user>			HTTP authentication username\n"
 		"	--password=<password>		HTTP authentication password\n"
+		"	--header='Header: value'		Add HTTP header. Multiple allowed\n"
 		"	--user-agent | -U <str>		Set HTTP user agent\n"
 		"	--post-data=STRING		use the POST method; send STRING as the data\n"
 		"	--post-file=FILE		use the POST method; send FILE as the data\n"
@@ -542,6 +545,7 @@  enum {
 	L_USER,
 	L_PASSWORD,
 	L_USER_AGENT,
+	L_HEADER,
 	L_POST_DATA,
 	L_POST_FILE,
 	L_SPIDER,
@@ -559,6 +563,7 @@  static const struct option longopts[] = {
 	[L_USER] = { "user", required_argument, NULL, 0 },
 	[L_PASSWORD] = { "password", required_argument, NULL, 0 },
 	[L_USER_AGENT] = { "user-agent", required_argument, NULL, 0 },
+	[L_HEADER] = { "header", required_argument, NULL, 0 },
 	[L_POST_DATA] = { "post-data", required_argument, NULL, 0 },
 	[L_POST_FILE] = { "post-file", required_argument, NULL, 0 },
 	[L_SPIDER] = { "spider", no_argument, NULL, 0 },
@@ -578,6 +583,7 @@  int main(int argc, char **argv)
 	const char *proxy_url;
 	char *username = NULL;
 	char *password = NULL;
+	int raw_headers_count = 0;
 	struct uclient *cl;
 	int longopt_idx = 0;
 	bool has_cert = false;
@@ -626,6 +632,16 @@  int main(int argc, char **argv)
 			case L_USER_AGENT:
 				user_agent = optarg;
 				break;
+			case L_HEADER:
+				if (!raw_headers) {
+					/* Max possible count of headers is the count of args (argc) - 2
+					 Since the first arg is program and last is a URL.
+					 But user may forget the URL and raw_headers is null terminated so allocate argc */
+					raw_headers = calloc(argc, sizeof(char *));
+				}
+				raw_headers[raw_headers_count] = optarg;
+				raw_headers_count++;
+				break;
 			case L_POST_DATA:
 				post_data = optarg;
 				break;
diff --git a/uclient-http.c b/uclient-http.c
index c2bba6b..3d59de8 100644
--- a/uclient-http.c
+++ b/uclient-http.c
@@ -96,6 +96,7 @@  struct uclient_http {
 
 	uint32_t nc;
 
+	const char **raw_headers;
 	struct blob_buf headers;
 	struct blob_buf meta;
 };
@@ -587,6 +588,20 @@  uclient_http_add_auth_header(struct uclient_http *uh)
 	return 0;
 }
 
+static void
+uclient_http_send_raw_headers(const struct uclient_http *uh) {
+	if (!uh->raw_headers) {
+		return;
+	}
+	const char **raw_headers = uh->raw_headers;
+	const char *raw_header = *raw_headers;
+	while (raw_header != NULL) {
+		ustream_printf(uh->us, "%s\r\n", raw_header);
+		raw_headers++;
+		raw_header = *raw_headers;
+	}
+}
+
 static int
 uclient_http_send_headers(struct uclient_http *uh)
 {
@@ -625,6 +640,7 @@  uclient_http_send_headers(struct uclient_http *uh)
 	if (err)
 		return err;
 
+	uclient_http_send_raw_headers(uh);
 	ustream_printf(uh->us, "\r\n");
 
 	uh->state = HTTP_STATE_HEADERS_SENT;
@@ -1026,6 +1042,21 @@  uclient_http_set_header(struct uclient *cl, const char *name, const char *value)
 	return 0;
 }
 
+int
+uclient_http_set_raw_headers(struct uclient *cl, const char **raw_headers)
+{
+	struct uclient_http *uh = container_of(cl, struct uclient_http, uc);
+
+	if (cl->backend != &uclient_backend_http)
+		return -1;
+
+	if (uh->state > HTTP_STATE_INIT)
+		return -1;
+
+	uh->raw_headers = raw_headers;
+	return 0;
+}
+
 static int
 uclient_http_send_data(struct uclient *cl, const char *buf, unsigned int len)
 {
diff --git a/uclient.h b/uclient.h
index 4f37364..f1977bc 100644
--- a/uclient.h
+++ b/uclient.h
@@ -121,6 +121,7 @@  extern const struct uclient_backend uclient_backend_http;
 
 int uclient_http_reset_headers(struct uclient *cl);
 int uclient_http_set_header(struct uclient *cl, const char *name, const char *value);
+int uclient_http_set_raw_headers(struct uclient *cl, const char **raw_headers);
 int uclient_http_set_request_type(struct uclient *cl, const char *type);
 int uclient_http_redirect(struct uclient *cl);