/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * Portions of this software are based upon public domain software
 * originally written at the National Center for Supercomputing Applications,
 * University of Illinois, Urbana-Champaign.
 */

#include "apache_multipart_buffer.h"

/*********************** internal functions *********************/

/*
  search for a string in a fixed-length byte string.
  if partial is true, partial matches are allowed at the end of the buffer.
  returns NULL if not found, or a pointer to the start of the first match.
*/
void* my_memstr(char* haystack, int haystacklen, const char* needle,
		int partial)
{
    int needlen = strlen(needle);
    int len = haystacklen;
    char *ptr = haystack;

    /* iterate through first character matches */
    while( (ptr = memchr(ptr, needle[0], len)) ) {
	/* calculate length after match */
	len = haystacklen - (ptr - (char *)haystack);

	/* done if matches up to capacity of buffer */
	if(memcmp(needle, ptr, needlen < len ? needlen : len) == 0 &&
	   (partial || len >= needlen))
	    break;

	/* next character */
	ptr++; len--;
    }

    return ptr;
}

/*
  fill up the buffer with client data.
  returns number of bytes added to buffer.
*/
int fill_buffer(multipart_buffer *self)
{
    int bytes_to_read, actual_read = 0;

    /* shift the existing data if necessary */
    if(self->bytes_in_buffer > 0 && self->buf_begin != self->buffer)
	memmove(self->buffer, self->buf_begin, self->bytes_in_buffer);
    self->buf_begin = self->buffer;

    /* calculate the free space in the buffer */
    bytes_to_read = self->bufsize - self->bytes_in_buffer;

    if (bytes_to_read >= self->r->remaining) {
        bytes_to_read = self->r->remaining - strlen(self->boundary);
#ifdef DEBUG
        ap_log_rerror(MPB_ERROR, "mozilla 0.97 hack: '%ld'", self->r->remaining);
#endif
    }

    /* read the required number of bytes */
    if(bytes_to_read > 0) {
	char *buf = self->buffer + self->bytes_in_buffer;
	ap_hard_timeout("[libapreq] multipart_buffer.c:fill_buffer", self->r);
	actual_read = ap_get_client_block(self->r, buf, bytes_to_read);
	ap_kill_timeout(self->r);

	/* update the buffer length */
	if(actual_read > 0)
	  self->bytes_in_buffer += actual_read;
    }

    return actual_read;
}

/*
  gets the next CRLF terminated line from the input buffer.
  if it doesn't find a CRLF, and the buffer isn't completely full, returns
  NULL; otherwise, returns the beginning of the null-terminated line,
  minus the CRLF.

  note that we really just look for LF terminated lines. this works
  around a bug in internet explorer for the macintosh which sends mime
  boundaries that are only LF terminated when you use an image submit
  button in a multipart/form-data form.
 */
char* next_line(multipart_buffer *self)
{
    /* look for LF in the data */
    char* line = self->buf_begin;
    char* ptr = memchr(self->buf_begin, '\n', self->bytes_in_buffer);

    /* LF found */
    if(ptr) {
	/* terminate the string, remove CRLF */
	if((ptr - line) > 0 && *(ptr-1) == '\r') *(ptr-1) = 0;
	else *ptr = 0;

	/* bump the pointer */
	self->buf_begin = ptr + 1;
	self->bytes_in_buffer -= (self->buf_begin - line);
    }

    /* no LF found */
    else {
	/* buffer isn't completely full, fail */
	if(self->bytes_in_buffer < self->bufsize)
	  return NULL;

	/* return entire buffer as a partial line */
	line[self->bufsize] = 0;
	self->buf_begin = ptr;
	self->bytes_in_buffer = 0;
    }

    return line;
}

/* returns the next CRLF terminated line from the client */
char* get_line(multipart_buffer *self)
{
    char* ptr = next_line(self);

    if(!ptr) {
	fill_buffer(self);
	ptr = next_line(self);
    }

#ifdef DEBUG
    ap_log_rerror(MPB_ERROR, "get_line: '%s'", ptr);
#endif

    return ptr;
}

