1
0
Fork 0

Compare commits

...

10 Commits

Author SHA1 Message Date
Xavier Del Campo dc8b14d990
Add PUT server example 2023-11-20 16:34:24 +01:00
Xavier Del Campo 1750bbd7ec
http.c. Limit multipart/form-data to POST 2023-11-20 16:34:24 +01:00
Xavier Del Campo 1755ee663c
http: Add support for PUT
Notes:

- Since curl would use the "Expect: 100-continue" header field for PUT
operations, this was a good operation to fix the existing issues in its
implementation.

Breaking changes:

- expect_continue is no longer exclusive to struct http_post. Now, it
has been moved into struct http_payload and it is up to users to check
it.
2023-11-20 16:34:24 +01:00
xavi 8f1ad3124e Merge pull request 'Fix double-free on failed `server_client_close`' (#2) from midokura-xavi/libweb:fix-double-free into master
Reviewed-on: xavi/libweb#2
2023-11-20 16:27:28 +01:00
Xavier Del Campo 59a67a7541
Fix double-free on failed server_client_close
Even if server_client_close fails, it is needed for client_free to
remove the dangling reference from h->clients.
2023-11-20 16:25:58 +01:00
xavi 44676b84fd Merge pull request 'Allow `listen_port` to return selected port number' (#1) from midokura-xavi/libweb:listen-port into master
Reviewed-on: xavi/libweb#1
2023-11-20 16:24:15 +01:00
Xavier Del Campo 2561ec32fe
doc: Update handler_{loop,listen} 2023-11-20 16:08:08 +01:00
Xavier Del Campo c9edbbc4b0
examples/hello: Update according to handler_loop 2023-11-20 16:06:20 +01:00
Xavier Del Campo 98f5f52461
Split handler_loop from handler_listen
Some applications might set up a struct handler object to listen on any
port i.e., 0, but still need a way to determine which port number was
eventually selected by the implementation.

Therefore, handler_listen has been reduced to the server initialization
bit, whereas the main loop has been split into its own function, namely
handler_loop.

Because of these changes, it no longer made sense for libweb to write
the selected port to standard output, as this is something now
applications can do on their own.
2023-11-20 16:06:19 +01:00
Xavier Del Campo 8280cc40b9
README.md: Use generic term for copyright holders
Similarly to other projects within the free software community, a
generic term is used so as to avoid listing every single contributor to
the project.
2023-11-20 16:06:15 +01:00
22 changed files with 531 additions and 99 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@
*.so.*
examples/hello/hello
examples/html/html
examples/put/put

View File

@ -178,7 +178,7 @@ essential for `slcl`.
```
libweb, a simple and lightweight web framework.
Copyright (C) 2023 Xavier Del Campo Romero
Copyright (C) 2023 libweb contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by

View File

@ -9,6 +9,7 @@ OBJECTS = \
$(DESTDIR)$(man3dir)/handler_alloc.3 \
$(DESTDIR)$(man3dir)/handler_free.3 \
$(DESTDIR)$(man3dir)/handler_listen.3 \
$(DESTDIR)$(man3dir)/handler_loop.3 \
$(DESTDIR)$(man3dir)/html_node_add_attr.3 \
$(DESTDIR)$(man3dir)/html_node_add_child.3 \
$(DESTDIR)$(man3dir)/html_node_add_sibling.3 \

View File

@ -77,10 +77,11 @@ for a list of possible errors.
.BR handler_alloc (3),
.BR handler_free (3),
.BR handler_listen (3),
.BR handler_loop (3),
.BR libweb_handler (7).
.SH COPYRIGHT
Copyright (C) 2023 Xavier Del Campo Romero.
Copyright (C) 2023 libweb contributors.
.P
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by

View File

@ -1,4 +1,4 @@
.TH HANDLER_ALLOC 3 2023-09-13 0.1.0 "libweb Library Reference"
.TH HANDLER_ALLOC 3 2023-11-16 0.2.0 "libweb Library Reference"
.SH NAME
handler_alloc \- allocate a web server handler object
@ -48,10 +48,11 @@ for a list of possible errors.
.BR handler_free (3),
.BR handler_add (3),
.BR handler_listen (3),
.BR handler_loop (3),
.BR libweb_handler (7).
.SH COPYRIGHT
Copyright (C) 2023 Xavier Del Campo Romero.
Copyright (C) 2023 libweb contributors.
.P
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by

View File

@ -1,42 +1,37 @@
.TH HANDLER_LISTEN 3 2023-09-14 0.1.0 "libweb Library Reference"
.TH HANDLER_LISTEN 3 2023-11-16 0.2.0 "libweb Library Reference"
.SH NAME
handler_listen \- listen to and handle incoming connections on a web
server
handler_listen \- initialize server to listen to a given port
.SH SYNOPSIS
.LP
.nf
#include <libweb/handler.h>
.P
int handler_listen(struct handler *\fIh\fP, unsigned short \fIport\fP);
int handler_listen(struct handler *\fIh\fP, unsigned short \fIport\fP, unsigned short *\fIoutport\fP);
.fi
.SH DESCRIPTION
The
.IR handler_listen (3)
function listens for connections on the TCP port number given by
.I port
on a
function initializes the internal server on a
.I struct handler
object pointed to by
.IR h ,
which must be previously allocated by a call to
.IR handler_alloc (3).
.IR handler_alloc (3),
to listen on the TCP port given by
.IR port .
Also, the
If
.I outport
is a valid pointer,
.IR handler_listen (3)
function validates incoming requests and calls the configured
callbacks previously given by one or more calls to
.IR handler_add (3).
The
.IR handler_listen (3)
function blocks until either
.I SIGTERM
or
.I SIGINT
are triggered.
shall assign the object pointed to by
.I outport
to the port number the server shall listen to. This is typically meant
for servers that listen on any port, but the caller needs to know which
port was eventually selected by the implementation.
.SH RETURN VALUE
On success, zero is returned. On error, a negative integer is returned.
@ -44,28 +39,16 @@ On success, zero is returned. On error, a negative integer is returned.
.SH ERRORS
No errors are defined.
.SH FUTURE DIRECTIONS
When no configured endpoint matches the incoming request,
.I libweb
shall respond with a
.B 404 Not Found
HTTP status code with no payload. Since some library users might want
to provide custom pages for such error condition, future versions of
this library shall replace the harcoded response with an additional
callback on
.IR "struct handler_cfg" ,
similarly to its member
.IR length .
.SH SEE ALSO
.BR handler_alloc (3),
.BR handler_free (3),
.BR handler_add (3),
.BR handler_loop (3),
.BR libweb_handler (7),
.BR signal (7).
.SH COPYRIGHT
Copyright (C) 2023 Xavier Del Campo Romero.
Copyright (C) 2023 libweb contributors.
.P
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by

75
doc/man3/handler_loop.3 Normal file
View File

@ -0,0 +1,75 @@
.TH HANDLER_LOOP 3 2023-11-16 0.2.0 "libweb Library Reference"
.SH NAME
handler_loop \- listen to and handle incoming connections on a web
server
.SH SYNOPSIS
.LP
.nf
#include <libweb/handler.h>
.P
int handler_loop(struct handler *\fIh\fP);
.fi
.SH DESCRIPTION
The
.IR handler_loop (3)
function listens for connections on a
.I struct handler
object pointed to by
.IR h ,
which must be previously allocated by a call to
.IR handler_alloc (3)
and initialized by a call to
.IR handler_listen (3),
in a loop.
Also, the
.IR handler_loop (3)
function validates incoming requests and calls the configured
callbacks previously given by one or more calls to
.IR handler_add (3).
The
.IR handler_loop (3)
function blocks until either
.I SIGTERM
or
.I SIGINT
are triggered.
.SH RETURN VALUE
On success, zero is returned. On error, a negative integer is returned.
.SH ERRORS
No errors are defined.
.SH FUTURE DIRECTIONS
When no configured endpoint matches the incoming request,
.I libweb
shall respond with a
.B 404 Not Found
HTTP status code with no payload. Since some library users might want
to provide custom pages for such error condition, future versions of
this library shall replace the harcoded response with an additional
callback on
.IR "struct handler_cfg" ,
similarly to its member
.IR length .
.SH SEE ALSO
.BR handler_alloc (3),
.BR handler_free (3),
.BR handler_add (3),
.BR handler_listen (3),
.BR libweb_handler (7),
.BR signal (7).
.SH COPYRIGHT
Copyright (C) 2023 libweb contributors.
.P
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

View File

@ -51,9 +51,15 @@ operation.
.IP \(bu 2
.IR handler_listen (3):
initializes the server on a
.I "struct handler"
object to listen on a given port.
.IP \(bu 2
.IR handler_loop (3):
puts a
.I "struct handler"
object to initialize the server and handle connections in a loop.
object to handle connections in a loop.
The
.IR handler_alloc (3)
@ -200,11 +206,16 @@ int main(int argc, char *argv[])
goto end;
}
if (handler_listen(h, port))
if (handler_listen(h, port, NULL))
{
fprintf(stderr, "%s: handler_listen failed\en", __func__);
goto end;
}
else if (handler_loop(h))
{
fprintf(stderr, "%s: handler_loop failed\en", __func__);
goto end;
}
ret = EXIT_SUCCESS;
@ -220,10 +231,12 @@ end:
.BR handler_alloc (3),
.BR handler_add (3),
.BR handler_free (3),
.BR handler_listen (3),
.BR handler_loop (3),
.BR libweb_http (7).
.SH COPYRIGHT
Copyright (C) 2023 Xavier Del Campo Romero.
Copyright (C) 2023 libweb contributors.
.P
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by

View File

@ -2,3 +2,4 @@ cmake_minimum_required(VERSION 3.13)
add_subdirectory(headers)
add_subdirectory(hello)
add_subdirectory(html)
add_subdirectory(put)

View File

@ -3,7 +3,8 @@
all: \
headers \
hello \
html
html \
put
clean:
+cd hello && $(MAKE) clean

View File

@ -1,9 +1,13 @@
# "Hello world" example
This example shows a minimal setup for an application using `libweb`. When
executed, it starts a HTTP/1.1 server on port `8080` and returns an example
website reading "Hello from libweb!" when either `/` or `/index.html` are
accessed by clients.
executed, it starts a HTTP/1.1 server on a random port, which is then printed
to the standard output, and returns an example website when either `/` or
`/index.html` are accessed by clients, reading:
```
Hello from libweb!
```
## How to build

View File

@ -81,7 +81,6 @@ static int on_length(const unsigned long long len,
int main(int argc, char *argv[])
{
int ret = EXIT_FAILURE;
const short port = 8080;
const struct handler_cfg cfg =
{
.length = on_length
@ -103,12 +102,22 @@ int main(int argc, char *argv[])
goto end;
}
if (handler_listen(h, port))
unsigned short outport;
if (handler_listen(h, 0, &outport))
{
fprintf(stderr, "%s: handler_listen failed\n", __func__);
goto end;
}
printf("Listening on port %hu\n", outport);
if (handler_loop(h))
{
fprintf(stderr, "%s: handler_loop failed\n", __func__);
goto end;
}
ret = EXIT_SUCCESS;
end:

View File

@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.13)
project(put C)
add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} PRIVATE web dynstr)

26
examples/put/Makefile Normal file
View File

@ -0,0 +1,26 @@
.POSIX:
PROJECT = put
DEPS = \
main.o
LIBWEB = ../../libweb.a
DYNSTR = ../../dynstr/libdynstr.a
CFLAGS = -I ../../include -I ../../dynstr/include -g
LIBWEB_FLAGS = -L ../../ -l web
DYNSTR_FLAGS = -L ../../dynstr -l dynstr
all: $(PROJECT)
clean:
rm -f $(DEPS)
FORCE:
$(PROJECT): $(DEPS) $(LIBWEB) $(DYNSTR)
$(CC) $(LDFLAGS) $(DEPS) $(LIBWEB_FLAGS) $(DYNSTR_FLAGS) -o $@
$(LIBWEB): FORCE
+cd ../../ && $(MAKE)
$(DYNSTR): FORCE
+cd ../../dynstr && $(MAKE)

18
examples/put/README.md Normal file
View File

@ -0,0 +1,18 @@
# HTTP `PUT` example
This example shows a minimal setup for an application using `libweb` that only
accepts `PUT` requests. When executed, it starts a HTTP/1.1 server on a random
port that shall be printed to standard output. When a `PUT` request is received,
`libweb` shall store the body into a file in the `/tmp` directory, and print the
path of the temporary file to the standard output.
## How to build
If using `make(1)`, just run `make` from this directory.
If using CMake, examples are built by default when configuring the project
from [the top-level `CMakeLists.txt`](../../CMakeLists.txt).
## How to run
Run the executable without any command line arguments.

90
examples/put/main.c Normal file
View File

@ -0,0 +1,90 @@
#include <dynstr.h>
#include <libweb/handler.h>
#include <libweb/html.h>
#include <libweb/http.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
static int on_put(const struct http_payload *const pl,
struct http_response *const r, void *const user)
{
if (pl->expect_continue)
{
*r = (const struct http_response)
{
.status = HTTP_STATUS_CONTINUE
};
return 0;
}
printf("File uploaded to %s\n", pl->u.put.tmpname);
*r = (const struct http_response)
{
.status = HTTP_STATUS_OK
};
return 0;
}
static int on_length(const unsigned long long len,
const struct http_cookie *const c, struct http_response *const r,
void *const user)
{
*r = (const struct http_response)
{
.status = HTTP_STATUS_OK
};
return 0;
}
int main(int argc, char *argv[])
{
int ret = EXIT_FAILURE;
const struct handler_cfg cfg =
{
.tmpdir = "/tmp",
.length = on_length
};
struct handler *const h = handler_alloc(&cfg);
static const char *const urls[] = {"/*"};
if (!h)
{
fprintf(stderr, "%s: handler_alloc failed\n", __func__);
goto end;
}
for (size_t i = 0; i < sizeof urls / sizeof *urls; i++)
if (handler_add(h, urls[i], HTTP_OP_PUT, on_put, NULL))
{
fprintf(stderr, "%s: handler_add failed\n", __func__);
goto end;
}
unsigned short outport;
if (handler_listen(h, 0, &outport))
{
fprintf(stderr, "%s: handler_listen failed\n", __func__);
goto end;
}
printf("Listening on port %hu\n", outport);
if (handler_loop(h))
{
fprintf(stderr, "%s: handler_loop failed\n", __func__);
goto end;
}
ret = EXIT_SUCCESS;
end:
handler_free(h);
return ret;
}

View File

@ -152,13 +152,13 @@ static void client_free(struct client *const c)
static int remove_client_from_list(struct handler *const h,
struct client *const c)
{
int ret = -1;
int ret = 0;
if (server_client_close(h->server, c->c))
{
fprintf(stderr, "%s: server_client_close failed\n",
__func__);
goto end;
ret = -1;
}
for (struct client *cl = h->clients, *prev = NULL; cl;
@ -175,21 +175,24 @@ static int remove_client_from_list(struct handler *const h,
}
}
ret = 0;
end:
client_free(c);
return ret;
}
int handler_listen(struct handler *const h, const unsigned short port)
int handler_listen(struct handler *const h, const unsigned short port,
unsigned short *const outport)
{
if (!(h->server = server_init(port)))
if (!(h->server = server_init(port, outport)))
{
fprintf(stderr, "%s: server_init failed\n", __func__);
return -1;
}
return 0;
}
int handler_loop(struct handler *const h)
{
for (;;)
{
bool exit, io;

268
http.c
View File

@ -38,11 +38,11 @@ struct http_ctx
char *resource, *field, *value, *boundary;
size_t len;
struct post
struct payload
{
char *path;
unsigned long long len, read;
} post;
} payload;
union
{
@ -85,11 +85,19 @@ struct http_ctx
char *name, *filename, *tmpname, *value;
} *forms;
} mf;
struct put
{
char *tmpname;
int fd;
off_t written;
} put;
} u;
struct http_arg *args;
size_t n_args, n_headers;
struct http_header *headers;
bool has_length, expect_continue;
} ctx;
struct write_ctx
@ -340,6 +348,27 @@ end:
return ret;
}
static int get_op(const char *const line, const size_t n,
enum http_op *const out)
{
static const char *const ops[] =
{
[HTTP_OP_GET] = "GET",
[HTTP_OP_POST] = "POST",
[HTTP_OP_HEAD] = "HEAD",
[HTTP_OP_PUT] = "PUT"
};
for (enum http_op op = 0; op < sizeof ops / sizeof *ops; op++)
if (!strncmp(line, ops[op], n))
{
*out = op;
return 0;
}
return -1;
}
static int start_line(struct http_ctx *const h)
{
const char *const line = h->line;
@ -361,13 +390,7 @@ static int start_line(struct http_ctx *const h)
struct ctx *const c = &h->ctx;
const size_t n = op - line;
if (!strncmp(line, "GET", n))
c->op = HTTP_OP_GET;
else if (!strncmp(line, "POST", n))
c->op = HTTP_OP_POST;
else if (!strncmp(line, "HEAD", n))
c->op = HTTP_OP_HEAD;
else
if (get_op(line, n, &c->op))
{
fprintf(stderr, "%s: unsupported HTTP op %.*s\n",
__func__, (int)n, line);
@ -467,6 +490,16 @@ static void ctx_free(struct ctx *const c)
free(m->forms);
}
else if (c->op == HTTP_OP_PUT)
{
struct put *const p = &c->u.put;
if (p->fd >= 0 && close(p->fd))
fprintf(stderr, "%s: close(2) p->fd: %s\n",
__func__, strerror(errno));
free(c->u.put.tmpname);
}
free(c->field);
free(c->value);
@ -859,7 +892,7 @@ static int set_length(struct http_ctx *const h, const char *const len)
char *end;
errno = 0;
h->ctx.post.len = strtoull(len, &end, 10);
const unsigned long long value = strtoull(len, &end, 10);
if (errno || *end != '\0')
{
@ -868,6 +901,26 @@ static int set_length(struct http_ctx *const h, const char *const len)
return 1;
}
struct ctx *const c = &h->ctx;
switch (c->op)
{
case HTTP_OP_POST:
/* Fall through. */
case HTTP_OP_PUT:
c->payload.len = value;
c->u.put = (const struct put){.fd = -1};
break;
case HTTP_OP_GET:
/* Fall through. */
case HTTP_OP_HEAD:
fprintf(stderr, "%s: unexpected header for HTTP op %d\n",
__func__, c->op);
return 1;
}
c->has_length = true;
return 0;
}
@ -887,6 +940,12 @@ static int set_content_type(struct http_ctx *const h, const char *const type)
__func__, (int)n, type);
return 1;
}
else if (h->ctx.op != HTTP_OP_POST)
{
fprintf(stderr, "%s: multipart/form-data only expected for POST\n",
__func__);
return 1;
}
const char *boundary = sep + 1;
@ -962,7 +1021,7 @@ static struct http_payload ctx_to_payload(const struct ctx *const c)
};
}
static int process_payload(struct http_ctx *const h, const char *const line)
static int process_payload(struct http_ctx *const h)
{
struct ctx *const c = &h->ctx;
const struct http_payload p = ctx_to_payload(c);
@ -1008,25 +1067,28 @@ static int expect(struct http_ctx *const h, const char *const value)
if (!strcmp(value, "100-continue"))
{
struct ctx *const c = &h->ctx;
const struct http_payload p =
if (!c->has_length)
{
.u.post.expect_continue = true,
.cookie =
{
.field = c->field,
.value = c->value
},
fprintf(stderr, "%s: 100-continue without expected content\n",
__func__);
return 1;
}
.op = c->op,
.resource = c->resource
};
switch (c->op)
{
case HTTP_OP_POST:
/* Fall through. */
case HTTP_OP_PUT:
c->expect_continue = true;
break;
const int ret = h->cfg.payload(&p, &h->wctx.r, h->cfg.user);
if (ret)
return ret;
return start_response(h);
case HTTP_OP_GET:
/* Fall through. */
case HTTP_OP_HEAD:
fprintf(stderr, "%s: unexpected op %d\n", __func__, c->op);
return 1;
}
}
return 0;
@ -1133,7 +1195,39 @@ static int check_length(struct http_ctx *const h)
.value = c->value
};
return h->cfg.length(c->post.len, &cookie, &h->wctx.r, h->cfg.user);
return h->cfg.length(c->payload.len, &cookie, &h->wctx.r, h->cfg.user);
}
static int process_expect(struct http_ctx *const h)
{
struct ctx *const c = &h->ctx;
const int res = check_length(h);
if (res)
{
if (res < 0)
{
fprintf(stderr, "%s: check_length failed\n", __func__);
return res;
}
h->wctx.close = true;
return start_response(h);
}
struct http_payload p = ctx_to_payload(c);
p.expect_continue = true;
const int ret = h->cfg.payload(&p, &h->wctx.r, h->cfg.user);
h->wctx.op = c->op;
if (ret)
return ret;
c->state = BODY_LINE;
return start_response(h);
}
static int header_cr_line(struct http_ctx *const h)
@ -1148,12 +1242,14 @@ static int header_cr_line(struct http_ctx *const h)
case HTTP_OP_GET:
/* Fall through. */
case HTTP_OP_HEAD:
return process_payload(h, line);
return process_payload(h);
case HTTP_OP_POST:
{
if (!c->post.len)
return process_payload(h, line);
if (!c->payload.len)
return process_payload(h);
else if (c->expect_continue)
return process_expect(h);
else if (c->boundary)
{
const int res = check_length(h);
@ -1175,6 +1271,21 @@ static int header_cr_line(struct http_ctx *const h)
c->state = BODY_LINE;
return 0;
}
case HTTP_OP_PUT:
if (!c->has_length)
{
fprintf(stderr, "%s: expected Content-Length header\n",
__func__);
return 1;
}
else if (!c->payload.len)
return process_payload(h);
else if (c->expect_continue)
return process_expect(h);
c->state = BODY_LINE;
return 0;
}
}
@ -1472,7 +1583,7 @@ static int process_mf_line(struct http_ctx *const h)
[MF_END_BOUNDARY_CR_LINE] = end_boundary_line
};
h->ctx.post.read += strlen(h->line) + strlen("\r\n");
h->ctx.payload.read += strlen(h->line) + strlen("\r\n");
return state[h->ctx.u.mf.state](h);
}
@ -1525,7 +1636,7 @@ static int read_mf_body_to_mem(struct http_ctx *const h, const void *const buf,
memcpy(&h->line[m->written], buf, n);
m->written += n;
m->len += n;
c->post.read += n;
c->payload.read += n;
return 0;
}
@ -1549,7 +1660,7 @@ static int read_mf_body_to_file(struct http_ctx *const h, const void *const buf,
m->written += res;
m->len += res;
c->post.read += res;
c->payload.read += res;
return 0;
}
@ -1826,7 +1937,7 @@ static int read_multiform(struct http_ctx *const h, bool *const close)
{
/* Note: the larger the buffer below, the less CPU load. */
char buf[sizeof h->line];
struct post *const p = &h->ctx.post;
struct payload *const p = &h->ctx.payload;
const unsigned long long left = p->len - p->read;
const size_t rem = left > sizeof buf ? sizeof buf : left;
const int r = h->cfg.read(buf, rem, h->cfg.user);
@ -1846,7 +1957,7 @@ static int read_body_to_mem(struct http_ctx *const h, bool *const close)
return rw_error(r, close);
struct ctx *const c = &h->ctx;
struct post *const p = &c->post;
struct payload *const p = &c->payload;
if (p->read >= sizeof h->line - 1)
{
@ -1881,10 +1992,91 @@ static int read_body_to_mem(struct http_ctx *const h, bool *const close)
return 0;
}
static int read_put_body_to_file(struct http_ctx *const h,
const void *const buf, const size_t n)
{
struct ctx *const c = &h->ctx;
struct put *const put = &c->u.put;
ssize_t res;
if (!put->tmpname && !(put->tmpname = get_tmp(h->cfg.tmpdir)))
{
fprintf(stderr, "%s: get_tmp failed\n", __func__);
return -1;
}
else if (put->fd < 0 && (put->fd = mkstemp(put->tmpname)) < 0)
{
fprintf(stderr, "%s: mkstemp(3): %s\n", __func__, strerror(errno));
return -1;
}
else if ((res = pwrite(put->fd, buf, n, c->payload.read)) < 0)
{
fprintf(stderr, "%s: pwrite(2): %s\n", __func__, strerror(errno));
return -1;
}
c->payload.read += res;
return 0;
}
static int read_to_file(struct http_ctx *const h, bool *const close)
{
char buf[BUFSIZ];
struct ctx *const c = &h->ctx;
struct payload *const p = &c->payload;
const unsigned long long left = p->len - p->read;
const size_t rem = left > sizeof buf ? sizeof buf : left;
const int r = h->cfg.read(buf, rem, h->cfg.user);
if (r <= 0)
return rw_error(r, close);
else if (read_put_body_to_file(h, buf, r))
return -1;
else if (p->read >= p->len)
{
const struct http_payload pl =
{
.cookie =
{
.field = c->field,
.value = c->value
},
.op = c->op,
.resource = c->resource,
.u.put =
{
.tmpname = c->u.put.tmpname
}
};
return send_payload(h, &pl);
}
return 0;
}
static int read_body(struct http_ctx *const h, bool *const close)
{
return h->ctx.boundary ? read_multiform(h, close)
: read_body_to_mem(h, close);
const struct ctx *const c = &h->ctx;
switch (c->op)
{
case HTTP_OP_POST:
return c->boundary ? read_multiform(h, close)
: read_body_to_mem(h, close);
case HTTP_OP_PUT:
return read_to_file(h, close);
case HTTP_OP_GET:
/* Fall through. */
case HTTP_OP_HEAD:
break;
}
fprintf(stderr, "%s: unexpected op %d\n", __func__, c->op);
return -1;
}
static int process_line(struct http_ctx *const h)

View File

@ -20,6 +20,8 @@ struct handler *handler_alloc(const struct handler_cfg *cfg);
void handler_free(struct handler *h);
int handler_add(struct handler *h, const char *url, enum http_op op,
handler_fn f, void *user);
int handler_listen(struct handler *h, unsigned short port);
int handler_listen(struct handler *h, unsigned short port,
unsigned short *outport);
int handler_loop(struct handler *h);
#endif /* HANDLER_H */

View File

@ -16,7 +16,8 @@ struct http_payload
{
HTTP_OP_GET,
HTTP_OP_POST,
HTTP_OP_HEAD
HTTP_OP_HEAD,
HTTP_OP_PUT
} op;
const char *resource;
@ -30,7 +31,6 @@ struct http_payload
{
struct http_post
{
bool expect_continue;
const char *data;
size_t nfiles, npairs;
@ -44,6 +44,11 @@ struct http_payload
const char *name, *tmpname, *filename;
} *files;
} post;
struct http_put
{
const char *tmpname;
} put;
} u;
const struct http_arg
@ -53,6 +58,7 @@ struct http_payload
size_t n_args, n_headers;
const struct http_header *headers;
bool expect_continue;
};
#define HTTP_STATUSES \

View File

@ -4,7 +4,7 @@
#include <stdbool.h>
#include <stddef.h>
struct server *server_init(unsigned short port);
struct server *server_init(unsigned short port, unsigned short *outport);
struct server_client *server_poll(struct server *s, bool *io, bool *exit);
int server_read(void *buf, size_t n, struct server_client *c);
int server_write(const void *buf, size_t n, struct server_client *c);

View File

@ -59,7 +59,6 @@ int server_client_close(struct server *const s, struct server_client *const c)
{
fprintf(stderr, "%s: close(2): %s\n",
__func__, strerror(errno));
return -1;
}
else if (ref->prev)
ref->prev->next = next;
@ -320,7 +319,8 @@ static int init_signals(void)
return 0;
}
struct server *server_init(const unsigned short port)
struct server *server_init(const unsigned short port,
unsigned short *const outport)
{
struct server *const s = malloc(sizeof *s);
@ -373,8 +373,9 @@ struct server *server_init(const unsigned short port)
fprintf(stderr, "%s: getsockname(2): %s\n", __func__, strerror(errno));
goto failure;
}
else if (outport)
*outport = ntohs(in.sin_port);
printf("Listening on port %hu\n", ntohs(in.sin_port));
return s;
failure: