From dc6ba44ea66c471305908a8053d7de56d705e499 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Sat, 28 Oct 2017 00:03:18 +0200 Subject: [SERVER] Add function to parse x-www-form-urlencoded strings Use it to properly parse RPC queries. Will also come in handy when parsing POST body for calls that actually trigger any actions in the server (reload, alt-servers, ...) --- src/server/picohttpparser/picohttpparser.h | 1 - src/server/rpc.c | 71 +++++++++++++++++++----------- src/server/urldecode.c | 61 +++++++++++++++++++++++++ src/server/urldecode.h | 19 ++++++++ 4 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 src/server/urldecode.c create mode 100644 src/server/urldecode.h (limited to 'src/server') diff --git a/src/server/picohttpparser/picohttpparser.h b/src/server/picohttpparser/picohttpparser.h index 582a1b8..b315795 100644 --- a/src/server/picohttpparser/picohttpparser.h +++ b/src/server/picohttpparser/picohttpparser.h @@ -44,7 +44,6 @@ struct string { size_t l; }; - /* contains name and value of a header (name == NULL if is a continuing line * of a multiline header */ struct phr_header { diff --git a/src/server/rpc.c b/src/server/rpc.c index 480f5bb..6b958be 100644 --- a/src/server/rpc.c +++ b/src/server/rpc.c @@ -7,6 +7,7 @@ #include "../shared/sockhelper.h" #include "fileutil.h" #include "picohttpparser/picohttpparser.h" +#include "urldecode.h" #include #include @@ -32,8 +33,9 @@ static int aclCount = 0; static dnbd3_access_rule_t aclRules[MAX_ACLS]; static json_int_t randomRunId; -static bool handleStatus(int sock, struct string *path, int permissions); +static bool handleStatus(int sock, int permissions, struct field *fields, size_t fields_num); static bool sendReply(int sock, const char *status, const char *ctype, const char *payload, ssize_t plen, int keepAlive); +static void parsePath(struct string *path, struct string *file, struct field *getv, size_t *getc); static int getacl(dnbd3_host_t *host); static void addacl(int argc, char **argv, void *data); static void loadAcl(); @@ -48,7 +50,7 @@ void rpc_sendStatsJson(int sock, dnbd3_host_t* host, const void* data, const int sendReply( sock, "403 Forbidden", "text/plain", "Access denied", -1, HTTP_CLOSE ); return; } - char headerBuf[1000]; + char headerBuf[3000]; if ( dataLen > 0 ) { // We call this function internally with a maximum data len of sizeof(dnbd3_request_t) so no bounds checking memcpy( headerBuf, data, dataLen ); @@ -89,12 +91,17 @@ void rpc_sendStatsJson(int sock, dnbd3_host_t* host, const void* data, const int } while ( true ); if ( method.s != NULL && path.s != NULL ) { // Handle stuff + struct string file; + struct field getv[10]; + size_t getc = 10; + parsePath( &path, &file, getv, &getc ); if ( method.s && method.s[0] == 'P' ) { // POST only methods + } // Don't care if GET or POST - if ( STRSTART( path, "/query" ) ) { - ok = handleStatus( sock, &path, permissions ); + if ( STRCMP( file, "/query" ) ) { + ok = handleStatus( sock, permissions, getv, getc ); } else { ok = sendReply( sock, "404 Not found", "text/plain", "Nothing", -1, HTTP_KEEPALIVE ); } @@ -109,33 +116,27 @@ void rpc_sendStatsJson(int sock, dnbd3_host_t* host, const void* data, const int } while (true); } -static bool handleStatus(int sock, struct string *path, int permissions) +static bool handleStatus(int sock, int permissions, struct field *fields, size_t fields_num) { bool ok; bool stats = false, images = false, clients = false, space = false; - if ( strstr( path->s, "stats" ) != NULL ) { - if ( !(permissions & ACL_STATS) ) { - return sendReply( sock, "403 Forbidden", "text/plain", "No permission to access statistics", -1, HTTP_KEEPALIVE ); - } - stats = true; +#define SETVAR(var) if ( !var && STRCMP(fields[i].value, #var) ) var = true + for (size_t i = 0; i < fields_num; ++i) { + if ( !STRCMP( fields[i].name, "q" ) ) continue; + SETVAR(stats); + else SETVAR(space); + else SETVAR(images); + else SETVAR(clients); } - if ( strstr( path->s, "space" ) != NULL ) { - if ( !(permissions & ACL_STATS) ) { - return sendReply( sock, "403 Forbidden", "text/plain", "No permission to access statistics", -1, HTTP_KEEPALIVE ); - } - space = true; +#undef SETVAR + if ( ( stats || space ) && !(permissions & ACL_STATS) ) { + return sendReply( sock, "403 Forbidden", "text/plain", "No permission to access statistics", -1, HTTP_KEEPALIVE ); } - if ( strstr( path->s, "images" ) != NULL ) { - if ( !(permissions & ACL_IMAGE_LIST) ) { - return sendReply( sock, "403 Forbidden", "text/plain", "No permission to access image list", -1, HTTP_KEEPALIVE ); - } - images = true; + if ( images && !(permissions & ACL_IMAGE_LIST) ) { + return sendReply( sock, "403 Forbidden", "text/plain", "No permission to access image list", -1, HTTP_KEEPALIVE ); } - if ( strstr(path->s, "clients" ) != NULL ) { - if ( !(permissions & ACL_CLIENT_LIST) ) { - return sendReply( sock, "403 Forbidden", "text/plain", "No permission to access client list", -1, HTTP_KEEPALIVE ); - } - clients = true; + if ( clients && !(permissions & ACL_CLIENT_LIST) ) { + return sendReply( sock, "403 Forbidden", "text/plain", "No permission to access client list", -1, HTTP_KEEPALIVE ); } // Call this first because it will update the total bytes sent counter json_t *jsonClients = NULL; @@ -197,10 +198,30 @@ static bool sendReply(int sock, const char *status, const char *ctype, const cha // Wait for flush shutdown( sock, SHUT_WR ); while ( read( sock, buffer, sizeof buffer ) > 0 ); + return false; } return true; } +static void parsePath(struct string *path, struct string *file, struct field *getv, size_t *getc) +{ + size_t i = 0; + while ( i < path->l && path->s[i] != '?' ) ++i; + if ( i == path->l ) { + *getc = 0; + *file = *path; + return; + } + file->s = path->s; + file->l = i; + ++i; + path->s += i; + path->l -= i; + urldecode( path, getv, getc ); + path->s -= i; + path->l += i; +} + static int getacl(dnbd3_host_t *host) { if ( aclCount == 0 ) return 0x7fffff; // For now compat mode - no rules defined == all access diff --git a/src/server/urldecode.c b/src/server/urldecode.c new file mode 100644 index 0000000..4553097 --- /dev/null +++ b/src/server/urldecode.c @@ -0,0 +1,61 @@ +#include "urldecode.h" +#include +#include + +#define hex2int(a) do { \ + if ( a >= 'a' ) { \ + a = (char)(a - ( 'a' - 'A' - 10 )); \ + } else if ( a > 'F' ) { \ + goto normie; \ + } else if ( a >= 'A' ) { \ + a = (char)(a - ( 'A' - 10 )); \ + } else if ( a < '0' || a > '9' ) { \ + goto normie; \ + } else { \ + a = (char)(a - '0'); \ + } \ +} while (0) + +void urldecode(struct string* str, struct field *out, size_t *out_num) +{ + char *src = (char*)str->s; + char *dst = src; + const char * const end = str->s + str->l; + char a, b; + size_t max_out = *out_num; + *out_num = 0; + do { + if ( *out_num == max_out ) return; + out->name.s = dst; + while ( src < end && *src != '=' ) { + *dst++ = *src++; + } + if ( src == end ) return; + out->name.l = (size_t)( dst - out->name.s ); + ++src; + out->value.s = ++dst; + while ( src < end && *src != '&' ) { + if ( *src == '%' && src + 2 < end ) { + if ( src[1] > 'f' || src[2] > 'f' ) goto normie; + a = src[1]; + hex2int(a); + b = src[2]; + hex2int(b); + *dst++ = (char)( (16 * a) + b ); + src += 3; + } else if (*src == '+') { + *dst++ = (char)' '; + ++src; + } else { + normie:; + *dst++ = *src++; + } + } + out->value.l = (size_t)( dst - out->value.s ); + out++; + (*out_num)++; + if ( src++ >= end ) return; + ++dst; + } while ( 1 ); +} + diff --git a/src/server/urldecode.h b/src/server/urldecode.h new file mode 100644 index 0000000..e27f8f8 --- /dev/null +++ b/src/server/urldecode.h @@ -0,0 +1,19 @@ +#ifndef _URLENCODE_H_ +#define _URLENCODE_H_ + +#include "picohttpparser/picohttpparser.h" + +struct field { + struct string name; + struct string value; +}; + +/** + * decode given x-form-urlencoded string. Breaks constness rules by + * casting the const char* s from str to char* and modifying it, then + * populating out with pointers into it, so make sure the memory + * is actually writable. + */ +void urldecode(struct string* str, struct field *out, size_t *out_num); + +#endif -- cgit v1.2.3-55-g7522