in mailnews/mime/src/mimemsig.cpp [135:395]
static int MimeMultipartSigned_parse_line(const char* line, int32_t length,
MimeObject* obj) {
MimeMultipart* mult = (MimeMultipart*)obj;
MimeMultipartSigned* sig = (MimeMultipartSigned*)obj;
MimeMultipartParseState old_state = mult->state;
bool hash_line_p = true;
bool no_headers_p = false;
int status = 0;
/* First do the parsing for normal multipart/ objects by handing it off to
the superclass method. This includes calling the create_child and
close_child methods.
*/
status =
(((MimeObjectClass*)(&MIME_SUPERCLASS))->parse_line(line, length, obj));
if (status < 0) return status;
/* The instance variable MimeMultipartClass->state tracks motion through
the various stages of multipart/ parsing. The instance variable
MimeMultipartSigned->state tracks the difference between the first
part (the body) and the second part (the signature.) This second,
more specific state variable is updated by noticing the transitions
of the first, more general state variable.
*/
if (old_state != mult->state) /* there has been a state change */
{
switch (mult->state) {
case MimeMultipartPreamble:
PR_ASSERT(0); /* can't move *in* to preamble state. */
sig->state = MimeMultipartSignedPreamble;
break;
case MimeMultipartHeaders:
/* If we're moving in to the Headers state, then that means
that this line is the preceding boundary string (and we
should ignore it.)
*/
hash_line_p = false;
if (sig->state == MimeMultipartSignedPreamble)
sig->state = MimeMultipartSignedBodyFirstHeader;
else if (sig->state == MimeMultipartSignedBodyFirstLine ||
sig->state == MimeMultipartSignedBodyLine)
sig->state = MimeMultipartSignedSignatureHeaders;
else if (sig->state == MimeMultipartSignedSignatureFirstLine ||
sig->state == MimeMultipartSignedSignatureLine)
sig->state = MimeMultipartSignedEpilogue;
break;
case MimeMultipartPartFirstLine:
if (sig->state == MimeMultipartSignedBodyFirstHeader) {
sig->state = MimeMultipartSignedBodyFirstLine;
no_headers_p = true;
} else if (sig->state == MimeMultipartSignedBodyHeaders)
sig->state = MimeMultipartSignedBodyFirstLine;
else if (sig->state == MimeMultipartSignedSignatureHeaders)
sig->state = MimeMultipartSignedSignatureFirstLine;
else
sig->state = MimeMultipartSignedEpilogue;
break;
case MimeMultipartPartLine:
PR_ASSERT(sig->state == MimeMultipartSignedBodyFirstLine ||
sig->state == MimeMultipartSignedBodyLine ||
sig->state == MimeMultipartSignedSignatureFirstLine ||
sig->state == MimeMultipartSignedSignatureLine);
if (sig->state == MimeMultipartSignedBodyFirstLine)
sig->state = MimeMultipartSignedBodyLine;
else if (sig->state == MimeMultipartSignedSignatureFirstLine)
sig->state = MimeMultipartSignedSignatureLine;
break;
case MimeMultipartEpilogue:
sig->state = MimeMultipartSignedEpilogue;
break;
default: /* bad state */
NS_ERROR("bad state in MultipartSigned parse line");
return -1;
break;
}
}
/* Perform multipart/signed-related actions on this line based on the state
of the parser.
*/
switch (sig->state) {
case MimeMultipartSignedPreamble:
/* Do nothing. */
break;
case MimeMultipartSignedBodyFirstLine:
/* We have just moved out of the MimeMultipartSignedBodyHeaders
state, so cache away the headers that apply only to the body part.
*/
NS_ASSERTION(mult->hdrs, "null multipart hdrs");
NS_ASSERTION(!sig->body_hdrs,
"signed part shouldn't have already have body_hdrs");
sig->body_hdrs = mult->hdrs;
mult->hdrs = 0;
/* fall through. */
[[fallthrough]];
case MimeMultipartSignedBodyFirstHeader:
case MimeMultipartSignedBodyHeaders:
case MimeMultipartSignedBodyLine:
if (!sig->crypto_closure) {
/* Set error change */
PR_SetError(0, 0);
/* Initialize the signature verification library. */
sig->crypto_closure =
(((MimeMultipartSignedClass*)obj->clazz)->crypto_init)(obj);
if (!sig->crypto_closure) {
status = PR_GetError();
NS_ASSERTION(status < 0, "got non-negative status");
if (status >= 0) status = -1;
return status;
}
}
if (hash_line_p) {
/* this is the first hashed line if this is the first header
(that is, if it's the first line in the header state after
a state change.)
*/
bool first_line_p =
(no_headers_p || sig->state == MimeMultipartSignedBodyFirstHeader);
if (sig->state == MimeMultipartSignedBodyFirstHeader)
sig->state = MimeMultipartSignedBodyHeaders;
/* The newline issues here are tricky, since both the newlines
before and after the boundary string are to be considered part
of the boundary: this is so that a part can be specified such
that it does not end in a trailing newline.
To implement this, we send a newline *before* each line instead
of after, except for the first line, which is not preceded by a
newline.
For purposes of cryptographic hashing, we always hash line
breaks as CRLF -- the canonical, on-the-wire linebreaks, since
we have no idea of knowing what line breaks were used on the
originating system (SMTP rightly destroys that information.)
*/
/* Remove the trailing newline... */
if (length > 0 && line[length - 1] == '\n') length--;
if (length > 0 && line[length - 1] == '\r') length--;
PR_ASSERT(sig->crypto_closure);
if (!first_line_p) {
/* Push out a preceding newline... */
char nl[] = CRLF;
status = (((MimeMultipartSignedClass*)obj->clazz)
->crypto_data_hash(nl, 2, sig->crypto_closure));
if (status < 0) return status;
}
/* Now push out the line sans trailing newline. */
if (length > 0)
status = (((MimeMultipartSignedClass*)obj->clazz)
->crypto_data_hash(line, length, sig->crypto_closure));
if (status < 0) return status;
}
break;
case MimeMultipartSignedSignatureHeaders:
if (sig->crypto_closure && old_state != mult->state) {
/* We have just moved out of the MimeMultipartSignedBodyLine
state, so tell the signature verification library that we've
reached the end of the signed data.
*/
status = (((MimeMultipartSignedClass*)obj->clazz)->crypto_data_eof)(
sig->crypto_closure, false);
if (status < 0) return status;
}
break;
case MimeMultipartSignedSignatureFirstLine:
/* We have just moved out of the MimeMultipartSignedSignatureHeaders
state, so cache away the headers that apply only to the sig part.
*/
PR_ASSERT(mult->hdrs);
PR_ASSERT(!sig->sig_hdrs);
sig->sig_hdrs = mult->hdrs;
mult->hdrs = 0;
/* If the signature block has an encoding, set up a decoder for it.
(Similar logic is in MimeLeafClass->parse_begin.)
*/
{
MimeDecoderData* (*fn)(MimeConverterOutputCallback, MimeClosure) = 0;
nsCString encoding;
encoding.Adopt(MimeHeaders_get(
sig->sig_hdrs, HEADER_CONTENT_TRANSFER_ENCODING, true, false));
if (encoding.IsEmpty())
;
else if (!PL_strcasecmp(encoding.get(), ENCODING_BASE64))
fn = &MimeB64DecoderInit;
else if (!PL_strcasecmp(encoding.get(), ENCODING_QUOTED_PRINTABLE)) {
sig->sig_decoder_data = MimeQPDecoderInit(
(((MimeMultipartSignedClass*)obj->clazz)->crypto_signature_hash),
sig->crypto_closure);
if (!sig->sig_decoder_data) return MIME_OUT_OF_MEMORY;
} else if (!PL_strcasecmp(encoding.get(), ENCODING_UUENCODE) ||
!PL_strcasecmp(encoding.get(), ENCODING_UUENCODE2) ||
!PL_strcasecmp(encoding.get(), ENCODING_UUENCODE3) ||
!PL_strcasecmp(encoding.get(), ENCODING_UUENCODE4))
fn = &MimeUUDecoderInit;
else if (!PL_strcasecmp(encoding.get(), ENCODING_YENCODE))
fn = &MimeYDecoderInit;
if (fn) {
sig->sig_decoder_data = fn(
(((MimeMultipartSignedClass*)obj->clazz)->crypto_signature_hash),
sig->crypto_closure);
if (!sig->sig_decoder_data) return MIME_OUT_OF_MEMORY;
}
}
/* Show these headers to the crypto module. */
if (hash_line_p) {
status =
(((MimeMultipartSignedClass*)obj->clazz)->crypto_signature_init)(
sig->crypto_closure, obj, sig->sig_hdrs);
if (status < 0) return status;
}
/* fall through. */
[[fallthrough]];
case MimeMultipartSignedSignatureLine:
if (hash_line_p) {
/* Feed this line into the signature verification routines. */
if (sig->sig_decoder_data)
status =
MimeDecoderWrite(sig->sig_decoder_data, line, length, nullptr);
else
status =
(((MimeMultipartSignedClass*)obj->clazz)
->crypto_signature_hash(line, length, sig->crypto_closure));
if (status < 0) return status;
}
break;
case MimeMultipartSignedEpilogue:
/* Nothing special to do here. */
break;
default: /* bad state */
PR_ASSERT(0);
return -1;
}
return status;
}