aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--examples/CMakeLists.txt1
-rw-r--r--examples/Makefile3
-rw-r--r--examples/put/CMakeLists.txt4
-rw-r--r--examples/put/Makefile26
-rw-r--r--examples/put/README.md18
-rw-r--r--examples/put/main.c90
-rw-r--r--http.c268
-rw-r--r--include/libweb/http.h10
9 files changed, 380 insertions, 41 deletions
diff --git a/.gitignore b/.gitignore
index e39347a..58d8edc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@
*.so.*
examples/hello/hello
examples/html/html
+examples/put/put
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 7353458..4c2a6a2 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -2,3 +2,4 @@ cmake_minimum_required(VERSION 3.13)
add_subdirectory(headers)
add_subdirectory(hello)
add_subdirectory(html)
+add_subdirectory(put)
diff --git a/examples/Makefile b/examples/Makefile
index 672f209..6d99b4f 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -3,7 +3,8 @@
all: \
headers \
hello \
- html
+ html \
+ put
clean:
+cd hello && $(MAKE) clean
diff --git a/examples/put/CMakeLists.txt b/examples/put/CMakeLists.txt
new file mode 100644
index 0000000..200ae28
--- /dev/null
+++ b/examples/put/CMakeLists.txt
@@ -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)
diff --git a/examples/put/Makefile b/examples/put/Makefile
new file mode 100644
index 0000000..972208b
--- /dev/null
+++ b/examples/put/Makefile
@@ -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)
diff --git a/examples/put/README.md b/examples/put/README.md
new file mode 100644
index 0000000..afa9191
--- /dev/null
+++ b/examples/put/README.md
@@ -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.
diff --git a/examples/put/main.c b/examples/put/main.c
new file mode 100644
index 0000000..cf38248
--- /dev/null
+++ b/examples/put/main.c
@@ -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;
+}
diff --git a/http.c b/http.c
index 80086e1..2026a8e 100644
--- a/http.c
+++ b/http.c
@@ -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 =
- {
- .u.post.expect_continue = true,
- .cookie =
- {
- .field = c->field,
- .value = c->value
- },
-
- .op = c->op,
- .resource = c->resource
- };
- const int ret = h->cfg.payload(&p, &h->wctx.r, h->cfg.user);
+ if (!c->has_length)
+ {
+ fprintf(stderr, "%s: 100-continue without expected content\n",
+ __func__);
+ return 1;
+ }
- if (ret)
- return ret;
+ switch (c->op)
+ {
+ case HTTP_OP_POST:
+ /* Fall through. */
+ case HTTP_OP_PUT:
+ c->expect_continue = true;
+ break;
- 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)
diff --git a/include/libweb/http.h b/include/libweb/http.h
index ddb6a89..7af66b5 100644
--- a/include/libweb/http.h
+++ b/include/libweb/http.h
@@ -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 \