/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsMsgLocalFolderHdrs.h"
#include "nsMsgSend.h"
#include "nsMsgSendPart.h"
#include "nsIMimeConverter.h"
#include "nsCOMPtr.h"
#include "nsIComponentManager.h"
#include "nsMsgI18N.h"
#include "nsMsgCompUtils.h"
#include "nsMsgMimeCID.h"
#include "nsMimeTypes.h"
#include "prmem.h"
#include "nsMsgPrompts.h"
#include "nsNativeCharsetUtils.h"
#include "nsNetUtil.h"
#include "nsISeekableStream.h"
#include "nsReadLine.h"
#include "nsILineInputStream.h"
#include "nsComposeStrings.h"
#include "mozilla/mailnews/MimeEncoder.h"
static char *mime_mailto_stream_read_buffer = 0;
int32_t nsMsgSendPart::M_counter = 0;
nsMsgSendPart::nsMsgSendPart(nsIMsgSend* state, const char *part_charset)
{
PL_strncpy(m_charset_name, (part_charset ? part_charset : "UTF-8"), sizeof(m_charset_name)-1);
m_charset_name[sizeof(m_charset_name)-1] = '\0';
m_children = nullptr;
m_numchildren = 0;
// if we're not added as a child, the default part number will be "1".
m_partNum = "1";
SetMimeDeliveryState(state);
m_parent = nullptr;
m_buffer = nullptr;
m_type = nullptr;
m_other = nullptr;
m_strip_sensitive_headers = false;
m_firstBlock = false;
m_needIntlConversion = false;
m_mainpart = false;
m_just_hit_CR = false;
}
nsMsgSendPart::~nsMsgSendPart()
{
for (int i=0 ; i < m_numchildren; i++)
delete m_children[i];
delete [] m_children;
PR_FREEIF(m_buffer);
PR_FREEIF(m_other);
PR_FREEIF(m_type);
}
nsresult nsMsgSendPart::CopyString(char** dest, const char* src)
{
NS_ASSERTION(src, "src null");
PR_FREEIF(*dest);
if (!src)
*dest = PL_strdup("");
else
*dest = PL_strdup(src);
return *dest? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}
nsresult nsMsgSendPart::SetFile(nsIFile *file)
{
m_file = file;
return NS_OK;
}
nsresult nsMsgSendPart::SetBuffer(const char* buffer)
{
PR_FREEIF(m_buffer);
return CopyString(&m_buffer, buffer);
}
nsresult nsMsgSendPart::SetType(const char* type)
{
PR_FREEIF(m_type);
m_type = PL_strdup(type);
return m_type ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}
nsresult nsMsgSendPart::SetOtherHeaders(const char* other)
{
return CopyString(&m_other, other);
}
nsresult nsMsgSendPart::SetMimeDeliveryState(nsIMsgSend *state)
{
m_state = state;
if (GetNumChildren() > 0)
{
for (int i = 0; i < GetNumChildren(); i++)
{
nsMsgSendPart *part = GetChild(i);
if (part)
part->SetMimeDeliveryState(state);
}
}
return NS_OK;
}
nsresult nsMsgSendPart::AppendOtherHeaders(const char* more)
{
if (!m_other)
return SetOtherHeaders(more);
if (!more || !*more)
return NS_OK;
char* tmp = (char *) PR_Malloc(sizeof(char) * (PL_strlen(m_other) + PL_strlen(more) + 2));
if (!tmp)
return NS_ERROR_OUT_OF_MEMORY;
PL_strcpy(tmp, m_other);
PL_strcat(tmp, more);
PR_FREEIF(m_other);
m_other = tmp;
return NS_OK;
}
nsresult nsMsgSendPart::SetMainPart(bool value)
{
m_mainpart = value;
return NS_OK;
}
nsresult nsMsgSendPart::AddChild(nsMsgSendPart* child)
{
m_numchildren++;
nsMsgSendPart** tmp = new nsMsgSendPart* [m_numchildren];
if (tmp == nullptr) return NS_ERROR_OUT_OF_MEMORY;
for (int i=0 ; i<m_numchildren-1 ; i++) {
tmp[i] = m_children[i];
}
delete [] m_children;
m_children = tmp;
m_children[m_numchildren - 1] = child;
child->m_parent = this;
nsCString partNum(m_partNum);
partNum.Append(".");
partNum.AppendInt(m_numchildren);
child->m_partNum = partNum;
return NS_OK;
}
nsMsgSendPart * nsMsgSendPart::DetachChild(int32_t whichOne)
{
nsMsgSendPart *returnValue = nullptr;
NS_ASSERTION(whichOne >= 0 && whichOne < m_numchildren, "parameter out of range");
if (whichOne >= 0 && whichOne < m_numchildren)
{
returnValue = m_children[whichOne];
if (m_numchildren > 1)
{
nsMsgSendPart** tmp = new nsMsgSendPart* [m_numchildren-1];
if (tmp != nullptr)
{
// move all the other kids over
for (int i=0 ; i<m_numchildren-1 ; i++)
{
if (i >= whichOne)
tmp[i] = m_children[i+1];
else
tmp[i] = m_children[i];
}
delete [] m_children;
m_children = tmp;
m_numchildren--;
}
}
else
{
delete [] m_children;
m_children = nullptr;
m_numchildren = 0;
}
}
if (returnValue)
returnValue->m_parent = nullptr;
return returnValue;
}
nsMsgSendPart* nsMsgSendPart::GetChild(int32_t which)
{
NS_ASSERTION(which >= 0 && which < m_numchildren, "parameter out of range");
if (which >= 0 && which < m_numchildren) {
return m_children[which];
}
return nullptr;
}
nsresult nsMsgSendPart::PushBody(const char* buffer, int32_t length)
{
nsresult status = NS_OK;
const char* encoded_data = buffer;
if (m_encoder)
{
status = m_encoder->Write(encoded_data, length);
}
else
{
// Merely translate all linebreaks to CRLF.
const char *in = encoded_data;
const char *end = in + length;
char *buffer, *out;
buffer = mime_get_stream_write_buffer();
// XXX -1 is not a valid nsresult
NS_ENSURE_TRUE(buffer, static_cast<nsresult>(-1));
NS_ASSERTION(encoded_data != buffer, "encoded_data == buffer");
out = buffer;
for (; in < end; in++) {
if (m_just_hit_CR) {
m_just_hit_CR = false;
if (*in == '\n') {
// The last thing we wrote was a CRLF from hitting a CR.
// So, we don't want to do anything from a following LF;
// we want to ignore it.
continue;
}
}
if (*in == '\r' || *in == '\n') {
/* Write out the newline. */
*out++ = '\r';
*out++ = '\n';
status = mime_write_message_body(m_state, buffer,
out - buffer);
if (NS_FAILED(status)) return status;
out = buffer;
if (*in == '\r') {
m_just_hit_CR = true;
}
out = buffer;
} else {
/* Fix for bug #95985. We can't assume that all lines are shorter
than 4096 chars (MIME_BUFFER_SIZE), so we need to test
for this here. sfraser.
*/
if (out - buffer >= MIME_BUFFER_SIZE)
{
status = mime_write_message_body(m_state, buffer, out - buffer);
if (NS_FAILED(status)) return status;
out = buffer;
}
*out++ = *in;
}
}
/* Flush the last line. */
if (out > buffer) {
status = mime_write_message_body(m_state, buffer, out - buffer);
if (NS_FAILED(status)) return status;
out = buffer;
}
}
if (encoded_data && encoded_data != buffer) {
PR_Free((char *) encoded_data);
}
return status;
}
/* Partition the headers into those which apply to the message as a whole;
those which apply to the message's contents; and the Content-Type header
itself. (This relies on the fact that all body-related headers begin with
"Content-".)
(How many header parsers are in this program now?)
*/
static nsresult
divide_content_headers(const char *headers,
char **message_headers,
char **content_headers,
char **content_type_header)
{
const char *tail;
char *message_tail, *content_tail, *type_tail;
int L = 0;
if (headers)
L = PL_strlen(headers);
if (L == 0)
return NS_OK;
*message_headers = (char *)PR_Malloc(L+1);
if (!*message_headers)
return NS_ERROR_OUT_OF_MEMORY;
*content_headers = (char *)PR_Malloc(L+1);
if (!*content_headers) {
PR_Free(*message_headers);
return NS_ERROR_OUT_OF_MEMORY;
}
*content_type_header = (char *)PR_Malloc(L+1);
if (!*content_type_header) {
PR_Free(*message_headers);
PR_Free(*content_headers);
return NS_ERROR_OUT_OF_MEMORY;
}
message_tail = *message_headers;
content_tail = *content_headers;
type_tail = *content_type_header;
tail = headers;
while (*tail)
{
const char *head = tail;
char **out;
while(true) {
/* Loop until we reach a newline that is not followed by whitespace.
*/
if (tail[0] == 0 ||
((tail[0] == '\r' || tail[0] == '\n') &&
!(tail[1] == ' ' || tail[1] == '\t' || tail[1] == '\n')))
{
/* Swallow the whole newline. */
if (tail[0] == '\r' && tail[1] == '\n')
tail++;
if (*tail)
tail++;
break;
}
tail++;
}
/* Decide which block this header goes into.
*/
if (!PL_strncasecmp(head, "Content-Type:", 13))
out = &type_tail;
else
if (!PL_strncasecmp(head, "Content-", 8))
out = &content_tail;
else
out = &message_tail;
memcpy(*out, head, (tail-head));
*out += (tail-head);
}
*message_tail = 0;
*content_tail = 0;
*type_tail = 0;
if (!**message_headers) {
PR_Free(*message_headers);
*message_headers = 0;
}
if (!**content_headers) {
PR_Free(*content_headers);
*content_headers = 0;
}
if (!**content_type_header) {
PR_Free(*content_type_header);
*content_type_header = 0;
}
#ifdef DEBUG
// ### mwelch Because of the extreme difficulty we've had with
// duplicate part headers, I'm going to put in an
// ASSERT here which makes sure that no duplicate
// Content-Type or Content-Transfer-Encoding headers
// leave here undetected.
const char* tmp;
if (*content_type_header) {
tmp = PL_strstr(*content_type_header, "Content-Type");
if (tmp) {
tmp++; // get past the first occurrence
NS_ASSERTION(!PL_strstr(tmp, "Content-Type"), "Content-part already present");
}
}
if (*content_headers) {
tmp = PL_strstr(*content_headers, "Content-Transfer-Encoding");
if (tmp) {
tmp++; // get past the first occurrence
NS_ASSERTION(!PL_strstr(tmp, "Content-Transfer-Encoding"), "Content-Transfert already present");
}
}
#endif // DEBUG
return NS_OK;
}
#define SKIP_EMPTY_PART 1966
nsresult
nsMsgSendPart::Write()
{
nsresult status = NS_OK;
char *separator = nullptr;
bool needToWriteCRLFAfterEncodedBody = false;
#define PUSHLEN(str, length) \
do { \
status = mime_write_message_body(m_state, str, length); \
if (NS_FAILED(status)) goto FAIL; \
} while (0) \
#define PUSH(str) PUSHLEN(str, PL_strlen(str))
// rhp: Suppress the output of parts that are empty!
if ( (m_parent) &&
(m_numchildren == 0) &&
( (!m_buffer) || (!*m_buffer) ) &&
(!m_file) &&
(!m_mainpart) )
// XXX SKIP_EMPTY_PART (= 1966) is not a valid nsresult
return static_cast<nsresult>(SKIP_EMPTY_PART);
if (m_mainpart && m_type && PL_strcmp(m_type, TEXT_HTML) == 0)
{
if (m_file)
{
// The "insert HTML links" code requires a memory buffer,
// so read the file into memory.
NS_ASSERTION(m_buffer == nullptr, "not-null buffer");
int32_t length = 0;
int64_t fileSize;
if (NS_SUCCEEDED(m_file->GetFileSize(&fileSize)))
length = fileSize;
m_buffer = (char *) PR_Malloc(sizeof(char) * (length + 1));
if (m_buffer)
{
nsCOMPtr<nsIInputStream> inputFile;
nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputFile), m_file);
if (NS_SUCCEEDED(rv))
{
uint32_t bytesRead;
rv = inputFile->Read(m_buffer, length, &bytesRead);
inputFile->Close();
m_buffer[length] = '\0';
}
else
PR_Free(m_buffer);
}
}
}
if (m_parent && m_parent->m_type &&
!PL_strcasecmp(m_parent->m_type, MULTIPART_DIGEST) &&
m_type &&
(!PL_strcasecmp(m_type, MESSAGE_RFC822) ||
!PL_strcasecmp(m_type, MESSAGE_NEWS)))
{
// If we're in a multipart/digest, and this document is of type
// message/rfc822, then it's appropriate to emit no headers.
//
}
else
{
char *message_headers = 0;
char *content_headers = 0;
char *content_type_header = 0;
status = divide_content_headers(m_other,
&message_headers,
&content_headers,
&content_type_header);
if (NS_FAILED(status))
goto FAIL;
/* First, write out all of the headers that refer to the message
itself (From, Subject, MIME-Version, etc.)
*/
if (message_headers)
{
PUSH(message_headers);
PR_Free(message_headers);
message_headers = 0;
}
/* Now allow the crypto library to (potentially) insert some text
(it may want to wrap the body in an envelope.) */
if (!m_parent) {
status = m_state->BeginCryptoEncapsulation();
if (NS_FAILED(status)) goto FAIL;
}
/* Now make sure there's a Content-Type header.
*/
if (!content_type_header)
{
NS_ASSERTION(m_type && *m_type, "null ptr");
bool needsCharset = mime_type_needs_charset(m_type ? m_type : TEXT_PLAIN);
if (needsCharset)
{
content_type_header = PR_smprintf("Content-Type: %s; charset=%s" CRLF,
(m_type ? m_type : TEXT_PLAIN), m_charset_name);
}
else
content_type_header = PR_smprintf("Content-Type: %s" CRLF,
(m_type ? m_type : TEXT_PLAIN));
if (!content_type_header)
{
if (content_headers)
PR_Free(content_headers);
status = NS_ERROR_OUT_OF_MEMORY;
goto FAIL;
}
}
/* If this is a compound object, tack a boundary string onto the
Content-Type header. this
*/
if (m_numchildren > 0)
{
int L;
char *ct2;
NS_ASSERTION(m_type, "null ptr");
if (!separator)
{
separator = mime_make_separator("");
if (!separator)
{
status = NS_ERROR_OUT_OF_MEMORY;
goto FAIL;
}
}
L = PL_strlen(content_type_header);
if (content_type_header[L-1] == '\n')
content_type_header[--L] = 0;
if (content_type_header[L-1] == '\r')
content_type_header[--L] = 0;
ct2 = PR_smprintf("%s;\r\n boundary=\"%s\"" CRLF, content_type_header, separator);
PR_Free(content_type_header);
if (!ct2)
{
if (content_headers)
PR_Free(content_headers);
status = NS_ERROR_OUT_OF_MEMORY;
goto FAIL;
}
content_type_header = ct2;
}
// Now write out the Content-Type header...
NS_ASSERTION(content_type_header && *content_type_header, "null ptr");
PUSH(content_type_header);
PR_Free(content_type_header);
content_type_header = 0;
/* ...followed by all of the other headers that refer to the body of
the message (Content-Transfer-Encoding, Content-Dispositon, etc.)
*/
if (content_headers)
{
PUSH(content_headers);
PR_Free(content_headers);
content_headers = 0;
}
}
PUSH(CRLF); // A blank line, to mark the end of headers.
m_firstBlock = true;
/* only convert if we need to tag charset */
m_needIntlConversion = mime_type_needs_charset(m_type);
if (m_buffer)
{
status = PushBody(m_buffer, PL_strlen(m_buffer));
if (NS_FAILED(status))
goto FAIL;
}
else if (m_file)
{
nsCOMPtr<nsIInputStream> inputStream;
nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), m_file);
if (NS_FAILED(rv))
{
// mysteriously disappearing?
nsCOMPtr<nsIMsgSendReport> sendReport;
m_state->GetSendReport(getter_AddRefs(sendReport));
if (sendReport)
{
nsAutoString error_msg;
nsMsgBuildMessageWithTmpFile(m_file, error_msg);
sendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false);
}
status = NS_MSG_UNABLE_TO_OPEN_TMP_FILE;
goto FAIL;
}
nsCString curLine;
bool more = true;
/* Kludge to avoid having to allocate memory on the toy computers... */
if (!mime_mailto_stream_read_buffer)
{
mime_mailto_stream_read_buffer = (char *) PR_Malloc(MIME_BUFFER_SIZE);
if (!mime_mailto_stream_read_buffer)
{
status = NS_ERROR_OUT_OF_MEMORY;
goto FAIL;
}
}
char *buffer = mime_mailto_stream_read_buffer;
if (m_strip_sensitive_headers)
{
// We are attaching a message, so we should be careful to
// strip out certain sensitive internal header fields.
bool skipping = false;
nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>);
NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY);
while (more)
{
// NS_ReadLine doesn't return line termination chars.
rv = NS_ReadLine(inputStream.get(), lineBuffer.get(), curLine, &more);
curLine.Append(CRLF);
char *line = (char *) curLine.get();
if (skipping) {
if (*line == ' ' || *line == '\t')
continue;
else
skipping = false;
}
if (!PL_strncasecmp(line, "From -", 6) ||
!PL_strncasecmp(line, "BCC:", 4) ||
!PL_strncasecmp(line, "FCC:", 4) ||
!PL_strncasecmp(line, CONTENT_LENGTH ":", CONTENT_LENGTH_LEN+1) ||
!PL_strncasecmp(line, "Lines:", 6) ||
!PL_strncasecmp(line, "Status:", 7) ||
!PL_strncasecmp(line, X_MOZILLA_STATUS ":", X_MOZILLA_STATUS_LEN+1) ||
!PL_strncasecmp(line, X_MOZILLA_STATUS2 ":", X_MOZILLA_STATUS2_LEN+1) ||
!PL_strncasecmp(line, X_MOZILLA_DRAFT_INFO ":", X_MOZILLA_DRAFT_INFO_LEN+1) ||
!PL_strncasecmp(line, X_MOZILLA_NEWSHOST ":", X_MOZILLA_NEWSHOST_LEN+1) ||
!PL_strncasecmp(line, X_UIDL ":", X_UIDL_LEN+1) ||
!PL_strncasecmp(line, "X-VM-", 5)) /* hi Kyle */
{
skipping = true;
continue;
}
PUSH(line);
if (curLine.Length() == 2) {
nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(inputStream);
// seek back the amount of data left in the line buffer...
seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, lineBuffer->start - lineBuffer->end);
break; // Now can do normal reads for the body.
}
}
lineBuffer = nullptr;
}
while (NS_SUCCEEDED(status))
{
uint32_t bytesRead;
nsresult rv = inputStream->Read(buffer, MIME_BUFFER_SIZE, &bytesRead);
if (NS_FAILED(rv))
{
nsCOMPtr<nsIMsgSendReport> sendReport;
m_state->GetSendReport(getter_AddRefs(sendReport));
if (sendReport)
{
nsAutoString error_msg;
nsMsgBuildMessageWithFile(m_file, error_msg);
sendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false);
status = NS_MSG_UNABLE_TO_OPEN_FILE;
goto FAIL;
}
}
status = PushBody(buffer, bytesRead);
if (NS_FAILED(status))
goto FAIL;
if (bytesRead < MIME_BUFFER_SIZE)
break;
}
}
if (m_encoder)
{
nsresult rv = m_encoder->Flush();
m_encoder = nullptr;
needToWriteCRLFAfterEncodedBody = !m_parent;
if (NS_FAILED(rv))
{
// XXX -1 is not a valid nsresult
status = static_cast<nsresult>(-1);
goto FAIL;
}
}
//
// Ok, from here we loop and drive the the output of all children
// for this message.
//
if (m_numchildren > 0)
{
bool writeSeparator = true;
for (int i = 0 ; i < m_numchildren ; i ++)
{
if (writeSeparator)
{
PUSH(CRLF);
PUSH("--");
PUSH(separator);
PUSH(CRLF);
}
status = m_children[i]->Write();
if (NS_FAILED(status))
goto FAIL;
// XXX SKIP_EMPTY_PART (= 1966) is not a valid nsresult
if (status == static_cast<nsresult>(SKIP_EMPTY_PART))
writeSeparator = false;
else
writeSeparator = true;
}
PUSH(CRLF);
PUSH("--");
PUSH(separator);
PUSH("--");
PUSH(CRLF);
}
else if (needToWriteCRLFAfterEncodedBody)
PUSH(CRLF);
FAIL:
PR_FREEIF(separator);
return status;
}