/**
 * Mime.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * Yet Another Telephony Engine - a fully featured software PBX and IVR
 * Copyright (C) 2004-2014 Null Team
 *
 * This software is distributed under multiple licenses;
 * see the COPYING file in the main directory for licensing
 * information for this specific distribution.
 *
 * This use of this software may be subject to additional restrictions.
 * See the LEGAL file in the main directory for details.
 *
 * 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.
 */

#include <stdlib.h>
#include "yatemime.h"

using namespace TelEngine;

// Utility function, checks if a character is a folded line continuation
static bool isContinuationBlank(char c)
{
    return ((c == ' ') || (c == '\t'));
}

/**
 * MimeHeaderLine
 */
MimeHeaderLine::MimeHeaderLine(const char* name, const String& value, char sep)
    : NamedString(name), m_separator(sep ? sep : ';')
{
    if (value.null())
	return;
    XDebug(DebugAll,"MimeHeaderLine::MimeHeaderLine('%s','%s') [%p]",name,value.c_str(),this);
    int sp = findSep(value,m_separator);
    if (sp < 0) {
	assign(value);
	return;
    }
    assign(value,sp);
    trimBlanks();
    while (sp < (int)value.length()) {
	int ep = findSep(value,m_separator,sp+1);
	if (ep <= sp)
	    ep = value.length();
	int eq = value.find('=',sp+1);
	if ((eq > 0) && (eq < ep)) {
	    String pname(value.substr(sp+1,eq-sp-1));
	    String pvalue(value.substr(eq+1,ep-eq-1));
	    pname.trimBlanks();
	    pvalue.trimBlanks();
	    if (!pname.null()) {
		XDebug(DebugAll,"hdr param name='%s' value='%s'",pname.c_str(),pvalue.c_str());
		m_params.append(new NamedString(pname,pvalue));
	    }
	}
	else {
	    String pname(value.substr(sp+1,ep-sp-1));
	    pname.trimBlanks();
	    if (!pname.null()) {
		XDebug(DebugAll,"hdr param name='%s' (no value)",pname.c_str());
		m_params.append(new NamedString(pname));
	    }
	}
	sp = ep;
    }
}

MimeHeaderLine::MimeHeaderLine(const MimeHeaderLine& original, const char* newName)
    : NamedString(newName ? newName : original.name().c_str(),original),
      m_separator(original.separator())
{
    XDebug(DebugAll,"MimeHeaderLine::MimeHeaderLine(%p '%s') [%p]",&original,name().c_str(),this);
    const ObjList* l = &original.params();
    for (; l; l = l->next()) {
	const NamedString* t = static_cast<const NamedString*>(l->get());
	if (t)
	    m_params.append(new NamedString(t->name(),*t));
    }
}

MimeHeaderLine::~MimeHeaderLine()
{
    XDebug(DebugAll,"MimeHeaderLine::~MimeHeaderLine() [%p]",this);
}

void* MimeHeaderLine::getObject(const String& name) const
{
    if (name == YATOM("MimeHeaderLine"))
	return const_cast<MimeHeaderLine*>(this);
    return NamedString::getObject(name);
}

MimeHeaderLine* MimeHeaderLine::clone(const char* newName) const
{
    return new MimeHeaderLine(*this,newName);
}

void MimeHeaderLine::buildLine(String& line, bool header) const
{
    if (header)
	line << name() << ": ";
    line << *this;
    const ObjList* p = &m_params;
    for (; p; p = p->next()) {
	NamedString* s = static_cast<NamedString*>(p->get());
	if (s) {
	    line << separator() << s->name();
	    if (!s->null())
		line << "=" << *s;
	}
    }
}

const NamedString* MimeHeaderLine::getParam(const char* name) const
{
    if (!(name && *name))
	return 0;
    const ObjList* l = &m_params;
    for (; l; l = l->next()) {
	const NamedString* t = static_cast<const NamedString*>(l->get());
	if (t && (t->name() &= name))
	    return t;
    }
    return 0;
}

void MimeHeaderLine::setParam(const char* name, const char* value)
{
    ObjList* p = m_params.find(name);
    if (p)
	*static_cast<NamedString*>(p->get()) = value;
    else
	m_params.append(new NamedString(name,value));
}

