From 68eb526d7b5a276fc767738193902e7f7b2cfed6 Mon Sep 17 00:00:00 2001 From: Alexander Drozdov Date: Wed, 13 Mar 2019 10:51:55 +0300 Subject: [PATCH] http: add WebDAV methods support WebDAV introduced new HTTP methods (RFC4918): PROPFIND, PROPPATCH, MKCOL, LOCK, UNLOCK, COPY, MOVE. Add support of the methods. --- http.c | 108 +++++++++++++++++++++++++++++++++----- include/event2/http.h | 14 ++++- test/regress_http.c | 118 +++++++++++++++++++++++++++++++++++------- 3 files changed, 208 insertions(+), 32 deletions(-) diff --git a/http.c b/http.c index 89e6afa2..799cfb36 100644 --- a/http.c +++ b/http.c @@ -331,6 +331,27 @@ evhttp_method(enum evhttp_cmd_type type) case EVHTTP_REQ_PATCH: method = "PATCH"; break; + case EVHTTP_REQ_PROPFIND: + method = "PROPFIND"; + break; + case EVHTTP_REQ_PROPPATCH: + method = "PROPPATCH"; + break; + case EVHTTP_REQ_MKCOL: + method = "MKCOL"; + break; + case EVHTTP_REQ_LOCK: + method = "LOCK"; + break; + case EVHTTP_REQ_UNLOCK: + method = "UNLOCK"; + break; + case EVHTTP_REQ_COPY: + method = "COPY"; + break; + case EVHTTP_REQ_MOVE: + method = "MOVE"; + break; default: method = NULL; break; @@ -1794,7 +1815,7 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line, size_t len) } break; case 4: - /* The method length is 4 bytes, leaving only the methods "POST" and "HEAD" */ + /* The method length is 4 bytes, leaving only the methods POST, HEAD, LOCK, COPY and MOVE */ switch (*method) { case 'P': if (method[3] == 'T' && method[2] == 'S' && method[1] == 'O') { @@ -1806,12 +1827,27 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line, size_t len) type = EVHTTP_REQ_HEAD; } break; + case 'L': + if (method[3] == 'K' && method[2] == 'C' && method[1] == 'O') { + type = EVHTTP_REQ_LOCK; + } + break; + case 'C': + if (method[3] == 'Y' && method[2] == 'P' && method[1] == 'O') { + type = EVHTTP_REQ_COPY; + } + break; + case 'M': + if (method[3] == 'E' && method[2] == 'V' && method[1] == 'O') { + type = EVHTTP_REQ_MOVE; + } + break; default: break; } break; case 5: - /* Method length is 5 bytes, which can only encompass PATCH and TRACE */ + /* Method length is 5 bytes, which can only encompass PATCH, TRACE and MKCOL */ switch (*method) { case 'P': if (method[4] == 'H' && method[3] == 'C' && method[2] == 'T' && method[1] == 'A') { @@ -1823,23 +1859,34 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line, size_t len) type = EVHTTP_REQ_TRACE; } + break; + case 'M': + if (method[4] == 'L' && method[3] == 'O' && method[2] == 'C' && method[1] == 'K') { + type = EVHTTP_REQ_MKCOL; + } break; default: break; } break; case 6: - /* Method length is 6, only valid method 6 bytes in length is DELEte */ - - /* If the first byte isn't 'D' then it's invalid */ - if (*method != 'D') { - break; - } - - if (method[5] == 'E' && method[4] == 'T' && method[3] == 'E' && method[2] == 'L' && method[1] == 'E') { - type = EVHTTP_REQ_DELETE; + /* Method length is 6, only valid methods 6 bytes in length is DELETE and UNLOCK */ + switch (*method) { + case 'D': + if (method[5] == 'E' && method[4] == 'T' && method[3] == 'E' && + method[2] == 'L' && method[1] == 'E') { + type = EVHTTP_REQ_DELETE; + } + break; + case 'U': + if (method[5] == 'K' && method[4] == 'C' && method[3] == 'O' && + method[2] == 'L' && method[1] == 'N') { + type = EVHTTP_REQ_UNLOCK; + } + break; + default: + break; } - break; case 7: /* Method length is 7, only valid methods are "OPTIONS" and "CONNECT" */ @@ -1861,6 +1908,36 @@ evhttp_parse_request_line(struct evhttp_request *req, char *line, size_t len) default: break; } + break; + case 8: + /* Method length is 8, only valid method 8 bytes in length is PROPFIND */ + + /* If the first byte isn't 'P' then it's invalid */ + if (*method != 'P') { + break; + } + + if (method[7] == 'D' && method[6] == 'N' && method[5] == 'I' && + method[4] == 'F' && method[3] == 'P' && method[2] == 'O' && + method[1] == 'R') { + type = EVHTTP_REQ_PROPFIND; + } + + break; + case 9: + /* Method length is 9, only valid method 9 bytes in length is PROPPATCH */ + + /* If the first byte isn't 'P' then it's invalid */ + if (*method != 'P') { + break; + } + + if (method[8] == 'H' && method[7] == 'C' && method[6] == 'T' && + method[5] == 'A' && method[4] == 'P' && method[3] == 'P' && + method[2] == 'O' && method[1] == 'R') { + type = EVHTTP_REQ_PROPPATCH; + } + break; } /* switch */ @@ -2208,6 +2285,13 @@ evhttp_method_may_have_body(enum evhttp_cmd_type type) case EVHTTP_REQ_POST: case EVHTTP_REQ_PUT: case EVHTTP_REQ_PATCH: + case EVHTTP_REQ_PROPFIND: + case EVHTTP_REQ_PROPPATCH: + case EVHTTP_REQ_MKCOL: + case EVHTTP_REQ_LOCK: + case EVHTTP_REQ_UNLOCK: + case EVHTTP_REQ_COPY: + case EVHTTP_REQ_MOVE: case EVHTTP_REQ_GET: case EVHTTP_REQ_DELETE: diff --git a/include/event2/http.h b/include/event2/http.h index 393c536f..53c38444 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -524,7 +524,10 @@ void evhttp_send_reply_end(struct evhttp_request *req); */ /** The different request types supported by evhttp. These are as specified - * in RFC2616, except for PATCH which is specified by RFC5789. + * in RFC2616, except for: + * - PATCH which is specified by RFC5789 + * - PROPFIND, PROPPATCH, MKCOL, LOCK, UNLOCK, COPY, MOVE + * which are specified by RFC4918 * * By default, only some of these methods are accepted and passed to user * callbacks; use evhttp_set_allowed_methods() to change which methods @@ -539,7 +542,14 @@ enum evhttp_cmd_type { EVHTTP_REQ_OPTIONS = 1 << 5, EVHTTP_REQ_TRACE = 1 << 6, EVHTTP_REQ_CONNECT = 1 << 7, - EVHTTP_REQ_PATCH = 1 << 8 + EVHTTP_REQ_PATCH = 1 << 8, + EVHTTP_REQ_PROPFIND= 1 << 9, + EVHTTP_REQ_PROPPATCH=1 << 10, + EVHTTP_REQ_MKCOL = 1 << 11, + EVHTTP_REQ_LOCK = 1 << 12, + EVHTTP_REQ_UNLOCK = 1 << 13, + EVHTTP_REQ_COPY = 1 << 14, + EVHTTP_REQ_MOVE = 1 << 15, }; /** a request object can represent either a request or a reply */ diff --git a/test/regress_http.c b/test/regress_http.c index 5e4e1a68..e29cd1b5 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -77,7 +77,7 @@ static void http_large_cb(struct evhttp_request *req, void *arg); static void http_chunked_cb(struct evhttp_request *req, void *arg); static void http_post_cb(struct evhttp_request *req, void *arg); static void http_put_cb(struct evhttp_request *req, void *arg); -static void http_delete_cb(struct evhttp_request *req, void *arg); +static void http_genmethod_cb(struct evhttp_request *req, void *arg); static void http_delay_cb(struct evhttp_request *req, void *arg); static void http_large_delay_cb(struct evhttp_request *req, void *arg); static void http_badreq_cb(struct evhttp_request *req, void *arg); @@ -157,7 +157,14 @@ http_setup_gencb(ev_uint16_t *pport, struct event_base *base, int mask, evhttp_set_cb(myhttp, "/streamed", http_chunked_cb, base); evhttp_set_cb(myhttp, "/postit", http_post_cb, base); evhttp_set_cb(myhttp, "/putit", http_put_cb, base); - evhttp_set_cb(myhttp, "/deleteit", http_delete_cb, base); + evhttp_set_cb(myhttp, "/deleteit", http_genmethod_cb, base); + evhttp_set_cb(myhttp, "/propfind", http_genmethod_cb, base); + evhttp_set_cb(myhttp, "/proppatch", http_genmethod_cb, base); + evhttp_set_cb(myhttp, "/mkcol", http_genmethod_cb, base); + evhttp_set_cb(myhttp, "/lockit", http_genmethod_cb, base); + evhttp_set_cb(myhttp, "/unlockit", http_genmethod_cb, base); + evhttp_set_cb(myhttp, "/copyit", http_genmethod_cb, base); + evhttp_set_cb(myhttp, "/moveit", http_genmethod_cb, base); evhttp_set_cb(myhttp, "/delay", http_delay_cb, base); evhttp_set_cb(myhttp, "/largedelay", http_large_delay_cb, base); evhttp_set_cb(myhttp, "/badrequest", http_badreq_cb, base); @@ -775,18 +782,36 @@ http_large_delay_cb(struct evhttp_request *req, void *arg) evhttp_connection_fail_(delayed_client, EVREQ_HTTP_EOF); } -/* - * HTTP DELETE test, just piggyback on the basic test - */ - static void -http_delete_cb(struct evhttp_request *req, void *arg) +http_genmethod_cb(struct evhttp_request *req, void *arg) { + const char *uri = evhttp_request_get_uri(req); struct evbuffer *evb = evbuffer_new(); int empty = evhttp_find_header(evhttp_request_get_input_headers(req), "Empty") != NULL; - - /* Expecting a DELETE request */ - if (evhttp_request_get_command(req) != EVHTTP_REQ_DELETE) { + enum evhttp_cmd_type method; + + if (!strcmp(uri, "/deleteit")) + method = EVHTTP_REQ_DELETE; + else if (!strcmp(uri, "/propfind")) + method = EVHTTP_REQ_PROPFIND; + else if (!strcmp(uri, "/proppatch")) + method = EVHTTP_REQ_PROPPATCH; + else if (!strcmp(uri, "/mkcol")) + method = EVHTTP_REQ_MKCOL; + else if (!strcmp(uri, "/lockit")) + method = EVHTTP_REQ_LOCK; + else if (!strcmp(uri, "/unlockit")) + method = EVHTTP_REQ_UNLOCK; + else if (!strcmp(uri, "/copyit")) + method = EVHTTP_REQ_COPY; + else if (!strcmp(uri, "/moveit")) + method = EVHTTP_REQ_MOVE; + else { + fprintf(stdout, "FAILED (unexpected path)\n"); + exit(1); + } + /* Expecting correct request method */ + if (evhttp_request_get_command(req) != method) { fprintf(stdout, "FAILED (delete type)\n"); exit(1); } @@ -802,12 +827,12 @@ http_delete_cb(struct evhttp_request *req, void *arg) } static void -http_delete_test(void *arg) +http_genmethod_test(void *arg, enum evhttp_cmd_type method, const char *name, const char *path) { struct basic_test_data *data = arg; struct bufferevent *bev; + struct evbuffer *evb; evutil_socket_t fd = EVUTIL_INVALID_SOCKET; - const char *http_request; ev_uint16_t port = 0; struct evhttp *http = http_setup(&port, data->base, 0); @@ -818,18 +843,20 @@ http_delete_test(void *arg) fd = http_connect("127.0.0.1", port); tt_assert(fd != EVUTIL_INVALID_SOCKET); + evhttp_set_allowed_methods(http, method); + /* Stupid thing to send a request */ bev = bufferevent_socket_new(data->base, fd, 0); bufferevent_setcb(bev, http_readcb, http_writecb, http_errorcb, data->base); - - http_request = - "DELETE /deleteit HTTP/1.1\r\n" + evb = bufferevent_get_output(bev); + evbuffer_add_printf( + evb, + "%s %s HTTP/1.1\r\n" "Host: somehost\r\n" "Connection: close\r\n" - "\r\n"; - - bufferevent_write(bev, http_request, strlen(http_request)); + "\r\n" + "body", name, path); event_base_dispatch(data->base); @@ -845,6 +872,54 @@ http_delete_test(void *arg) evutil_closesocket(fd); } +static void +http_delete_test(void *arg) +{ + http_genmethod_test(arg, EVHTTP_REQ_DELETE, "DELETE", "/deleteit"); +} + +static void +http_propfind_test(void *arg) +{ + http_genmethod_test(arg, EVHTTP_REQ_PROPFIND, "PROPFIND", "/propfind"); +} + +static void +http_proppatch_test(void *arg) +{ + http_genmethod_test(arg, EVHTTP_REQ_PROPPATCH, "PROPPATCH", "/proppatch"); +} + +static void +http_mkcol_test(void *arg) +{ + http_genmethod_test(arg, EVHTTP_REQ_MKCOL, "MKCOL", "/mkcol"); +} + +static void +http_lock_test(void *arg) +{ + http_genmethod_test(arg, EVHTTP_REQ_LOCK, "LOCK", "/lockit"); +} + +static void +http_unlock_test(void *arg) +{ + http_genmethod_test(arg, EVHTTP_REQ_UNLOCK, "UNLOCK", "/unlockit"); +} + +static void +http_copy_test(void *arg) +{ + http_genmethod_test(arg, EVHTTP_REQ_COPY, "COPY", "/copyit"); +} + +static void +http_move_test(void *arg) +{ + http_genmethod_test(arg, EVHTTP_REQ_MOVE, "MOVE", "/moveit"); +} + static void http_sent_cb(struct evhttp_request *req, void *arg) { @@ -4998,6 +5073,13 @@ struct testcase_t http_testcases[] = { HTTP(post), HTTP(put), HTTP(delete), + HTTP(propfind), + HTTP(proppatch), + HTTP(mkcol), + HTTP(lock), + HTTP(unlock), + HTTP(copy), + HTTP(move), HTTP(allowed_methods), HTTP(failure), HTTP(connection), -- 2.40.0