/*
 * Copyright (C) 2008 iptelorg GmbH
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio 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 General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

/*! \file
 * \brief Parser :: Body handling
 *
 * \ingroup parser
 * @{
 */


#include "../trim.h"
#include "parser_f.h"
#include "parse_content.h"
#include "parse_param.h"
#include "keys.h"
#include "parse_body.h"

/*! \brief returns the value of boundary parameter from the Content-Type HF */
static inline int get_boundary_param(struct sip_msg *msg, str *boundary)
{
	str s;
	char *c;
	param_t *p, *list;

#define is_boundary(c)                                                    \
	(((c)[0] == 'b' || (c)[0] == 'B') && ((c)[1] == 'o' || (c)[1] == 'O') \
			&& ((c)[2] == 'u' || (c)[2] == 'U')                           \
			&& ((c)[3] == 'n' || (c)[3] == 'N')                           \
			&& ((c)[4] == 'd' || (c)[4] == 'D')                           \
			&& ((c)[5] == 'a' || (c)[5] == 'A')                           \
			&& ((c)[6] == 'r' || (c)[6] == 'R')                           \
			&& ((c)[7] == 'y' || (c)[7] == 'Y'))

#define boundary_param_len (sizeof("boundary") - 1)

	/* get the pointer to the beginning of the parameter list */
	s.s = msg->content_type->body.s;
	s.len = msg->content_type->body.len;
	c = find_not_quoted(&s, ';');
	if(!c)
		return -1;
	c++;
	s.len = s.len - (c - s.s);
	s.s = c;
	trim_leading(&s);

	if(s.len <= 0)
		return -1;

	/* parse the parameter list, and search for boundary */
	if(parse_params(&s, CLASS_ANY, NULL, &list) < 0)
		return -1;

	boundary->s = NULL;
	for(p = list; p; p = p->next)
		if((p->name.len == boundary_param_len) && is_boundary(p->name.s)) {
			boundary->s = p->body.s;
			boundary->len = p->body.len;
			break;
		}
	free_params(list);
	if(!boundary->s || !boundary->len)
		return -1;

	DBG("boundary is \"%.*s\"\n", boundary->len, boundary->s);
	return 0;
}

/*! \brief search the next boundary in the buffer */
static inline char *search_boundary(char *buf, char *buf_end, str *boundary)
{
	char *c;

	c = buf;
	while(c + 2 /* -- */ + boundary->len < buf_end) {
		if((*c == '-') && (*(c + 1) == '-')
				&& (memcmp(c + 2, boundary->s, boundary->len) == 0))
			return c; /* boundary found */

		/* go to the next line */
		while((c < buf_end) && (*c != '\n'))
			c++;
		c++;
	}
	return NULL;
}

/*! \brief extract the body of a part from a multipart SIP msg body */
inline static char *get_multipart_body(
		char *buf, char *buf_end, str *boundary, int *len)
{
	char *beg, *end;

	if(buf >= buf_end)
		goto error;

	beg = buf;
	while((beg < buf_end) && (*beg != '\r') && (*beg != '\n')) {
		while((beg < buf_end) && (*beg != '\n'))
			beg++;
		beg++;
		if(beg >= buf_end)
			goto error;
	}
	/* CRLF delimiter found, the body begins right after it */
	while((beg < buf_end) && (*beg != '\n'))
		beg++;
	beg++;
	if(beg >= buf_end)
		goto error;

	if(!(end = search_boundary(beg, buf_end, boundary)))
		goto error;

	/* CRLF preceding the boundary belongs to the boundary
	and not to the body */
	if(end > beg && *(end - 1) == '\n')
		end--;
	if(end > beg && *(end - 1) == '\r')
		end--;

	if(end < beg)
		goto error;

	*len = end - beg;
	return beg;
error:
	ERR("failed to extract the body from the multipart mime type\n");
	return NULL;
}


/*! \brief macros from parse_hname2.c */
#define READ(val) \
	(*(val + 0) + (*(val + 1) << 8) + (*(val + 2) << 16) + (*(val + 3) << 24))

#define LOWER_DWORD(d) ((d) | 0x20202020)

/*! \brief Returns the pointer within the msg body to the given type/subtype,
 * and sets the length of the body part.
 * The result can be the whole msg body, or a part of a multipart body.
 */