void MimeHeaderLine::delParam(const char* name)
{
    ObjList* p = m_params.find(name);
    if (p)
	p->remove();
}

// Utility function, puts quotes around a string
void MimeHeaderLine::addQuotes(String& str, bool force)
{
    str.trimBlanks();
    unsigned int l = str.length();
    if (force || (l < 2) || (str[0] != '"') || (str[l-1] != '"')) {
	str = "\"" + str + "\"";
	force = true;
    }
    for (l = 1; l < str.length() - 1; l++) {
	switch (str.at(l)) {
	    case '\\':
		if (!force) {
		    // check only, don't quote again
		    switch (str.at(l+1)) {
			case '\\':
			case '"':
			    // already quoted, skip it
			    l++;
			    continue;
		    }
		}
		// fall through
	    case '"':
		str = str.substr(0,l) + "\\" + str.substr(l);
		l++;
	    default:
		break;
	}
    }
}

// Utility function, removes quotes around a string
void MimeHeaderLine::delQuotes(String& str, bool force)
{
    str.trimBlanks();
    unsigned int l = str.length();
    if ((l >= 2) && (str[0] == '"') && (str[l-1] == '"')) {
	str = str.substr(1,l-2);
	str.trimBlanks();
	force = true;
    }
    if (force) {
	for (l = 0; l < str.length(); l++) {
	    if (str.at(l) == '\\')
		str = str.substr(0,l) + str.substr(l+1);
	    // since the string is shorter the loop will skip past next char
	}
    }
}

// Utility function, puts quotes around a string
String MimeHeaderLine::quote(const String& str, bool force)
{
    String tmp(str);
    addQuotes(tmp,force);
    return tmp;
}

// Utility function, removed quotes around a string
String MimeHeaderLine::unquote(const String& str, bool force)
{
    String tmp(str);
    delQuotes(tmp,force);
    return tmp;
}

// Utility function to find a separator not in "quotes" or inside <uri>
int MimeHeaderLine::findSep(const char* str, char sep, int offs)
{
    if (!(str && sep))
	return -1;
    str += offs;
    bool inQ = false;
    bool inU = false;
    char c;
    for (; (c = *str++) ; offs++) {
	if (inQ) {
	    if (c == '"')
		inQ = false;
	    continue;
	}
	if (inU) {
	    if (c == '>')
		inU = false;
	    continue;
	}
	if (c == sep)
	    return offs;
	switch (c) {
	    case '"':
		inQ = true;
		break;
	    case '<':
		inU = true;
		break;
	}
    }
    return -1;
}

// Build a string from a list of MIME header lines
void MimeHeaderLine::buildHeaders(String& buf, const ObjList& headers)
{
    for (ObjList* o = headers.skipNull(); o; o = o->skipNext()) {
	MimeHeaderLine* hdr = static_cast<MimeHeaderLine*>(o->get());
	String line;
	hdr->buildLine(line);
	buf << line << "\r\n";
    }
}


/**
 * MimeAuthLine
 */
MimeAuthLine::MimeAuthLine(const char* name, const String& value)
    : MimeHeaderLine(name,String::empty(),',')
{
    XDebug(DebugAll,"MimeAuthLine::MimeAuthLine('%s','%s') [%p]",name,value.c_str(),this);
    if (value.null())
	return;
    int sp = value.find(' ');
    if (sp < 0) {
	assign(value);
	return;
    }
    assign(value,sp);
    trimBlanks();
    while (sp < (int)value.length()) {
	int ep = value.find(m_separator,sp+1);
	int quot = value.find('"',sp+1);
	if ((quot > sp) && (quot < ep)) {
	    quot = value.find('"',quot+1);
	    if (quot > sp)
		ep = value.find(m_separator,quot+1);
	}
	if (ep <= sp)
	    ep = value.length();
	int eq = value.find('=',sp+1);
	if ((eq > 0) && (eq < ep)) {
	    String pname(value.substr(sp+1,eq-sp-1));
	    String pvalue(value.substr(eq+1,ep-eq-1));
	    pname.trimBlanks();
	    pvalue.trimBlanks();
	    if (!pname.null()) {
		XDebug(DebugAll,"auth param name='%s' value='%s'",pname.c_str(),pvalue.c_str());
		m_params.append(new NamedString(pname,pvalue));
	    }
	}
	else {
	    String pname(value.substr(sp+1,ep-sp-1));
	    pname.trimBlanks();
	    if (!pname.null()) {
		XDebug(DebugAll,"auth param name='%s' (no value)",pname.c_str());
		m_params.append(new NamedString(pname));
	    }
	}
	sp = ep;
    }
}

