/**
 * message.cpp
 * Yet Another SIP Stack
 * 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 <yatesip.h>
#include "util.h"

#include <string.h>
#include <stdlib.h>


using namespace TelEngine;

static Regexp s_angled("<\\([^>]\\+\\)>");

SIPMessage::SIPMessage(const SIPMessage& original)
    : RefObject(),
      version(original.version), method(original.method), uri(original.uri),
      code(original.code), reason(original.reason),
      body(0), msgTraceId(original.msgTraceId), msgPrint(true), m_ep(0),
      m_valid(original.isValid()), m_answer(original.isAnswer()),
      m_outgoing(original.isOutgoing()), m_ack(original.isACK()),
      m_cseq(-1), m_flags(original.getFlags()), m_dontSend(original.m_dontSend)
{
    DDebug(DebugAll,"SIPMessage::SIPMessage(&%p) [%p]",
	&original,this);
    if (original.body)
	setBody(original.body->clone());
    setParty(original.getParty());
    setSequence(original.getSequence());
    bool via1 = true;
    const ObjList* l = &original.header;
    for (; l; l = l->next()) {
	const MimeHeaderLine* hl = static_cast<MimeHeaderLine*>(l->get());
	if (!hl)
	    continue;
	// CSeq must not be copied, a new one will be built by complete()
	if (hl->name() &= "CSeq")
	    continue;
	MimeHeaderLine* nl = hl->clone();
	// this is a new transaction so let complete() add randomness
	if (via1 && (nl->name() &= "Via")) {
	    via1 = false;
	    nl->delParam("branch");
	}
	addHeader(nl);
    }
}

SIPMessage::SIPMessage(const char* _method, const char* _uri, const char* _version)
    : version(_version), method(_method), uri(_uri), code(0),
      body(0), msgPrint(true), m_ep(0), m_valid(true),
      m_answer(false), m_outgoing(true), m_ack(false), m_cseq(-1), m_flags(-1),
      m_dontSend(false)
{
    DDebug(DebugAll,"SIPMessage::SIPMessage('%s','%s','%s') [%p]",
	_method,_uri,_version,this);
}

SIPMessage::SIPMessage(SIPParty* ep, const char* buf, int len, unsigned int* bodyLen)
    : code(0), body(0), msgPrint(true), m_ep(ep), m_valid(false),
      m_answer(false), m_outgoing(false), m_ack(false), m_cseq(-1), m_flags(-1),
      m_dontSend(false)
{
    DDebug(DebugInfo,"SIPMessage::SIPMessage(%p,%d) [%p]\r\n------\r\n%s------",
	buf,len,this,buf);
    if (m_ep)
	m_ep->ref();
    if (!(buf && *buf)) {
	TraceDebug(msgTraceId,DebugWarn,"Empty message text in [%p]",this);
	return;
    }
    if (len < 0)
	len = ::strlen(buf);
    m_valid = parse(buf,len,bodyLen);
}

SIPMessage::SIPMessage(const SIPMessage* message, int _code, const char* _reason)
    : code(_code), body(0), msgPrint(true),
      m_ep(0), m_valid(false),
      m_answer(true), m_outgoing(true), m_ack(false), m_cseq(-1), m_flags(-1),
      m_dontSend(false)
{
    DDebug(DebugAll,"SIPMessage::SIPMessage(%p,%d,'%s') [%p]",
	message,_code,_reason,this);
    if (!_reason)
	_reason = lookup(code,SIPResponses,"Unknown Reason Code");
    reason = _reason;
    if (!(message && message->isValid()))
	return;
    m_flags = message->getFlags();
    m_ep = message->getParty();
    if (m_ep)
	m_ep->ref();
    version = message->version;
    uri = message->uri;
    method = message->method;
    msgTraceId = message->msgTraceId;
    m_cseq = message->getCSeq();
    copyAllHeaders(message,"Via");
    copyAllHeaders(message,"Record-Route");
    copyHeader(message,"From");
    copyHeader(message,"To");
    copyHeader(message,"Call-ID");
    copyHeader(message,"CSeq");
    m_valid = true;
}

SIPMessage::SIPMessage(const SIPMessage* original, const SIPMessage* answer)
    : method("ACK"), code(0),
      body(0), msgPrint(true), m_ep(0), m_valid(false),
      m_answer(false), m_outgoing(true), m_ack(true), m_cseq(-1), m_flags(-1),
      m_dontSend(false)
{
    DDebug(DebugAll,"SIPMessage::SIPMessage(%p,%p) [%p]",original,answer,this);
    if (!(original && original->isValid()))
	return;
    m_flags = original->getFlags();
    m_ep = original->getParty();
    if (m_ep)
	m_ep->ref();
    version = original->version;
    uri = original->uri;
    msgTraceId = original->msgTraceId;
    copyAllHeaders(original,"Via");
    MimeHeaderLine* hl = const_cast<MimeHeaderLine*>(getHeader("Via"));
    if (!hl) {
	String tmp;
	tmp << version << "/" << getParty()->getProtoName();
	if (getParty()) {
	    tmp << " ";
	    getParty()->appendAddr(tmp,true);
	}
	hl = new MimeHeaderLine("Via",tmp);
	header.append(hl);
    }
    if (answer && ((answer->code / 100) == 2) && (original->method &= "INVITE")) {
	String tmp("z9hG4bK");
	tmp << (int)Random::random();
	hl->setParam("branch",tmp);
	const MimeHeaderLine* co = answer->getHeader("Contact");
	if (co) {
	    uri = *co;
	    static Regexp r("^[^<]*<\\([^>]*\\)>.*$");
	    if (uri.matches(r))
		uri = uri.matchString(1);
	}
	// new transaction - get/apply routeset unless INVITE already knew it
	if (!original->getHeader("Route")) {
	    ObjList* routeset = answer->getRoutes();
	    addRoutes(routeset);
	    TelEngine::destruct(routeset);
	}
    }
    m_cseq = original->getCSeq();
    copyAllHeaders(original,"Route");
    copyHeader(original,"From");
    copyHeader(original,"To");
    copyHeader(original,"Call-ID");
    String tmp;
    tmp << m_cseq << " " << method;
    addHeader("CSeq",tmp);
    copyHeader(original,"Max-Forwards");
    copyAllHeaders(original,"Contact");
    copyAllHeaders(original,"Authorization");
    copyAllHeaders(original,"Proxy-Authorization");
    copyHeader(original,"User-Agent");
    m_valid = true;
}

SIPMessage::~SIPMessage()
{
    DDebug(DebugAll,"SIPMessage::~SIPMessage() [%p]",this);
    m_valid = false;
    setParty();
    setBody();
}

void SIPMessage::complete(SIPEngine* engine, const char* user, const char* domain, const char* dlgTag, int flags)
{
    DDebug(engine,DebugAll,"SIPMessage::complete(%p,'%s','%s','%s',%d)%s%s%s [%p]",
	engine,user,domain,dlgTag,flags,
	isACK() ? " ACK" : "",
	isOutgoing() ? " OUT" : "",
	isAnswer() ? " ANS" : "",
	this);
    if (!engine)
	return;
    if (-1 == flags)
	flags = m_flags;
    if (-1 == flags)
	flags = engine->flags();
    m_flags = flags;

    // don't complete incoming messages
    if (!isOutgoing())
	return;

    if (!getParty()) {
	engine->buildParty(this);
	if (!getParty()) {
	    TraceDebug(msgTraceId,engine,DebugCrit,"Could not complete party-less SIP message [%p]",this);
	    return;
	}
    }
    String partyLAddr;
    int partyLPort = 0;
    getParty()->getAddr(partyLAddr,partyLPort,true);

    // only set the dialog tag on ACK
    if (isACK()) {
	MimeHeaderLine* hl = const_cast<MimeHeaderLine*>(getHeader("To"));
	if (dlgTag && hl && !hl->getParam("tag"))
	    hl->setParam("tag",dlgTag);
	return;
    }

    String localDomain;
    if (!domain) {
	if (partyLPort && (partyLPort != 5060))
	    SocketAddr::appendTo(localDomain,partyLAddr,partyLPort);
	else
	    SocketAddr::appendAddr(localDomain,partyLAddr);
	domain = localDomain;
    }

    MimeHeaderLine* hl = const_cast<MimeHeaderLine*>(getHeader("Via"));
    if (!hl) {
	String tmp;
	tmp << version << "/" << getParty()->getProtoName();
	tmp << " ";
	SocketAddr::appendTo(tmp,partyLAddr,partyLPort);
	hl = new MimeHeaderLine("Via",tmp);
	if (isReliable() && 0 == (flags & NoConnReuse))
	    hl->setParam("alias");
	if (!((flags & (NotReqRport|RportAfterBranch)) || isAnswer() || isACK()))
	    hl->setParam("rport");
	header.append(hl);
    }
    if (!(isAnswer() || hl->getParam("branch"))) {
	String tmp("z9hG4bK");
	tmp << (unsigned int)Random::random();
	hl->setParam("branch",tmp);
    }
    if (isAnswer()) {
	if (!(flags & NotSetReceived)) {
	    Lock lock(getParty()->mutex());
	    hl->setParam("received",getParty()->getPartyAddr());
	}
	const String* rport = hl->getParam("rport");
	if (rport && rport->null() && !(flags & NotSetRport))
	    const_cast<String&>(*rport) = getParty()->getPartyPort();
    }
    else if ((flags & RportAfterBranch) && !((flags & NotReqRport) || isACK() || hl->getParam("rport")))
	hl->setParam("rport");

    if (!isAnswer()) {
	hl = const_cast<MimeHeaderLine*>(getHeader("From"));
	if (!hl) {
	    String tmp = uri.startsWith("sips:") ? "<sips:" : "<sip:";
	    if (user)
		tmp << String::uriEscape(user,'@',"+?&") << "@";
	    tmp << domain << ">";
	    hl = new MimeHeaderLine("From",tmp);
	    header.append(hl);
	}
	if (!hl->getParam("tag"))
	    hl->setParam("tag",String((unsigned int)Random::random()));
    }

    hl = const_cast<MimeHeaderLine*>(getHeader("To"));
    if (!(isAnswer() || hl)) {
	String tmp;
	tmp << "<" << uri << ">";
	hl = new MimeHeaderLine("To",tmp);
	header.append(hl);
    }
    if (hl && dlgTag && !hl->getParam("tag"))
	hl->setParam("tag",dlgTag);

    if (!(isAnswer() || getHeader("Call-ID"))) {
	String tmp;
	tmp << (unsigned int)Random::random() << "@" << domain;
	addHeader("Call-ID",tmp);
    }

    if (!isAnswer()) {
	hl = const_cast<MimeHeaderLine*>(getHeader("CSeq"));
	if (hl) {
	    if (m_cseq <= 0) {
		int sep = hl->find(' ');
		if (sep > 0)
		    m_cseq = hl->substr(0,sep).toInteger(-1,10);
	    }
	}
	else {
	    String tmp;
	    if (m_cseq <= 0) {
		SIPSequence* seq = getSequence();
		if (!seq)
		    seq = engine->getSequence();
#ifdef DEBUG
		else
		    TraceDebug(msgTraceId,engine,DebugAll,"Using local sequence %p last=%d [%p]",
			seq,seq->getLastCSeq(),this);
#endif
		m_cseq = seq->getNextCSeq();
	    }
	    tmp << m_cseq << " " << method;
	    addHeader("CSeq",tmp);
	}
    }

    const char* info = isAnswer() ? "Server" : "User-Agent";
    if (!((flags & NotAddAgent) || getHeader(info) || engine->getUserAgent().null()))
	addHeader(info,engine->getUserAgent());

    // keep 100 answers short - they are hop to hop anyway
    if (isAnswer() && (code == 100))
	return;

    if (!(isAnswer() || getHeader("Max-Forwards"))) {
	String tmp(engine->getMaxForwards());
	addHeader("Max-Forwards",tmp);
    }

    if ((method == YSTRING("INVITE") || method == YSTRING("UPDATE")) && !getHeader("Contact")) {
	// automatically add a contact field to (re)INVITE, UPDATE and their answers
	String tmp(user);
	if (!tmp) {
	    tmp = uri;
	    static Regexp r(":\\([^:@]*\\)@");
	    tmp.matches(r);
	    tmp = tmp.matchString(1).uriUnescape();
	}
	if (tmp)
	    tmp = tmp.uriEscape('@',"+?&") + "@";
	tmp = (uri.startsWith("sips:") ? "<sips:" : "<sip:") + tmp;
	SocketAddr::appendTo(tmp,partyLAddr,partyLPort) << ">";
	addHeader("Contact",tmp);
    }

    if (!((flags & NotAddAllow) || getHeader("Allow")))
	addHeader("Allow",engine->getAllowed());
}

bool SIPMessage::copyHeader(const SIPMessage* message, const char* name, const char* newName)
{
    const MimeHeaderLine* hl = message ? message->getHeader(name) : 0;
    if (hl) {
	header.append(hl->clone(newName));
	return true;
    }
    return false;
}

int SIPMessage::copyAllHeaders(const SIPMessage* message, const char* name, const char* newName)
{
    if (!(message && name && *name))
	return 0;
    int c = 0;
    const ObjList* l = &message->header;
    for (; l; l = l->next()) {
	const MimeHeaderLine* hl = static_cast<const MimeHeaderLine*>(l->get());
	if (hl && (hl->name() &= name)) {
	    ++c;
	    header.append(hl->clone(newName));
	}
    }
    return c;
}

bool SIPMessage::parseFirst(String& line)
{
    XDebug(DebugAll,"SIPMessage::parse firstline= '%s'",line.c_str());
    if (line.null())
	return false;
    static Regexp r("^\\([Ss][Ii][Pp]/[0-9]\\.[0-9]\\+\\)[[:space:]]\\+\\([0-9][0-9][0-9]\\)[[:space:]]\\+\\(.*\\)$");
    if (line.matches(r)) {
	// Answer: <version> <code> <reason-phrase>
	m_answer = true;
	version = line.matchString(1).toUpper();
	code = line.matchString(2).toInteger();
	reason = line.matchString(3);
	DDebug(DebugAll,"got answer version='%s' code=%d reason='%s'",
	    version.c_str(),code,reason.c_str());
    }
    else {
	static Regexp r2("^\\([[:alpha:]]\\+\\)[[:space:]]\\+\\([^[:space:]]\\+\\)[[:space:]]\\+\\([Ss][Ii][Pp]/[0-9]\\.[0-9]\\+\\)$");
	if (line.matches(r2)) {
	    // Request: <method> <uri> <version>
	    m_answer = false;
	    method = line.matchString(1).toUpper();
	    uri = line.matchString(2);
	    version = line.matchString(3).toUpper();
	    DDebug(DebugAll,"got request method='%s' uri='%s' version='%s'",
		method.c_str(),uri.c_str(),version.c_str());
	    if (method == YSTRING("ACK"))
		m_ack = true;
	}
	else {
	    TraceDebug(msgTraceId,DebugAll,"Invalid SIP line '%s'",line.c_str());
	    return false;
	}
    }
    return true;
}

bool SIPMessage::parse(const char* buf, int len, unsigned int* bodyLen)
{
    DDebug(DebugAll,"SIPMessage::parse(%p,%d) [%p]",buf,len,this);
    String* line = 0;
    while (len > 0) {
	line = MimeBody::getUnfoldedLine(buf,len);
	if (!line->null())
	    break;
	// Skip any initial empty lines
	TelEngine::destruct(line);
    }
    if (!line)
	return false;
    if (!parseFirst(*line)) {
	line->destruct();
	return false;
    }
    line->destruct();
    int clen = -1;
    while (len > 0) {
	line = MimeBody::getUnfoldedLine(buf,len);
	if (line->null()) {
	    // Found end of headers
	    line->destruct();
	    break;
	}
	int col = line->find(':');
	if (col <= 0) {
	    line->destruct();
	    return false;
	}
	String name = line->substr(0,col);
	name.trimBlanks();
	if (name.null()) {
	    line->destruct();
	    return false;
	}
	name = uncompactForm(name);
	*line >> ":";
	line->trimBlanks();
	XDebug(DebugAll,"SIPMessage::parse header='%s' value='%s'",name.c_str(),line->c_str());

	if ((name &= "WWW-Authenticate") ||
	    (name &= "Proxy-Authenticate") ||
	    (name &= "Authorization") ||
	    (name &= "Proxy-Authorization"))
	    header.append(new MimeAuthLine(name,*line));
	else
	    header.append(new MimeHeaderLine(name,*line));

	if ((clen < 0) && (name &= "Content-Length"))
	    clen = line->toInteger(-1,10);
	else if ((m_cseq < 0) && (name &= "CSeq")) {
	    int sep = line->find(' ');
	    if (sep > 0) {
		m_cseq = line->substr(0,sep).toInteger(-1,10);
		if (m_answer) {
		    method = line->substr(sep + 1);
		    method.trimBlanks().toUpper();
		}
	    }
	}
	line->destruct();
    }
    if (!bodyLen) {
	if (clen >= 0) {
	    if (clen > len)
		TraceDebug(msgTraceId,"SIPMessage",DebugMild,"Content length is %d but only %d in buffer",clen,len);
	    else if (clen < len) {
		DDebug("SIPMessage",DebugInfo,"Got %d garbage bytes after content",len - clen);
		len = clen;
	    }
	}
	buildBody(buf,len);
    }
    else
	*bodyLen = (clen >= 0) ? clen : 0;
    DDebug(DebugAll,"SIPMessage::parse %d header lines, body %p",
	header.count(),body);
    return true;
}

SIPMessage* SIPMessage::fromParsing(SIPParty* ep, const char* buf, int len, unsigned int* bodyLen)
{
    SIPMessage* msg = new SIPMessage(ep,buf,len,bodyLen);
    if (msg->isValid())
	return msg;
    DDebug("SIPMessage",DebugInfo,"Invalid message");
    msg->destruct();
    return 0;
}

// Build message's body. Reset it before
void SIPMessage::buildBody(const char* buf, int len)
{
    TelEngine::destruct(body);
    if (!buf)
	return;
    if (len < 0)
	len = ::strlen(buf);
    const MimeHeaderLine* cType = getHeader("Content-Type");
    if (cType)
	body = MimeBody::build(buf,len,*cType);
    // Move extra Content- header lines to body
    if (body) {
	ListIterator iter(header);
	for (GenObject* o = 0; (o = iter.get());) {
	    MimeHeaderLine* line = static_cast<MimeHeaderLine*>(o);
	    if (!line->startsWith("Content-",false,true) || (*line &= "Content-Length"))
		continue;
	    // Delete Content-Type and move all other lines to body
	    bool delobj = (line == cType);
	    header.remove(o,delobj);
	    if (!delobj)
		body->appendHdr(line);
	}
    }
    DDebug(DebugAll,"SIPMessage::buildBody %d header lines, body %p",
	header.count(),body);
}

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

const MimeHeaderLine* SIPMessage::getLastHeader(const char* name) const
{
    if (!(name && *name))
	return 0;
    const MimeHeaderLine* res = 0;
    const ObjList* l = &header;
    for (; l; l = l->next()) {
	const MimeHeaderLine* t = static_cast<const MimeHeaderLine*>(l->get());
	if (t && (t->name() &= name))
	    res = t;
    }
    return res;
}

void SIPMessage::clearHeaders(const char* name)
{
    if (!(name && *name))
	return;
    ObjList* l = &header;
    while (l) {
	const MimeHeaderLine* t = static_cast<const MimeHeaderLine*>(l->get());
	if (t && (t->name() &= name))
	    l->remove();
	else
	    l = l->next();
    }
}

int SIPMessage::countHeaders(const char* name) const
{
    if (!(name && *name))
	return 0;
    int res = 0;
    const ObjList* l = &header;
    for (; l; l = l->next()) {
	const MimeHeaderLine* t = static_cast<const MimeHeaderLine*>(l->get());
	if (t && (t->name() &= name))
	    ++res;
    }
    return res;
}

const NamedString* SIPMessage::getParam(const char* name, const char* param, bool last) const
{
    const MimeHeaderLine* hl = last ? getLastHeader(name) : getHeader(name);
    return hl ? hl->getParam(param) : 0;
}

const String& SIPMessage::getHeaderValue(const char* name, bool last) const
{
    const MimeHeaderLine* hl = last ? getLastHeader(name) : getHeader(name);
    return hl ? *static_cast<const String*>(hl) : String::empty();
}

const String& SIPMessage::getParamValue(const char* name, const char* param, bool last) const
{
    const NamedString* ns = getParam(name,param,last);
    return ns ? *static_cast<const String*>(ns) : String::empty();
}

const String& SIPMessage::getHeaders() const
{
    if (isValid() && m_string.null()) {
	if (isAnswer())
	    m_string << version << " " << code << " " << reason << "\r\n";
	else
	    m_string << method << " " << uri << " " << version << "\r\n";

	const ObjList* l = &header;
	for (; l; l = l->next()) {
	    MimeHeaderLine* t = static_cast<MimeHeaderLine*>(l->get());
	    if (t) {
		t->buildLine(m_string);
		m_string << "\r\n";
	    }
	}
    }
    return m_string;
}

const DataBlock& SIPMessage::getBuffer() const
{
    if (isValid() && m_data.null()) {
	m_data.assign((void*)(getHeaders().c_str()),getHeaders().length());
	if (body) {
	    String s;
	    body->buildHeaders(s);
	    s << "Content-Length: " << body->getBody().length() << "\r\n\r\n";
	    m_data += s;
	}
	else
	    m_data += "Content-Length: 0\r\n\r\n";
	if (body)
	    m_data += body->getBody();
#ifdef DEBUG
	if (debugAt(DebugInfo)) {
	    String buf((char*)m_data.data(),m_data.length());
	    TraceDebug(msgTraceId,DebugInfo,"SIPMessage::getBuffer() [%p]\r\n------\r\n%s------",
		this,buf.c_str());
	}
#endif
    }
    return m_data;
}

void SIPMessage::setBody(MimeBody* newbody)
{
    if (newbody == body)
	return;
    TelEngine::destruct(body);
    body = newbody;
}

void SIPMessage::setParty(SIPParty* ep)
{
    if (ep == m_ep)
	return;
    if (ep && !ep->ref())
	ep = 0;
    XDebug(DebugAll,"SIPMessage::setParty(%p) current=%p [%p]",ep,m_ep,this);
    SIPParty* tmp = m_ep;
    m_ep = ep;
    TelEngine::destruct(tmp);
}

MimeAuthLine* SIPMessage::buildAuth(const String& username, const String& password,
    const String& meth, const String& uri, bool proxy, SIPEngine* engine) const
{
    const char* hdr = proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
    const ObjList* l = &header;
    for (; l; l = l->next()) {
	const MimeAuthLine* t = YOBJECT(MimeAuthLine,l->get());
	if (t && (t->name() &= hdr) && (*t &= "Digest")) {
	    String nonce(t->getParam("nonce"));
	    MimeHeaderLine::delQuotes(nonce);
	    if (nonce.null())
		continue;
	    String realm(t->getParam("realm"));
	    MimeHeaderLine::delQuotes(realm);
	    int par = uri.find(';');
	    String msguri = uri.substr(0,par);
	    NamedList qop(TelEngine::c_safe(t->getParam("qop")));
	    if (qop) {
		MimeHeaderLine::delQuotes(qop);
		if (qop == YSTRING("auth")) {
		    String nc("00000001");
		    if (engine)
			engine->ncGet(nc);
		    qop.addParam("nc",nc);
		    MD5 md5;
		    md5 << String((unsigned int)Random::random()) << nc << String(Time::secNow());
		    qop.addParam("cnonce",md5.hexDigest());
		}
		else
		    continue;
	    }
	    String response;
	    SIPEngine::buildAuth(username,realm,password,nonce,meth,msguri,response,qop);
	    MimeAuthLine* auth = new MimeAuthLine(proxy ? "Proxy-Authorization" : "Authorization","Digest");
	    auth->setParam("username",MimeHeaderLine::quote(username));
	    auth->setParam("realm",MimeHeaderLine::quote(realm));
	    auth->setParam("nonce",MimeHeaderLine::quote(nonce));
	    auth->setParam("uri",MimeHeaderLine::quote(msguri));
	    auth->setParam("response",MimeHeaderLine::quote(response));
	    auth->setParam("algorithm","MD5");
	    // copy opaque data as-is, only if present
	    const NamedString* opaque = t->getParam(YSTRING("opaque"));
	    if (opaque)
		auth->setParam(opaque->name(),*opaque);
	    if (qop) {
		auth->setParam("qop",qop);
		NamedIterator iter(qop);
		for (const NamedString* ns = 0; 0 != (ns = iter.get());) {
		    if (ns->name() == YSTRING("nc"))
			auth->setParam(ns->name(),*ns);
		    else
			auth->setParam(ns->name(),MimeHeaderLine::quote(*ns));
		}
	    }
	    return auth;
	}
    }
    return 0;
}

MimeAuthLine* SIPMessage::buildAuth(const SIPMessage& original, SIPEngine* engine) const
{
    if (original.getAuthUsername().null())
	return 0;
    return buildAuth(original.getAuthUsername(),original.getAuthPassword(),
	original.method,original.uri,(code == 407),engine);
}

ObjList* SIPMessage::getRoutes() const
{
    ObjList* list = 0;
    const ObjList* l = &header;
    for (; l; l = l->next()) {
	const MimeHeaderLine* h = YOBJECT(MimeHeaderLine,l->get());
	if (h && (h->name() &= "Record-Route")) {
	    int p = 0;
	    while (p >= 0) {
		MimeHeaderLine* line = 0;
		int s = MimeHeaderLine::findSep(*h,',',p);
		String tmp;
		if (s < 0) {
		    if (p)
			tmp = h->substr(p);
		    else
			line = new MimeHeaderLine(*h,"Route");
		    p = -1;
		}
		else {
		    if (s > p)
			tmp = h->substr(p,s-p);
		    p = s + 1;
		}
		tmp.trimBlanks();
		if (tmp)
		    line = new MimeHeaderLine("Route",tmp);
		if (!line)
		    continue;
		if (!list)
		    list = new ObjList;
		if (isAnswer())
		    // route set learned from an answer, reverse order
		    list->insert(line);
		else
		    // route set learned from a request, preserve order
		    list->append(line);
	    }
	}
    }
    return list;
}

void SIPMessage::addRoutes(const ObjList* routes)
{
    if (isAnswer() || !routes)
	return;
    MimeHeaderLine* hl = YOBJECT(MimeHeaderLine,routes->get());
    if (hl) {
	// check if first route is to a RFC 2543 proxy
	String tmp = *hl;
	if (tmp.matches(s_angled))
	    tmp = tmp.matchString(1);
	if (tmp.find(";lr") < 0) {
	    // prepare a new final route
	    hl = new MimeHeaderLine("Route","<" + uri + ">");
	    // set the first route as Request-URI and then skip it
	    uri = tmp;
	    routes = routes->next();
	}
	else
	    hl = 0;
    }

    // add (remaining) routes
    for (; routes; routes = routes->next()) {
	const MimeHeaderLine* h = YOBJECT(MimeHeaderLine,routes->get());
	if (h)
	    addHeader(h->clone());
    }

    // if first route was to a RFC 2543 proxy add the old Request-URI
    if (hl)
	addHeader(hl);
}


SIPDialog::SIPDialog(const SIPDialog& original)
    : String(original),
      localURI(original.localURI), localTag(original.localTag),
      remoteURI(original.remoteURI), remoteTag(original.remoteTag),
      remoteCSeq(original.remoteCSeq), m_seq(original.getSequence())
{
    DDebug("SIPDialog",DebugAll,"callid '%s' local '%s;tag=%s' remote '%s;tag=%s' [%p]",
	c_str(),localURI.c_str(),localTag.c_str(),remoteURI.c_str(),remoteTag.c_str(),this);
}

SIPDialog& SIPDialog::operator=(const SIPDialog& original)
{
    String::operator=(original);
    localURI = original.localURI;
    localTag = original.localTag;
    remoteURI = original.remoteURI;
    remoteTag = original.remoteTag;
    remoteCSeq = original.remoteCSeq;
    setSequence(original.getSequence());
    DDebug("SIPDialog",DebugAll,"callid '%s' local '%s;tag=%s' remote '%s;tag=%s' [%p]",
	c_str(),localURI.c_str(),localTag.c_str(),remoteURI.c_str(),remoteTag.c_str(),this);
    return *this;
}

SIPDialog& SIPDialog::operator=(const String& callid)
{
    String::operator=(callid);
    localURI.clear();
    localTag.clear();
    remoteURI.clear();
    remoteTag.clear();
    DDebug("SIPDialog",DebugAll,"callid '%s' local '%s;tag=%s' remote '%s;tag=%s' [%p]",
	c_str(),localURI.c_str(),localTag.c_str(),remoteURI.c_str(),remoteTag.c_str(),this);
    return *this;
}

SIPDialog::SIPDialog(const SIPMessage& message)
    : String(message.getHeaderValue("Call-ID")),
     remoteCSeq(-1)
{
    bool local = message.isOutgoing() ^ message.isAnswer();
    const MimeHeaderLine* hl = message.getHeader(local ? "From" : "To");
    localURI = hl;
    if (localURI.matches(s_angled))
        localURI = localURI.matchString(1);
    if (hl)
	localTag = hl->getParam("tag");
    hl = message.getHeader(local ? "To" : "From");
    remoteURI = hl;
    if (remoteURI.matches(s_angled))
        remoteURI = remoteURI.matchString(1);
    if (hl)
	remoteTag = hl->getParam("tag");
    setSequence(message.getSequence());
    if (!(message.isOutgoing() || message.isAnswer() || message.isACK() || remoteCSeq >= message.getCSeq()))
	remoteCSeq = message.getCSeq();
    DDebug("SIPDialog",DebugAll,"callid '%s' local '%s;tag=%s' remote '%s;tag=%s' [%p]",
	c_str(),localURI.c_str(),localTag.c_str(),remoteURI.c_str(),remoteTag.c_str(),this);
}

SIPDialog& SIPDialog::operator=(const SIPMessage& message)
{
    const char* cid = message.getHeaderValue("Call-ID");
    if (cid)
	String::operator=(cid);
    bool local = message.isOutgoing() ^ message.isAnswer();
    const MimeHeaderLine* hl = message.getHeader(local ? "From" : "To");
    localURI = hl;
    if (localURI.matches(s_angled))
        localURI = localURI.matchString(1);
    if (hl)
	localTag = hl->getParam("tag");
    hl = message.getHeader(local ? "To" : "From");
    remoteURI = hl;
    if (remoteURI.matches(s_angled))
        remoteURI = remoteURI.matchString(1);
    if (hl)
	remoteTag = hl->getParam("tag");
    SIPSequence* seq = message.getSequence();
    if (seq)
	setSequence(seq);
    if (!(message.isOutgoing() || message.isAnswer() || message.isACK() || remoteCSeq >= message.getCSeq()))
	remoteCSeq = message.getCSeq();
    DDebug("SIPDialog",DebugAll,"callid '%s' local '%s;tag=%s' remote '%s;tag=%s' [%p]",
	c_str(),localURI.c_str(),localTag.c_str(),remoteURI.c_str(),remoteTag.c_str(),this);
    return *this;
}

bool SIPDialog::matches(const SIPDialog& other, bool ignoreURIs) const
{
    return
	String::operator==(other) &&
	localTag == other.localTag &&
	remoteTag == other.remoteTag &&
	(ignoreURIs ||
	    (localURI == other.localURI &&
	    remoteURI == other.remoteURI));
}

void SIPDialog::setCSeq(int32_t cseq)
{
    SIPSequence* seq = new SIPSequence(cseq);
    setSequence(seq);
    TelEngine::destruct(seq);
}

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