| /* |
| * Copyright (C) Tildeslash Ltd. All rights reserved. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU Affero General Public License version 3. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU Affero General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * In addition, as a special exception, the copyright holders give |
| * permission to link the code of portions of this program with the |
| * OpenSSL library under certain conditions as described in each |
| * individual source file, and distribute linked combinations |
| * including the two. |
| * |
| * You must obey the GNU Affero General Public License in all respects |
| * for all of the code used other than OpenSSL. |
| */ |
| |
| #include "config.h" |
| |
| #ifdef HAVE_STDIO_H |
| #include <stdio.h> |
| #endif |
| |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| |
| #ifdef HAVE_ERRNO_H |
| #include <errno.h> |
| #endif |
| |
| #ifdef HAVE_STDARG_H |
| #include <stdarg.h> |
| #endif |
| |
| #ifdef HAVE_SYS_TYPES_H |
| #include <sys/types.h> |
| #endif |
| |
| #ifdef HAVE_SYS_SOCKET_H |
| #include <sys/socket.h> |
| #endif |
| |
| #if TIME_WITH_SYS_TIME |
| # include <sys/time.h> |
| # include <time.h> |
| #else |
| # if HAVE_SYS_TIME_H |
| # include <sys/time.h> |
| # else |
| # include <time.h> |
| # endif |
| #endif |
| |
| #ifdef HAVE_SETJMP_H |
| #include <setjmp.h> |
| #endif |
| |
| #ifdef HAVE_STRING_H |
| #include <string.h> |
| #endif |
| |
| #ifdef HAVE_STRINGS_H |
| #include <strings.h> |
| #endif |
| |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| |
| #ifdef HAVE_LIMITS_H |
| #include <limits.h> |
| #endif |
| |
| #include "processor.h" |
| #include "base64.h" |
| |
| // libmonit |
| #include "util/Str.h" |
| |
| |
| /** |
| * A naive quasi HTTP Processor module that can handle HTTP requests |
| * received from a client, and return responses based on those |
| * requests. |
| * |
| * This Processor delegates the actual handling of the request and |
| * reponse to so called cervlets, which must implement two methods; |
| * doGet and doPost. |
| * |
| * NOTES |
| * This Processor is command oriented and if a second slash '/' is |
| * found in the URL it's asumed to be the PATHINFO. In other words |
| * this processor perceive an URL as: |
| * |
| * /COMMAND?QUERYSTRING/PATHINFO |
| * |
| * The doGet/doPost routines act's on the COMMAND. See the |
| * cervlet.c code in this dir. for an example. |
| * |
| * @file |
| */ |
| |
| |
| /* -------------------------------------------------------------- Prototypes */ |
| |
| |
| static void do_service(Socket_T); |
| static void destroy_entry(void *); |
| static char *get_date(char *, int); |
| static char *get_server(char *, int); |
| static void create_headers(HttpRequest); |
| static void send_response(HttpResponse); |
| static int basic_authenticate(HttpRequest); |
| static void done(HttpRequest, HttpResponse); |
| static void destroy_HttpRequest(HttpRequest); |
| static void reset_response(HttpResponse res); |
| static HttpParameter parse_parameters(char *); |
| static int create_parameters(HttpRequest req); |
| static void destroy_HttpResponse(HttpResponse); |
| static HttpRequest create_HttpRequest(Socket_T); |
| static void internal_error(Socket_T, int, char *); |
| static HttpResponse create_HttpResponse(Socket_T); |
| static int is_authenticated(HttpRequest, HttpResponse); |
| static int get_next_token(char *s, int *cursor, char **r); |
| |
| |
| /* ------------------------------------------------------------------ Public */ |
| |
| |
| /** |
| * Process a HTTP request. This is done by dispatching to the service |
| * function. |
| * @param s A Socket_T representing the client connection |
| */ |
| void *http_processor(Socket_T s) { |
| |
| if(! can_read(socket_get_socket(s), REQUEST_TIMEOUT)) { |
| internal_error(s, SC_REQUEST_TIMEOUT, "Time out when handling the Request"); |
| } else { |
| do_service(s); |
| } |
| socket_free(&s); |
| |
| return NULL; |
| |
| } |
| |
| |
| /** |
| * Callback for implementors of cervlet functions. |
| * @param doGetFunc doGet function |
| * @param doPostFunc doPost function |
| */ |
| void add_Impl(void(*doGet)(HttpRequest, HttpResponse), void(*doPost)(HttpRequest, HttpResponse)) { |
| Impl.doGet= doGet; |
| Impl.doPost= doPost; |
| } |
| |
| |
| /** |
| * Send an error message |
| * @param res HttpResponse object |
| * @param code Error Code to lookup and send |
| * @param msg Optional error message (may be NULL) |
| */ |
| void send_error(HttpResponse res, int code, const char *msg) { |
| char server[STRLEN]; |
| const char *err= get_status_string(code); |
| |
| reset_response(res); |
| set_content_type(res, "text/html"); |
| set_status(res, code); |
| StringBuffer_append(res->outputbuffer, |
| "<html><head><title>%d %s</title></head>"\ |
| "<body bgcolor=#FFFFFF><h2>%s</h2>%s<p>"\ |
| "<hr><a href='%s'><font size=-1>%s</font></a>"\ |
| "</body></html>\r\n", |
| code, err, err, msg?msg:"", SERVER_URL, get_server(server, STRLEN)); |
| DEBUG("HttpRequest error: %s %d %s\n", SERVER_PROTOCOL, code, msg ? msg : err); |
| } |
| |
| |
| /* -------------------------------------------------------------- Properties */ |
| |
| |
| /** |
| * Adds a response header with the given name and value. If the header |
| * had already been set the new value overwrites the previous one. |
| * @param res HttpResponse object |
| * @param name Header key name |
| * @param value Header key value |
| */ |
| void set_header(HttpResponse res, const char *name, const char *value) { |
| HttpHeader h= NULL; |
| |
| ASSERT(res); |
| ASSERT(name); |
| |
| NEW(h); |
| h->name= Str_dup(name); |
| h->value= Str_dup(value); |
| if(res->headers) { |
| HttpHeader n, p; |
| for( n= p= res->headers; p; n= p, p= p->next) { |
| if(!strcasecmp(p->name, name)) { |
| FREE(p->value); |
| p->value= Str_dup(value); |
| destroy_entry(h); |
| return; |
| } |
| } |
| n->next= h; |
| } else { |
| res->headers= h; |
| } |
| } |
| |
| |
| /** |
| * Sets the status code for the response |
| * @param res HttpResponse object |
| * @param code A HTTP status code <100-510> |
| * @param msg The status code string message |
| */ |
| void set_status(HttpResponse res, int code) { |
| res->status= code; |
| res->status_msg= get_status_string(code); |
| } |
| |
| |
| /** |
| * Set the response content-type |
| * @param res HttpResponse object |
| * @param mime Mime content type, e.g. text/html |
| */ |
| void set_content_type(HttpResponse res, const char *mime) { |
| set_header(res, "Content-Type", mime); |
| } |
| |
| |
| /** |
| * Returns the value of the specified header |
| * @param req HttpRequest object |
| * @param name Header name to lookup the value for |
| * @return The value of the specified header, NULL if not found |
| */ |
| const char *get_header(HttpRequest req, const char *name) { |
| HttpHeader p; |
| |
| for(p= req->headers; p; p= p->next) { |
| if(!strcasecmp(p->name, name)) { |
| return (p->value); |
| } |
| } |
| return NULL; |
| } |
| |
| |
| /** |
| * Returns the value of the specified parameter |
| * @param req HttpRequest object |
| * @param name The request parameter key to lookup the value for |
| * @return The value of the specified parameter, or NULL if not found |
| */ |
| const char *get_parameter(HttpRequest req, const char *name) { |
| HttpParameter p; |
| |
| for(p= req->params; p; p= p->next) { |
| if(!strcasecmp(p->name, name)) { |
| return (p->value); |
| } |
| } |
| return NULL; |
| } |
| |
| |
| /** |
| * Returns a string containing all (extra) headers found in the |
| * response. The headers are newline separated in the returned |
| * string. |
| * @param res HttpResponse object |
| * @return A String containing all headers set in the Response object |
| */ |
| char *get_headers(HttpResponse res) { |
| HttpHeader p; |
| char buf[RES_STRLEN]; |
| char *b= buf; |
| |
| *buf=0; |
| for(p= res->headers; (((b-buf) + STRLEN) < RES_STRLEN) && p; p= p->next) { |
| b+= snprintf(b, STRLEN,"%s: %s\r\n", p->name, p->value); |
| } |
| return buf[0]?Str_dup(buf):NULL; |
| } |
| |
| |
| /** |
| * Lookup the corresponding HTTP status string for the given status |
| * code |
| * @param status A HTTP status code |
| * @return A default status message for the specified HTTP status |
| * code. |
| */ |
| const char *get_status_string(int status) { |
| switch (status) { |
| case SC_OK: |
| return "OK"; |
| case SC_ACCEPTED: |
| return "Accepted"; |
| case SC_BAD_GATEWAY: |
| return "Bad Gateway"; |
| case SC_BAD_REQUEST: |
| return "Bad Request"; |
| case SC_CONFLICT: |
| return "Conflict"; |
| case SC_CONTINUE: |
| return "Continue"; |
| case SC_CREATED: |
| return "Created"; |
| case SC_EXPECTATION_FAILED: |
| return "Expectation Failed"; |
| case SC_FORBIDDEN: |
| return "Forbidden"; |
| case SC_GATEWAY_TIMEOUT: |
| return "Gateway Timeout"; |
| case SC_GONE: |
| return "Gone"; |
| case SC_VERSION_NOT_SUPPORTED: |
| return "HTTP Version Not Supported"; |
| case SC_INTERNAL_SERVER_ERROR: |
| return "Internal Server Error"; |
| case SC_LENGTH_REQUIRED: |
| return "Length Required"; |
| case SC_METHOD_NOT_ALLOWED: |
| return "Method Not Allowed"; |
| case SC_MOVED_PERMANENTLY: |
| return "Moved Permanently"; |
| case SC_MOVED_TEMPORARILY: |
| return "Moved Temporarily"; |
| case SC_MULTIPLE_CHOICES: |
| return "Multiple Choices"; |
| case SC_NO_CONTENT: |
| return "No Content"; |
| case SC_NON_AUTHORITATIVE: |
| return "Non-Authoritative Information"; |
| case SC_NOT_ACCEPTABLE: |
| return "Not Acceptable"; |
| case SC_NOT_FOUND: |
| return "Not Found"; |
| case SC_NOT_IMPLEMENTED: |
| return "Not Implemented"; |
| case SC_NOT_MODIFIED: |
| return "Not Modified"; |
| case SC_PARTIAL_CONTENT: |
| return "Partial Content"; |
| case SC_PAYMENT_REQUIRED: |
| return "Payment Required"; |
| case SC_PRECONDITION_FAILED: |
| return "Precondition Failed"; |
| case SC_PROXY_AUTHENTICATION_REQUIRED: |
| return "Proxy Authentication Required"; |
| case SC_REQUEST_ENTITY_TOO_LARGE: |
| return "Request Entity Too Large"; |
| case SC_REQUEST_TIMEOUT: |
| return "Request Timeout"; |
| case SC_REQUEST_URI_TOO_LARGE: |
| return "Request URI Too Large"; |
| case SC_RANGE_NOT_SATISFIABLE: |
| return "Requested Range Not Satisfiable"; |
| case SC_RESET_CONTENT: |
| return "Reset Content"; |
| case SC_SEE_OTHER: |
| return "See Other"; |
| case SC_SERVICE_UNAVAILABLE: |
| return "Service Unavailable"; |
| case SC_SWITCHING_PROTOCOLS: |
| return "Switching Protocols"; |
| case SC_UNAUTHORIZED: |
| return "Unauthorized"; |
| case SC_UNSUPPORTED_MEDIA_TYPE: |
| return "Unsupported Media Type"; |
| case SC_USE_PROXY: |
| return "Use Proxy"; |
| default: { |
| return "Unknown HTTP status"; |
| } |
| } |
| } |
| |
| |
| /* ----------------------------------------------------------------- Private */ |
| |
| |
| /** |
| * Receives standard HTTP requests from a client socket and dispatches |
| * them to the doXXX methods defined in a cervlet module. |
| */ |
| static void do_service(Socket_T s) { |
| volatile HttpResponse res= create_HttpResponse(s); |
| volatile HttpRequest req= create_HttpRequest(s); |
| |
| if(res && req) { |
| if(is_authenticated(req, res)) { |
| if(IS(req->method, METHOD_GET)) { |
| Impl.doGet(req, res); |
| } else if(IS(req->method, METHOD_POST)) { |
| Impl.doPost(req, res); |
| } else { |
| send_error(res, SC_NOT_IMPLEMENTED, "Method not implemented"); |
| } |
| } |
| send_response(res); |
| } |
| done(req, res); |
| } |
| |
| |
| /** |
| * Return a (RFC1123) Date string |
| */ |
| static char *get_date(char *result, int size) { |
| time_t now; |
| |
| time(&now); |
| if(strftime(result, size, DATEFMT, gmtime(&now)) <= 0) { |
| *result= 0; |
| } |
| return result; |
| } |
| |
| |
| /** |
| * Return this server name + version |
| */ |
| static char *get_server(char *result, int size) { |
| snprintf(result, size, "%s %s", SERVER_NAME, Run.httpdsig?SERVER_VERSION:""); |
| return result; |
| } |
| |
| |
| /** |
| * Send the response to the client. If the response has already been |
| * commited, this function does nothing. |
| */ |
| static void send_response(HttpResponse res) { |
| Socket_T S= res->S; |
| |
| if(!res->is_committed) { |
| char date[STRLEN]; |
| char server[STRLEN]; |
| char *headers= get_headers(res); |
| int length = StringBuffer_length(res->outputbuffer); |
| |
| res->is_committed= TRUE; |
| get_date(date, STRLEN); |
| get_server(server, STRLEN); |
| socket_print(S, "%s %d %s\r\n", res->protocol, res->status, |
| res->status_msg); |
| socket_print(S, "Date: %s\r\n", date); |
| socket_print(S, "Server: %s\r\n", server); |
| socket_print(S, "Content-Length: %d\r\n", length); |
| socket_print(S, "Connection: close\r\n"); |
| if(headers) |
| socket_print(S, "%s", headers); |
| socket_print(S, "\r\n"); |
| if(length) |
| socket_write(S, (unsigned char *)StringBuffer_toString(res->outputbuffer), length); |
| FREE(headers); |
| } |
| } |
| |
| |
| /* --------------------------------------------------------------- Factories */ |
| |
| |
| /** |
| * Returns a new HttpRequest object wrapping the client request |
| */ |
| static HttpRequest create_HttpRequest(Socket_T S) { |
| HttpRequest req= NULL; |
| char url[REQ_STRLEN]; |
| char line[REQ_STRLEN]; |
| char protocol[STRLEN]; |
| char method[REQ_STRLEN]; |
| |
| if(socket_readln(S, line, REQ_STRLEN) == NULL) { |
| internal_error(S, SC_BAD_REQUEST, "No request found"); |
| return NULL; |
| } |
| Str_chomp(line); |
| if(sscanf(line, "%1023s %1023s HTTP/%3[1.0]", method, url, protocol) != 3) { |
| internal_error(S, SC_BAD_REQUEST, "Cannot parse request"); |
| return NULL; |
| } |
| if(strlen(url) >= MAX_URL_LENGTH) { |
| internal_error(S, SC_BAD_REQUEST, "[error] URL too long"); |
| return NULL; |
| } |
| NEW(req); |
| req->S= S; |
| Util_urlDecode(url); |
| req->url= Str_dup(url); |
| req->method= Str_dup(method); |
| req->protocol= Str_dup(protocol); |
| create_headers(req); |
| if(!create_parameters(req)) { |
| destroy_HttpRequest(req); |
| internal_error(S, SC_BAD_REQUEST, "Cannot parse Request parameters"); |
| return NULL; |
| } |
| return req; |
| } |
| |
| |
| /** |
| * Returns a new HttpResponse object wrapping a default response. Use |
| * the set_XXX methods to change the object. |
| */ |
| static HttpResponse create_HttpResponse(Socket_T S) { |
| HttpResponse res= NULL; |
| |
| NEW(res); |
| res->S= S; |
| res->status= SC_OK; |
| res->outputbuffer= StringBuffer_create(256); |
| res->is_committed= FALSE; |
| res->protocol= SERVER_PROTOCOL; |
| res->status_msg= get_status_string(SC_OK); |
| return res; |
| } |
| |
| |
| /** |
| * Create HTTP headers for the given request |
| */ |
| static void create_headers(HttpRequest req) { |
| Socket_T S; |
| char *value; |
| HttpHeader header= NULL; |
| char line[REQ_STRLEN]; |
| |
| S= req->S; |
| while(1) { |
| if(! socket_readln(S, line, sizeof(line))) |
| break; |
| if(!strcasecmp(line, "\r\n") || !strcasecmp(line, "\n")) |
| break; |
| if(NULL != (value= strchr(line, ':'))) { |
| NEW(header); |
| *value++= 0; |
| Str_trim(line); |
| Str_trim(value); |
| Str_chomp(value); |
| header->name= Str_dup(line); |
| header->value= Str_dup(value); |
| header->next= req->headers; |
| req->headers= header; |
| } |
| } |
| } |
| |
| |
| /** |
| * Create parameters for the given request. Returns FALSE if an error |
| * occurs. |
| */ |
| static int create_parameters(HttpRequest req) { |
| char query_string[REQ_STRLEN]= {0}; |
| |
| if(IS(req->method, METHOD_POST) && get_header(req, "Content-Length")) { |
| int n; |
| int len; |
| Socket_T S = req->S; |
| const char *cl = get_header(req, "Content-Length"); |
| if(! cl || sscanf(cl, "%d", &len) != 1) { |
| return FALSE; |
| } |
| if(len < 0 || len >= REQ_STRLEN) |
| return FALSE; |
| if(len==0) |
| return TRUE; |
| if(((n= socket_read(S, query_string, len)) <= 0) || (n != len)) { |
| return FALSE; |
| } |
| query_string[n]= 0; |
| } else if(IS(req->method, METHOD_GET)) { |
| char *p; |
| if(NULL != (p= strchr(req->url, '?'))) { |
| *p++= 0; |
| strncpy(query_string, p, sizeof(query_string) - 1); |
| query_string[sizeof(query_string) - 1] = 0; |
| } |
| } |
| if(*query_string) { |
| char *p; |
| if(NULL != (p= strchr(query_string, '/'))) { |
| *p++= 0; |
| req->pathinfo= Str_dup(p); |
| } |
| req->params= parse_parameters(query_string); |
| } |
| return TRUE; |
| } |
| |
| |
| /* ----------------------------------------------------------------- Cleanup */ |
| |
| |
| /** |
| * Clear the response output buffer and headers |
| */ |
| static void reset_response(HttpResponse res) { |
| if(res->headers) { |
| destroy_entry(res->headers); |
| res->headers= NULL; /* Release Pragma */ |
| } |
| StringBuffer_clear(res->outputbuffer); |
| } |
| |
| |
| /** |
| * Finalize the request and response object. |
| */ |
| static void done(HttpRequest req, HttpResponse res) { |
| destroy_HttpRequest(req); |
| destroy_HttpResponse(res); |
| } |
| |
| |
| /** |
| * Free a HttpRequest object |
| */ |
| static void destroy_HttpRequest(HttpRequest req) { |
| if(req) { |
| FREE(req->method); |
| FREE(req->url); |
| FREE(req->pathinfo); |
| FREE(req->protocol); |
| FREE(req->remote_user); |
| if(req->headers) |
| destroy_entry(req->headers); |
| if(req->params) |
| destroy_entry(req->params); |
| FREE(req); |
| } |
| } |
| |
| |
| /** |
| * Free a HttpResponse object |
| */ |
| static void destroy_HttpResponse(HttpResponse res) { |
| if(res) { |
| StringBuffer_free(&(res->outputbuffer)); |
| if(res->headers) |
| destroy_entry(res->headers); |
| FREE(res); |
| } |
| } |
| |
| |
| /** |
| * Free a (linked list of) http entry object(s). Both HttpHeader and |
| * HttpParameter are of this type. |
| */ |
| static void destroy_entry(void *p) { |
| struct entry *h= p; |
| |
| if(h->next) { |
| destroy_entry(h->next); |
| } |
| FREE(h->name); |
| FREE(h->value); |
| FREE(h); |
| } |
| |
| |
| /* ----------------------------------------------------- Checkers/Validators */ |
| |
| |
| /** |
| * Do Basic Authentication if this auth. style is allowed. |
| */ |
| static int is_authenticated(HttpRequest req, HttpResponse res) { |
| if(Run.credentials!=NULL) { |
| if(! basic_authenticate(req)) { |
| send_error(res, SC_UNAUTHORIZED, |
| "You are <b>not</b> authorized to access <i>monit</i>. " |
| "Either you supplied the wrong credentials (e.g. bad " |
| "password), or your browser doesn't understand how to supply " |
| "the credentials required"); |
| set_header(res, "WWW-Authenticate", "Basic realm=\"monit\""); |
| return FALSE; |
| } |
| } |
| return TRUE; |
| } |
| |
| |
| /** |
| * Authenticate the basic-credentials (uname/password) submitted by |
| * the user. |
| */ |
| static int basic_authenticate(HttpRequest req) { |
| size_t n; |
| char *password; |
| char buf[STRLEN]; |
| char uname[STRLEN]; |
| const char *credentials= get_header(req, "Authorization"); |
| |
| if(! (credentials && Str_startsWith(credentials, "Basic "))) { |
| return FALSE; |
| } |
| strncpy(buf, &credentials[6], sizeof(buf) - 1); |
| buf[sizeof(buf) - 1] = 0; |
| if((n= decode_base64((unsigned char*)uname, buf))<=0) { |
| return FALSE; |
| } |
| uname[n]= 0; |
| password= strchr(uname, ':'); |
| if(password==NULL) { |
| return FALSE; |
| } |
| *password++= 0; |
| if(*uname==0 || *password==0) { |
| return FALSE; |
| } |
| /* Check if user exist */ |
| if(NULL==Util_getUserCredentials(uname)) { |
| LogError("Warning: Client '%s' supplied unknown user '%s'" |
| " accessing monit httpd\n", socket_get_remote_host(req->S), uname); |
| return FALSE; |
| } |
| /* Check if user has supplied the right password */ |
| if(! Util_checkCredentials(uname, password)) { |
| LogError("Warning: Client '%s' supplied wrong password for user '%s'" |
| " accessing monit httpd\n", socket_get_remote_host(req->S), uname); |
| return FALSE; |
| } |
| req->remote_user= Str_dup(uname); |
| return TRUE; |
| } |
| |
| |
| /* --------------------------------------------------------------- Utilities */ |
| |
| |
| /** |
| * Send an error message to the client. This is a helper function, |
| * used internal if the service function fails to setup the framework |
| * properly; i.e. with a valid HttpRequest and a valid HttpResponse. |
| */ |
| static void internal_error(Socket_T S, int status, char *msg) { |
| char date[STRLEN]; |
| char server[STRLEN]; |
| const char *status_msg= get_status_string(status); |
| |
| get_date(date, STRLEN); |
| get_server(server, STRLEN); |
| socket_print(S, |
| "%s %d %s\r\n" |
| "Date: %s\r\n" |
| "Server: %s\r\n" |
| "Content-Type: text/html\r\n" |
| "Connection: close\r\n" |
| "\r\n" |
| "<html><head><title>%s</title></head>" |
| "<body bgcolor=#FFFFFF><h2>%s</h2>%s<p>" |
| "<hr><a href='%s'><font size=-1>%s</font></a>" |
| "</body></html>\r\n", |
| SERVER_PROTOCOL, status, status_msg, date, server, |
| status_msg, status_msg, msg, SERVER_URL, server); |
| DEBUG("HttpRequest error: %s %d %s\n", SERVER_PROTOCOL, status, msg ? msg : status_msg); |
| } |
| |
| |
| /** |
| * Parse request parameters from the given query string and return a |
| * linked list of HttpParameters |
| */ |
| static HttpParameter parse_parameters(char *query_string) { |
| #define KEY 1 |
| #define VALUE 2 |
| int token; |
| int cursor= 0; |
| char *key= NULL; |
| char *value= NULL; |
| HttpParameter head= NULL; |
| |
| while((token= get_next_token(query_string, &cursor, &value))) { |
| if(token==KEY) |
| key= value; |
| else if(token==VALUE) { |
| HttpParameter p= NULL; |
| if(!key) goto error; |
| NEW(p); |
| p->name= key; |
| p->value= value; |
| p->next= head; |
| head= p; |
| key= NULL; |
| } |
| } |
| return head; |
| error: |
| FREE(key); |
| FREE(value); |
| if ( head != NULL ) { |
| destroy_entry(head); |
| } |
| return NULL; |
| } |
| |
| |
| /** |
| * A mini-scanner for tokenizing a query string |
| */ |
| static int get_next_token(char *s, int *cursor, char **r) { |
| int i= *cursor; |
| |
| while(s[*cursor]) { |
| if(s[*cursor+1]=='=') { |
| *cursor+= 1; |
| *r= Str_ndup(&s[i], (*cursor-i)); |
| return KEY; |
| } |
| if(s[*cursor]=='=') { |
| while(s[*cursor] && s[*cursor]!='&') *cursor+= 1; |
| if(s[*cursor]=='&') { |
| *r= Str_ndup(&s[i+1], (*cursor-i)-1); |
| *cursor+= 1; |
| } else |
| *r= Str_ndup(&s[i+1], (*cursor-i)); |
| return VALUE; |
| } |
| *cursor+= 1; |
| } |
| return FALSE; |
| } |