MimeAuthLine::MimeAuthLine(const MimeAuthLine& original, const char* newName)
    : MimeHeaderLine(original,newName)
{
}

void* MimeAuthLine::getObject(const String& name) const
{
    if (name == YATOM("MimeAuthLine"))
	return const_cast<MimeAuthLine*>(this);
    return MimeHeaderLine::getObject(name);
}

MimeHeaderLine* MimeAuthLine::clone(const char* newName) const
{
    return new MimeAuthLine(*this,newName);
}

void MimeAuthLine::buildLine(String& line, bool header) const
{
    if (header)
	line << name() << ": ";
    line << *this;
    const ObjList* p = &m_params;
    for (bool first = true; p; p = p->next()) {
	NamedString* s = static_cast<NamedString*>(p->get());
	if (s) {
	    if (first)
		first = false;
	    else
		line << separator();
	    line << " " << s->name();
	    if (!s->null())
		line << "=" << *s;
	}
    }
}


/**
 * MimeBody
 */
YCLASSIMP(MimeBody,GenObject)

MimeBody::MimeBody(const String& type)
    : m_type("Content-Type",type)
{
    m_type.toLower();
    DDebug(DebugAll,"MimeBody::MimeBody('%s') [%p]",m_type.c_str(),this);
}

// Build from header line
// Make sure the name of the header line is correct
MimeBody::MimeBody(const MimeHeaderLine& type)
    : m_type(type,"Content-Type")
{
    m_type.toLower();
    DDebug(DebugAll,"MimeBody::MimeBody('%s','%s') [%p]",
	m_type.name().c_str(),m_type.c_str(),this);
}

MimeBody::~MimeBody()
{
    DDebug(DebugAll,"MimeBody::~MimeBody() '%s' [%p]",m_type.c_str(),this);
}

// Find the first (sub)body matching the given type
MimeBody* MimeBody::getFirst(const String& type) const
{
    if (type.null())
	return 0;
    if (getType() == type)
	return const_cast<MimeBody*>(this);
    return isMultipart() ? (static_cast<const MimeMultipartBody*>(this))->findBody(type) : 0;
}

// Find an additional header line by its name
MimeHeaderLine* MimeBody::findHdr(const String& name, const MimeHeaderLine* start) const
{
    ObjList* o = m_headers.skipNull();
    if (!o)
	return 0;
    // Find start point
    if (start)
	for (; o; o = o->skipNext())
	    if (start == o->get()) {
		o = o->skipNext();
		break;
	    }
    // Find the header
    for (; o; o = o->skipNext()) {
	MimeHeaderLine* hdr = static_cast<MimeHeaderLine*>(o->get());
	if (hdr->name() &= name)
	    return hdr;
    }
    return 0;
}

// Replace the value of an existing parameter or add a new one
bool MimeBody::setParam(const char* name, const char* value, const char* header)
{
    MimeHeaderLine* hdr = (!(header && *header)) ? &m_type : findHdr(header);
    if (hdr)
	hdr->setParam(name,value);
    return (hdr != 0);
}

// Remove a header parameter
bool MimeBody::delParam(const char* name, const char* header)
{
    MimeHeaderLine* hdr = (!(header && *header)) ? &m_type : findHdr(header);
    if (hdr)
	hdr->delParam(name);
    return (hdr != 0);
}

// Get a header parameter
const NamedString* MimeBody::getParam(const char* name, const char* header) const
{
    const MimeHeaderLine* hdr = (!(header && *header)) ? &m_type : findHdr(header);
    return hdr ? hdr->getParam(name) : 0;
}

