mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
677 lines
20 KiB
C
677 lines
20 KiB
C
/*
|
|
This file is part of libmicrohttpd
|
|
(C) 2007 Daniel Pittman and Christian Grothoff
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library 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
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/**
|
|
* @file postprocessor.c
|
|
* @brief Methods for parsing POST data
|
|
* @author Christian Grothoff
|
|
*/
|
|
|
|
#include "internal.h"
|
|
|
|
/**
|
|
* States in the PP parser's state machine.
|
|
*/
|
|
enum PP_State
|
|
{
|
|
|
|
PP_Init = 0,
|
|
|
|
PP_HaveKey = 1,
|
|
|
|
PP_ExpectNewLine = 2,
|
|
|
|
PP_ExpectNewLineR = 3,
|
|
|
|
PP_ExpectNewLineN = 4,
|
|
|
|
PP_Headers = 5,
|
|
|
|
PP_SkipRNRN = 6,
|
|
|
|
PP_SkipNRN = 7,
|
|
|
|
PP_SkipRN = 8,
|
|
|
|
PP_SkipN = 9,
|
|
|
|
PP_ValueToBoundary = 10,
|
|
|
|
PP_FinalDash = 11,
|
|
|
|
PP_Error = 9999,
|
|
|
|
|
|
};
|
|
|
|
/**
|
|
* Internal state of the post-processor.
|
|
*/
|
|
struct MHD_PostProcessor
|
|
{
|
|
|
|
/**
|
|
* The connection for which we are doing
|
|
* POST processing.
|
|
*/
|
|
struct MHD_Connection *connection;
|
|
|
|
/**
|
|
* Function to call with POST data.
|
|
*/
|
|
MHD_PostDataIterator ikvi;
|
|
|
|
/**
|
|
* Extra argument to ikvi.
|
|
*/
|
|
void *cls;
|
|
|
|
/**
|
|
* Encoding as given by the headers of the
|
|
* connection.
|
|
*/
|
|
const char *encoding;
|
|
|
|
/**
|
|
* Pointer to the name given in disposition.
|
|
*/
|
|
char *content_disposition;
|
|
|
|
/**
|
|
* Pointer to the (current) content type.
|
|
*/
|
|
char *content_type;
|
|
|
|
/**
|
|
* Pointer to the (current) filename.
|
|
*/
|
|
char *filename;
|
|
|
|
/**
|
|
* Pointer to the (current) encoding.
|
|
*/
|
|
char *transfer_encoding;
|
|
|
|
/**
|
|
* Unprocessed value bytes due to escape
|
|
* sequences (URL-encoding only).
|
|
*/
|
|
char xbuf[8];
|
|
|
|
/**
|
|
* Size of our buffer for the key.
|
|
*/
|
|
unsigned int buffer_size;
|
|
|
|
/**
|
|
* Current position in the key buffer.
|
|
*/
|
|
unsigned int buffer_pos;
|
|
|
|
/**
|
|
* Current position in xbuf.
|
|
*/
|
|
unsigned int xbuf_pos;
|
|
|
|
/**
|
|
* Current offset in the value being processed.
|
|
*/
|
|
unsigned int value_offset;
|
|
|
|
/**
|
|
* State of the parser.
|
|
*/
|
|
enum PP_State state;
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
* Create a PostProcessor.
|
|
*
|
|
* A PostProcessor can be used to (incrementally)
|
|
* parse the data portion of a POST request.
|
|
*
|
|
* @param connection the connection on which the POST is
|
|
* happening (used to determine the POST format)
|
|
* @param buffer_size maximum number of bytes to use for
|
|
* internal buffering (used only for the parsing,
|
|
* specifically the parsing of the keys). A
|
|
* tiny value (256-1024) should be sufficient.
|
|
* Do NOT use 0.
|
|
* @param ikvi iterator to be called with the parsed data
|
|
* @param cls first argument to ikvi
|
|
* @return NULL on error (out of memory, unsupported encoding),
|
|
* otherwise a PP handle
|
|
*/
|
|
struct MHD_PostProcessor *
|
|
MHD_create_post_processor (struct MHD_Connection *connection,
|
|
unsigned int buffer_size,
|
|
MHD_PostDataIterator ikvi, void *cls)
|
|
{
|
|
struct MHD_PostProcessor *ret;
|
|
const char *encoding;
|
|
|
|
if ((buffer_size < 256) || (connection == NULL) || (ikvi == NULL))
|
|
abort ();
|
|
encoding = MHD_lookup_connection_value (connection,
|
|
MHD_HEADER_KIND,
|
|
MHD_HTTP_HEADER_CONTENT_TYPE);
|
|
if (encoding == NULL)
|
|
return NULL;
|
|
if ((0 != strcasecmp (MHD_HTTP_POST_ENCODING_FORM_URLENCODED,
|
|
encoding)) &&
|
|
(0 != strcasecmp (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA, encoding)))
|
|
return NULL;
|
|
ret = malloc (sizeof (struct MHD_PostProcessor) + buffer_size + 1);
|
|
if (ret == NULL)
|
|
return NULL;
|
|
memset (ret, 0, sizeof (struct MHD_PostProcessor));
|
|
ret->connection = connection;
|
|
ret->ikvi = ikvi;
|
|
ret->cls = cls;
|
|
ret->encoding = encoding;
|
|
ret->buffer_size = buffer_size;
|
|
ret->state = PP_Init;
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* On-stack buffer that we use for un-escaping of the value.
|
|
*/
|
|
#define XBUF_SIZE 1024
|
|
|
|
/**
|
|
* Process url-encoded POST data.
|
|
*/
|
|
static int
|
|
post_process_urlencoded (struct MHD_PostProcessor *pp,
|
|
const char *post_data, unsigned int post_data_len)
|
|
{
|
|
unsigned int equals;
|
|
unsigned int amper;
|
|
unsigned int poff;
|
|
unsigned int xoff;
|
|
unsigned int delta;
|
|
char *buf;
|
|
char xbuf[XBUF_SIZE + 1];
|
|
|
|
buf = (char *) &pp[1];
|
|
poff = 0;
|
|
while (poff < post_data_len)
|
|
{
|
|
switch (pp->state)
|
|
{
|
|
case PP_Init:
|
|
equals = 0;
|
|
while ((equals + poff < post_data_len) &&
|
|
(post_data[equals + poff] != '='))
|
|
equals++;
|
|
if (equals + pp->buffer_pos > pp->buffer_size)
|
|
{
|
|
pp->state = PP_Error; /* out of memory */
|
|
return MHD_NO;
|
|
}
|
|
memcpy (&buf[pp->buffer_pos], &post_data[poff], equals);
|
|
pp->buffer_pos += equals;
|
|
if (equals + poff == post_data_len)
|
|
return MHD_YES; /* no '=' yet */
|
|
buf[pp->buffer_pos] = '\0'; /* 0-terminate key */
|
|
pp->buffer_pos = 0; /* reset for next key */
|
|
MHD_http_unescape (buf);
|
|
poff += equals + 1;
|
|
pp->state = PP_HaveKey;
|
|
pp->value_offset = 0;
|
|
break;
|
|
case PP_HaveKey:
|
|
/* obtain rest of value from previous iteration */
|
|
memcpy (xbuf, pp->xbuf, pp->xbuf_pos);
|
|
xoff = pp->xbuf_pos;
|
|
pp->xbuf_pos = 0;
|
|
|
|
/* find last position in input buffer that is part of the value */
|
|
amper = 0;
|
|
while ((amper + poff < post_data_len) &&
|
|
(post_data[amper + poff] != '&') &&
|
|
(post_data[amper + poff] != '\n') &&
|
|
(post_data[amper + poff] != '\r'))
|
|
amper++;
|
|
|
|
/* compute delta, the maximum number of bytes that we will be able to
|
|
process right now (either amper-limited of xbuf-size limited) */
|
|
delta = amper;
|
|
if (delta > XBUF_SIZE - xoff)
|
|
delta = XBUF_SIZE - xoff;
|
|
|
|
/* move input into processing buffer */
|
|
memcpy (&xbuf[xoff], &post_data[poff], delta);
|
|
xoff += delta;
|
|
poff += delta;
|
|
|
|
/* find if escape sequence is at the end of the processing buffer;
|
|
if so, exclude those from processing (reduce delta to point at
|
|
end of processed region) */
|
|
delta = xoff;
|
|
if ((delta > 0) && (xbuf[delta - 1] == '%'))
|
|
delta--;
|
|
else if ((delta > 1) && (xbuf[delta - 2] == '%'))
|
|
delta -= 2;
|
|
|
|
/* if we have an incomplete escape sequence, save it to
|
|
pp->xbuf for later */
|
|
if (delta < xoff)
|
|
{
|
|
memcpy (pp->xbuf, &xbuf[delta], xoff - delta);
|
|
pp->xbuf_pos = xoff - delta;
|
|
xoff = delta;
|
|
}
|
|
|
|
/* If we have nothing to do (delta == 0) and
|
|
not just because the value is empty (are
|
|
waiting for more data), go for next iteration */
|
|
if ((xoff == 0) && (poff == post_data_len))
|
|
continue;
|
|
|
|
/* unescape */
|
|
xbuf[xoff] = '\0'; /* 0-terminate in preparation */
|
|
MHD_http_unescape (xbuf);
|
|
|
|
/* finally: call application! */
|
|
pp->ikvi (pp->cls, MHD_POSTDATA_KIND, (const char *) &pp[1], /* key */
|
|
NULL, NULL, NULL, xbuf, pp->value_offset, xoff);
|
|
pp->value_offset += xoff;
|
|
|
|
/* are we done with the value? */
|
|
if (poff < post_data_len)
|
|
{
|
|
/* we found the end of the value! */
|
|
pp->state = PP_Init;
|
|
poff++; /* skip '&' or new-lines */
|
|
|
|
if ((post_data[poff - 1] == '\n') ||
|
|
(post_data[poff - 1] == '\r'))
|
|
pp->state = PP_ExpectNewLine;
|
|
}
|
|
break;
|
|
case PP_ExpectNewLine:
|
|
if ((post_data[poff] == '\n') || (post_data[poff] == '\r'))
|
|
{
|
|
poff++;
|
|
/* we are done, report error if we receive any more... */
|
|
pp->state = PP_Error;
|
|
return MHD_YES;
|
|
}
|
|
return MHD_NO;
|
|
case PP_Error:
|
|
return MHD_NO;
|
|
default:
|
|
abort (); /* should never happen! */
|
|
}
|
|
}
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* If the given line matches the prefix, strdup the
|
|
* rest of the line into the suffix ptr.
|
|
*
|
|
* @return MHD_YES if there was a match, MHD_NO if not
|
|
*/
|
|
static int
|
|
try_match_header (const char *prefix, char *line, char **suffix)
|
|
{
|
|
if (0 == strncasecmp (prefix, line, strlen (prefix)))
|
|
{
|
|
*suffix = strdup (&line[strlen (prefix)]);
|
|
return MHD_YES;
|
|
}
|
|
return MHD_NO;
|
|
}
|
|
|
|
/**
|
|
* Decode multipart POST data.
|
|
*
|
|
* TODO: If the content-type is multipart/mixed, we do not do anything
|
|
* special. However, we should probably break the individual values
|
|
* apart and give them to the callback individually (will require some
|
|
* additional states & state).
|
|
*
|
|
* TODO: this code has never been tested...
|
|
*
|
|
* See http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4
|
|
*/
|
|
static int
|
|
post_process_multipart (struct MHD_PostProcessor *pp,
|
|
const char *post_data, unsigned int post_data_len)
|
|
{
|
|
char *buf;
|
|
const char *boundary;
|
|
unsigned int max;
|
|
unsigned int ioff;
|
|
unsigned int poff;
|
|
unsigned int newline;
|
|
unsigned int endquote;
|
|
size_t blen;
|
|
|
|
buf = (char *) &pp[1];
|
|
ioff = 0;
|
|
poff = 0;
|
|
boundary =
|
|
&pp->encoding[strlen (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)];
|
|
/* Q: should this be "strcasestr"? */
|
|
if (NULL != strstr (boundary, "boundary="))
|
|
boundary = strstr (boundary, "boundary=") + strlen ("boundary=");
|
|
else
|
|
return MHD_NO; /* failed to determine boundary */
|
|
blen = strlen (boundary);
|
|
if (blen * 2 + 2 > pp->buffer_size)
|
|
return MHD_NO; /* (will be) out of memory */
|
|
while ((poff < post_data_len) || (pp->buffer_pos > ioff))
|
|
{
|
|
/* first, move data to our internal buffer */
|
|
max = pp->buffer_size - pp->buffer_pos;
|
|
if ((max < ioff) && (max < post_data_len))
|
|
{
|
|
memmove (buf, &buf[ioff], pp->buffer_pos - ioff);
|
|
pp->buffer_pos -= ioff;
|
|
ioff = 0;
|
|
max = pp->buffer_size - pp->buffer_pos;
|
|
}
|
|
if (max > post_data_len)
|
|
max = post_data_len;
|
|
memcpy (&buf[pp->buffer_pos], post_data, max);
|
|
poff += max;
|
|
pp->buffer_pos += max;
|
|
|
|
switch (pp->state)
|
|
{
|
|
case PP_Init:
|
|
/* we're looking for the boundary */
|
|
if (pp->buffer_pos < 2 + blen + ioff)
|
|
goto END;
|
|
if ((0 != memcmp ("--", &buf[ioff], 2)) ||
|
|
(0 != memcmp (&buf[ioff + 2], boundary, blen)))
|
|
return MHD_NO; /* expected boundary */
|
|
|
|
/* remove boundary from buffer */
|
|
ioff += 2 + blen;
|
|
|
|
/* next: start with headers */
|
|
pp->state = PP_ExpectNewLineR;
|
|
break;
|
|
case PP_ExpectNewLineR:
|
|
if (buf[ioff] == '-')
|
|
{
|
|
/* last boundary ends with "--" */
|
|
ioff++;
|
|
pp->state = PP_FinalDash;
|
|
break;
|
|
}
|
|
if (buf[ioff] == '\r')
|
|
{
|
|
ioff++;
|
|
pp->state = PP_ExpectNewLineN;
|
|
break;
|
|
}
|
|
/* fall through! */
|
|
case PP_ExpectNewLineN:
|
|
if (buf[ioff] == '\n')
|
|
{
|
|
ioff++;
|
|
pp->state = PP_Headers;
|
|
break;
|
|
}
|
|
return MHD_NO;
|
|
case PP_Headers:
|
|
newline = 0;
|
|
while ((newline + ioff < pp->buffer_pos) &&
|
|
(buf[newline + ioff] != '\r') &&
|
|
(buf[newline + ioff] != '\n'))
|
|
newline++;
|
|
if (newline == pp->buffer_size)
|
|
return MHD_NO; /* out of memory */
|
|
if (newline + ioff == pp->buffer_pos)
|
|
{
|
|
/* try to make more room */
|
|
memmove (buf, &buf[ioff], pp->buffer_pos - ioff);
|
|
pp->buffer_pos -= ioff;
|
|
ioff = 0;
|
|
break;
|
|
}
|
|
if (newline == 0)
|
|
{
|
|
pp->state = PP_SkipRNRN;
|
|
break;
|
|
}
|
|
buf[ioff + newline] = '\0';
|
|
if ((MHD_YES
|
|
== try_match_header ("Content-Disposition: form-data; name=\"",
|
|
&buf[ioff],
|
|
&pp->content_disposition)) &&
|
|
(pp->content_disposition != NULL) &&
|
|
(0 < strlen (pp->content_disposition)))
|
|
{
|
|
/* find end-quote; then check if we also have a filename! */
|
|
endquote = 0;
|
|
while ((pp->content_disposition[endquote] != '\"') &&
|
|
(pp->content_disposition[endquote] != '\0'))
|
|
endquote++;
|
|
pp->content_disposition[endquote++] = '\0'; /* remove end-quote */
|
|
if ((MHD_YES
|
|
== try_match_header (" filename=",
|
|
&pp->content_disposition[endquote],
|
|
&pp->filename)) &&
|
|
(pp->filename != NULL) && (0 < strlen (pp->filename)))
|
|
pp->filename[strlen (pp->filename) - 1] = '\0'; /* remove end-quote */
|
|
}
|
|
try_match_header ("Content-Type: ", &buf[ioff], &pp->content_type);
|
|
try_match_header ("Content-Transfer-Encoding: ",
|
|
&buf[ioff], &pp->transfer_encoding);
|
|
break;
|
|
case PP_SkipRNRN:
|
|
if (buf[ioff] == '\r')
|
|
{
|
|
ioff++;
|
|
pp->state = PP_SkipNRN;
|
|
break;
|
|
}
|
|
/* fall through! */
|
|
case PP_SkipNRN:
|
|
if (buf[ioff] == '\n')
|
|
{
|
|
ioff++;
|
|
pp->state = PP_SkipRN;
|
|
break;
|
|
}
|
|
return MHD_NO; /* parse error */
|
|
case PP_SkipRN:
|
|
if (buf[ioff] == '\r')
|
|
{
|
|
ioff++;
|
|
pp->state = PP_SkipN;
|
|
break;
|
|
}
|
|
/* fall through! */
|
|
case PP_SkipN:
|
|
if (buf[ioff] == '\n')
|
|
{
|
|
ioff++;
|
|
pp->state = PP_ValueToBoundary;
|
|
pp->value_offset = 0;
|
|
break;
|
|
}
|
|
return MHD_NO; /* parse error */
|
|
case PP_ValueToBoundary:
|
|
/* all data in buf until the boundary
|
|
(\r\n--+boundary) is part of the value */
|
|
newline = 0;
|
|
while (1)
|
|
{
|
|
while ((newline + ioff + 4 < pp->buffer_pos) &&
|
|
(0 != memcmp ("\r\n--", &buf[newline + ioff], 4)))
|
|
newline++;
|
|
if (newline + blen + 4 > pp->buffer_size)
|
|
{
|
|
/* boundary not in sight --
|
|
process data, then make room for more! */
|
|
if (MHD_NO == pp->ikvi (pp->cls,
|
|
MHD_POSTDATA_KIND,
|
|
pp->content_disposition,
|
|
pp->filename,
|
|
pp->content_type,
|
|
pp->transfer_encoding,
|
|
&buf[ioff],
|
|
pp->value_offset, newline))
|
|
{
|
|
pp->state = PP_Error;
|
|
break;
|
|
}
|
|
pp->value_offset += newline;
|
|
ioff += newline;
|
|
memmove (buf, &buf[ioff], pp->buffer_pos - ioff);
|
|
pp->buffer_pos -= ioff;
|
|
break;
|
|
}
|
|
if (newline + blen + 4 < pp->buffer_pos)
|
|
{
|
|
/* can check for boundary right now! */
|
|
if (0 == memcmp (&buf[newline + ioff + 4], boundary, blen))
|
|
{
|
|
/* found: process data, then look for more */
|
|
if (MHD_NO == pp->ikvi (pp->cls,
|
|
MHD_POSTDATA_KIND,
|
|
pp->content_disposition,
|
|
pp->filename,
|
|
pp->content_type,
|
|
pp->transfer_encoding,
|
|
&buf[ioff],
|
|
pp->value_offset, newline))
|
|
{
|
|
pp->state = PP_Error;
|
|
break;
|
|
}
|
|
|
|
/* clean up! */
|
|
if (pp->content_type != NULL)
|
|
{
|
|
free (pp->content_type);
|
|
pp->content_type = NULL;
|
|
}
|
|
if (pp->content_disposition != NULL)
|
|
{
|
|
free (pp->content_disposition);
|
|
pp->content_disposition = NULL;
|
|
}
|
|
if (pp->filename != NULL)
|
|
{
|
|
free (pp->filename);
|
|
pp->filename = NULL;
|
|
}
|
|
if (pp->transfer_encoding != NULL)
|
|
{
|
|
free (pp->transfer_encoding);
|
|
pp->transfer_encoding = NULL;
|
|
}
|
|
pp->value_offset = 0;
|
|
ioff += newline + 2; /* skip data + new line */
|
|
pp->state = PP_Init;
|
|
break;
|
|
}
|
|
/* not the boundary, look further! */
|
|
newline += 4;
|
|
continue;
|
|
}
|
|
|
|
|
|
}
|
|
break;
|
|
case PP_FinalDash:
|
|
if (buf[ioff] == '-')
|
|
{
|
|
/* last boundary ends with "--" */
|
|
ioff++;
|
|
pp->state = PP_Error;
|
|
break;
|
|
}
|
|
return MHD_NO; /* parse error */
|
|
case PP_Error:
|
|
return MHD_NO;
|
|
default:
|
|
abort (); /* should never happen! */
|
|
|
|
}
|
|
}
|
|
END:
|
|
memmove (buf, &buf[ioff], pp->buffer_pos - ioff);
|
|
pp->buffer_pos -= ioff;
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* Parse and process POST data.
|
|
* Call this function when POST data is available
|
|
* (usually during an MHD_AccessHandlerCallback)
|
|
* with the upload_data and upload_data_size.
|
|
* Whenever possible, this will then cause calls
|
|
* to the MHD_IncrementalKeyValueIterator.
|
|
*
|
|
* @param pp the post processor
|
|
* @param post_data post_data_len bytes of POST data
|
|
* @param post_data_len length of post_data
|
|
* @return MHD_YES on success, MHD_NO on error
|
|
* (out-of-memory, iterator aborted, parse error)
|
|
*/
|
|
int
|
|
MHD_post_process (struct MHD_PostProcessor *pp,
|
|
const char *post_data, unsigned int post_data_len)
|
|
{
|
|
if (post_data_len == 0)
|
|
return MHD_YES;
|
|
if (0 == strcasecmp (MHD_HTTP_POST_ENCODING_FORM_URLENCODED, pp->encoding))
|
|
return post_process_urlencoded (pp, post_data, post_data_len);
|
|
if (0 ==
|
|
strncasecmp (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA, pp->encoding,
|
|
strlen (MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA)))
|
|
return post_process_multipart (pp, post_data, post_data_len);
|
|
/* this should never be reached */
|
|
return MHD_NO;
|
|
}
|
|
|
|
/**
|
|
* Release PostProcessor resources.
|
|
*/
|
|
void
|
|
MHD_destroy_post_processor (struct MHD_PostProcessor *pp)
|
|
{
|
|
/* These internal strings need cleaning up since
|
|
the post-processing may have been interrupted
|
|
at any stage */
|
|
if (pp->content_type != NULL)
|
|
free (pp->content_type);
|
|
if (pp->content_disposition != NULL)
|
|
free (pp->content_disposition);
|
|
if (pp->filename != NULL)
|
|
free (pp->filename);
|
|
if (pp->transfer_encoding != NULL)
|
|
free (pp->transfer_encoding);
|
|
free (pp);
|
|
}
|
|
|
|
/* end of postprocessor.c */
|