Skip to content

Commit 085d11a

Browse files
committed
usr.bin/fetch: Handle custom headers and method for HTTP
Modern APIs often use HTTP method and headers as part of their interface. Add functionality to fetch allowing HTTP method and headers to be specified. The option names align with those used by curl. Ability to provide request body data remains unimplemented. Signed-off-by: Ryan Moeller <[email protected]>
1 parent fc8060b commit 085d11a

File tree

2 files changed

+70
-17
lines changed

2 files changed

+70
-17
lines changed

usr.bin/fetch/fetch.1

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2929
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3030
.\"
31-
.Dd October 7, 2023
31+
.Dd September 27, 2025
3232
.Dt FETCH 1
3333
.Os
3434
.Sh NAME
@@ -43,6 +43,7 @@
4343
.Op Fl -ca-path= Ns Ar dir
4444
.Op Fl -cert= Ns Ar file
4545
.Op Fl -crl= Ns Ar file
46+
.Op Fl H Ar header
4647
.Op Fl i Ar file
4748
.Op Fl -key= Ns Ar file
4849
.Op Fl N Ar file
@@ -58,6 +59,7 @@
5859
.Op Fl T Ar seconds
5960
.Op Fl -user-agent= Ns Ar agent-string
6061
.Op Fl w Ar seconds
62+
.Op Fl X Ar method
6163
.Ar URL ...
6264
.Nm
6365
.Op Fl 146AadFlMmnPpqRrsUv
@@ -67,6 +69,7 @@
6769
.Op Fl -ca-path= Ns Ar dir
6870
.Op Fl -cert= Ns Ar file
6971
.Op Fl -crl= Ns Ar file
72+
.Op Fl H Ar header
7073
.Op Fl i Ar file
7174
.Op Fl -key= Ns Ar file
7275
.Op Fl N Ar file
@@ -82,6 +85,7 @@
8285
.Op Fl T Ar seconds
8386
.Op Fl -user-agent= Ns Ar agent-string
8487
.Op Fl w Ar seconds
88+
.Op Fl X Ar method
8589
.Fl h Ar host Fl f Ar file Oo Fl c Ar dir Oc
8690
.Sh DESCRIPTION
8791
The
@@ -164,6 +168,11 @@ The file to retrieve is named
164168
on the remote host.
165169
This option is deprecated and is provided for backward compatibility
166170
only.
171+
.It Fl H Ar header , Fl -header= Ns Ar header
172+
Send
173+
.Ar header
174+
as an additional header line with the HTTP request.
175+
This option may be specified multiple times.
167176
.It Fl h Ar host
168177
The file to retrieve is located on the host
169178
.Ar host .
@@ -306,6 +315,10 @@ Increase verbosity level.
306315
When the
307316
.Fl a
308317
flag is specified, wait this many seconds between successive retries.
318+
.It Fl X Ar method , Fl -request= Ns Ar method
319+
Use the given
320+
.Ar method
321+
for HTTP requests.
309322
.El
310323
.Pp
311324
If

usr.bin/fetch/fetch.c

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
*/
3131

3232
#include <sys/param.h>
33+
#include <sys/queue.h>
3334
#include <sys/socket.h>
3435
#include <sys/stat.h>
3536
#include <sys/time.h>
@@ -39,6 +40,7 @@
3940
#include <errno.h>
4041
#include <getopt.h>
4142
#include <signal.h>
43+
#include <stdbool.h>
4244
#include <stdint.h>
4345
#include <stdio.h>
4446
#include <stdlib.h>
@@ -85,7 +87,10 @@ static int v_tty; /* stdout is a tty */
8587
static int v_progress; /* whether to display progress */
8688
static pid_t pgrp; /* our process group */
8789
static long w_secs; /* -w: retry delay */
90+
static char *method; /* -X: HTTP method */
8891
static int family = PF_UNSPEC; /* -[46]: address family to use */
92+
static struct http_headers headers = TAILQ_HEAD_INITIALIZER(headers);
93+
/* -H: HTTP headers */
8994

9095
static int sigalrm; /* SIGALRM received */
9196
static int siginfo; /* SIGINFO received */
@@ -127,6 +132,7 @@ static struct option longopts[] =
127132
{ "direct", no_argument, NULL, 'd' },
128133
{ "force-restart", no_argument, NULL, 'F' },
129134
/* -f not mapped, since it's deprecated */
135+
{ "header", required_argument, NULL, 'H' },
130136
/* -h not mapped, since it's deprecated */
131137
{ "if-modified-since", required_argument, NULL, 'i' },
132138
{ "symlink", no_argument, NULL, 'l' },
@@ -146,6 +152,7 @@ static struct option longopts[] =
146152
{ "passive-portrange-default", no_argument, NULL, 'T' },
147153
{ "verbose", no_argument, NULL, 'v' },
148154
{ "retry-delay", required_argument, NULL, 'w' },
155+
{ "request", required_argument, NULL, 'X' },
149156

150157
/* options without a single character equivalent */
151158
{ "bind-address", required_argument, NULL, OPTION_BIND_ADDRESS },
@@ -418,11 +425,23 @@ query_auth(struct url *URL)
418425
return (0);
419426
}
420427