const DataBlock& MimeBody::getBody() const
{
    if (m_body.null())
	buildBody();
    return m_body;
}

static const Regexp s_appStringFull("^dtmf\\|json\\|yaml\\|x-yaml$");
static const Regexp s_appStringPlusEnd("+\\(xml\\|json\\)$");

// Method to build a MIME body from a type and data buffer
MimeBody* MimeBody::build(const char* buf, int len, const MimeHeaderLine& type)
{
    DDebug(DebugAll,"MimeBody::build(%p,%d,'%s')",buf,len,type.c_str());
    if ((len <= 0) || !buf)
	return 0;
    String what = type;
    what.toLower();
    if (what.startSkip("application/",false)) {
	if (what == YSTRING("sdp"))
	    return new MimeSdpBody(type,buf,len);
	if (s_appStringFull.matches(what) || s_appStringPlusEnd.matches(what))
	    ;
	else if (what == YSTRING("dtmf-relay"))
	    return new MimeLinesBody(type,buf,len);
	else
	    return new MimeBinaryBody(type,buf,len);
    }
    else if (what.startsWith("multipart/"))
	return new MimeMultipartBody(type,buf,len);
    else if (what.startsWith("text/"))
	;
    else if ((what.length() >= 7) && what.endsWith("+xml"))
	;
    else if (what == YSTRING("message/sipfrag"))
	return new MimeLinesBody(type,buf,len);
    else
	return new MimeBinaryBody(type,buf,len);
    // Build string body
    MimeStringBody* b = new MimeStringBody(type,buf,len);
    if (len == (int)b->text().length())
	return b;
    DDebug(DebugInfo,"MimeBody::build(%p,%d,'%s') falling back to binary",buf,len,type.c_str());
    TelEngine::destruct(b);
    return new MimeBinaryBody(type,buf,len);
}

String* MimeBody::getUnfoldedLine(const char*& buf, int& len)
{
    String* res = new String;
    const char* b = buf;
    const char* s = b;
    int l = len;
    int e = 0;
    for (;(l > 0); ++b, --l) {
	bool goOut = false;
	switch (*b) {
	    case '\r':
		// CR is optional but skip over it if exists
		if ((l > 0) && (b[1] == '\n')) {
		    ++b;
		    --l;
		}
	    case '\n':
		++b;
		--l;
		{
		    String line(s,e);
		    *res << line;
		}
		// Skip over any continuation characters at start of next line
		goOut = true;
		while ((l > 0) && *res && isContinuationBlank(b[0])) {
		    ++b;
		    --l;
		    goOut = false;
		}
		s = b;
		e = 0;
		if (!goOut) {
		    --b;
		    ++l;
		}
		break;
	    case '\0':
		// Should not happen - but let's accept what we got
		*res << s;
		goOut = true;
		if (l <= 16) {
		    // If there are maximum 16 NULs suppress the warning
		    e = l;
		    do {
			++b;
			--l;
		    } while (l && !*b);
		}
		if (l)
		    Debug(DebugMild,"Unexpected NUL character while unfolding lines");
#ifdef DEBUG
		else
		    Debug(DebugInfo,"Dropped %d final NUL characters while unfolding lines",e);
#endif
		// End parsing
		b += l;
		l = 0;
		e = 0;
		break;
	    default:
		// Just count this character - we'll pick it later
		++e;
	}
	// Exit without adjusting p and l
	if (goOut)
	    break;
    }
    buf = b;
    len = l;
    // Collect any leftover characters
    if (e) {
	String line(s,e);
	*res << line;
    }
    res->trimBlanks();
    return res;
}


/**
 * MimeMultipartBody
 */
// Constructor to build an empty multipart body
MimeMultipartBody::MimeMultipartBody(const char* subtype, const char* boundary)
    : MimeBody((subtype && *subtype) ? (String("multipart/") + subtype) : "multipart/mixed")
{
    String b = boundary;
    b.trimBlanks();
    if (b.null())
	b << (int)Random::random() << "_" << (unsigned int)Time::now();
    if (b.length() > 70)
	b = b.substr(0,70);
    setParam("boundary",b);
}