char *get_body_part(struct sip_msg *msg, unsigned short type,
		unsigned short subtype, int *len)
{
	int mime;
	unsigned int umime;
	char *c, *c2, *buf_end;
	str boundary;

#define content_type_len (sizeof("Content-Type") - 1)

	if((mime = parse_content_type_hdr(msg)) <= 0)
		return NULL;

	if(mime == ((type << 16) | subtype)) {
		/* content-type is type/subtype */
		c = get_body(msg);
		if(c)
			*len = msg->buf + msg->len - c;
		return c;

	} else if((mime >> 16) == TYPE_MULTIPART) {
		/* type is multipart/something, search for type/subtype part */

		if(get_boundary_param(msg, &boundary)) {
			ERR("failed to get boundary parameter\n");
			return NULL;
		}
		if(!(c = get_body(msg)))
			return NULL;
		buf_end = msg->buf + msg->len;

		/* check all the body parts delimited by the boundary value,
		and search for the Content-Type HF with the given
		type/subtype */
	next_part:
		while((c = search_boundary(c, buf_end, &boundary))) {
			/* skip boundary */
			c += 2 + boundary.len;

			if((c + 2 > buf_end) || ((*c == '-') && (*(c + 1) == '-')))
				/* end boundary, no more body part
				will follow */
				return NULL;

			/* go to the next line */
			while((c < buf_end) && (*c != '\n'))
				c++;
			c++;
			if(c >= buf_end)
				return NULL;

			/* try to find the content-type header */
			while((c < buf_end) && (*c != '\r') && (*c != '\n')) {
				if(c + content_type_len >= buf_end)
					return NULL;

				if((LOWER_DWORD(READ(c)) == _cont_)
						&& (LOWER_DWORD(READ(c + 4)) == _ent__)
						&& (LOWER_DWORD(READ(c + 8)) == _type_)) {
					/* Content-Type HF found */
					c += content_type_len;
					while((c < buf_end) && ((*c == ' ') || (*c == '\t')))
						c++;

					if(c + 1 /* : */ >= buf_end)
						return NULL;

					if(*c != ':')
						/* not really a Content-Type HF */
						goto next_hf;
					c++;

					/* search the end of the header body,
					decode_mime_type() needs it */
					c2 = c;
					while(((c2 < buf_end) && (*c2 != '\n'))
							|| ((c2 + 1 < buf_end) && (*c2 == '\n')
									&& ((*(c2 + 1) == ' ')
											|| (*(c2 + 1) == '\t'))))
						c2++;

					if(c2 >= buf_end)
						return NULL;
					if(*(c2 - 1) == '\r')
						c2--;

					if(!decode_mime_type(c, c2, &umime)) {
						ERR("failed to decode the mime type\n");
						return NULL;
					}

					/* c2 points to the CRLF at the end of the line,
					move the pointer to the beginning of the next line */
					c = c2;
					if((c < buf_end) && (*c == '\r'))
						c++;
					if((c < buf_end) && (*c == '\n'))
						c++;

					if(umime != ((type << 16) | subtype)) {
						/* this is not the part we are looking for */
						goto next_part;
					}

					/* the requested type/subtype is found! */
					return get_multipart_body(c, buf_end, &boundary, len);
				}
			next_hf:
				/* go to the next line */
				while((c < buf_end) && (*c != '\n'))
					c++;
				c++;
			}
			/* CRLF delimiter reached,
			no Content-Type HF was found */
		}
	}
	return NULL;
}


/**
 * \brief trim_leading_hts
 *
 * trim leading all spaces ' ' and horizontal tabs '\\t' characters.
 * \param buffer pointer to the beginning of the buffer
 * \param end_buffer pointer to the end of the buffer
 * \return pointer to the first non-match character if success,
 * pointer to NULL if the end_buffer is reached.
 */
char *trim_leading_hts(char *buffer, char *end_buffer)
{
	char *cpy_buffer = buffer;
	while((cpy_buffer < end_buffer)
			&& ((*cpy_buffer == ' ') || (*cpy_buffer == '\t'))) {
		cpy_buffer++;
	}

	return ((cpy_buffer < end_buffer) ? cpy_buffer : NULL);
}


/**
 * \brief trim_leading_e_r
 *
 * trim leading characters until get a '\\r'.
 * \param buffer pointer to the beginning of the buffer
 * \param end_buffer pointer to the end of the buffer
 * \return pointer to the first '\\r' character if success,
 * pointer to NULL if the end_buffer is reached.
 */
char *trim_leading_e_r(char *buffer, char *end_buffer)
{
	char *cpy_buffer = buffer;
	while((cpy_buffer < end_buffer) && (*cpy_buffer != '\r')) {
		cpy_buffer++;
	}
	return ((cpy_buffer < end_buffer) ? cpy_buffer : NULL);
}