428+
/*
429+
* Decide how to invoke libfetch
430+
*/
431+
static FILE *
432+
invoke(struct url *url, struct url_stat *us, const char *flags, bool is_http)
433+
{
434+
if (is_http)
435+
return (fetchXReqHTTP(url, us, method ? method : "GET", flags,
436+
&headers, NULL, NULL));
437+
return (fetchXGet(url, us, flags));
438+
}
439+
421440
/*
422441
* Fetch a file
423442
*/
424443
static int
425-
fetch(char *URL, const char *path, int *is_http)
444+
fetch(char *URL, const char *path, bool *is_http)
426445
{
427446
struct url *url;
428447
struct url_stat us;
@@ -576,7 +595,7 @@ fetch(char *URL, const char *path, int *is_http)
576595
/* start the transfer */
577596
if (timeout)
578597
alarm(timeout);
579-
f = fetchXGet(url, &us, flags);
598+
f = invoke(url, &us, flags, *is_http);
580599
if (timeout)
581600
alarm(0);
582601
if (sigalrm || sigint)
@@ -708,7 +727,7 @@ fetch(char *URL, const char *path, int *is_http)
708727
* from scratch if we want the whole file
709728
*/
710729
url->offset = 0;
711-
if ((f = fetchXGet(url, &us, flags)) == NULL) {
730+
if ((f = invoke(url, &us, flags, *is_http)) == NULL) {
712731
warnx("%s: %s", URL, fetchLastErrString);
713732
goto failure;
714733
}
@@ -883,22 +902,40 @@ fetch(char *URL, const char *path, int *is_http)
883902
return (r);
884903
}
885904

905+
static void
906+
add_header(char *arg)
907+
{
908+
struct http_header *hdr;
909+
char *split;
910+
911+
if ((split = strchr(arg, ':')) == NULL)
912+
errx(1, "invalid header (%s)", arg);
913+
*split = '\0';
914+
if ((hdr = malloc(sizeof(*hdr))) == NULL)
915+
errx(1, "%s", strerror(ENOMEM));
916+
hdr->name = arg;
917+
hdr->value = split + 1;
918+
hdr->value += strspn(hdr->value, " \t"); /* skip leading whitespace */
919+
TAILQ_INSERT_TAIL(&headers, hdr, headers);
920+
}
921+
886922
static void
887923
usage(void)
888924
{
889-
fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
925+
fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
890926
"usage: fetch [-146AadFlMmnPpqRrsUv] [-B bytes] [--bind-address=host]",
891927
" [--ca-cert=file] [--ca-path=dir] [--cert=file] [--crl=file]",
892-
" [-i file] [--key=file] [-N file] [--no-passive] [--no-proxy=list]",
893-
" [--no-sslv3] [--no-tlsv1] [--no-verify-hostname] [--no-verify-peer]",
894-
" [-o file] [--referer=URL] [-S bytes] [-T seconds]",
895-
" [--user-agent=agent-string] [-w seconds] URL ...",
928+
" [-H header] [-i file] [--key=file] [-N file] [--no-passive]",
929+
" [--no-proxy=list] [--no-sslv3] [--no-tlsv1] [--no-verify-hostname]",
930+
" [--no-verify-peer] [-o file] [--referer=URL] [-S bytes] [-T seconds]",
931+
" [--user-agent=agent-string] [-w seconds] [-X method] URL ...",
896932
" fetch [-146AadFlMmnPpqRrsUv] [-B bytes] [--bind-address=host]",
897933
" [--ca-cert=file] [--ca-path=dir] [--cert=file] [--crl=file]",
898-
" [-i file] [--key=file] [-N file] [--no-passive] [--no-proxy=list]",
899-
" [--no-sslv3] [--no-tlsv1] [--no-verify-hostname] [--no-verify-peer]",
900-
" [-o file] [--referer=URL] [-S bytes] [-T seconds]",
901-
" [--user-agent=agent-string] [-w seconds] -h host -f file [-c dir]");
934+
" [-H header] [-i file] [--key=file] [-N file] [--no-passive]",
935+
" [--no-proxy=list] [--no-sslv3] [--no-tlsv1] [--no-verify-hostname]",
936+
" [--no-verify-peer] [-o file] [--referer=URL] [-S bytes] [-T seconds]",
937+
" [--user-agent=agent-string] [-w seconds] [-X method] -h host -f file",
938+
" [-c dir]");
902939
}
903940

904941

@@ -912,11 +949,12 @@ main(int argc, char *argv[])
912949
struct sigaction sa;
913950
const char *p, *s;
914951
char *end, *q;
915-
int c, e, is_http, r;
952+
int c, e, r;
953+
bool is_http;
916954

917955

918956
while ((c = getopt_long(argc, argv,
919-
"146AaB:bc:dFf:Hh:i:lMmN:nPpo:qRrS:sT:tUvw:",
957+
"146AaB:bc:dFf:H:h:i:lMmN:nPpo:qRrS:sT:tUvw:X:",
920958
longopts, NULL)) != -1)
921959
switch (c) {
922960
case '1':
@@ -956,8 +994,7 @@ main(int argc, char *argv[])
956994
f_filename = optarg;
957995
break;
958996
case 'H':
959-
warnx("the -H option is now implicit, "
960-
"use -U to disable");
997+
add_header(optarg);
961998
break;
962999
case 'h':
9631000
h_hostname = optarg;
@@ -1031,6 +1068,9 @@ main(int argc, char *argv[])
10311068
if (*optarg == '\0' || *end != '\0')
10321069
errx(1, "invalid delay (%s)", optarg);
10331070
break;
1071+
case 'X':
1072+
method = optarg;
1073+
break;
10341074
case OPTION_BIND_ADDRESS:
10351075
setenv("FETCH_BIND_ADDRESS", optarg, 1);
10361076
break;

0 commit comments

Comments
 (0)