Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F36624501
mime-maker.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
19 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
*
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. */
unsigned
int
partid
;
/* The part ID. */
};
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
;
unsigned
int
partid_counter
;
/* Counter assign part ids. */
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
->
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 %u]
\n
"
,
level
*
2
,
""
,
part
->
partid
);
for
(
hdr
=
part
->
headers
;
hdr
;
hdr
=
hdr
->
next
)
{
log_debug
(
"%*s%s: %s
\n
"
,
level
*
2
,
""
,
hdr
->
name
,
hdr
->
value
);
}
if
(
part
->
body
)
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
;
}
/* Find the part node from the PARTID. */
static
part_t
find_part
(
part_t
root
,
unsigned
int
partid
)
{
part_t
node
,
n
;
for
(
node
=
root
->
child
;
node
;
node
=
node
->
next
)
{
if
(
node
->
partid
==
partid
)
return
root
;
if
((
n
=
find_part
(
node
,
partid
)))
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
;
char
*
p
;
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
;
}
for
(
p
=
hdr
->
value
+
strlen
(
hdr
->
value
)
-
1
;
(
p
>=
hdr
->
value
&&
(
*
p
==
' '
||
*
p
==
'\t'
||
*
p
==
'\n'
||
*
p
==
'\r'
));
p
--
)
*
p
=
0
;
if
(
!
(
p
>=
hdr
->
value
))
{
xfree
(
hdr
->
value
);
xfree
(
hdr
);
return
gpg_error
(
GPG_ERR_INV_VALUE
);
/* Only spaces. */
}
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
||
part
->
child
)
&&
!
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
||
part
->
child
)
{
/* 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
->
partid
=
++
ctx
->
partid_counter
;
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. A container can be used instead of a
* body. */
gpg_error_t
mime_maker_add_container
(
mime_maker_t
ctx
)
{
gpg_error_t
err
;
part_t
part
;
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
->
boundary
)
return
gpg_error
(
GPG_ERR_CONFLICT
);
/* There is already a container. */
/* 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
->
boundary
=
generate_boundary
(
ctx
);
if
(
!
part
->
boundary
)
{
err
=
gpg_error_from_syserror
();
xfree
(
part
->
child
);
part
->
child
=
NULL
;
return
err
;
}
part
=
part
->
child
;
part
->
partid
=
++
ctx
->
partid_counter
;
ctx
->
current_part
=
part
;
return
0
;
}
/* Finish the current container. */
gpg_error_t
mime_maker_end_container
(
mime_maker_t
ctx
)
{
gpg_error_t
err
;
part_t
parent
;
err
=
ensure_part
(
ctx
,
&
parent
);
if
(
err
)
return
err
;
if
(
!
parent
)
return
gpg_error
(
GPG_ERR_CONFLICT
);
/* No container. */
while
(
parent
->
next
)
parent
=
parent
->
next
;
ctx
->
current_part
=
parent
;
return
0
;
}
/* Return the part-ID of the current part. */
unsigned
int
mime_maker_get_partid
(
mime_maker_t
ctx
)
{
if
(
ensure_part
(
ctx
,
NULL
))
return
0
;
/* Ooops. */
return
ctx
->
current_part
->
partid
;
}
/* Write a header and handle emdedded LFs. If BOUNDARY is not NULL it
* is appended to the value. */
/* Fixme: Add automatic line wrapping. */
static
gpg_error_t
write_header
(
mime_maker_t
ctx
,
const
char
*
name
,
const
char
*
value
,
const
char
*
boundary
)
{
const
char
*
s
;
es_fprintf
(
ctx
->
outfp
,
"%s: "
,
name
);
/* Note that add_header made sure that VALUE does not end with a LF.
* Thus we can assume that a LF is followed by non-whitespace. */
for
(
s
=
value
;
*
s
;
s
++
)
{
if
(
*
s
==
'\n'
)
es_fputs
(
"
\r\n\t
"
,
ctx
->
outfp
);
else
es_fputc
(
*
s
,
ctx
->
outfp
);
}
if
(
boundary
)
{
if
(
s
>
value
&&
s
[
-1
]
!=
';'
)
es_fputc
(
';'
,
ctx
->
outfp
);
es_fprintf
(
ctx
->
outfp
,
"
\r\n\t
boundary=
\"
%s
\"
"
,
boundary
);
}
es_fputs
(
"
\r\n
"
,
ctx
->
outfp
);
return
es_ferror
(
ctx
->
outfp
)
?
gpg_error_from_syserror
()
:
0
;
}
static
gpg_error_t
write_gap
(
mime_maker_t
ctx
)
{
es_fputs
(
"
\r\n
"
,
ctx
->
outfp
);
return
es_ferror
(
ctx
->
outfp
)
?
gpg_error_from_syserror
()
:
0
;
}
static
gpg_error_t
write_boundary
(
mime_maker_t
ctx
,
const
char
*
boundary
,
int
last
)
{
es_fprintf
(
ctx
->
outfp
,
"
\r\n
--%s%s
\r\n
"
,
boundary
,
last
?
"--"
:
""
);
return
es_ferror
(
ctx
->
outfp
)
?
gpg_error_from_syserror
()
:
0
;
}
/* Fixme: Apply required encoding. */
static
gpg_error_t
write_body
(
mime_maker_t
ctx
,
const
void
*
body
,
size_t
bodylen
)
{
const
char
*
s
;
for
(
s
=
body
;
bodylen
;
s
++
,
bodylen
--
)
{
if
(
*
s
==
'\n'
&&
!
(
s
>
(
const
char
*
)
body
&&
s
[
-1
]
==
'\r'
))
es_fputc
(
'\r'
,
ctx
->
outfp
);
es_fputc
(
*
s
,
ctx
->
outfp
);
}
return
es_ferror
(
ctx
->
outfp
)
?
gpg_error_from_syserror
()
:
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"
))
err
=
write_header
(
ctx
,
hdr
->
name
,
hdr
->
value
,
part
->
boundary
);
else
err
=
write_header
(
ctx
,
hdr
->
name
,
hdr
->
value
,
NULL
);
if
(
err
)
return
err
;
}
err
=
write_gap
(
ctx
);
if
(
err
)
return
err
;
if
(
part
->
body
)
{
err
=
write_body
(
ctx
,
part
->
body
,
part
->
bodylen
);
if
(
err
)
return
err
;
}
if
(
part
->
child
)
{
log_assert
(
part
->
boundary
);
err
=
write_boundary
(
ctx
,
part
->
boundary
,
0
);
if
(
!
err
)
err
=
write_tree
(
ctx
,
part
,
part
->
child
);
if
(
!
err
)
err
=
write_boundary
(
ctx
,
part
->
boundary
,
1
);
if
(
err
)
return
err
;
}
if
(
part
->
next
)
{
log_assert
(
parent
&&
parent
->
boundary
);
err
=
write_boundary
(
ctx
,
parent
->
boundary
,
0
);
if
(
err
)
return
err
;
}
}
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. Note 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
;
}
/* Create a stream object from the MIME part identified by PARTID and
* store it at R_STREAM. If PARTID identifies a container the entire
* tree is returned. Using that function may read stream objects
* which have been added as MIME bodies. The caller must close the
* stream object. */
gpg_error_t
mime_maker_get_part
(
mime_maker_t
ctx
,
unsigned
int
partid
,
estream_t
*
r_stream
)
{
gpg_error_t
err
;
part_t
part
;
estream_t
fp
;
*
r_stream
=
NULL
;
/* When the entire tree is requested, we make sure that all missing
* headers are applied. We don't do that if only a part is
* requested because the additional headers (like Date:) will only
* be added to part 0 headers anyway. */
if
(
!
partid
)
{
err
=
add_missing_headers
(
ctx
);
if
(
err
)
return
err
;
part
=
ctx
->
mail
;
}
else
part
=
find_part
(
ctx
->
mail
,
partid
);
/* For now we use a memory stream object; however it would also be
* possible to create an object created on the fly while the caller
* is reading the returned stream. */
fp
=
es_fopenmem
(
0
,
"w+b"
);
if
(
!
fp
)
return
gpg_error_from_syserror
();
ctx
->
outfp
=
fp
;
err
=
write_tree
(
ctx
,
NULL
,
part
);
ctx
->
outfp
=
NULL
;
if
(
!
err
)
{
es_rewind
(
fp
);
*
r_stream
=
fp
;
}
else
es_fclose
(
fp
);
return
err
;
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Thu, Feb 26, 7:21 PM (4 h, 27 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
70/e6/c3c5d959385f0d37fe9484ef932a
Attached To
rG GnuPG
Event Timeline
Log In to Comment