/**
 * \brief part_multipart_headers_cmp
 *
 * trim leading characters until get a '\\r'.
 * \param buffer pointer to the beginning of the headers in a part of the multipart body
 * \param end_buffer pointer to the end of the headers in the multipart body
 * \param content_type if NULL Content-Type: disabled in the search
 * \param content_subtype if NULL Content-Subtype: disabled in the search
 * \param content_id if NULL then Content-ID: disabled in the search
 * \param content_length if NULL then Content-Length: disabled in the search
 * \return true, if the part of the multipart body has :
 * -- Content-Type   that matches content_type / content_subtype. (if Content-Type enabled) &&
 * -- Content-ID     that matches content_id. (if Content-ID enabled) &&
 * -- Content-Length that matches content_length. (if Content-Length enabled)
 * - false, if any of them doesn't match.
 */
int part_multipart_headers_cmp(char *buffer, char *end_buffer,
		unsigned short content_type, unsigned short content_subtype,
		char *content_id, char *content_length)
{
	int error = 0;
	char *error_msg = NULL;

	char *cpy_c = NULL;
	char *cpy_d = NULL;

	char *value_ini = NULL;
	char *value_fin = NULL;
	unsigned int umime;

	int found = 0;
	int found_content_type = 0;
	int found_content_id = 0;
	int found_content_length = 0;

	if((buffer == NULL) || (end_buffer == NULL)) {
		error = -1;
		error_msg = "buffer and/or end_buffer are NULL";
	} else {
		cpy_c = buffer;
		cpy_d = end_buffer;

		if((content_type == 0) && (content_subtype == 0)) {
			found_content_type = 1;
		}
		if(content_id == NULL) {
			found_content_id = 1;
		}
		if(content_length == NULL) {
			found_content_length = 1;
		}

		found = found_content_type * found_content_id * found_content_length;
		while((!found) && (!error) && (cpy_c < cpy_d)) {
			if((cpy_c + 8) < cpy_d) {
				if((LOWER_DWORD(READ(cpy_c)) == _cont_)
						&& (LOWER_DWORD(READ(cpy_c + 4)) == _ent__)) {
					cpy_c += 8;
					if((!found_content_type) && ((cpy_c + 5) < cpy_d)
							&& ((*(cpy_c + 0) == 't') || (*(cpy_c + 0) == 'T'))
							&& ((*(cpy_c + 1) == 'y') || (*(cpy_c + 1) == 'Y'))
							&& ((*(cpy_c + 2) == 'p') || (*(cpy_c + 2) == 'P'))
							&& ((*(cpy_c + 3) == 'e') || (*(cpy_c + 3) == 'E'))
							&& (*(cpy_c + 4) == ':')) {
						cpy_c += 5;
						/* value_ has the content of the header */
						value_ini = trim_leading_hts(cpy_c, cpy_d);
						value_fin = trim_leading_e_r(cpy_c, cpy_d);
						if((value_ini != NULL) && (value_fin != NULL)) {
							cpy_c = value_fin;
							if(decode_mime_type(value_ini, value_fin, &umime)) {
								if(umime
										== ((content_type << 16)
												| content_subtype)) {
									found_content_type = 1;
								} else {
									error = -2;
									error_msg = "MIME types mismatch";
								}
							} else {
								error = -3;
								error_msg = "Failed to decode MIME type";
							}
						} else {
							error = -4;
							error_msg = "Failed to perform trim_leading_hts || "
										"trim_leading_e_r";
						}
					} else if((!found_content_id) && ((cpy_c + 3) < cpy_d)
							  && ((*(cpy_c + 0) == 'i')
									  || (*(cpy_c + 0) == 'I'))
							  && ((*(cpy_c + 1) == 'd')
									  || (*(cpy_c + 1) == 'D'))
							  && (*(cpy_c + 2) == ':')) {
						cpy_c += 3;
						/* value_ has the content of the header */
						value_ini = trim_leading_hts(cpy_c, cpy_d);
						value_fin = trim_leading_e_r(cpy_c, cpy_d);
						if((value_ini != NULL) && (value_fin != NULL)) {
							cpy_c = value_fin;
							if(strncmp(content_id, value_ini,
									   value_fin - value_ini)
									== 0) {
								found_content_id = 1;
							} else {
								error = -5;
								error_msg = "Content-ID mismatch";
							}
						} else {
							error = -6;
							error_msg = "Failed to perform trim_leading_hts || "
										"trim_leading_e_r";
						}
					} else if((!found_content_length) && ((cpy_c + 7) < cpy_d)
							  && ((*(cpy_c + 0) == 'l')
									  || (*(cpy_c + 0) == 'L'))
							  && ((*(cpy_c + 1) == 'e')
									  || (*(cpy_c + 1) == 'E'))
							  && ((*(cpy_c + 2) == 'n')
									  || (*(cpy_c + 2) == 'N'))
							  && ((*(cpy_c + 3) == 'g')
									  || (*(cpy_c + 3) == 'G'))
							  && ((*(cpy_c + 4) == 't')
									  || (*(cpy_c + 4) == 'T'))
							  && ((*(cpy_c + 5) == 'h')
									  || (*(cpy_c + 5) == 'H'))
							  && (*(cpy_c + 6) == ':')) {
						cpy_c += 7;
						/* value_ has the content of the header */
						value_ini = trim_leading_hts(cpy_c, cpy_d);
						value_fin = trim_leading_e_r(cpy_c, cpy_d);
						if((value_ini != NULL) && (value_fin != NULL)) {
							cpy_c = value_fin;
							if(strncmp(content_length, value_ini,
									   value_fin - value_ini)
									== 0) {
								found_content_length = 1;
							} else {
								error = -7;
								error_msg = "Content-Length mismatch";
							}
						} else {
							error = -8;
							error_msg = "Failed to perform trim_leading_hts || "
										"trim_leading_e_r";
						}
					} else {
						/* Next characters don't match "Type:" or "ID:" or "Length:" OR
					     * header already parsed (maybe duplicates?) and founded OR
					     * header initially set as disabled and it doesn't need to be treated.
					     * This is NOT an error. */
						;
					}
				} else {
					/* First 8 characters don't match "Content-"
					 * This is NOT an error. */
					;
				}
			} else {
				/* Nothing matched */
				error = -9;
				error_msg = "Unsuccessfully reached the end of the buffer";
			}
			found = found_content_type * found_content_id
					* found_content_length;
			if((!found) && (!error)) {
				value_fin = trim_leading_e_r(cpy_c, cpy_d);
				if(value_fin != NULL) {
					cpy_c = value_fin;
					if((cpy_c + 1) < cpy_d) {
						if((*cpy_c == '\r') && (*(cpy_c + 1) == '\n')) {
							cpy_c++;
							cpy_c++;
						} else {
							error = -10;
							error_msg = "Each line must end with a \r\n";
						}
					} else {
						error = -11;
						error_msg = "We reached the end of the buffer";
					}
				} else {
					error = -12;
					error_msg = "Failed to perform trim_leading_e_r";
				}
			}
		} /* End main while loop */
	}

	if(error < 0) {
		if((error == -2) || (error == -9)) {
			LM_DBG("result code: \"%i\" text: \"%s\".\n", error, error_msg);
		} else {
			LM_ERR("error code: \"%i\" error text: \"%s\".\n", error,
					error_msg);
		}
		return 0;
	} else {
		return found;
	}
}