// Constructor from block of data
MimeMultipartBody::MimeMultipartBody(const String& type, const char* buf, int len)
    : MimeBody(type)
{
    parse(buf,len);
}

// Constructor from block of data
MimeMultipartBody::MimeMultipartBody(const MimeHeaderLine& type, const char* buf, int len)
    : MimeBody(type)
{
    parse(buf,len);
}

MimeMultipartBody::~MimeMultipartBody()
{
}

// Copy constructor
MimeMultipartBody::MimeMultipartBody(const MimeMultipartBody& original)
    : MimeBody(original.getType())
{
    for (ObjList* o = original.m_bodies.skipNull(); o; o = o->skipNext()) {
	MimeBody* body = static_cast<MimeBody*>(o->get());
	m_bodies.append(body->clone());
    }
}

// Find object by class name, descend into parts
void* MimeMultipartBody::getObject(const String& name) const
{
    if (name == YATOM("MimeMultipartBody"))
	return const_cast<MimeMultipartBody*>(this);
    void* res = MimeBody::getObject(name);
    if (res)
	return res;
    for (ObjList* o = m_bodies.skipNull(); o; o = o->skipNext()) {
	const MimeBody* body = static_cast<const MimeBody*>(o->get());
	res = body->getObject(name);
	if (res)
	    return res;
    }
    return 0;
}

// Find a body. Enclosed multiparts are also searched for the requested body
MimeBody* MimeMultipartBody::findBody(const String& content, MimeBody** start) const
{
    MimeBody* localStart = start ? *start : 0;
    MimeBody* body = 0;
    XDebug(DebugAll,"MimeMultipartBody::findBody(%s,%p) [%p]",
	content.c_str(),localStart,this);
    for (ObjList* o = m_bodies.skipNull(); !body && o; o = o->skipNext()) {
	body = static_cast<MimeBody*>(o->get());
	if (!localStart) {
	    if (content == body->getType())
		break;
	}
	else if (body == localStart)
	    localStart = 0;
	// Check inside multiparts for starting point or requested body
	if (body->isMultipart())
	    body = (static_cast<MimeMultipartBody*>(body))->findBody(content,&localStart);
	else
	    body = 0;
    }
    XDebug(DebugInfo,"MimeMultipartBody::findBody() body=%p start=%p [%p]",
	body,localStart,this);
    if (start)
	*start = localStart;
    return body;
}

// Duplicate this MIME body
MimeBody* MimeMultipartBody::clone() const
{
    return new MimeMultipartBody(*this);
}

// Method that is called internally to build the binary encoded body
void MimeMultipartBody::buildBody() const
{
    String boundary;
    if (!getBoundary(boundary))
	return;

    String crlf = "\r\n";
    String boundaryLast = boundary + "--" + crlf;
    boundary << crlf;

    // Build this body. Add a boundary before each component
    ObjList* o = m_bodies.skipNull();
    if (o)
	for (; o; o = o->skipNext()) {
	    MimeBody* body = static_cast<MimeBody*>(o->get());
	    String hdr;
	    body->buildHeaders(hdr);
	    m_body += boundary;
	    m_body += hdr;
	    m_body += crlf;
	    m_body += body->getBody();
	}
    else
	m_body += boundary;

    // Add termination boundary
    m_body += boundaryLast;
}

// Skip chars until boundary line ends
// Check for last boundary
static void finalizeBoundary(const char*& buf, int& len, bool& last, const char* boundary)
{
    last = (len >= 2 && buf[0] == '-' && buf[1] == '-');
    if (last) {
	buf += 2;
	len -= 2;
	// Ignore the rest, we don't need the epilogue
	return;
    }
    // RFC 2046 states the boundary line should be padded with spaces only
    // and end with CR/LF. We allow any char until LF
    for (; len && *buf != '\n'; buf++, len--)
	;
    if (len) {
	buf++;
	len--;
    }
    if (!len) {
	Debug(DebugNote,"Unexpected multipart end for boundary '%s'",boundary + 4);
	last = true;
    }
}

