diff --git a/engine/Mime.cpp b/engine/Mime.cpp index 5f8d0707..d23f83ff 100644 --- a/engine/Mime.cpp +++ b/engine/Mime.cpp @@ -20,6 +20,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include #include "yatemime.h" using namespace TelEngine; @@ -208,6 +209,17 @@ int MimeHeaderLine::findSep(const char* str, char sep, int offs) 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(o->get()); + String line; + hdr->buildLine(line); + buf << line << "\r\n"; + } +} + /** * MimeAuthLine @@ -322,7 +334,7 @@ MimeBody::~MimeBody() } // Find an additional header line by its name -MimeHeaderLine* MimeBody::findHdr(const char* name, const MimeHeaderLine* start) const +MimeHeaderLine* MimeBody::findHdr(const String& name, const MimeHeaderLine* start) const { ObjList* o = m_headers.skipNull(); if (!o) @@ -389,8 +401,8 @@ MimeBody* MimeBody::build(const char* buf, int len, const MimeHeaderLine& type) return new MimeLinesBody(type,buf,len); if (what.startsWith("text/") || (what == "application/dtmf")) return new MimeStringBody(type,buf,len); - if (what == "application/isup") - return new MimeIsupBody(type,buf,len); + if (what.startsWith("multipart/")) + return new MimeMultipartBody(type,buf,len); return new MimeBinaryBody(type,buf,len); } @@ -461,6 +473,269 @@ String* MimeBody::getUnfoldedLine(const char*& buf, int& len) } +/** + * MimeMultipartBody + */ +YCLASSIMP(MimeMultipartBody,MimeBody) + +// 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; + if (b.null()) + b << (int)::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(o->get()); + m_bodies.append(body->clone()); + } +} + +// 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; + Debug(DebugNote,"MimeMultipartBody::findBody(%s,%p) [%p]", + content.c_str(),localStart,this); + for (ObjList* o = m_bodies.skipNull(); o; o = o->skipNext()) { + MimeBody* body = static_cast(o->get()); + // Start point was found + if (!localStart) { + if (content == body->getType()) + return body; + if (body->isMultipart()) { + body = (static_cast(body))->findBody(content); + if (body) + return body; + } + continue; + } + // Reset starting point if found + if (body == localStart) + localStart = 0; + // Check inside multiparts for starting point or requested body + if (body->isMultipart()) { + body = (static_cast(body))->findBody(content,&localStart); + if (body) + return body; + } + } + if (start) + *start = localStart; + Debug(DebugNote,"MimeMultipartBody::findBody(). Not found%s [%p]", + localStart?"":". Found start point",this); + return 0; +} + +// 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 +{ + const NamedString* b = getParam("boundary"); + String boundary = "\r\n--"; + if (b) + boundary << *b; + + ObjList* o = m_bodies.skipNull(); + if (o) + for (; o; o = o->skipNext()) { + MimeBody* body = static_cast(o->get()); + String hdr = "\r\n"; + body->buildHeaders(hdr); + // Build it + m_body += boundary; + m_body += hdr; + m_body += body->getBody(); + } + else + m_body += boundary; + // Add termination boundary + boundary << "--\r\n"; + m_body += boundary; +} + +// 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); + if (!buf || len <= 0) + return; + + const NamedString* b = getParam("boundary"); + String boundary = "\r\n--"; + if (b) + boundary << *b; + // RFC 2046 pg. 22: Remove trailing blanks from boundary + unsigned int i = boundary.length() - 1; + for (const char* d = boundary.c_str(); i > 3; i--) + if (d[i] != ' ' && d[i] != '\t') + break; + if (i != boundary.length() - 1) + boundary.assign(boundary.c_str(),i + 1); + if (boundary.length() < 5) + DDebug(DebugMild,"MimeMultipartBody::parse(). Boundary is empty [%p]",this); + + bool endData; + // Find first boundary: ignore the data before it + // The first boundary might not contain the CRLF at beginning + findBoundary(buf,len,boundary.c_str() + 2,boundary.length()-2,endData); + + XDebug(DebugAll,"Searching for bodies len=%d [%p]",len,this); + + // Parse for bodies + while (!endData) { + const char* start = buf; + int l = findBoundary(buf,len,boundary.c_str(),boundary.length(),endData); + if (l <= 0) + continue; + XDebug(DebugInfo,"Found %d body data [%p]",l,this); + // Get 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; + } + 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(); + DDebug(DebugAll,"MimeMultipartBody::parse() header='%s' value='%s'", + name.c_str(),line->c_str()); + MimeHeaderLine* ct = new MimeHeaderLine(name,*line); + hdr.append(ct); + if (name &= "Content-Type") + cType = ct; + TelEngine::destruct(line); + } + // Append body to list and move extra headers to it + MimeBody* body = cType ? MimeBody::build(buf,len,*cType) : 0; + if (!body) { + XDebug(DebugNote,"Failed to build body%s [%p]", + cType?"":": Content-Type header is missing",this); + continue; + } + m_bodies.append(body); + XDebug(DebugInfo,"Body '%s' created. Adding %u additional headers [%p]", + cType->c_str(),hdr.count() - 1,this); + ListIterator iter(hdr); + for (GenObject* o = 0; (o = iter.get());) { + MimeHeaderLine* line = static_cast(o); + if (line == cType) + continue; + hdr.remove(o,false); + body->appendHdr(line); + } + } +} + +// 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& endData) +{ + if (len <= 0) { + endData = true; + return 0; + } + + endData = false; + unsigned int l = len; + int bodyLen = 0; + + while (l) { + // Skip until the first char of boundary + for (; l >= bLen && *buf != boundary[0]; l--, buf++) + bodyLen++; + // Check if we have enough data for boundary + if (l < bLen) { + bodyLen += l; + buf += l; + l = 0; + break; + } + // Check boundary + unsigned int n = 0; + for(; n < bLen && *buf == boundary[n]; n++, buf++, l--) + ; + // Not found + if (n < bLen) { + bodyLen += n; + continue; + } + // Check end of data + if (l > 2 && buf[0] == '-' && buf[1] == '-') { + buf += 2; + len -= 2; + endData = true; + } + // Skip until the end of line or data + for (; l > 1; buf++, l--) + if (buf[0] == '\r' && buf[1] == '\n') { + buf += 2; + len -= 2; + break; + } + break; + } + + len = l; + if (!len) + endData = true; + return bodyLen; +} + + /** * MimeSdpBody */ @@ -598,41 +873,6 @@ MimeBody* MimeBinaryBody::clone() const } -/** - * MimeIsupBody - */ -YCLASSIMP(MimeIsupBody,MimeBinaryBody) - -MimeIsupBody::MimeIsupBody() - : MimeBinaryBody(String("application/isup"),0,0) -{ -} - -MimeIsupBody::MimeIsupBody(const String& type, const char* buf, int len) - : MimeBinaryBody(type,buf,len) -{ -} - -MimeIsupBody::MimeIsupBody(const MimeHeaderLine& type, const char* buf, int len) - : MimeBinaryBody(type,buf,len) -{ -} - -MimeIsupBody::MimeIsupBody(const MimeIsupBody& original) - : MimeBinaryBody(original) -{ -} - -MimeIsupBody::~MimeIsupBody() -{ -} - -MimeBody* MimeIsupBody::clone() const -{ - return new MimeIsupBody(*this); -} - - /** * MimeStringBody */ diff --git a/yatemime.h b/yatemime.h index 016d212b..d665f8dd 100644 --- a/yatemime.h +++ b/yatemime.h @@ -148,7 +148,7 @@ public: static String quote(const String& str); /** - * Utility function to find a separator not in "quotes" or inside . + * Utility function to find a separator not in "quotes" or inside \. * @param str Input string used to find the separator. * @param sep The separator to find. * @param offs Starting offset in input string. @@ -156,6 +156,15 @@ public: */ static int findSep(const char* str, char sep, int offs = 0); + /** + * Build a string from a list of MIME header lines. + * Add a CR/LF terminator after each line + * @param buf Destination string + * @param headers The list with the header lines + * @param eoln Header line separator + */ + static void buildHeaders(String& buf, const ObjList& headers); + protected: ObjList m_params; // Header list of parameters char m_separator; // Parameter separator @@ -252,9 +261,10 @@ public: /** * Remove an additional header line from this body * @param hdr The header line to remove + * @param delobj True to delete the header, false to remove from list without deleting it */ - inline void removeHdr(MimeHeaderLine* hdr) - { if (hdr) m_headers.remove(hdr); } + inline void removeHdr(MimeHeaderLine* hdr, bool delobj = true) + { if (hdr) m_headers.remove(hdr,delobj); } /** * Find an additional header line by its name. The names are compared case insensitive @@ -262,7 +272,18 @@ public: * @param start The starting point in the list. 0 to start from the beginning * @return Pointer to MimeHeaderLine or 0 if not found */ - MimeHeaderLine* findHdr(const char* name, const MimeHeaderLine* start = 0) const; + MimeHeaderLine* findHdr(const String& name, const MimeHeaderLine* start = 0) const; + + /** + * Build a string with this body's header lines + * @param buf Destination string + * @param eoln Header line separator + */ + inline void buildHeaders(String& buf) { + m_type.buildLine(buf); + buf << "\r\n"; + MimeHeaderLine::buildHeaders(buf,m_headers); + } /** * Replace the value of an existing parameter or add a new one @@ -293,7 +314,8 @@ public: const NamedString* getParam(const char* name, const char* header = 0) const; /** - * Retrive the binary encoding of this MIME body + * Retrive the binary encoding of this MIME body. Build the body if empty. + * The body doesn't contain the Content-Type header or the additional headers * @return Block of binary data */ const DataBlock& getBody() const; @@ -371,6 +393,126 @@ private: MimeHeaderLine m_type; // Content type header line }; +/** + * An object holding the bodies of a multipart MIME + * @short MIME multipart container + */ +class YATE_API MimeMultipartBody : public MimeBody +{ +public: + /** + * Constructor to build an empty multipart body + * @param subtype The multipart subtype + * @param boundary The string used as separator for enclosed bodies. + * A random one will be created if missing. The length will be truncated + * to 70 if this value is exceeded + */ + MimeMultipartBody(const char* subtype = "mixed", const char* boundary = 0); + + /** + * Constructor from block of data + * @param type The value of the Content-Type header line + * @param buf Pointer to buffer of data + * @param len Length of data in buffer + */ + MimeMultipartBody(const String& type, const char* buf, int len); + + /** + * Constructor from block of data + * @param type The content type header line + * @param buf Pointer to buffer of data + * @param len Length of data in buffer + */ + MimeMultipartBody(const MimeHeaderLine& type, const char* buf, int len); + + /** + * Destructor + */ + virtual ~MimeMultipartBody(); + + /** + * Get the list of bodies enclosed contained in this multipart + * @return The list of bodies enclosed contained in this multipart + */ + inline const ObjList& bodies() const + { return m_bodies; } + + /** + * Append a body to this multipart + * @param body The body to append + */ + inline void appendBody(MimeBody* body) + { if (body) m_bodies.append(body); } + + /** + * Remove a body from this multipart + * @param body The body to remove + * @param delobj True to delete the body, false to remove from list without deleting it + */ + inline void removeBody(MimeBody* body, bool delobj = true) + { if (body) m_bodies.remove(body,delobj); } + + /** + * Find a body. Enclosed multiparts are also searched for the requested body + * @param name The value of the body to find. Must be lower case + * @param start The starting point in the list. 0 to start from the beginning. + * Be aware that this parameter is used internally to search within enclosed + * multipart bodies and set to 0 when the starting point is found + * @return Pointer to MimeBody or 0 if not found + */ + MimeBody* findBody(const String& content, MimeBody** start = 0) const; + + /** + * RTTI method, get a pointer to a derived class given the class name + * @param name Name of the class we are asking for + * @return Pointer to the requested class or NULL if this object doesn't implement it + */ + virtual void* getObject(const String& name) const; + + /** + * Check if this body is multipart (can hold other MIME bodies) + * @return True if this body is multipart + */ + virtual bool isMultipart() const + { return true; } + + /** + * Duplicate this MIME body + * @return Copy of this MIME body + */ + virtual MimeBody* clone() const; + +protected: + /** + * Copy constructor + */ + MimeMultipartBody(const MimeMultipartBody& original); + + /** + * Method that is called internally to build the binary encoded body + */ + virtual void buildBody() const; + + /** + * Parse a data buffer and append any valid body to this multipart + * Ignore prolog, epilog and invalid bodies + * @param buf Pointer to buffer of data + * @param len Length of data in buffer + */ + void parse(const char* buf, int len); + +private: + // 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 findBoundary(const char*& buf, int& len, + const char* boundary, unsigned int bLen, bool& endData); + + ObjList m_bodies; // The list of bodies contained in this multipart +}; + /** * An object holding the lines of an application/sdp MIME type * @short MIME for application/sdp @@ -385,7 +527,7 @@ public: /** * Constructor from block of data - * @param type Name of the MIME type/subtype, should be "application/sdp" + * @param type The value of the Content-Type header line * @param buf Pointer to buffer of data * @param len Length of data in buffer */ @@ -480,7 +622,7 @@ class YATE_API MimeBinaryBody : public MimeBody public: /** * Constructor from block of data - * @param type Name of the specific MIME type/subtype + * @param type The value of the Content-Type header line * @param buf Pointer to buffer of data * @param len Length of data in buffer */ @@ -524,59 +666,6 @@ protected: virtual void buildBody() const; }; -/** - * An object holding a binary block of an ISUP message - * @short MIME for application/isup - */ -class YATE_API MimeIsupBody : public MimeBinaryBody -{ -public: - /** - * Constructor - */ - MimeIsupBody(); - - /** - * Constructor from block of data - * @param type The line containing the Content-Type header value - * @param buf Pointer to buffer of data - * @param len Length of data in buffer - */ - MimeIsupBody(const String& type, const char* buf, int len); - - /** - * Constructor from block of data - * @param type The content type header line - * @param buf Pointer to buffer of data - * @param len Length of data in buffer - */ - MimeIsupBody(const MimeHeaderLine& type, const char* buf, int len); - - /** - * Destructor - */ - virtual ~MimeIsupBody(); - - /** - * RTTI method, get a pointer to a derived class given the class name - * @param name Name of the class we are asking for - * @return Pointer to the requested class or NULL if this object doesn't implement it - */ - virtual void* getObject(const String& name) const; - - /** - * Duplicate this MIME body - * @return Copy of this MIME body - a new MimeIsupBody - */ - virtual MimeBody* clone() const; - -protected: - /** - * Copy constructor - */ - MimeIsupBody(const MimeIsupBody& original); -}; - /** * An object holding MIME data as just one text string * @short MIME for one text string @@ -586,7 +675,7 @@ class YATE_API MimeStringBody : public MimeBody public: /** * Constructor from block of data - * @param type Name of the specific MIME type/subtype + * @param type The value of the Content-Type header line * @param buf Pointer to buffer of data * @param len Length of data in buffer */ @@ -649,7 +738,7 @@ class YATE_API MimeLinesBody : public MimeBody public: /** * Constructor from block of data - * @param type Name of the specific MIME type/subtype + * @param type The value of the Content-Type header line * @param buf Pointer to buffer of data * @param len Length of data in buffer */