Skip to content

Commit 3cc6de0

Browse files
committed
usr.bin/fetch: Add options for HTTP upload data
Add a -D/--data option to specify a file from which to read HTTP request body contents, with the special case that "-" will read from stdin. This option deviates from curl, taking a much more barebones approach for simplicity. Add a -C/--chunked option to specify that the HTTP request body contents are to use chunked transfer-encoding. Otherwise, it is automatically used by libfetch when the size of the file cannot be predetermined. Signed-off-by: Ryan Moeller <[email protected]>
1 parent c1c9b3e commit 3cc6de0

File tree

2 files changed

+67
-14
lines changed

2 files changed

+67
-14
lines changed

usr.bin/fetch/fetch.1

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,22 @@
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 September 27, 2025
31+
.Dd September 30, 2025
3232
.Dt FETCH 1
3333
.Os
3434
.Sh NAME
3535
.Nm fetch
3636
.Nd retrieve a file by Uniform Resource Locator
3737
.Sh SYNOPSIS
3838
.Nm
39-
.Op Fl 146AadFlMmnPpqRrsUv
39+
.Op Fl 146AaCdFlMmnPpqRrsUv
4040
.Op Fl B Ar bytes
4141
.Op Fl -bind-address= Ns Ar host
4242
.Op Fl -ca-cert= Ns Ar file
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 D Ar file
4647
.Op Fl H Ar header
4748
.Op Fl i Ar file
4849
.Op Fl -key= Ns Ar file
@@ -62,13 +63,14 @@
6263
.Op Fl X Ar method
6364
.Ar URL ...
6465
.Nm
65-
.Op Fl 146AadFlMmnPpqRrsUv
66+
.Op Fl 146AaCdFlMmnPpqRrsUv
6667
.Op Fl B Ar bytes
6768
.Op Fl -bind-address= Ns Ar host
6869
.Op Fl -ca-cert= Ns Ar file
6970
.Op Fl -ca-path= Ns Ar dir
7071
.Op Fl -cert= Ns Ar file
7172
.Op Fl -crl= Ns Ar file
73+
.Op Fl D Ar file
7274
.Op Fl H Ar header
7375
.Op Fl i Ar file
7476
.Op Fl -key= Ns Ar file
@@ -126,6 +128,10 @@ flag).
126128
.It Fl -bind-address= Ns Ar host
127129
Specifies a hostname or IP address to which sockets used for outgoing
128130
connections will be bound.
131+
.It Fl C , -chunked
132+
Force chunked transfer of HTTP request body contents.
133+
By default, the transfer is automatically chunk-encoded when the body size
134+
cannot be predetermined.
129135
.It Fl c Ar dir
130136
The file to retrieve is in directory
131137
.Ar dir
@@ -153,6 +159,16 @@ Points to certificate revocation list
153159
.Ar file ,
154160
which has to be in PEM format and may contain peer certificates that have
155161
been revoked.
162+
.It Fl D Ar file , Fl -data= Ns Ar file
163+
Send the contents of
164+
.Ar file
165+
as the body of HTTP requests.
166+
If
167+
.Ar file
168+
is a single dash
169+
.Pq Sq Fl ,
170+
.Nm
171+
reads the body from the standard input.
156172
.It Fl d , -direct
157173
Use a direct connection even if a proxy is configured.
158174
.It Fl F , -force-restart