// Parse a data buffer and append any valid body to this multipart
// Ignore prolog, epilog and invalid bodies
void MimeMultipartBody::parse(const char* buf, int len)
{
    DDebug(DebugAll,"MimeMultipartBody::parse(%p,%d,'%s') [%p]",
	buf,len,getType().c_str(),this);

    String boundary;
    if (!(buf && len > 0 && getBoundary(boundary)))
	return;

    // Find first boundary: ignore the data before it
    bool endBody = false;
    bool foundFirst = false;
    // RFC 2046: First boundary may be preceded by a preamble
    // If there is a preamble, CR/LF MUST be present in boundary
    // If there is no preamble CR/LF may not be present, the buffer may start with -
    if (buf[0] == '-') {
	const char* tmp = boundary.c_str() + 2;
	int tmpLen = boundary.length() - 2;
	if (tmpLen <= len) {
	    int i = 0;
	    for (; i < tmpLen; i++)
		if (buf[i] != tmp[i])
		    break;
	    if (i == tmpLen) {
		foundFirst = true;
		buf += tmpLen;
		len -= tmpLen;
		finalizeBoundary(buf,len,endBody,boundary);
	    }
	}
    }
    if (!foundFirst)
	findBoundary(buf,len,boundary,boundary.length(),endBody);

    // Parse for bodies
    XDebug(DebugInfo,"Start parsing boundary=%s len=%d [%p]",
	boundary.c_str() + 4,len,this);
    while (len > 0) {
	if (endBody)
	    break;
	// Find next boundary. Get the length of data before it
	// 'start' will point to the beginning of an enclosed body
	const char* start = buf;
	int l = findBoundary(buf,len,boundary.c_str(),boundary.length(),endBody);
	XDebug(DebugInfo,"Found %d length body (remain=%d) [%p]",l,len,this);
	if (l <= 0)
	    continue;
	// Parse 'start' for body headers
	ObjList hdr;
	MimeHeaderLine* cType = 0;
	while (l) {
	    String* line = MimeBody::getUnfoldedLine(start,l);
	    // Found end of headers
	    if (line->null()) {
		TelEngine::destruct(line);
		break;
	    }
	    XDebug(DebugAll,"Found line '%s' [%p]",line->c_str(),this);
	    int col = line->find(':');
	    // Check if this is a valid header line
	    if (col <= 0) {
		TelEngine::destruct(line);
		continue;
	    }
	    String name = line->substr(0,col);
	    name.trimBlanks();
	    if (!name) {
		TelEngine::destruct(line);
		continue;
	    }
	    *line >> ":";
	    line->trimBlanks();
	    MimeHeaderLine* ct = new MimeHeaderLine(name,*line);
	    hdr.append(ct);
	    if (name &= "Content-Type")
		cType = ct;
	    TelEngine::destruct(line);
	}
	// 'start' is now pointing to the enclosed body's content
	// Append body to list and move extra headers to it
	MimeBody* body = cType ? MimeBody::build(start,l,*cType) : 0;
	if (!body) {
	    DDebug(DebugNote,
		"Failed to build enclosed body (length=%d)%s [%p]",
		l,cType?"":": Content-Type header is missing",this);
	    continue;
	}
	m_bodies.append(body);
	for (ObjList* o = hdr.skipNull(); o;) {
	    if (cType != o->get()) {
		body->appendHdr(static_cast<MimeHeaderLine*>(o->remove(false)));
		o = o->skipNull();
	    }
	    else
		o = o->skipNext();
	}
	XDebug(DebugInfo,"Added ('%s',%p) with %u additional headers [%p]",
	    cType->c_str(),body,body->headers().count(),this);
    }
    XDebug(DebugInfo,"End parsing boundary=%s%s [%p]",boundary.c_str() + 4,
	(endBody && len > 0)?" .Found garbage after body":"",this);
}

