/*************************************************************************** mimemessage.cpp - description ------------------- begin : Sat Mar 8 2003 copyright : (C) 2003 by Mike K. Bennett email : mkb137b@hotmail.com ***************************************************************************/ /*************************************************************************** * * * This program 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. * * * ***************************************************************************/ /* The decode...() functions and getCodecByName() come from KMail and are therefore (C) KMail developers. */ #include "mimemessage.h" #include <qregexp.h> #include <qtextcodec.h> #include <kdebug.h> #include <kglobal.h> #include <klocale.h> #include <kcharsets.h> #include <kmdcodec.h> #include <stdlib.h> #include "../kmessdebug.h" // The constructor MimeMessage::MimeMessage() : QObject(0, "MimeMessage") { #ifdef KMESSDEBUG_MIMEMESSAGE kdDebug() << "MimeMessage - Constructor." << endl; #endif } // The constructor that parses a message MimeMessage::MimeMessage(const QString &message) : QObject(0, "MimeMessage") { #ifdef KMESSDEBUG_MIMEMESSAGE kdDebug() << "MimeMessage - Constructor." << endl; #endif parseMessage( message ); } MimeMessage::MimeMessage(const QByteArray &message) : QObject(0, "MimeMessage") { #ifdef KMESSDEBUG_MIMEMESSAGE kdDebug() << "MimeMessage - Constructor." << endl; #endif // we get the header without the binary data that comes after int nullPos = message.find('\0'); QString messageHeader = QString::fromUtf8(message.data(), (nullPos == -1) ? message.size() : nullPos ); QRegExp rx("Content-Type: ([A-Za-z0-9$!*/\\-]*)"); rx.search( messageHeader ); QString type = rx.cap(1); // if the type is p2p, we must take care of the binary data that comes after the header if( type == "application/x-msnmsgrp2p" ) { #ifdef KMESSDEBUG_MIMEMESSAGE kdDebug() << "MimeMessage - The message is of type P2P" << endl; #endif QString msg(message); uint endMime = msg.find("\r\n\r\n"); uint startBin = endMime + 4; // 2 newlines uint binLength = message.size() - startBin; // Extract the find Mime fields from the message (+1 base) parseMessage( QString::fromUtf8( message.data(), startBin ) ); // Extract the binary data as well binaryData_.duplicate(message.data() + startBin, binLength); #ifdef KMESSTEST ASSERT(binaryData_.size() == binLength); #endif #ifdef KMESSDEBUG_MIMEMESSAGE QString hexHeader = ""; uint headerEnd = (binLength < 48 ? binLength : 48); const char hexMap[] = "0123456789abcdef"; for (uint i = 0; i < headerEnd; i++) { int upper = (binaryData_[i] & 0xf0) >> 4; int lower = (binaryData_[i] & 0x0f); hexHeader += hexMap[upper]; hexHeader += hexMap[lower]; hexHeader += " "; } kdDebug() << "MimeMessage: P2P header is: " << hexHeader << endl; #endif } else { // Added fromUtf8 call to translate Unicode characters (like Chinese) parseMessage( QString::fromUtf8( message.data(), message.size()) ); } } // The copy constructor MimeMessage::MimeMessage(const MimeMessage& other) : QObject() { #ifdef KMESSDEBUG_MIMEMESSAGE kdDebug() << "MimeMessage: copy constructor" << endl; #endif // Get the body of the other message fields_ = other.fields_; values_ = other.values_; body_ = other.body_; binaryData_ = other.binaryData_; // QValueList is implicitly shared, so the assign operation // does not create another copy. It uses "copy on write". // QByteArray is explicitly shared, but it's not a problem here, // since we don't modify the array data of binaryData_. #ifdef KMESSTEST // Make sure the ascii-zero characters are copied correctly: ASSERT( binaryData_.size() == other.binaryData_.size() ); ASSERT( fields_.size() == other.fields_.size() ); #endif } // The destructor MimeMessage::~MimeMessage() { } // Add a field to the message void MimeMessage::addField(const QString& field, const QString& value) { fields_ << field; values_ << value; } // Change a field, or add it 00169 void MimeMessage::setField(const QString& field, const QString& value) { int index= fields_.findIndex(field); if(-1 == index) { addField(field, value); } else { values_[index]= value; } } // decodes MIME strings like =?iso...=...?= ... QString MimeMessage::decodeRFC2047String(const QCString& aStr) const { QString result; QCString charset; char *pos, *beg, *end, *mid=0; QCString str, cstr, LWSP_buffer; char encoding='Q', ch; bool valid, lastWasEncodedWord=FALSE; const int maxLen=200; int i; if (aStr.find("=?") < 0) { //QString str = messageCodec->toUnicode(aStr); QString str = QString::fromUtf8( aStr ); if (str.find('\n') == -1) return str; QString str2((QChar*)0, str.length()); uint i = 0; while (i < str.length()) { if (str[i] == '\n') { str2 += ' '; i += 2; } else { str2 += str[i]; i++; } } return str2; } for (pos=aStr.data(); *pos; pos++) { // line unfolding if ( pos[0] == '\r' && pos[1] == '\n' ) { pos++; continue; } if ( pos[0] == '\n' ) continue; // collect LWSP after encoded-words, // because we might need to throw it out // (when the next word is an encoded-word) if ( lastWasEncodedWord && ( pos[0] == ' ' || pos[0] == '\t' ) ) { LWSP_buffer += pos[0]; continue; } // verbatimly copy normal text if (pos[0]!='=' || pos[1]!='?') { result += LWSP_buffer + pos[0]; LWSP_buffer = 0; lastWasEncodedWord = FALSE; continue; } // found possible encoded-word beg = pos+2; end = beg; valid = TRUE; // parse charset name charset = ""; for (i=2,pos+=2; i<maxLen && (*pos!='?'&&(*pos==' '||ispunct(*pos)||isalnum(*pos))); i++) { charset += *pos; pos++; } if (*pos!='?' || i<4 || i>=maxLen) valid = FALSE; else { // get encoding and check delimiting question marks encoding = toupper(pos[1]); if (pos[2]!='?' || (encoding!='Q' && encoding!='B')) valid = FALSE; pos+=3; i+=3; } if (valid) { mid = pos; // search for end of encoded part while (i<maxLen && *pos && !(*pos=='?' && *(pos+1)=='=')) { i++; pos++; } end = pos+2;//end now points to the first char after the encoded string if (i>=maxLen || !*pos) valid = FALSE; } if (valid) { // valid encoding: decode and throw away separating LWSP ch = *pos; *pos = '\0'; str = QCString(mid).left((int)(mid - pos - 1)); if (encoding == 'Q') { // decode quoted printable text for (i=str.length()-1; i>=0; i--) if (str[i]=='_') str[i]=' '; cstr = KCodecs::quotedPrintableDecode(str); } else { // decode base64 text cstr = KCodecs::base64Decode(str); } QTextCodec *codec = getCodecByName(charset); if (!codec) { result += QString::fromUtf8( cstr ); } else { result += codec->toUnicode(cstr); } lastWasEncodedWord = TRUE; *pos = ch; pos = end -1; } else { // invalid encoding, keep separating LWSP. //result += "=?"; //pos = beg -1; // because pos gets increased shortly afterwards pos = beg - 2; result += LWSP_buffer; result += *pos++; result += *pos; lastWasEncodedWord = FALSE; } LWSP_buffer = 0; } return result; } // Return the body of the message const QString& MimeMessage::getBody() const { return body_; } // Return the P2P data of the message const QByteArray& MimeMessage::getBinaryData() const { return binaryData_; } // Finds a QTextCodec by name QTextCodec* MimeMessage::getCodecByName(const QCString& codecName ) { if ( codecName.isEmpty() ) { return 0; } return KGlobal::charsets()->codecForName( codecName.lower() ); } // Return the field and value at the given index void MimeMessage::getFieldAndValue(QString& field, QString& value, const uint& index) const { if ( index < fields_.count() ) { field = fields_[index]; value = values_[index]; } else { field = QString::null; value = QString::null; } } // Return the message fields as a big string QString MimeMessage::getFields() const { QString message = ""; // Get the fields and values for ( uint i = 0; i < fields_.count(); i++ ) { message += fields_[i] + ": " + values_[i] + "\r\n"; } return message; } // Return the entire message as a big string QByteArray MimeMessage::getMessage() const { // Combine the message parts, convert to UTF8 string QCString textPart = QString(getFields() + "\r\n" + body_).utf8(); if(binaryData_.isEmpty()) { // Downgrade to QByteArray. // If this is not done manually, it happens anyway, but with a '\0' padded. QByteArray textPartBin; textPartBin.duplicate(textPart.data(), textPart.length()); return textPartBin; } else { // Concatenate the message and binary data QByteArray p2pMessage; uint totalLength = textPart.length() + binaryData_.size(); char *rawData = new char[ totalLength ]; uint offset = 0; // Copy the parts to the array memcpy(rawData + offset, textPart.data(), textPart.length()); offset += textPart.length(); memcpy(rawData + offset, binaryData_.data(), binaryData_.size()); offset += binaryData_.size(); #ifdef KMESSTEST ASSERT( offset == totalLength ); #endif // Load the rawData in the QByteArray, and return it p2pMessage.assign( rawData, totalLength ); // does the deletion automatically return p2pMessage; } } // The total number of fields uint MimeMessage::getNoFields() const { return fields_.count(); } // Get one parameter of a value that has multiple parameters QString MimeMessage::getSubValue(const QString& field, const QString& subField) const { QString value, parameter; int left, right; // Get the value referred to by the field value = getValue( field ); if ( !value.isNull() ) { // If the subfield isn't specified, then get whatever is at the start of the message until the // first semicolon or the end of the line if ( subField.isNull() ) { left = 0; } else { // The left of the parameter is "subField=", the right is the next semicolon or the end of the line left = value.find( subField + "=" ); if ( left >= 0 ) { left += subField.length() + 1; } } if ( left >= 0 ) { right = value.find( ";", left ); if ( right < 0 ) { right = value.length(); } // Get the parameter parameter = value.mid( left, ( right - left ) ); return parameter; } } return QString::null; } // Get a value given a field const QString& MimeMessage::getValue(const QString& field ) const { // Search the fields for a match for ( uint i = 0; i < fields_.count(); i++ ) { if ( fields_[i] == field ) { return values_[i]; } } kdDebug() << "MimeMessage - WARNING - This message contained no field \"" << field << "\"." << endl; return QString::null; } // Test whether a given field exists in the message header bool MimeMessage::hasField(const QString& field) const { // Search the fields for a match for ( uint i = 0; i < fields_.count(); i++ ) { if ( fields_[i] == field ) { return true; } } return false; } // Parse the message into type, body, and fields and values void MimeMessage::parseMessage(const QString& message) { #ifdef KMESSDEBUG_MIMEMESSAGE kdDebug() << "MimeMessage - Parse the message." << endl; #endif QString head, field, value; QStringList lines; // Split the message into head and body splitMessage( head, body_, message ); // Split the head into its various lines splitHead( lines, head ); // Split all the lines into field and value for ( uint i = 0; i < lines.count(); i++ ) { splitLine( field, value, lines[i] ); if ( !field.isEmpty() ) { // Add the fields and values to the lists addField( field, value ); } } #ifdef KMESSTEST ASSERT( fields_.count() == values_.count() ); #endif } // Print the contents of the message to kdDebug (for debugging purposes) void MimeMessage::print() const { kdDebug() << "MimeMessage - Printing MIME message, " << fields_.count() << " fields." << endl; // Get the fields and values for ( uint i = 0; i < fields_.count(); i++ ) { kdDebug() << "MimeMessage - Print - Field: \"" << fields_[i] << "\" value: \"" << values_[i] << "\"" << endl; } kdDebug() << "MimeMessage - Print - Body:" << endl << body_ << endl; } // Set the message body void MimeMessage::setBody(const QString& body) { body_ = body; } // Set the P2P data of the message void MimeMessage::setBinaryData(const char header[48], const char* body, const char footer[4], uint bodylength) { // Concatenate the message. QByteArray newBinaryData; uint totalLength = 48 + bodylength + 4; char *rawData = new char[ totalLength ]; uint offset = 0; // Copy the parts to the array memcpy(rawData + offset, header, 48); offset += 48; memcpy(rawData + offset, body, bodylength); offset += bodylength; memcpy(rawData + offset, footer, 4); offset += 4; #ifdef KMESSTEST ASSERT( offset == totalLength ); #endif // Does auto-deletion automatically binaryData_.assign(rawData, totalLength); } // Split a line between field and value void MimeMessage::splitLine(QString& field, QString& value, const QString& line) const { int index; index = line.find(":"); if ( index >= 0 ) { field = line.left( index ); if ( ( index + 1 ) < (int)line.length() ) { value = line.right( line.length() - index - 2 ); } else { value = QString::null; } } else { kdDebug() << "MimeMessage: WARNING - Couldn't split line '" << line << "'" << endl; } } // Split the message head into components and store it in the string list void MimeMessage::splitHead(QStringList& stringList, const QString& head) const { int left = 0, right = 0; QString line; stringList.clear(); right = head.find( "\r\n" ); while ( right >= 0 ) { // Get the message between "left" and "right". line = head.mid( left, right - left ); stringList << line; // Find the next occurence of "\r\n" after left. left = right + 2; right = head.find( "\r\n", left ); if(right == -1 && ! head.endsWith("\r\n")) { // This happens with MSNP2P/SLP content messages // I try to parse with the MimeMessage constructor. line = head.mid(left); stringList << line; } } } // Split a message into head and body void MimeMessage::splitMessage(QString& head, QString& body, const QString& message) const { int index; // The message is split into head and body at "\r\n\r\n" index = message.find( "\r\n\r\n" ); if ( index < 0 ) { head = message; body = ""; } else { head = message.left( index + 2 ); // Keep a "\r\n" at the end body = message.right( message.length() - index - 4 ); } } #include "mimemessage.moc"