/* finds a boundary */
int find_boundary(multipart_buffer *self, char *boundary)
{
    char *line;

    /* loop thru lines */
    while( (line = get_line(self)) ) {
#ifdef DEBUG
      ap_log_rerror(MPB_ERROR, "find_boundary: '%s' ?= '%s'",
		    line, boundary);
#endif

      /* finished if we found the boundary */
      if(strEQ(line, boundary))
	  return 1;
    }

    /* didn't find the boundary */
    return 0;
}

/*********************** external functions *********************/

/* create new multipart_buffer structure */
multipart_buffer *multipart_buffer_new(char *boundary, long length, request_rec *r)
{
    multipart_buffer *self = (multipart_buffer *)
	ap_pcalloc(r->pool, sizeof(multipart_buffer));

    int minsize = strlen(boundary)+6;
    if(minsize < FILLUNIT) minsize = FILLUNIT;

    self->r = r;
    self->buffer = (char *) ap_pcalloc(r->pool, minsize+1);
    self->bufsize = minsize;
    self->request_length = length;
    self->boundary = ap_pstrcat(r->pool, "--", boundary, NULL);
    self->boundary_next = ap_pstrcat(r->pool, "\n", self->boundary, NULL);
    self->buf_begin = self->buffer;
    self->bytes_in_buffer = 0;

    return self;
}

/* parse headers and return them in an apache table */
table *multipart_buffer_headers(multipart_buffer *self)
{
    table *tab;
    char *line;

    /* didn't find boundary, abort */
    if(!find_boundary(self, self->boundary)) return NULL;

    /* get lines of text, or CRLF_CRLF */
    tab = ap_make_table(self->r->pool, 10);
    while( (line = get_line(self)) && strlen(line) > 0 ) {
	/* add header to table */
	char *key = line;
	char *value = strchr(line, ':');

	if(value) {
	    *value = 0;
	    do { value++; } while(ap_isspace(*value));

#ifdef DEBUG
	    ap_log_rerror(MPB_ERROR,
			  "multipart_buffer_headers: '%s' = '%s'",
			  key, value);
#endif

	    ap_table_add(tab, key, value);
	}
	else {
#ifdef DEBUG
	    ap_log_rerror(MPB_ERROR,
			  "multipart_buffer_headers: '%s' = ''", key);
#endif

	    ap_table_add(tab, key, "");
	}
    }

    return tab;
}

/* read until a boundary condition */
int multipart_buffer_read(multipart_buffer *self, char *buf, int bytes)
{
    int len, max;
    char *bound;

    /* fill buffer if needed */
    if(bytes > self->bytes_in_buffer) fill_buffer(self);

    /* look for a potential boundary match, only read data up to that point */
    if( (bound = my_memstr(self->buf_begin, self->bytes_in_buffer,
			   self->boundary_next, 1)) )
	max = bound - self->buf_begin;
    else
	max = self->bytes_in_buffer;

    /* maximum number of bytes we are reading */
    len = max < bytes-1 ? max : bytes-1;

    /* if we read any data... */
    if(len > 0) {
	/* copy the data */
	memcpy(buf, self->buf_begin, len);
	buf[len] = 0;
	if(bound && len > 0 && buf[len-1] == '\r') buf[--len] = 0;

	/* update the buffer */
	self->bytes_in_buffer -= len;
	self->buf_begin += len;
    }

#ifdef DEBUG
    ap_log_rerror(MPB_ERROR, "multipart_buffer_read: %d bytes", len);
#endif

    return len;
}

/*
  XXX: this is horrible memory-usage-wise, but we only expect
  to do this on small pieces of form data.
*/
char *multipart_buffer_read_body(multipart_buffer *self)
{
    char buf[FILLUNIT], *out = "";

    while(multipart_buffer_read(self, buf, sizeof(buf)))
	out = ap_pstrcat(self->r->pool, out, buf, NULL);

#ifdef DEBUG
    ap_log_rerror(MPB_ERROR, "multipart_buffer_read_body: '%s'", out);
#endif

    return out;
}

/* eof if we are out of bytes, or if we hit the final boundary */
int multipart_buffer_eof(multipart_buffer *self)
{
    if( (self->bytes_in_buffer == 0 && fill_buffer(self) < 1) )
	return 1;
    else
	return 0;
}