usr.bin/fetch/fetch.c

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@
5757
static int A_flag; /* -A: do not follow 302 redirects */
5858
static int a_flag; /* -a: auto retry */
5959
static off_t B_size; /* -B: buffer size */
60-
static int b_flag; /*! -b: workaround TCP bug */
61-
static char *c_dirname; /* -c: remote directory */
60+
static int C_flag; /* -C: force HTTP request chunk-encoding */
61+
static char *c_dirname; /* -c: remote directory */
62+
static char *D_filename; /* -D: HTTP request body content file */
6263
static int d_flag; /* -d: direct connection */
6364
static int F_flag; /* -F: restart without checking mtime */
6465
static char *f_filename; /* -f: file to fetch */
@@ -128,7 +129,9 @@ static struct option longopts[] =
128129
{ "no-redirect", no_argument, NULL, 'A' },
129130
{ "retry", no_argument, NULL, 'a' },
130131
{ "buffer-size", required_argument, NULL, 'B' },
132+
{ "chunked", no_argument, NULL, 'C' },
131133
/* -c not mapped, since it's deprecated */
134+
{ "data", required_argument, NULL, 'D' },
132135
{ "direct", no_argument, NULL, 'd' },
133136
{ "force-restart", no_argument, NULL, 'F' },
134137
/* -f not mapped, since it's deprecated */
@@ -431,9 +434,36 @@ query_auth(struct url *URL)
431434
static FILE *
432435
invoke(struct url *url, struct url_stat *us, const char *flags, bool is_http)
433436
{
434-
if (is_http)
435-
return (fetchXReqHTTP(url, us, method ? method : "GET", flags,
436-
&headers, NULL, NULL));
437+
static bool rewind_stdin = false;
438+
FILE *body, *f;
439+
440+
if (is_http) {
441+
if (D_filename == NULL)
442+
body = NULL;
443+
else if (strcmp(D_filename, "-") == 0) {
444+
if (rewind_stdin) {
445+
if (fseek(stdin, 0, SEEK_SET) == 0)
446+
clearerr(stdin);
447+
else {
448+
snprintf(fetchLastErrString,
449+
MAXERRSTRING, "cannot rewind: "
450+
"stdin is not seekable");
451+
return (NULL);
452+
}
453+
}
454+
rewind_stdin = true;
455+
body = stdin;
456+
} else if ((body = fopen(D_filename, "r")) == NULL) {
457+
snprintf(fetchLastErrString, MAXERRSTRING, "%s: %s",
458+
D_filename, strerror(errno));
459+
return (NULL);
460+
}
461+
f = fetchXReqHTTP(url, us, method ? method : "GET", flags,
462+
&headers, NULL, body);
463+
if (body != NULL && body != stdin)
464+
fclose(body);
465+
return (f);
466+
}
437467
return (fetchXGet(url, us, flags));
438468
}
439469

@@ -522,6 +552,8 @@ fetch(char *URL, const char *path, bool *is_http)
522552
strcat(flags, "d");
523553
if (A_flag)
524554
strcat(flags, "A");
555+
if (C_flag)
556+
strcat(flags, "C");
525557
timeout = T_secs ? T_secs : http_timeout;
526558
if (i_flag) {
527559
if (stat(i_filename, &sb)) {
@@ -923,15 +955,15 @@ static void
923955
usage(void)
924956
{
925957
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",
926-
"usage: fetch [-146AadFlMmnPpqRrsUv] [-B bytes] [--bind-address=host]",
958+
"usage: fetch [-146AaCdFlMmnPpqRrsUv] [-B bytes] [--bind-address=host]",
927959
" [--ca-cert=file] [--ca-path=dir] [--cert=file] [--crl=file]",
928-
" [-H header] [-i file] [--key=file] [-N file] [--no-passive]",
960+
" [-D file] [-H header] [-i file] [--key=file] [-N file] [--no-passive]",
929961
" [--no-proxy=list] [--no-sslv3] [--no-tlsv1] [--no-verify-hostname]",
930962
" [--no-verify-peer] [-o file] [--referer=URL] [-S bytes] [-T seconds]",
931963
" [--user-agent=agent-string] [-w seconds] [-X method] URL ...",
932-
" fetch [-146AadFlMmnPpqRrsUv] [-B bytes] [--bind-address=host]",
964+
" fetch [-146AaCdFlMmnPpqRrsUv] [-B bytes] [--bind-address=host]",
933965
" [--ca-cert=file] [--ca-path=dir] [--cert=file] [--crl=file]",
934-
" [-H header] [-i file] [--key=file] [-N file] [--no-passive]",
966+
" [-D file] [-H header] [-i file] [--key=file] [-N file] [--no-passive]",
935967
" [--no-proxy=list] [--no-sslv3] [--no-tlsv1] [--no-verify-hostname]",
936968
" [--no-verify-peer] [-o file] [--referer=URL] [-S bytes] [-T seconds]",
937969
" [--user-agent=agent-string] [-w seconds] [-X method] -h host -f file",
@@ -954,7 +986,7 @@ main(int argc, char *argv[])
954986

955987

956988
while ((c = getopt_long(argc, argv,
957-
"146AaB:bc:dFf:H:h:i:lMmN:nPpo:qRrS:sT:tUvw:X:",
989+
"146AaB:bCc:D:dFf:H:h:i:lMmN:nPpo:qRrS:sT:tUvw:X:",
958990
longopts, NULL)) != -1)
959991
switch (c) {
960992
case '1':
@@ -979,11 +1011,16 @@ main(int argc, char *argv[])
9791011
break;
9801012
case 'b':
9811013
warnx("warning: the -b option is deprecated");
982-
b_flag = 1;
1014+
break;
1015+
case 'C':
1016+
C_flag = 1;
9831017
break;
9841018
case 'c':
9851019
c_dirname = optarg;
9861020
break;
1021+
case 'D':
1022+
D_filename = optarg;
1023+
break;
9871024
case 'd':
9881025
d_flag = 1;
9891026
break;

0 commit comments

Comments
 (0)