// Parse input buffer for first body boundary or data end
// Advance buffer pass the boundary line and decrease the buffer length
// Set endData to true if a final boundary was found or the end of the
//  buffer was reached
// Return the length of data before the found boundary
int MimeMultipartBody::findBoundary(const char*& buf, int& len,
	const char* boundary, unsigned int bLen, bool& endBody)
{
    if (len <= 0) {
	endBody = true;
	return 0;
    }

    endBody = false;
    int bodyLen = 0;
    bool found = false;

    while (len) {
	// Skip until the first char of boundary
	for (; len >= (int)bLen && *buf != boundary[0]; len--, buf++)
	    bodyLen++;
	// Current char is the first char of the boundary
	// Check if we have enough data for boundary
	if (len < (int)bLen) {
	    bodyLen += len;
	    buf += len;
	    len = 0;
	    break;
	}
	// Check boundary
	unsigned int n = 0;
	for(; n < bLen && *buf == boundary[n]; n++, buf++, len--)
	    ;
	// Not found
	if (n < bLen) {
	    bodyLen += n;
	    continue;
	}
	found = true;
	finalizeBoundary(buf,len,endBody,boundary);
	break;
    }

    if (!found)
	Debug(DebugNote,"Expected multipart boundary '%s' not found",boundary + 4);
    if (!len)
	endBody = true;
    return found ? bodyLen : 0;
}

// Build a boundary string to be used when parsing or building body
// Remove quotes if present. Trim blanks
// Insert CRLF and boundary marks ('--') before parameter
bool MimeMultipartBody::getBoundary(String& boundary) const
{
    boundary = "";
    const NamedString* b = getParam("boundary");
    if (b) {
	String tmp = *b;
	MimeHeaderLine::delQuotes(tmp);
	// RFC 2046 pg. 22: Trailing blanks are not part of the boundary
	// Trim leading blanks also
	tmp.trimBlanks();
	if (!tmp.null()) {
	    boundary = "\r\n--";
	    boundary << tmp;
	}
    }
    if (boundary.null())
	Debug(DebugMild,"MimeMultipartBody::getBoundary() Parameter is %s [%p]",
	    b?"empty":"missing",this);
    return !boundary.null();
}


/**
 * MimeSdpBody
 */
YCLASSIMP(MimeSdpBody,MimeBody)

MimeSdpBody::MimeSdpBody(bool hashing)
    : MimeBody("application/sdp"),
      m_lineAppend(&m_lines), m_hash(0), m_hashing(hashing)
{
}

MimeSdpBody::MimeSdpBody(const String& type, const char* buf, int len)
    : MimeBody(type),
      m_lineAppend(&m_lines), m_hash(0), m_hashing(false)
{
    buildLines(buf,len);
}

MimeSdpBody::MimeSdpBody(const MimeHeaderLine& type, const char* buf, int len)
    : MimeBody(type),
      m_lineAppend(&m_lines), m_hash(0), m_hashing(false)
{
    buildLines(buf,len);
}

MimeSdpBody::MimeSdpBody(const MimeSdpBody& original)
    : MimeBody(original.getType()),
      m_lineAppend(&m_lines), m_hash(original.m_hash), m_hashing(false)
{
    const ObjList* l = &original.m_lines;
    for (; l; l = l->next()) {
    	const NamedString* t = static_cast<NamedString*>(l->get());
        if (t)
	    addLine(t->name(),*t);
    }
    m_hashing = original.m_hashing;
}

MimeSdpBody::~MimeSdpBody()
{
}

void MimeSdpBody::buildBody() const
{
    DDebug(DebugAll,"MimeSdpBody::buildBody() [%p]",this);
    const ObjList* l = &m_lines;
    for (; l; l = l->next()) {
    	const NamedString* t = static_cast<NamedString*>(l->get());
        if (t) {
	    String line;
	    line << t->name() << "=" << *t << "\r\n";
	    m_body += line;
	}
    }
}

MimeBody* MimeSdpBody::clone() const
{
    return new MimeSdpBody(*this);
}

const NamedString* MimeSdpBody::getLine(const char* name) const
{
    if (!(name && *name))
	return 0;
    const ObjList* l = &m_lines;
    for (; l; l = l->next()) {
    	const NamedString* t = static_cast<NamedString*>(l->get());
        if (t && (t->name() &= name))
	    return t;
    }
    return 0;
}

const NamedString* MimeSdpBody::getNextLine(const NamedString* line) const
{
    if (!line)
	return 0;
    const ObjList* l = m_lines.find(line);
    if (!l)
	return 0;
    l = l->next();
    for (; l; l = l->next()) {
    	const NamedString* t = static_cast<NamedString*>(l->get());
        if (t && (t->name() &= line->name()))
	    return t;
    }
    return 0;
}

