You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sems/core/AmMimeBody.cpp

823 lines
16 KiB

#include "AmMimeBody.h"
#include "sip/parse_common.h"
#include "sip/parse_header.h"
#include "sip/defs.h"
#include "log.h"
#include "AmUtils.h"
#include <memory>
using std::auto_ptr;
#define MULTIPART "multipart"
#define MULTIPART_MIXED "multipart/mixed"
#define MULTIPART_ALTERNATIVE "multipart/alternative"
#define BOUNDARY_str "boundary"
#define BOUNDARY_len (sizeof(BOUNDARY_str)-/*0-term*/1)
AmMimeBody::AmMimeBody()
: content_len(0),
payload(NULL)
{
}
AmMimeBody::AmMimeBody(const AmMimeBody& body)
: ct(body.ct),
hdrs(body.hdrs),
content_len(0),
payload(NULL)
{
if(body.payload && body.content_len) {
setPayload(body.payload,body.content_len);
}
for(Parts::const_iterator it = body.parts.begin();
it != body.parts.end(); ++it) {
parts.push_back(new AmMimeBody(**it));
}
}
AmMimeBody::~AmMimeBody()
{
clearParts();
clearPayload();
}
const AmMimeBody& AmMimeBody::operator = (const AmMimeBody& r_body)
{
ct = r_body.ct;
hdrs = r_body.hdrs;
if(r_body.payload && r_body.content_len) {
setPayload(r_body.payload,r_body.content_len);
}
else {
clearPayload();
}
clearParts();
for(Parts::const_iterator it = r_body.parts.begin();
it != r_body.parts.end(); ++it) {
parts.push_back(new AmMimeBody(**it));
}
return r_body;
}
AmContentType::AmContentType()
: mp_boundary(NULL)
{
}
AmContentType::AmContentType(const AmContentType& ct)
: type(ct.type),
subtype(ct.subtype),
mp_boundary(NULL)
{
for(Params::const_iterator it = ct.params.begin();
it != ct.params.end(); ++it) {
params.push_back(new Param(**it));
if((*it)->type == Param::BOUNDARY)
mp_boundary = params.back();
}
}
AmContentType::~AmContentType()
{
clearParams();
}
const AmContentType& AmContentType::operator = (const AmContentType& r_ct)
{
type = r_ct.type;
subtype = r_ct.subtype;
clearParams();
for(Params::const_iterator it = r_ct.params.begin();
it != r_ct.params.end(); ++it) {
params.push_back(new Param(**it));
if((*it)->type == Param::BOUNDARY)
mp_boundary = params.back();
}
return r_ct;
}
void AmContentType::clearParams()
{
mp_boundary = NULL;
while(!params.empty()){
delete params.front();
params.pop_front();
}
}
void AmContentType::resetBoundary()
{
Params::iterator it = params.begin();
while (it != params.end()) {
Params::iterator l_it = it;
it++;
if ((*l_it)->type == Param::BOUNDARY)
delete *l_it;
params.erase(l_it);
}
params.push_back(new Param(BOUNDARY_str, int2hex(get_random())));
params.back()->type = Param::BOUNDARY;
mp_boundary = params.back();
}
void AmMimeBody::clearParts()
{
while(!parts.empty()){
delete parts.front();
parts.pop_front();
}
}
void AmMimeBody::clearPart(Parts::iterator position)
{
parts.erase(position);
}
void AmMimeBody::clearPayload()
{
delete [] payload;
payload = NULL;
}
int AmContentType::parse(const string& ct)
{
enum {
CT_TYPE=0,
CT_SLASH_SWS,
CT_SUBTYPE_SWS,
CT_SUBTYPE
};
const char* c = ct.c_str();
const char* beg = c;
const char* end = c + ct.length();
int st = CT_TYPE;
int saved_st = 0;
for(;c < end; c++) {
switch(st){
case CT_TYPE:
switch(*c) {
case_CR_LF;
case SP:
case HTAB:
type = string(beg,c-beg);
st = CT_SLASH_SWS;
break;
case SLASH:
type = string(beg,c-beg);
st = CT_SUBTYPE_SWS;
break;
}
break;
case CT_SLASH_SWS:
switch(*c){
case_CR_LF;
case SP:
case HTAB:
break;
case SLASH:
st = CT_SUBTYPE_SWS;
break;
default:
DBG("Missing '/' after media type in 'Content-Type' hdr field\n");
return -1;
}
break;
case CT_SUBTYPE_SWS:
switch(*c){
case_CR_LF;
case SP:
case HTAB:
break;
default:
st = CT_SUBTYPE;
beg = c;
break;
}
break;
case CT_SUBTYPE:
switch(*c){
case_CR_LF;
case SP:
case HTAB:
case SEMICOLON:
subtype = string(beg,c-beg);
return parseParams(c,end);
}
break;
case_ST_CR(*c);
case ST_LF:
case ST_CRLF:
switch(saved_st){
case CT_TYPE:
if(!IS_WSP(*c)){
// should not happen: parse_headers() should already
// have triggered an error
DBG("Malformed Content-Type value: <%.*s>\n",(int)(end-beg),beg);
return -1;
}
else {
type = string(beg,(c-(st==ST_CRLF?2:1))-beg);
saved_st = CT_SLASH_SWS;
}
break;
case CT_SLASH_SWS:
case CT_SUBTYPE_SWS:
if(!IS_WSP(*c)){
// should not happen: parse_headers() should already
// have triggered an error
DBG("Malformed Content-Type value: <%.*s>\n",(int)(end-beg),beg);
return -1;
}
break;
case CT_SUBTYPE:
subtype = string(beg,(c-(st==ST_CRLF?2:1))-beg);
if(!IS_WSP(*c)){
// should not happen: parse_headers() should already
// have triggered an error
return 0;
}
return parseParams(c,end);
}
st = saved_st;
break;
}
}
// end-of-string
switch(st){
case CT_TYPE:
case CT_SLASH_SWS:
case CT_SUBTYPE_SWS:
DBG("Malformed Content-Type value: <%.*s>\n",(int)(end-beg),beg);
return -1;
case CT_SUBTYPE:
subtype = string(beg,c-beg);
break;
}
return 0;
}
int AmContentType::parseParams(const char* c, const char* end)
{
list<sip_avp*> avp_params;
if(parse_gen_params_sc(&avp_params, &c, end-c, '\0') < 0) {
if(!avp_params.empty()) free_gen_params(&avp_params);
return -1;
}
for(list<sip_avp*>::iterator it_ct_param = avp_params.begin();
it_ct_param != avp_params.end();++it_ct_param) {
DBG("parsed new content-type parameter: <%.*s>=<%.*s>",
(*it_ct_param)->name.len,(*it_ct_param)->name.s,
(*it_ct_param)->value.len,(*it_ct_param)->value.s);
Param* p = new Param(c2stlstr((*it_ct_param)->name),
c2stlstr((*it_ct_param)->value));
if(p->parseType()) {
free_gen_params(&avp_params);
delete p;
return -1;
}
if(p->type == Param::BOUNDARY)
mp_boundary = p;
params.push_back(p);
}
free_gen_params(&avp_params);
return 0;
}
int AmContentType::Param::parseType()
{
const char* c = name.c_str();
unsigned len = name.length();
switch(len){
case BOUNDARY_len:
if(!lower_cmp(c,BOUNDARY_str,len)){
if(value.empty()) {
DBG("Content-Type boundary parameter is missing a value\n");
return -1;
}
type = Param::BOUNDARY;
}
else type = Param::OTHER;
break;
default:
type = Param::OTHER;
break;
}
return 0;
}
int AmMimeBody::findNextBoundary(unsigned char** beg, unsigned char** end)
{
enum {
B_START=0,
B_CR,
B_LF,
B_HYPHEN,
B_BOUNDARY,
B_HYPHEN2,
B_HYPHEN3,
B_CR2,
B_LF2,
B_MATCHED
};
if(!ct.mp_boundary)
return -1;
unsigned char* c = *beg;
unsigned char* b_ini = (unsigned char*)ct.mp_boundary->value.c_str();
unsigned char* b = b_ini;
unsigned char* b_end = b + ct.mp_boundary->value.length();
int st=B_START;
// Allow the buffer to start directly
// with the boundary delimiter
if(*c == HYPHEN)
st=B_LF;
#define case_BOUNDARY(_st_,_ch_,_st1_) \
case _st_: \
switch(*c){ \
case _ch_: \
st = _st1_; \
break; \
default: \
st = B_START; \
break; \
} \
break
#define case_BOUNDARY2(_st_,_ch_,_st1_) \
case _st_: \
switch(*c){ \
case _ch_: \
st = _st1_; \
break; \
default: \
b = b_ini; \
is_final = false; \
st = B_START; \
break; \
} \
break
bool is_final = false;
for(;st != B_MATCHED && (c < *end-(b_end-b)); c++){
switch(st){
case B_START:
if(*c == CR) {
*beg = c;
st = B_CR;
}
break;
case_BOUNDARY(B_CR, LF, B_LF);
case B_LF:
switch(*c){
case HYPHEN:
st = B_HYPHEN;
break;
case CR:
st = B_CR;
break;
default:
st = B_START;
break;
}
break;
case_BOUNDARY(B_HYPHEN, HYPHEN, B_BOUNDARY);
case B_BOUNDARY:
if(*c == *b) {
if(++b == b_end) {
// reached end boundary buffer
st = B_HYPHEN2;
}
}
else {
b = b_ini;
st = B_START;
}
break;
case B_HYPHEN2:
switch(*c) {
case HYPHEN:
is_final = true;
st = B_HYPHEN3;
break;
case CR:
st = B_LF2;
break;
default:
b = b_ini;
st = B_START;
break;
}
break;
case_BOUNDARY2(B_HYPHEN3, HYPHEN, B_CR2);
case_BOUNDARY2(B_CR2, CR, B_LF2);
case_BOUNDARY2(B_LF2, LF, B_MATCHED);
}
}
if(st == B_MATCHED || (st == B_HYPHEN3) || (st == B_CR2) || (st == B_LF2)) {
*end = c;
return is_final ? 1 : 0;
}
return -1;
}
int AmMimeBody::parseSinglePart(unsigned char* buf, unsigned int len)
{
list<sip_header*> hdrs;
char* c = (char*)buf;
char* end = c + len;
// parse headers first
if(parse_headers(hdrs,&c,c+len) < 0) {
DBG("could not parse part headers\n");
free_headers(hdrs);
return -1;
}
auto_ptr<AmMimeBody> sub_part(new AmMimeBody());
string sub_part_hdrs;
string sub_part_ct;
for(list<sip_header*>::iterator it = hdrs.begin();
it != hdrs.end(); ++it) {
DBG("Part header: <%.*s>: <%.*s>\n",
(*it)->name.len,(*it)->name.s,
(*it)->value.len,(*it)->value.s);
if((*it)->type == sip_header::H_CONTENT_TYPE) {
sub_part_ct = c2stlstr((*it)->value);
}
else {
sub_part_hdrs += c2stlstr((*it)->name) + COLSP
+ c2stlstr((*it)->value) + CRLF;
}
}
if(!sub_part_ct.empty() &&
(sub_part->parse(sub_part_ct,(unsigned char*)c,end-c) == 0)) {
// TODO: check errors
DBG("Successfully parsed subpart.\n");
}
else {
DBG("Either no Content-Type, or subpart parsing failed.\n");
sub_part->ct.setType("");
sub_part->ct.setSubType("");
sub_part->setPayload((unsigned char*)c,end-c);
}
sub_part->setHeaders(sub_part_hdrs);
parts.push_back(sub_part.release());
free_headers(hdrs);
return 0;
}
int AmMimeBody::parseMultipart(const unsigned char* buf, unsigned int len)
{
if(!ct.mp_boundary) {
DBG("boundary parameter missing in a multipart MIME body\n");
return -1;
}
unsigned char* buf_end = (unsigned char*)buf + len;
unsigned char* part_end = (unsigned char*)buf;
unsigned char* next_part = (unsigned char*)buf_end;
int err = findNextBoundary(&part_end,&next_part);
if(err < 0) {
DBG("unexpected end-of-buffer\n");
return -1;
}
unsigned char* part_beg = NULL;
do {
part_beg = next_part;
part_end = part_beg;
next_part = buf_end;
err = findNextBoundary(&part_end,&next_part);
if(err < 0) {
DBG("unexpected end-of-buffer while searching for MIME body boundary\n");
return -1;
}
if(parseSinglePart(part_beg,part_end-part_beg) < 0) {
DBG("Failed parsing part\n");
}
else {
AmMimeBody* part = parts.back();
DBG("Added new part:\n%.*s\n",
part->content_len,part->payload);
}
} while(!err);
DBG("End-of-multipart body found\n");
return 0;
}
int AmMimeBody::parse(const string& content_type,
const unsigned char* buf,
unsigned int len)
{
if(ct.parse(content_type) < 0)
return -1;
if(ct.isType(MULTIPART)) {
DBG("parsing multi-part body\n");
return parseMultipart(buf,len);
}
else {
DBG("saving single-part body\n");
setPayload(buf,len);
}
return 0;
}
void AmMimeBody::convertToMultipart()
{
AmContentType n_ct;
n_ct.parse(MULTIPART_MIXED); // never fails
n_ct.resetBoundary();
AmMimeBody* n_part = new AmMimeBody(*this);
n_part->ct = ct;
parts.push_back(n_part);
ct = n_ct;
content_len = 0;
}
void AmMimeBody::convertToSinglepart()
{
if (parts.size() == 1) {
this->ct = parts.front()->ct;
setPayload(parts.front()->payload, parts.front()->content_len);
clearParts();
} else {
DBG("body does not have exactly one part\n");
}
}
void AmContentType::setType(const string& t)
{
type = t;
}
void AmContentType::setSubType(const string& st)
{
subtype = st;
}
bool AmContentType::isType(const string& t) const
{
return !lower_cmp_n(t.c_str(),t.length(),
type.c_str(),type.length());
}
bool AmContentType::isSubType(const string& st) const
{
return !lower_cmp_n(st.c_str(),st.length(),
subtype.c_str(),subtype.length());
}
void AmMimeBody::setHeaders(const string& hdrs)
{
this->hdrs = hdrs;
}
AmMimeBody* AmMimeBody::addPart(const string& content_type)
{
AmMimeBody* body = NULL;
if(ct.type.empty() && ct.subtype.empty()) {
// fill *this* body
if(ct.parse(content_type)) {
DBG("could not parse content-type\n");
return NULL;
}
body = this;
}
else if(!ct.isType(MULTIPART)) {
// convert to multipart
convertToMultipart();
body = new AmMimeBody();
if(body->ct.parse(content_type)) {
DBG("parsing new content-type failed\n");
delete body;
return NULL;
}
// add new part
parts.push_back(body);
}
return body;
}
int AmMimeBody::deletePart(const string& content_type)
{
if (!ct.isType(MULTIPART)) {
DBG("body is not multipart\n");
return -1;
}
for(Parts::iterator it = parts.begin();
it != parts.end(); ++it) {
if((*it)->hasContentType(content_type)) {
clearPart(it);
if (parts.size() == 1) convertToSinglepart();
return 0;
}
}
DBG("no match");
return -1;
}
void AmMimeBody::setPayload(const unsigned char* buf, unsigned int len)
{
if(payload)
clearPayload();
payload = new unsigned char [len+1];
memcpy(payload,buf,len);
content_len = len;
// zero-term for the SDP parser
payload[len] = '\0';
}
bool AmMimeBody::empty() const
{
return (!payload || !content_len)
&& parts.empty();
}
bool AmContentType::hasContentType(const string& content_type) const
{
if(content_type.empty() && type.empty() && subtype.empty())
return true;
if(content_type.empty() != (type.empty() && subtype.empty()))
return false;
// Quick & dirty comparison, might not always be correct
string cmp_ct = type + "/" + subtype;
return !lower_cmp_n(cmp_ct.c_str(),cmp_ct.length(),
content_type.c_str(),content_type.length());
}
string AmContentType::getStr() const
{
if(type.empty() && subtype.empty())
return "";
return type + "/" + subtype;
}
string AmContentType::getHdr() const
{
string ct = getStr();
if(ct.empty())
return ct;
for(Params::const_iterator it = params.begin();
it != params.end(); ++it) {
ct += ";" + (*it)->name + "=" + (*it)->value;
}
return ct;
}
bool AmMimeBody::isContentType(const string& content_type) const
{
return ct.hasContentType(content_type);
}
AmMimeBody* AmMimeBody::hasContentType(const string& content_type)
{
if(isContentType(content_type)) {
return this;
}
else if(ct.isType(MULTIPART)) {
for(Parts::iterator it = parts.begin();
it != parts.end(); ++it) {
if((*it)->hasContentType(content_type)) {
return *it;
}
}
}
return NULL;
}
const AmMimeBody* AmMimeBody::hasContentType(const string& content_type) const
{
if(isContentType(content_type)) {
return this;
}
else if(ct.isType(MULTIPART)) {
for(Parts::const_iterator it = parts.begin();
it != parts.end(); ++it) {
if((*it)->hasContentType(content_type)) {
return *it;
}
}
}
return NULL;
}
void AmMimeBody::print(string& buf) const
{
if(empty())
return;
if(content_len) {
buf += string((const char*)payload,content_len);
}
else {
// if (ct.mp_boundary == NULL)
// ct.resetBoundary();
for(Parts::const_iterator it = parts.begin();
it != parts.end(); ++it) {
buf += "--" + (ct.mp_boundary != NULL ? ct.mp_boundary->value : string("") ) + CRLF;
buf += SIP_HDR_CONTENT_TYPE COLSP + (*it)->getCTHdr() + CRLF;
buf += (*it)->hdrs + CRLF;
(*it)->print(buf);
buf += CRLF;
}
if(!parts.empty()) {
buf += "--" + (ct.mp_boundary != NULL ? ct.mp_boundary->value : string("") ) + "--" CRLF;
}
}
}