Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F23642664
mime-maker.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
16 KB
Subscribers
None
mime-maker.c
View Options
/* mime-maker.c - Create MIME structures
* Copyright (C) 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG 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 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include
<config.h>
#include
<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
"util.h"
#include
"zb32.h"
#include
"mime-maker.h"
/* All valid characters in a header name. */
#define HEADER_NAME_CHARS ("abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"-01234567890")
/* An object to store an header. Also used for a list of headers. */
struct
header_s
{
struct
header_s
*
next
;
char
*
value
;
/* Malloced value. */
char
name
[
1
];
/* Name. */
};
typedef
struct
header_s
*
header_t
;
/* An object to store a MIME part. A part is the header plus the
* content (body). */
struct
part_s
{
struct
part_s
*
next
;
/* Next part in the current container. */
struct
part_s
*
child
;
/* Child container. */
char
*
mediatype
;
/* Mediatype of the container (malloced). */
char
*
boundary
;
/* Malloced boundary string. */
header_t
headers
;
/* List of headers. */
header_t
*
headers_tail
;
/* Address of last header in chain. */
size_t
bodylen
;
/* Length of BODY. */
char
*
body
;
/* Malloced buffer with the body. This is the
* non-encoded value. */
};
typedef
struct
part_s
*
part_t
;
/* Definition of the mime parser object. */
struct
mime_maker_context_s
{
void
*
cookie
;
/* Cookie passed to all callbacks. */
unsigned
int
verbose
:
1
;
/* Enable verbose mode. */
unsigned
int
debug
:
1
;
/* Enable debug mode. */
part_t
mail
;
/* The MIME tree. */
part_t
current_part
;
int
boundary_counter
;
/* Used to create easy to read boundaries. */
char
*
boundary_suffix
;
/* Random string used in the boundaries. */
struct
b64state
*
b64state
;
/* NULL or malloced Base64 decoder state. */
/* Helper to convey the output stream to recursive functions. */
estream_t
outfp
;
};
/* Create a new mime make object. COOKIE is a values woich will be
* used as first argument for all callbacks registered with this
* object. */
gpg_error_t
mime_maker_new
(
mime_maker_t
*
r_maker
,
void
*
cookie
)
{
mime_maker_t
ctx
;
*
r_maker
=
NULL
;
ctx
=
xtrycalloc
(
1
,
sizeof
*
ctx
);
if
(
!
ctx
)
return
gpg_error_from_syserror
();
ctx
->
cookie
=
cookie
;
*
r_maker
=
ctx
;
return
0
;
}
static
void
release_parts
(
part_t
part
)
{
while
(
part
)
{
part_t
partnext
=
part
->
next
;
while
(
part
->
headers
)
{
header_t
hdrnext
=
part
->
headers
->
next
;
xfree
(
part
->
headers
);
part
->
headers
=
hdrnext
;
}
release_parts
(
part
->
child
);
xfree
(
part
->
mediatype
);
xfree
(
part
->
boundary
);
xfree
(
part
->
body
);
xfree
(
part
);
part
=
partnext
;
}
}
/* Release a mime maker object. */
void
mime_maker_release
(
mime_maker_t
ctx
)
{
if
(
!
ctx
)
return
;
release_parts
(
ctx
->
mail
);
xfree
(
ctx
->
boundary_suffix
);
xfree
(
ctx
);
}
/* Set verbose and debug mode. */
void
mime_maker_set_verbose
(
mime_maker_t
ctx
,
int
level
)
{
if
(
!
level
)
{
ctx
->
verbose
=
0
;
ctx
->
debug
=
0
;
}
else
{
ctx
->
verbose
=
1
;
if
(
level
>
10
)
ctx
->
debug
=
1
;
}
}
static
void
dump_parts
(
part_t
part
,
int
level
)
{
header_t
hdr
;
for
(;
part
;
part
=
part
->
next
)
{
log_debug
(
"%*s[part]
\n
"
,
level
*
2
,
""
);
for
(
hdr
=
part
->
headers
;
hdr
;
hdr
=
hdr
->
next
)
{
log_debug
(
"%*s%s: %s
\n
"
,
level
*
2
,
""
,
hdr
->
name
,
hdr
->
value
);
}
log_debug
(
"%*s[body %zu bytes]
\n
"
,
level
*
2
,
""
,
part
->
bodylen
);
if
(
part
->
child
)
{
log_debug
(
"%*s[container]
\n
"
,
level
*
2
,
""
);
dump_parts
(
part
->
child
,
level
+
1
);
}
}
}
/* Dump the mime tree for debugging. */
void
mime_maker_dump_tree
(
mime_maker_t
ctx
)
{
dump_parts
(
ctx
->
mail
,
0
);
}
/* Find the parent node for NEEDLE starting at ROOT. */
static
part_t
find_parent
(
part_t
root
,
part_t
needle
)
{
part_t
node
,
n
;
for
(
node
=
root
->
child
;
node
;
node
=
node
->
next
)
{
if
(
node
==
needle
)
return
root
;
if
((
n
=
find_parent
(
node
,
needle
)))
return
n
;
}
return
NULL
;
}
/* Create a boundary string. Outr codes is aware of the general
* structure of that string (gebins with "=-=") so that
* it can protect against accidentally-used boundaries within the
* content. */
static
char
*
generate_boundary
(
mime_maker_t
ctx
)
{
if
(
!
ctx
->
boundary_suffix
)
{
char
buffer
[
12
];
gcry_create_nonce
(
buffer
,
sizeof
buffer
);
ctx
->
boundary_suffix
=
zb32_encode
(
buffer
,
8
*
sizeof
buffer
);
if
(
!
ctx
->
boundary_suffix
)
return
NULL
;
}
ctx
->
boundary_counter
++
;
return
es_bsprintf
(
"=-=%02d-%s=-="
,
ctx
->
boundary_counter
,
ctx
->
boundary_suffix
);
}
/* Ensure that the context has a MAIL and CURRENT_PART object and
* return the parent object if available */
static
gpg_error_t
ensure_part
(
mime_maker_t
ctx
,
part_t
*
r_parent
)
{
if
(
!
ctx
->
mail
)
{
ctx
->
mail
=
xtrycalloc
(
1
,
sizeof
*
ctx
->
mail
);
if
(
!
ctx
->
mail
)
return
gpg_error_from_syserror
();
log_assert
(
!
ctx
->
current_part
);
ctx
->
current_part
=
ctx
->
mail
;
ctx
->
current_part
->
headers_tail
=
&
ctx
->
current_part
->
headers
;
}
log_assert
(
ctx
->
current_part
);
if
(
r_parent
)
*
r_parent
=
find_parent
(
ctx
->
mail
,
ctx
->
current_part
);
return
0
;
}
/* Transform a header name into a standard capitalized format.
* "Content-Type". Conversion stops at the colon. */
static
void
capitalize_header_name
(
char
*
name
)
{
unsigned
char
*
p
=
name
;
int
first
=
1
;
/* Special cases first. */
if
(
!
ascii_strcasecmp
(
name
,
"MIME-Version"
))
{
strcpy
(
name
,
"MIME-Version"
);
return
;
}
/* Regular cases. */
for
(;
*
p
&&
*
p
!=
':'
;
p
++
)
{
if
(
*
p
==
'-'
)
first
=
1
;
else
if
(
first
)
{
if
(
*
p
>=
'a'
&&
*
p
<=
'z'
)
*
p
=
*
p
-
'a'
+
'A'
;
first
=
0
;
}
else
if
(
*
p
>=
'A'
&&
*
p
<=
'Z'
)
*
p
=
*
p
-
'A'
+
'a'
;
}
}
/* Check whether a header with NAME has already been set into PART.
* NAME must be in canonical capitalized format. Return true or
* false. */
static
int
have_header
(
part_t
part
,
const
char
*
name
)
{
header_t
hdr
;
for
(
hdr
=
part
->
headers
;
hdr
;
hdr
=
hdr
->
next
)
if
(
!
strcmp
(
hdr
->
name
,
name
))
return
1
;
return
0
;
}
/* Helper to add a header to a part. */
static
gpg_error_t
add_header
(
part_t
part
,
const
char
*
name
,
const
char
*
value
)
{
gpg_error_t
err
;
header_t
hdr
;
size_t
namelen
;
const
char
*
s
;
if
(
!
value
)
{
s
=
strchr
(
name
,
'='
);
if
(
!
s
)
return
gpg_error
(
GPG_ERR_INV_ARG
);
namelen
=
s
-
name
;
value
=
s
+
1
;
}
else
namelen
=
strlen
(
name
);
hdr
=
xtrymalloc
(
sizeof
*
hdr
+
namelen
);
if
(
!
hdr
)
return
gpg_error_from_syserror
();
hdr
->
next
=
NULL
;
memcpy
(
hdr
->
name
,
name
,
namelen
);
hdr
->
name
[
namelen
]
=
0
;
/* Check that the header name is valid. We allow all lower and
* uppercase letters and, except for the first character, digits and
* the dash. */
if
(
strspn
(
hdr
->
name
,
HEADER_NAME_CHARS
)
!=
namelen
||
strchr
(
"-0123456789"
,
*
hdr
->
name
))
{
xfree
(
hdr
);
return
gpg_error
(
GPG_ERR_INV_NAME
);
}
capitalize_header_name
(
hdr
->
name
);
hdr
->
value
=
xtrystrdup
(
value
);
if
(
!
hdr
->
value
)
{
err
=
gpg_error_from_syserror
();
xfree
(
hdr
);
return
err
;
}
if
(
part
)
{
*
part
->
headers_tail
=
hdr
;
part
->
headers_tail
=
&
hdr
->
next
;
}
else
xfree
(
hdr
);
return
0
;
}
/* Add a header with NAME and VALUE to the current mail. A LF in the
* VALUE will be handled automagically. If NULL is used for VALUE it
* is expected that the NAME has the format "NAME=VALUE" and VALUE is
* taken from there.
*
* If no container has been added, the header will be used for the
* regular mail headers and not for a MIME part. If the current part
* is in a container and a body has been added, we append a new part
* to the current container. Thus for a non-MIME mail the caller
* needs to call this function followed by a call to add a body. When
* adding a Content-Type the boundary parameter must not be included.
*/
gpg_error_t
mime_maker_add_header
(
mime_maker_t
ctx
,
const
char
*
name
,
const
char
*
value
)
{
gpg_error_t
err
;
part_t
part
,
parent
;
/* Hack to use this function for a syntax check of NAME and VALUE. */
if
(
!
ctx
)
return
add_header
(
NULL
,
name
,
value
);
err
=
ensure_part
(
ctx
,
&
parent
);
if
(
err
)
return
err
;
part
=
ctx
->
current_part
;
if
(
part
->
body
&&
!
parent
)
{
/* We already have a body but no parent. Adding another part is
* thus not possible. */
return
gpg_error
(
GPG_ERR_CONFLICT
);
}
if
(
part
->
body
)
{
/* We already have a body and there is a parent. We now append
* a new part to the current container. */
part
=
xtrycalloc
(
1
,
sizeof
*
part
);
if
(
!
part
)
return
gpg_error_from_syserror
();
part
->
headers_tail
=
&
part
->
headers
;
log_assert
(
!
ctx
->
current_part
->
next
);
ctx
->
current_part
->
next
=
part
;
ctx
->
current_part
=
part
;
}
/* If no NAME and no VALUE has been given we do not add a header.
* This can be used to create a new part without any header. */
if
(
!
name
&&
!
value
)
return
0
;
/* If we add Content-Type, make sure that we have a MIME-version
* header first; this simply looks better. */
if
(
!
ascii_strcasecmp
(
name
,
"Content-Type"
)
&&
!
have_header
(
ctx
->
mail
,
"MIME-Version"
))
{
err
=
add_header
(
ctx
->
mail
,
"MIME-Version"
,
"1.0"
);
if
(
err
)
return
err
;
}
return
add_header
(
part
,
name
,
value
);
}
/* Helper for mime_maker_add_{body,stream}. */
static
gpg_error_t
add_body
(
mime_maker_t
ctx
,
const
void
*
data
,
size_t
datalen
)
{
gpg_error_t
err
;
part_t
part
,
parent
;
err
=
ensure_part
(
ctx
,
&
parent
);
if
(
err
)
return
err
;
part
=
ctx
->
current_part
;
if
(
part
->
body
)
return
gpg_error
(
GPG_ERR_CONFLICT
);
part
->
body
=
xtrymalloc
(
datalen
?
datalen
:
1
);
if
(
!
part
->
body
)
return
gpg_error_from_syserror
();
part
->
bodylen
=
datalen
;
if
(
data
)
memcpy
(
part
->
body
,
data
,
datalen
);
return
0
;
}
/* Add STRING as body to the mail or the current MIME container. A
* second call to this function is not allowed.
*
* FIXME: We may want to have an append_body to add more data to a body.
*/
gpg_error_t
mime_maker_add_body
(
mime_maker_t
ctx
,
const
char
*
string
)
{
return
add_body
(
ctx
,
string
,
strlen
(
string
));
}
/* This is the same as mime_maker_add_body but takes a stream as
* argument. As of now the stream is copied to the MIME object but
* eventually we may delay that and read the stream only at the time
* it is needed. Note that the address of the stream object must be
* passed and that the ownership of the stream is transferred to this
* MIME object. To indicate the latter the function will store NULL
* at the ADDR_STREAM so that a caller can't use that object anymore
* except for es_fclose which accepts a NULL pointer. */
gpg_error_t
mime_maker_add_stream
(
mime_maker_t
ctx
,
estream_t
*
stream_addr
)
{
void
*
data
;
size_t
datalen
;
es_rewind
(
*
stream_addr
);
if
(
es_fclose_snatch
(
*
stream_addr
,
&
data
,
&
datalen
))
return
gpg_error_from_syserror
();
*
stream_addr
=
NULL
;
return
add_body
(
ctx
,
data
,
datalen
);
}
/* Add a new MIME container. The caller needs to provide the media
* and media-subtype in MEDIATYPE. If MEDIATYPE is NULL
* "multipart/mixed" is assumed. This function will then add a
* Content-Type header with that media type and an approriate boundary
* string to the parent part. */
gpg_error_t
mime_maker_add_container
(
mime_maker_t
ctx
,
const
char
*
mediatype
)
{
gpg_error_t
err
;
part_t
part
;
if
(
!
mediatype
)
mediatype
=
"multipart/mixed"
;
err
=
ensure_part
(
ctx
,
NULL
);
if
(
err
)
return
err
;
part
=
ctx
->
current_part
;
if
(
part
->
body
)
return
gpg_error
(
GPG_ERR_CONFLICT
);
/* There is already a body. */
if
(
part
->
child
||
part
->
mediatype
||
part
->
boundary
)
return
gpg_error
(
GPG_ERR_CONFLICT
);
/* There is already a container. */
/* If a content type has not yet been set, do it now. The boundary
* will be added while writing the headers. */
if
(
!
have_header
(
ctx
->
mail
,
"Content-Type"
))
{
err
=
add_header
(
ctx
->
mail
,
"Content-Type"
,
mediatype
);
if
(
err
)
return
err
;
}
/* Create a child node. */
part
->
child
=
xtrycalloc
(
1
,
sizeof
*
part
->
child
);
if
(
!
part
->
child
)
return
gpg_error_from_syserror
();
part
->
child
->
headers_tail
=
&
part
->
child
->
headers
;
part
->
mediatype
=
xtrystrdup
(
mediatype
);
if
(
!
part
->
mediatype
)
{
err
=
gpg_error_from_syserror
();
xfree
(
part
->
child
);
part
->
child
=
NULL
;
return
err
;
}
part
->
boundary
=
generate_boundary
(
ctx
);
if
(
!
part
->
boundary
)
{
err
=
gpg_error_from_syserror
();
xfree
(
part
->
child
);
part
->
child
=
NULL
;
xfree
(
part
->
mediatype
);
part
->
mediatype
=
NULL
;
return
err
;
}
part
=
part
->
child
;
ctx
->
current_part
=
part
;
return
0
;
}
/* Write the Content-Type header with the boundary value. */
static
gpg_error_t
write_ct_with_boundary
(
mime_maker_t
ctx
,
const
char
*
value
,
const
char
*
boundary
)
{
const
char
*
s
;
if
(
!*
value
)
return
gpg_error
(
GPG_ERR_INV_VALUE
);
/* Empty string. */
for
(
s
=
value
+
strlen
(
value
)
-
1
;
(
s
>=
value
&&
(
*
s
==
' '
||
*
s
==
'\t'
||
*
s
==
'\n'
));
s
--
)
;
if
(
!
(
s
>=
value
))
return
gpg_error
(
GPG_ERR_INV_VALUE
);
/* Only spaces. */
/* Fixme: We should use a dedicated header write functions which
* properly wraps the header. */
es_fprintf
(
ctx
->
outfp
,
"Content-Type: %s%s
\n\t
boundary=
\"
%s
\"\n
"
,
value
,
(
*
s
==
';'
)
?
""
:
";"
,
boundary
);
return
0
;
}
/* Recursive worker for mime_maker_make. */
static
gpg_error_t
write_tree
(
mime_maker_t
ctx
,
part_t
parent
,
part_t
part
)
{
gpg_error_t
err
;
header_t
hdr
;
for
(;
part
;
part
=
part
->
next
)
{
for
(
hdr
=
part
->
headers
;
hdr
;
hdr
=
hdr
->
next
)
{
if
(
part
->
child
&&
!
strcmp
(
hdr
->
name
,
"Content-Type"
))
write_ct_with_boundary
(
ctx
,
hdr
->
value
,
part
->
boundary
);
else
es_fprintf
(
ctx
->
outfp
,
"%s: %s
\n
"
,
hdr
->
name
,
hdr
->
value
);
}
es_fputc
(
'\n'
,
ctx
->
outfp
);
if
(
part
->
body
)
{
if
(
es_write
(
ctx
->
outfp
,
part
->
body
,
part
->
bodylen
,
NULL
))
return
gpg_error_from_syserror
();
}
if
(
part
->
child
)
{
log_assert
(
part
->
boundary
);
if
(
es_fprintf
(
ctx
->
outfp
,
"
\n
--%s
\n
"
,
part
->
boundary
)
<
0
)
return
gpg_error_from_syserror
();
err
=
write_tree
(
ctx
,
part
,
part
->
child
);
if
(
err
)
return
err
;
if
(
es_fprintf
(
ctx
->
outfp
,
"
\n
--%s--
\n
"
,
part
->
boundary
)
<
0
)
return
gpg_error_from_syserror
();
}
if
(
part
->
next
)
{
log_assert
(
parent
&&
parent
->
boundary
);
if
(
es_fprintf
(
ctx
->
outfp
,
"
\n
--%s
\n
"
,
parent
->
boundary
)
<
0
)
return
gpg_error_from_syserror
();
}
}
return
0
;
}
/* Add headers we always require. */
static
gpg_error_t
add_missing_headers
(
mime_maker_t
ctx
)
{
gpg_error_t
err
;
if
(
!
ctx
->
mail
)
return
gpg_error
(
GPG_ERR_NO_DATA
);
if
(
!
have_header
(
ctx
->
mail
,
"MIME-Version"
))
{
/* Even if a Content-Type has never been set, we want to
* announce that we do MIME. */
err
=
add_header
(
ctx
->
mail
,
"MIME-Version"
,
"1.0"
);
if
(
err
)
goto
leave
;
}
if
(
!
have_header
(
ctx
->
mail
,
"Date"
))
{
char
*
p
=
rfctimestamp
(
make_timestamp
());
if
(
!
p
)
err
=
gpg_error_from_syserror
();
else
err
=
add_header
(
ctx
->
mail
,
"Date"
,
p
);
xfree
(
p
);
if
(
err
)
goto
leave
;
}
leave
:
return
err
;
}
/* Create message from the tree MIME and write it to FP. Noet that
* the output uses only a LF and a later called sendmail(1) is
* expected to convert them to network line endings. */
gpg_error_t
mime_maker_make
(
mime_maker_t
ctx
,
estream_t
fp
)
{
gpg_error_t
err
;
err
=
add_missing_headers
(
ctx
);
if
(
err
)
return
err
;
ctx
->
outfp
=
fp
;
err
=
write_tree
(
ctx
,
NULL
,
ctx
->
mail
);
ctx
->
outfp
=
NULL
;
return
err
;
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Tue, Jun 3, 5:35 AM (10 h, 6 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
fa/d2/0a31536ab99ebde6231fc7271281
Attached To
rG GnuPG
Event Timeline
Log In to Comment