NamedString* MimeSdpBody::addLine(const char* name, const char* value)
{
    if (m_hashing)
	m_hash = String::hash(value,String::hash(name,m_hash));
    NamedString* line = new NamedString(name,value);
    m_lineAppend = m_lineAppend->append(line);
    return line;
}

// Build the lines from a data buffer
void MimeSdpBody::buildLines(const char* buf, int len)
{
    while (len > 0) {
        String* line = getUnfoldedLine(buf,len);
	int eq = line->find('=');
	if (eq > 0)
	    addLine(line->substr(0,eq),line->substr(eq+1));
	line->destruct();
    }
}


/**
 * MimeBinaryBody
 */
YCLASSIMP(MimeBinaryBody,MimeBody)

MimeBinaryBody::MimeBinaryBody(const String& type, const char* buf, int len)
    : MimeBody(type)
{
    m_body.assign((void*)buf,len);
}

MimeBinaryBody::MimeBinaryBody(const MimeHeaderLine& type, const char* buf, int len)
    : MimeBody(type)
{
    m_body.assign((void*)buf,len);
}

MimeBinaryBody::MimeBinaryBody(const MimeBinaryBody& original)
    : MimeBody(original.getType())
{
    m_body = original.m_body;
}

MimeBinaryBody::~MimeBinaryBody()
{
}

void MimeBinaryBody::buildBody() const
{
    DDebug(DebugAll,"MimeBinaryBody::buildBody() [%p]",this);
    // nothing to do
}

MimeBody* MimeBinaryBody::clone() const
{
    return new MimeBinaryBody(*this);
}


/**
 * MimeStringBody
 */
YCLASSIMP(MimeStringBody,MimeBody)

MimeStringBody::MimeStringBody(const String& type, const char* buf, int len)
    : MimeBody(type), m_text(buf,len)
{
}

MimeStringBody::MimeStringBody(const MimeHeaderLine& type, const char* buf, int len)
    : MimeBody(type), m_text(buf,len)
{
}

MimeStringBody::MimeStringBody(const MimeStringBody& original)
    : MimeBody(original.getType()), m_text(original.m_text)
{
}

MimeStringBody::~MimeStringBody()
{
}

void MimeStringBody::buildBody() const
{
    DDebug(DebugAll,"MimeStringBody::buildBody() [%p]",this);
    m_body.assign((void*)m_text.c_str(),m_text.length());
}

MimeBody* MimeStringBody::clone() const
{
    return new MimeStringBody(*this);
}


/**
 * MimeLinesBody
 */
YCLASSIMP(MimeLinesBody,MimeBody)

MimeLinesBody::MimeLinesBody(const String& type, const char* buf, int len)
    : MimeBody(type)
{
    while (len > 0)
        m_lines.append(getUnfoldedLine(buf,len));
}

MimeLinesBody::MimeLinesBody(const MimeHeaderLine& type, const char* buf, int len)
    : MimeBody(type)
{
    while (len > 0)
        m_lines.append(getUnfoldedLine(buf,len));
}

MimeLinesBody::MimeLinesBody(const MimeLinesBody& original)
    : MimeBody(original.getType())
{
    const ObjList* l = &original.m_lines;
    for (; l; l = l->next()) {
    	const String* s = static_cast<String*>(l->get());
        if (s)
	    m_lines.append(new String(*s));
    }
}

MimeLinesBody::~MimeLinesBody()
{
}

void MimeLinesBody::buildBody() const
{
    DDebug(DebugAll,"MimeLinesBody::buildBody() [%p]",this);
    const ObjList* l = &m_lines;
    for (; l; l = l->next()) {
    	const String* s = static_cast<String*>(l->get());
        if (s) {
	    String line;
	    line << *s << "\r\n";
	    m_body += line;
	}
    }
}

MimeBody* MimeLinesBody::clone() const
{
    return new MimeLinesBody(*this);
}

/* vi: set ts=8 sw=4 sts=4 noet: */