/**
 * \brief get_body_part_by_filter
 *
 * Filters the multipart part from a given SIP message which matches the
 * Content-Type && || Content-ID  && || Content-Length
 *
 * \param msg SIP message
 * \param content_type if NULL Content-Type: disabled in the search
 * \param content_subtype if NULL Content-Subtype: disabled in the search
 * \param content_id if NULL then Content-ID: disabled in the search
 * \param content_length, if NULL then Content-Length: disabled in the search
 * \param len Length of the multipart message returned
 * \return pointer to the multipart if success,  NULL, if none of the multiparts match
 */
char *get_body_part_by_filter(struct sip_msg *msg, unsigned short content_type,
		unsigned short content_subtype, char *content_id, char *content_length,
		int *len)
{
	int mime;
	char *c, *d, *buf_end;
	str boundary;

	if((mime = parse_content_type_hdr(msg)) <= 0)
		return NULL;

	if((mime >> 16) == TYPE_MULTIPART) {
		/* type is multipart/something, search for type/subtype part */
		if(get_boundary_param(msg, &boundary)) {
			ERR("failed to get boundary parameter\n");
			return NULL;
		}
		if(!(c = get_body(msg)))
			return NULL;
		buf_end = msg->buf + msg->len;

		while((c = search_boundary(c, buf_end, &boundary))) {
			/* skip boundary */
			c += 2 + boundary.len;

			if((c + 2 > buf_end) || ((*c == '-') && (*(c + 1) == '-')))
				/* end boundary, no more body part will follow */
				return NULL;

			/* go to the next line */
			while((c < buf_end) && (*c != '\n'))
				c++;
			c++;
			if(c >= buf_end)
				return NULL;

			d = get_multipart_body(c, buf_end, &boundary, len);
			if(part_multipart_headers_cmp(c, d, content_type, content_subtype,
					   content_id, content_length)) {
				return d;
			}
		}
	}
	return NULL;
}

/** @} */
