d056cc17 |
/* |
c442ca9c |
* Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
* Copyright (C) 2007-2013 Sourcefire, Inc. |
2023340a |
* |
6ff4e486 |
* Authors: Nigel Horne, Török Edvin
*
* Also based on Matt Olney's pdf parser in snort-nrt. |
d056cc17 |
*
* This program is free software; you can redistribute it and/or modify |
2023340a |
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. |
d056cc17 |
*
* This program 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, write to the Free Software |
2023340a |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA. |
1eceda0e |
*
* TODO: Embedded fonts
* TODO: Predictor image handling |
d056cc17 |
*/
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif
|
240d3307 |
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h> |
511a59c7 |
#include <errno.h> |
ed6446ff |
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif |
9443ec4a |
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif |
240d3307 |
#include <zlib.h>
|
e746f010 |
#if HAVE_ICONV |
063f0d25 |
#include <iconv.h> |
e746f010 |
#endif |
063f0d25 |
|
45e60c0c |
#ifdef _WIN32
#include <stdint.h>
#endif
|
ed6446ff |
#include "clamav.h"
#include "others.h" |
654c0b96 |
#include "pdf.h" |
7aad5a3b |
#include "pdfdecode.h" |
a5afcb67 |
#include "scanners.h" |
747c2055 |
#include "fmap.h" |
f461d74f |
#include "str.h" |
dc200c6b |
#include "bytecode.h"
#include "bytecode_api.h" |
7606789f |
#include "arc4.h" |
374be101 |
#include "rijndael.h" |
7719760b |
#include "textnorm.h" |
5f31c9b4 |
#include "conv.h" |
ebcca55f |
#include "json_api.h" |
4956690d |
|
1eceda0e |
#ifdef CL_DEBUG |
5cd3f734 |
/*#define SAVE_TMP
*Save the file being worked on in tmp */ |
1eceda0e |
#endif
|
c1a785c4 |
struct pdf_struct;
|
6e33139f |
static int asciihexdecode(const char *buf, off_t len, char *output); |
5aad11ce |
static int ascii85decode(const char *buf, off_t len, unsigned char *output); |
bce73fe9 |
static const char *pdf_nextlinestart(const char *ptr, size_t len); |
ef8219b8 |
static const char *pdf_nextobject(const char *ptr, size_t len); |
da653b74 |
|
ebcca55f |
/* PDF statistics callbacks and related */ |
224d1c4d |
struct pdfname_action; |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
ebcca55f |
static void pdf_export_json(struct pdf_struct *);
|
224d1c4d |
static void ASCIIHexDecode_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void ASCII85Decode_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void EmbeddedFile_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void FlateDecode_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Image_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void LZWDecode_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void RunLengthDecode_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void CCITTFaxDecode_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void JBIG2Decode_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void DCTDecode_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void JPXDecode_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Crypt_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Standard_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Sig_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void JavaScript_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void OpenAction_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Launch_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Page_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Author_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Creator_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Producer_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void CreationDate_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void ModificationDate_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Title_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Subject_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Keywords_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Pages_cb(struct pdf_struct *, struct pdf_obj *, struct pdfname_action *);
static void Colors_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act); |
09ff1409 |
static void RichMedia_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act);
static void AcroForm_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act);
static void XFA_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act); |
63803da5 |
#endif |
ebcca55f |
/* End PDF statistics callbacks and related */ |
49bc4992 |
|
89d5207b |
static int pdf_readint(const char *q0, int len, const char *key);
static const char *pdf_getdict(const char *q0, int* len, const char *key);
static char *pdf_readval(const char *q, int len, const char *key);
static char *pdf_readstring(const char *q0, int len, const char *key, unsigned *slen, const char **qend, int noescape);
|
e7a27135 |
static int xrefCheck(const char *xref, const char *eof)
{
const char *q; |
15a8a022 |
|
5aad11ce |
while (xref < eof && (*xref == ' ' || *xref == '\n' || *xref == '\r')) |
15a8a022 |
xref++;
|
e7a27135 |
if (xref + 4 >= eof) |
15a8a022 |
return -1;
|
e010ed37 |
if (!memcmp(xref, "xref", strlen("xref"))) { |
15a8a022 |
cli_dbgmsg("cli_pdf: found xref\n");
return 0; |
e7a27135 |
} |
15a8a022 |
|
e7a27135 |
/* could be xref stream */
for (q=xref; q+5 < eof; q++) { |
e010ed37 |
if (!memcmp(q,"/XRef", strlen("/XRef"))) { |
15a8a022 |
cli_dbgmsg("cli_pdf: found /XRef\n");
return 0;
} |
e7a27135 |
} |
15a8a022 |
|
e7a27135 |
return -1;
}
|
4956690d |
/* define this to be noisy about things that we can't parse properly */ |
87a6cf95 |
#undef NOISY |
4956690d |
#ifdef NOISY
#define noisy_msg(pdf, ...) cli_infomsg(pdf->ctx, __VA_ARGS__) |
e09d8843 |
#define noisy_warnmsg(...) cli_warnmsg(__VA_ARGS__) |
4956690d |
#else |
e09d8843 |
#define noisy_msg(pdf, ...)
#define noisy_warnmsg(...) |
4956690d |
#endif
|
89d5207b |
/**
* @brief Searching BACKwards, find the next character that is not a whitespace. |
e010ed37 |
* |
89d5207b |
* @param q Index to start from (at the end of the search space) |
e010ed37 |
* @param start Beginning of the search space.
* |
89d5207b |
* @return const char* Address of the final non-whitespace character OR the same address as the start.
*/ |
e7a27135 |
static const char *findNextNonWSBack(const char *q, const char *start)
{ |
15a8a022 |
while (q > start && (*q == 0 || *q == 9 || *q == 0xa || *q == 0xc || *q == 0xd || *q == 0x20))
q--;
|
e7a27135 |
return q;
}
|
89d5207b |
/**
* @brief Searching FORwards, find the next character that is not a whitespace. |
e010ed37 |
* |
89d5207b |
* @param q Index to start from (at the end of the search space) |
e010ed37 |
* @param start Beginning of the search space.
* |
89d5207b |
* @return const char* Address of the final non-whitespace character OR the same address as the start.
*/
static const char *findNextNonWS(const char *q, const char *end)
{
while (q < end && (*q == 0 || *q == 9 || *q == 0xa || *q == 0xc || *q == 0xd || *q == 0x20))
q++;
return q;
}
/**
* @brief Find bounds of stream. |
e010ed37 |
* |
89d5207b |
* PDF streams are prefixed with "stream" and suffixed with "endstream".
* Return value indicates success or failure. |
e010ed37 |
* |
89d5207b |
* @param start start address of search space. |
e010ed37 |
* @param size size of search space |
89d5207b |
* @param[out] stream output param, address of start of stream data |
e010ed37 |
* @param[out] stream_size output param, size of stream data |
89d5207b |
* @param newline_hack hack to support newlines that are \r\n, and not just \n or just \r. |
e010ed37 |
*
* @return cl_error_t CL_SUCCESS if stream bounds were found.
* @return cl_error_t CL_BREAK if stream bounds could not be found.
* @return cl_error_t CL_EFORMAT if stream start was found, but not end. (truncated)
* @return cl_error_t CL_EARG if invalid args were provided. |
89d5207b |
*/ |
e010ed37 |
static cl_error_t find_stream_bounds(
const char *start,
size_t size,
const char **stream,
size_t *stream_size, |
89d5207b |
int newline_hack) |
3643f3d2 |
{ |
e010ed37 |
cl_error_t status = CL_BREAK;
const char *idx;
const char *stream_begin;
const char *endstream_begin;
size_t bytesleft = size;
if ((NULL == start) || (0 == bytesleft) || (NULL == stream) || (NULL == stream_size)) {
status = CL_EARG;
return status;
}
*stream = NULL;
*stream_size = 0; |
89d5207b |
/* Begin by finding the "stream" string that prefixes stream data. */ |
e010ed37 |
if ((stream_begin = cli_memstr(start, bytesleft, "stream", strlen("stream")))) {
idx = stream_begin + strlen("stream");
bytesleft -= idx - start; |
15a8a022 |
if (bytesleft < 0) |
e010ed37 |
goto done; |
15a8a022 |
|
89d5207b |
/* Skip any new line charcters. */ |
e010ed37 |
if (bytesleft >= 2 && idx[0] == '\xd' && idx[1] == '\xa') {
idx += 2;
if (newline_hack && (bytesleft > 2) && idx[0] == '\xa')
idx++;
} else if (bytesleft && idx[0] == '\xa') {
idx++; |
15a8a022 |
}
|
e010ed37 |
/* Pass back start of the stream data. */
*stream = idx; |
89d5207b |
|
e010ed37 |
bytesleft = size - (idx - start);
if (bytesleft <= 0)
goto done; |
15a8a022 |
|
e010ed37 |
/* Now find the "endstream" string that suffixes stream data. */
endstream_begin = cli_memstr(idx, bytesleft, "endstream", strlen("endstream"));
if (!endstream_begin) {
/* Couldn't find "endstream", but that's ok --
* -- we'll just count the rest of the provided buffer. */
cli_dbgmsg("find_stream_bounds: Truncated stream found!\n");
endstream_begin = start + size;
status = CL_EFORMAT; |
89d5207b |
} |
15a8a022 |
|
e010ed37 |
/* Pass back end of the stream data, as offset from start. */
*stream_size = endstream_begin - *stream; |
15a8a022 |
|
e010ed37 |
if (CL_EFORMAT != status)
status = CL_SUCCESS; |
3643f3d2 |
} |
15a8a022 |
|
e010ed37 |
done:
return status; |
3643f3d2 |
}
|
3955b361 |
/** |
e010ed37 |
* @brief Find the next *indirect* object in an object stream, adds it to our list of |
89d5207b |
* objects, and increments nobj. |
e010ed37 |
* |
89d5207b |
* Indirect objects in a stream DON'T begin with "obj" and end with "endobj".
* Instead, they have an obj ID and an offset from the first object to point you
* right at them. |
e010ed37 |
* |
89d5207b |
* If found, objstm->current will be updated to the next obj id. |
e010ed37 |
*
* All objects in an object stream are indirect and thus do not begin or start
* with "obj" or "endobj". Instead, the object stream takes the following |
89d5207b |
* format. |
e010ed37 |
* |
89d5207b |
* <dictionary describing stream> objstm content endobjstm |
e010ed37 |
* |
89d5207b |
* where content looks something like the following: |
e010ed37 |
* |
89d5207b |
* 15 0 16 3 17 46 (ab)<</IDS 8 0 R/JavaScript 27 0 R/URLS 9 0 R>><</Names[(Test)28 0 R]>> |
e010ed37 |
*
* In the above example, the literal string (ab) is indirect object # 15, and
* begins at offset 0 of the set of objects. The next object, # 16 begis at
* offset 3 is a dictionary. The final object is also a dictionary, beginning |
89d5207b |
* at offset 46. |
e010ed37 |
*
* @param pdf Pdf struct that keeps track of all information found in the PDF. |
89d5207b |
* @param objstm |
e010ed37 |
* |
89d5207b |
* @return CL_SUCCESS if success
* @return CL_EPARSE if parsing error
* @return CL_EMEM if error allocating memory
* @return CL_EARG if invalid arguments
*/
int pdf_findobj_in_objstm(struct pdf_struct *pdf, struct objstm_struct *objstm, struct pdf_obj **obj_found)
{
cl_error_t status = CL_EPARSE;
struct pdf_obj *obj = NULL; |
e010ed37 |
unsigned long objid = 0, objoff = 0; |
8ad8d01b |
long temp_long = 0; |
89d5207b |
const char *index = NULL;
size_t bytes_remaining = 0;
if (NULL == pdf || NULL == objstm) {
cli_warnmsg("pdf_findobj_in_objstm: invalid arguments\n");
return CL_EARG;
}
*obj_found = NULL;
index = objstm->streambuf + objstm->current_pair;
bytes_remaining = objstm->streambuf_len - objstm->current_pair;
obj = calloc(sizeof(struct pdf_obj), 1);
if (!obj) {
cli_warnmsg("pdf_findobj_in_objstm: out of memory finding objects in stream\n");
status = CL_EMEM;
goto done;
}
/* This object is in a stream, not in the regular map buffer. */
obj->objstm = objstm;
/* objstm->current_pair points directly to the obj id */ |
8ad8d01b |
if (CL_SUCCESS != cli_strntol_wrap(index, bytes_remaining, 0, 10, &temp_long)) { |
89d5207b |
/* Failed to find objid */
cli_dbgmsg("pdf_findobj_in_objstm: Failed to find objid for obj in object stream\n");
status = CL_EPARSE;
goto done; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("pdf_findobj_in_objstm: Encountered invalid negative objid (%ld).\n", temp_long);
status = CL_EPARSE;
goto done; |
89d5207b |
} |
8ad8d01b |
objid = (unsigned long)temp_long; |
89d5207b |
/* Find the obj offset that appears just after the obj id*/
while ((index < objstm->streambuf + objstm->streambuf_len) && isdigit(*index)) {
index++;
bytes_remaining--;
}
index = findNextNonWS(index, objstm->streambuf + objstm->first);
bytes_remaining = objstm->streambuf + objstm->streambuf_len - index;
|
8ad8d01b |
if (CL_SUCCESS != cli_strntol_wrap(index, bytes_remaining, 0, 10, &temp_long)) { |
89d5207b |
/* Failed to find obj offset */
cli_dbgmsg("pdf_findobj_in_objstm: Failed to find obj offset for obj in object stream\n");
status = CL_EPARSE;
goto done; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("pdf_findobj_in_objstm: Encountered invalid negative obj offset (%ld).\n", temp_long);
status = CL_EPARSE;
goto done; |
89d5207b |
} |
8ad8d01b |
objoff = (unsigned long)temp_long; |
89d5207b |
|
8965a5af |
if ((size_t)objstm->first + (size_t)objoff > objstm->streambuf_len) {
/* Alleged obj location is further than the length of the stream */
cli_dbgmsg("pdf_findobj_in_objstm: obj offset found is greater than the length of the stream.\n");
status = CL_EPARSE;
goto done;
}
|
89d5207b |
objstm->current = objstm->first + objoff;
obj->id = (objid << 8) | (0 & 0xff);
obj->start = objstm->current;
obj->flags = 0;
objstm->nobjs_found++;
while ((index < objstm->streambuf + objstm->streambuf_len) && isdigit(*index)) {
index++;
bytes_remaining--;
}
objstm->current_pair = (uint32_t)(findNextNonWS(index, objstm->streambuf + objstm->first) - objstm->streambuf);
/* Update current_pair, if there are more */
if ((objstm->nobjs_found < objstm->n) &&
(index < objstm->streambuf + objstm->streambuf_len))
{
unsigned long next_objid = 0, next_objoff = 0;
|
e010ed37 |
/*
* While we're at it, |
89d5207b |
* lets record the size as running up to the next object offset. |
e010ed37 |
* |
89d5207b |
* To do so, we will need to parse the next obj pair.
*/
/* objstm->current_pair points directly to the obj id */
index = objstm->streambuf + objstm->current_pair;
bytes_remaining = objstm->streambuf + objstm->streambuf_len - index;
|
8ad8d01b |
if (CL_SUCCESS != cli_strntol_wrap(index, bytes_remaining, 0, 10, &temp_long)) { |
89d5207b |
/* Failed to find objid for next obj */
cli_dbgmsg("pdf_findobj_in_objstm: Failed to find next objid for obj in object stream though there should be {%u} more.\n", objstm->n - objstm->nobjs_found);
status = CL_EPARSE;
goto done; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("pdf_findobj_in_objstm: Encountered invalid negative objid (%ld).\n", temp_long);
status = CL_EPARSE;
goto done; |
89d5207b |
} |
8ad8d01b |
next_objid = (unsigned long)temp_long; |
89d5207b |
/* Find the obj offset that appears just after the obj id*/
while ((index < objstm->streambuf + objstm->streambuf_len) && isdigit(*index)) {
index++;
bytes_remaining--;
}
index = findNextNonWS(index, objstm->streambuf + objstm->first);
bytes_remaining = objstm->streambuf + objstm->streambuf_len - index;
|
8ad8d01b |
if (CL_SUCCESS != cli_strntol_wrap(index, bytes_remaining, 0, 10, &temp_long)) { |
89d5207b |
/* Failed to find obj offset for next obj */
cli_dbgmsg("pdf_findobj_in_objstm: Failed to find next obj offset for obj in object stream though there should be {%u} more.\n", objstm->n - objstm->nobjs_found);
status = CL_EPARSE;
goto done; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("pdf_findobj_in_objstm: Encountered invalid negative obj offset (%ld).\n", temp_long);
status = CL_EPARSE;
goto done; |
89d5207b |
} |
8ad8d01b |
next_objoff = (unsigned long)temp_long;
if (next_objoff <= objoff) { |
d77b8ae0 |
/* Failed to find obj offset for next obj */
cli_dbgmsg("pdf_findobj_in_objstm: Found next obj offset for obj in object stream but it's less than or equal to the current one!\n");
status = CL_EPARSE;
goto done;
}
else if (objstm->first + next_objoff > objstm->streambuf_len) {
/* Failed to find obj offset for next obj */
cli_dbgmsg("pdf_findobj_in_objstm: Found next obj offset for obj in object stream but it's further out than the size of the stream!\n");
status = CL_EPARSE;
goto done;
} |
89d5207b |
|
d77b8ae0 |
obj->size = next_objoff - objoff; |
e010ed37 |
}
else |
89d5207b |
{
/*
* Should be no more objects. We should verify. |
e010ed37 |
* |
89d5207b |
* Either way... |
e010ed37 |
* obj->size should be the rest of the buffer. |
89d5207b |
*/
if (objstm->nobjs_found < objstm->n) {
cli_warnmsg("pdf_findobj_in_objstm: Fewer objects found in object stream than expected!\n");
}
obj->size = objstm->streambuf_len - obj->start;
}
/* Success! Add the object to the list of all objects found. */
pdf->nobjs++;
pdf->objs = cli_realloc2(pdf->objs, sizeof(struct pdf_obj*) * pdf->nobjs);
if (!pdf->objs) {
cli_warnmsg("pdf_findobj_in_objstm: out of memory finding objects in stream\n");
status = CL_EMEM;
goto done;
}
pdf->objs[pdf->nobjs-1] = obj;
*obj_found = obj;
status = CL_SUCCESS;
done:
if (CL_SUCCESS != status) {
if (NULL != obj) {
free(obj);
}
}
return status;
}
/**
* @brief Find the next *indirect* object. |
e010ed37 |
*
* Indirect objects located outside of an object stream are prefaced with:
* <objid> <genid> obj
*
* Each of the above are separated by whitespace of some sort.
*
* Indirect objects are postfaced with:
* endobj
*
* The specification does not say if whitespace is required before or after "endobj".
*
* Identify truncated objects.
* |
89d5207b |
* If found, pdf->offset will be updated to just after the "endobj".
* If truncated, pdf->offset will == pdf->size.
* If not found, pdf->offset will not be updated. |
e010ed37 |
*
* @param pdf Pdf context struct that keeps track of all information found in the PDF.
* |
89d5207b |
* @return CL_SUCCESS if success
* @return CL_BREAK if no more objects
* @return CL_EPARSE if parsing error
* @return CL_EMEM if error allocating memory |
3955b361 |
*/ |
89d5207b |
cl_error_t pdf_findobj(struct pdf_struct *pdf) |
e7a27135 |
{ |
89d5207b |
cl_error_t status = CL_EPARSE; |
e010ed37 |
const char *start, *idx, *genid_search_index, *objid_search_index;
const char *obj_begin = NULL, *obj_end = NULL;
const char *endobj_begin = NULL, *endobj_end = NULL;
|
89d5207b |
struct pdf_obj *obj = NULL; |
e010ed37 |
size_t bytesleft; |
bf6e777f |
unsigned long genid, objid; |
8ad8d01b |
long temp_long; |
e7a27135 |
pdf->nobjs++; |
89d5207b |
pdf->objs = cli_realloc2(pdf->objs, sizeof(struct pdf_obj*) * pdf->nobjs); |
e7a27135 |
if (!pdf->objs) { |
89d5207b |
status = CL_EMEM;
goto done;
}
obj = malloc(sizeof(struct pdf_obj));
if (!obj) {
status = CL_EMEM;
goto done; |
e7a27135 |
} |
89d5207b |
pdf->objs[pdf->nobjs-1] = obj; |
15a8a022 |
|
ab564992 |
memset(obj, 0, sizeof(*obj)); |
89d5207b |
start = pdf->map + pdf->offset; |
e7a27135 |
bytesleft = pdf->size - pdf->offset; |
89d5207b |
|
e010ed37 |
/*
* Start by searching for "obj"
*/
idx = start + 1;
while (bytesleft > 1 + strlen("obj")) {
/* `- 1` accounts for size of white space before obj */
idx = cli_memstr(idx, bytesleft - 1, "obj", strlen("obj"));
if (NULL == idx) {
status = CL_BREAK;
goto done; /* No more objs. */ |
89d5207b |
} |
15a8a022 |
|
e010ed37 |
/* verify that the word has a whitespace before it, and is not the end of
* a previous word */
idx--;
bytesleft = (pdf->size - pdf->offset) - (size_t)(idx - start); |
89d5207b |
|
e010ed37 |
if (*idx != 0 && *idx != 9 && *idx != 0xa && *idx != 0xc && *idx != 0xd && *idx != 0x20) {
/* This instance of "obj" appears to be part of a longer string. |
89d5207b |
* Skip it, and keep searching for an object. */ |
e010ed37 |
idx += 1 + strlen("obj");
bytesleft -= 1 + strlen("obj"); |
15a8a022 |
continue;
}
|
e010ed37 |
/* Found the beginning of the word */
obj_begin = idx;
obj_end = idx + 1 + strlen("obj"); |
15a8a022 |
|
e010ed37 |
break; |
89d5207b |
}
|
e010ed37 |
if ((NULL == obj_begin) || (NULL == obj_end)) {
status = CL_BREAK;
goto done; /* No more objs. */
} |
bdbae203 |
|
89d5207b |
/* Find the generation id (genid) that appears before the "obj" */ |
e010ed37 |
genid_search_index = findNextNonWSBack(obj_begin - 1, start);
while (genid_search_index > start && isdigit(*genid_search_index))
genid_search_index--; |
15a8a022 |
|
e010ed37 |
if (CL_SUCCESS != cli_strntol_wrap(genid_search_index, (size_t)((obj_begin) - genid_search_index), 0, 10, &temp_long)) { |
89d5207b |
cli_dbgmsg("pdf_findobj: Failed to parse object genid (# objects found: %u)\n", pdf->nobjs); |
3955b361 |
/* Failed to parse, probably not a real object. Skip past the "obj" thing, and continue. */ |
e010ed37 |
pdf->offset = obj_end - pdf->map; |
89d5207b |
status = CL_EPARSE;
goto done; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("pdf_findobj: Encountered invalid negative obj genid (%ld).\n", temp_long); |
e010ed37 |
pdf->offset = obj_end - pdf->map; |
8ad8d01b |
status = CL_EPARSE;
goto done; |
bf6e777f |
} |
8ad8d01b |
genid = (unsigned long)temp_long; |
89d5207b |
|
e010ed37 |
/* Find the object id (objid) that appears before the genid */
objid_search_index = findNextNonWSBack(genid_search_index - 1, start);
while (objid_search_index > start && isdigit(*objid_search_index))
objid_search_index--; |
15a8a022 |
|
e010ed37 |
if (CL_SUCCESS != cli_strntol_wrap(objid_search_index, (size_t)((genid_search_index) - objid_search_index), 0, 10, &temp_long)) { |
bf6e777f |
/* |
e010ed37 |
* Edge case:
*
* PDFs with multiple revisions will have %%EOF before the end of the file,
* followed by the next revision of the PDF, which will probably be an immediate objid.
*
* Example:
* %%EOF1 1 obj <blah> endobj
*
* If this is the case, we can detect it and continue parsing after the %%EOF. |
bf6e777f |
*/ |
e010ed37 |
if (objid_search_index - strlen("\%\%EO") > start) {
const char* lastfile = objid_search_index - strlen("\%\%EO"); |
bf6e777f |
if (0 != strncmp(lastfile, "\%\%EOF", 5)) { |
3955b361 |
/* Nope, wasn't %%EOF */ |
89d5207b |
cli_dbgmsg("pdf_findobj: Failed to parse object objid (# objects found: %u)\n", pdf->nobjs); |
3955b361 |
/* Skip past the "obj" thing, and continue. */ |
e010ed37 |
pdf->offset = obj_end - pdf->map;
status = CL_EPARSE; |
89d5207b |
goto done; |
bf6e777f |
} |
e010ed37 |
/* Yup, Looks, like the file continues after %%EOF. |
bf6e777f |
* Probably another revision. Keep parsing... */ |
e010ed37 |
objid_search_index++;
cli_dbgmsg("pdf_findobj: \%\%EOF detected before end of file, at offset: %zu\n", (size_t)(objid_search_index - pdf->map)); |
bf6e777f |
} else {
/* Failed parsing at the very beginning */ |
89d5207b |
cli_dbgmsg("pdf_findobj: Failed to parse object objid (# objects found: %u)\n", pdf->nobjs); |
3955b361 |
/* Probably not a real object. Skip past the "obj" thing, and continue. */ |
e010ed37 |
pdf->offset = obj_end - pdf->map;
status = CL_EPARSE; |
89d5207b |
goto done; |
bf6e777f |
}
/* Try again, with offset slightly adjusted */ |
e010ed37 |
if (CL_SUCCESS != cli_strntol_wrap(objid_search_index, (size_t)((genid_search_index - 1) - objid_search_index), 0, 10, &temp_long)) { |
89d5207b |
cli_dbgmsg("pdf_findobj: Failed to parse object objid (# objects found: %u)\n", pdf->nobjs); |
3955b361 |
/* Still failed... Probably not a real object. Skip past the "obj" thing, and continue. */ |
e010ed37 |
pdf->offset = obj_end - pdf->map;
status = CL_EPARSE; |
89d5207b |
goto done; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("pdf_findobj: Encountered invalid negative objid (%ld).\n", temp_long); |
e010ed37 |
pdf->offset = obj_end - pdf->map; |
8ad8d01b |
status = CL_EPARSE;
goto done; |
bf6e777f |
} |
8ad8d01b |
|
89d5207b |
cli_dbgmsg("pdf_findobj: There appears to be an additional revision. Continuing to parse...\n"); |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("pdf_findobj: Encountered invalid negative objid (%ld).\n", temp_long); |
e010ed37 |
pdf->offset = obj_end - pdf->map; |
8ad8d01b |
status = CL_EPARSE;
goto done; |
bf6e777f |
} |
8ad8d01b |
objid = (unsigned long)temp_long; |
89d5207b |
obj->id = (objid << 8) | (genid & 0xff); |
e010ed37 |
obj->start = obj_end - pdf->map; /* obj start begins just after the "obj" string */ |
e7a27135 |
obj->flags = 0; |
89d5207b |
|
e010ed37 |
/*
* We now have the objid, genid, and object start.
* Find the object end ("endobj").
*/
/* `- 1` accounts for size of white space before obj */
endobj_begin = cli_memstr(obj_end, pdf->map + pdf->size - obj_end, "endobj", strlen("endobj"));
if (NULL == endobj_begin) {
/* No end to object.
* PDF appears to be malformed or truncated.
* Will record the object size as going ot the end of the file.
* Will record that the object is truncated.
* Will position the pdf offset to the end of the PDF.
* The next iteration of this function will find no more objects. */
obj->flags |= 1 << OBJ_TRUNCATED;
obj->size = (pdf->map + pdf->size) - obj_end;
pdf->offset = pdf->size;
/* Truncated "object" found! */
status = CL_SUCCESS;
goto done; |
e7a27135 |
} |
e010ed37 |
endobj_end = endobj_begin + strlen("endobj"); |
15a8a022 |
|
e010ed37 |
/* Size of the object goes from "obj" <-> "endobject". */
obj->size = endobj_begin - obj_end;
pdf->offset = endobj_end - pdf->map; |
15a8a022 |
|
e010ed37 |
/*
* Object found!
*/ |
89d5207b |
status = CL_SUCCESS; /* truncated file, no end to obj. */
done:
if (status == CL_SUCCESS) { |
e010ed37 |
cli_dbgmsg("pdf_findobj: found %d %d obj @%lld, size: %zu bytes.\n", obj->id >> 8, obj->id&0xff, (long long)(obj->start + pdf->startoff), obj->size); |
89d5207b |
}
else
{
/* Remove the unused obj reference from our list of objects found */
/* No need to realloc pdf->objs back down. It won't leak. */
pdf->objs[pdf->nobjs-1] = NULL;
pdf->nobjs--;
/* Free up the obj struct. */
if (NULL != obj)
free(obj); |
e010ed37 |
if(status == CL_BREAK) {
cli_dbgmsg("pdf_findobj: No more objects (# objects found: %u)\n", pdf->nobjs);
} else if(status == CL_EMEM) {
cli_warnmsg("pdf_findobj: Error allocating memory (# objects found: %u)\n", pdf->nobjs);
} else {
cli_dbgmsg("pdf_findobj: Unexpected status code %d.\n", status);
} |
89d5207b |
}
|
e010ed37 |
return status; |
e7a27135 |
}
|
e09d8843 |
static size_t filter_writen(struct pdf_struct *pdf, struct pdf_obj *obj, int fout, const char *buf, size_t len, size_t *sum) |
3643f3d2 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
|
e09d8843 |
if (cli_checklimits("pdf", pdf->ctx, (unsigned long)*sum, 0, 0)) /* TODO: May truncate for large values on 64-bit platforms */ |
15a8a022 |
return len; /* pretend it was a successful write to suppress CL_EWRITE */
|
3643f3d2 |
*sum += len; |
15a8a022 |
|
e09d8843 |
return cli_writen(fout, buf, (unsigned int)len); |
3643f3d2 |
}
|
1d0cdc67 |
void pdfobj_flag(struct pdf_struct *pdf, struct pdf_obj *obj, enum pdf_flag flag) |
eb270d5a |
{
const char *s= "";
pdf->flags |= 1 << flag;
if (!cli_debug_flag) |
15a8a022 |
return;
|
eb270d5a |
switch (flag) { |
15a8a022 |
case UNTERMINATED_OBJ_DICT:
s = "dictionary not terminated";
break;
case ESCAPED_COMMON_PDFNAME:
/* like /JavaScript */
s = "escaped common pdfname";
break;
case BAD_STREAM_FILTERS:
s = "duplicate stream filters";
break;
case BAD_PDF_VERSION:
s = "bad pdf version";
break;
case BAD_PDF_HEADERPOS:
s = "bad pdf header position";
break;
case BAD_PDF_TRAILER:
s = "bad pdf trailer";
break;
case BAD_PDF_TOOMANYOBJS:
s = "too many pdf objs";
break;
case BAD_FLATE:
s = "bad deflate stream";
break;
case BAD_FLATESTART:
s = "bad deflate stream start";
break;
case BAD_STREAMSTART:
s = "bad stream start";
break;
case UNKNOWN_FILTER:
s = "unknown filter used";
break;
case BAD_ASCIIDECODE:
s = "bad ASCII decode";
break;
case HEX_JAVASCRIPT:
s = "hex javascript";
break;
case BAD_INDOBJ:
s = "referencing nonexistent obj";
break;
case HAS_OPENACTION:
s = "has /OpenAction";
break;
case HAS_LAUNCHACTION:
s = "has /LaunchAction";
break;
case BAD_STREAMLEN:
s = "bad /Length, too small";
break;
case ENCRYPTED_PDF:
s = "PDF is encrypted";
break;
case LINEARIZED_PDF:
s = "linearized PDF";
break;
case MANY_FILTERS:
s = "more than 2 filters per obj";
break;
case DECRYPTABLE_PDF:
s = "decryptable PDF";
break; |
eb270d5a |
} |
15a8a022 |
|
89d5207b |
cli_dbgmsg("pdfobj_flag: %s flagged in object %u %u\n", s, obj->id>>8, obj->id&0xff); |
eb270d5a |
}
|
930b9395 |
struct pdf_obj *find_obj(struct pdf_struct *pdf, struct pdf_obj *obj, uint32_t objid) |
3643f3d2 |
{ |
1412b807 |
uint32_t j;
uint32_t i; |
5aad11ce |
/* search starting at previous obj (if exists) */ |
89d5207b |
for (i = 0; i < pdf->nobjs; i++) {
if (pdf->objs[i] == obj)
break;
} |
15a8a022 |
|
89d5207b |
for (j = i; j < pdf->nobjs; j++) {
obj = pdf->objs[j]; |
15a8a022 |
if (obj->id == objid)
return obj; |
3643f3d2 |
} |
15a8a022 |
|
3643f3d2 |
/* restart search from beginning if not found */ |
89d5207b |
for (j = 0; j < i; j++) {
obj = pdf->objs[j]; |
15a8a022 |
if (obj->id == objid)
return obj; |
3643f3d2 |
} |
15a8a022 |
|
3643f3d2 |
return NULL;
}
|
89d5207b |
/**
* @brief Find and interpret the "/Length" dictionary key value. |
e010ed37 |
* |
89d5207b |
* The value may be: |
e010ed37 |
* - a direct object (i.e. just a number) |
89d5207b |
* - an indirect object, where the value is somewhere else in the document and we have to look it up.
* indirect objects are referenced using an object id (objid), generation id (genid) genid, and the letter 'R'. |
e010ed37 |
* |
89d5207b |
* Example dictionary with a single key "/Length" that relies direct object for the value. |
e010ed37 |
* |
89d5207b |
* 1 0 obj
* << /Length 534
* /Filter [ /ASCII85Decode /LZWDecode ]
* >>
* stream
* J..)6T`?p&<!J9%_[umg"B7/Z7KNXbN'S+,*Q/&"OLT'FLIDK#!n`$"<Atdi`\Vn%b%)&'cA*VnK\CJY(sF>c!Jnl@
* RM]WM;jjH6Gnc75idkL5]+cPZKEBPWdR>FF(kj1_R%W_d&/jS!;iuad7h?[L-F$+]]0A3Ck*$I0KZ?;<)CJtqi65Xb
* Vc3\n5ua:Q/=0$W<#N3U;H,MQKqfg1?:lUpR;6oN[C2E4ZNr8Udn.'p+?#X+1>0Kuk$bCDF/(3fL5]Oq)^kJZ!C2H1
* 'TO]Rl?Q:&'<5&iP!$Rq;BXRecDN[IJB`,)o8XJOSJ9sDS]hQ;Rj@!ND)bD_q&C\g:inYC%)&u#:u,M6Bm%IY!Kb1+
* ":aAa'S`ViJglLb8<W9k6Yl\\0McJQkDeLWdPN?9A'jX*al>iG1p&i;eVoK&juJHs9%;Xomop"5KatWRT"JQ#qYuL,
* JD?M$0QP)lKn06l1apKDC@\qJ4B!!(5m+j.7F790m(Vj88l8Q:_CZ(Gm1%X\N1&u!FKHMB~>
* endstream
* endobj |
e010ed37 |
* |
89d5207b |
* Example dictionary with a single key "/Length" that relies on an indirect object for the value. |
e010ed37 |
* |
89d5207b |
* 7 0 obj
* << /Length 8 0 R >> % An indirect reference to object 8, with generation id 0.
* stream
* BT
* /F1 12 Tf
* 72 712 Td
* ( A stream with an indirect length ) Tj
* ET
* endstream
* endobj |
e010ed37 |
* |
89d5207b |
* 8 0 obj
* 77 % The length of the preceding stream
* endobj |
e010ed37 |
* |
89d5207b |
* @param pdf Pdf context structure.
* @param obj Pdf object context structure.
* @param start Pointer start of the dictionary string.
* @param len Remaining length of the dictioary string in bytes.
* @return size_t Unsigned integer value of the "/Length" key
*/
static size_t find_length(struct pdf_struct *pdf, struct pdf_obj *obj, const char *dict_start, size_t dict_len) |
3643f3d2 |
{ |
89d5207b |
size_t length = 0;
const char *obj_start = dict_start;
size_t bytes_remaining = dict_len; |
8ad8d01b |
long temp_long = 0; |
89d5207b |
const char *index; |
15a8a022 |
|
89d5207b |
if (bytes_remaining < 8) { |
15a8a022 |
return 0; |
89d5207b |
} |
15a8a022 |
|
89d5207b |
/*
* Find the "/Length" dictionary key
*/
index = cli_memstr(obj_start, bytes_remaining, "/Length", 7);
if (!index) |
15a8a022 |
return 0;
|
89d5207b |
if (bytes_remaining < 1) { |
bf6e777f |
return 0;
}
|
89d5207b |
/* Step the index into the "/Length" string. */
index++;
bytes_remaining -= index - obj_start;
/* Find the start of the next direct or indirect object.
* pdf_nextobject() assumes we started searching from within a previous object */
obj_start = pdf_nextobject(index, bytes_remaining);
if (!obj_start)
return 0;
|
e010ed37 |
if (bytes_remaining < (size_t)(obj_start - index)) { |
89d5207b |
return 0;
}
bytes_remaining -= obj_start - index;
index = obj_start; |
e010ed37 |
|
89d5207b |
/* Read the value. This could either be the direct length value,
or the object id of the indirect object that has the length */ |
8ad8d01b |
if (CL_SUCCESS != cli_strntol_wrap(index, bytes_remaining, 0, 10, &temp_long)) {
cli_dbgmsg("find_length: failed to parse object length or objid\n");
return 0;
} else if (temp_long < 0) {
cli_dbgmsg("find_length: Encountered invalid negative object length or objid (%ld).\n", temp_long); |
89d5207b |
return 0;
} |
8ad8d01b |
length = (size_t)temp_long; /* length or maybe object id */ |
89d5207b |
|
e010ed37 |
/*
* Keep parsing, skipping past the first integer that might have been what we wanted.
* If it's an indirect object, we'll find a Generation ID followed by the letter 'R'
* I.e. something like " 0 R" |
89d5207b |
*/
while ((bytes_remaining > 0) && isdigit(*index)) {
index++;
bytes_remaining--; |
53cbdee3 |
} |
15a8a022 |
|
89d5207b |
if ((bytes_remaining > 0) && (*index == ' ')) { |
bf6e777f |
unsigned long genid; |
89d5207b |
index++;
bytes_remaining--;
|
8ad8d01b |
if (CL_SUCCESS != cli_strntol_wrap(index, bytes_remaining, 0, 10, &temp_long)) { |
89d5207b |
cli_dbgmsg("find_length: failed to parse object genid\n"); |
bf6e777f |
return 0; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("find_length: Encountered invalid negative object genid (%ld).\n", temp_long);
return 0; |
bf6e777f |
} |
8ad8d01b |
genid = (unsigned long)temp_long; |
15a8a022 |
|
89d5207b |
while((bytes_remaining > 0) && isdigit(*index)) {
index++;
bytes_remaining--;
}
if (bytes_remaining < 2) {
return 0; |
53cbdee3 |
} |
15a8a022 |
|
89d5207b |
if (index[0] == ' ' && index[1] == 'R') { |
e010ed37 |
/*
* Ok so we found a genid and that 'R'. Which means that first value |
89d5207b |
* was actually the objid.
* We can look up the indirect object using this information.
*/
unsigned long objid = length;
const char* indirect_obj_start = NULL; |
e010ed37 |
|
89d5207b |
cli_dbgmsg("find_length: length is in indirect object %lu %lu\n", objid, genid); |
15a8a022 |
obj = find_obj(pdf, obj, (length << 8) | (genid&0xff));
if (!obj) { |
89d5207b |
cli_dbgmsg("find_length: indirect object not found\n"); |
15a8a022 |
return 0;
}
|
89d5207b |
indirect_obj_start = pdf->map + obj->start;
bytes_remaining = pdf->size - obj->start; |
e010ed37 |
|
89d5207b |
/* Ok so we found the indirect object, lets read the value. */
index = pdf_nextobject(indirect_obj_start, bytes_remaining);
if (!index) {
cli_dbgmsg("find_length: next object not found\n"); |
15a8a022 |
return 0;
} |
e010ed37 |
if (bytes_remaining < (size_t)(index - indirect_obj_start)) { |
89d5207b |
return 0;
}
bytes_remaining -= index - indirect_obj_start; |
15a8a022 |
|
8ad8d01b |
/* Found the value, so lets parse it as a long, but prohibit negative lengths. */
if (CL_SUCCESS != cli_strntol_wrap(index, bytes_remaining, 0, 10, &temp_long)) { |
89d5207b |
cli_dbgmsg("find_length: failed to parse object length from indirect object\n"); |
bf6e777f |
return 0; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("find_length: Encountered invalid negative obj length (%ld).\n", temp_long);
return 0; |
bf6e777f |
} |
8ad8d01b |
length = (size_t)temp_long; |
15a8a022 |
} |
3643f3d2 |
} |
15a8a022 |
|
a9d034ee |
/* limit length */ |
e010ed37 |
if ((size_t)(obj_start - pdf->map) + length + 5 > pdf->size) |
89d5207b |
length = pdf->size - (obj_start - pdf->map) - 5; |
15a8a022 |
|
3643f3d2 |
return length;
}
|
7719760b |
#define DUMP_MASK ((1 << OBJ_CONTENTS) | (1 << OBJ_FILTER_FLATE) | (1 << OBJ_FILTER_DCT) | (1 << OBJ_FILTER_AH) | (1 << OBJ_FILTER_A85) | (1 << OBJ_EMBEDDED_FILE) | (1 << OBJ_JAVASCRIPT) | (1 << OBJ_OPENACTION) | (1 << OBJ_LAUNCHACTION)) |
ab564992 |
|
15a8a022 |
static int run_pdf_hooks(struct pdf_struct *pdf, enum pdf_phase phase, int fd, int dumpid) |
dc200c6b |
{
int ret;
struct cli_bc_ctx *bc_ctx;
cli_ctx *ctx = pdf->ctx;
fmap_t *map;
|
cd94be7a |
UNUSEDPARAM(dumpid);
|
dc200c6b |
bc_ctx = cli_bytecode_context_alloc();
if (!bc_ctx) { |
89d5207b |
cli_errmsg("run_pdf_hooks: can't allocate memory for bc_ctx\n"); |
15a8a022 |
return CL_EMEM; |
dc200c6b |
}
map = *ctx->fmap;
if (fd != -1) { |
15a8a022 |
map = fmap(fd, 0, 0);
if (!map) { |
89d5207b |
cli_dbgmsg("run_pdf_hooks: can't mmap pdf extracted obj\n"); |
15a8a022 |
map = *ctx->fmap;
fd = -1;
} |
dc200c6b |
} |
15a8a022 |
cli_bytecode_context_setpdf(bc_ctx, phase, pdf->nobjs, pdf->objs, &pdf->flags, pdf->size, pdf->startoff); |
dc200c6b |
cli_bytecode_context_setctx(bc_ctx, ctx); |
6ad45a29 |
ret = cli_bytecode_runhook(ctx, ctx->engine, bc_ctx, BC_PDF, map); |
dc200c6b |
cli_bytecode_context_destroy(bc_ctx); |
15a8a022 |
if (fd != -1)
funmap(map);
|
dc200c6b |
return ret;
}
|
bbfad9ba |
static void dbg_printhex(const char *msg, const char *hex, unsigned len); |
cd94be7a |
|
e09d8843 |
static void aes_decrypt(const unsigned char *in, size_t *length, unsigned char *q, char *key, unsigned key_n, int has_iv) |
374be101 |
{
unsigned long rk[RKLENGTH(256)];
unsigned char iv[16]; |
e09d8843 |
size_t len = *length; |
374be101 |
unsigned char pad, i; |
21a33457 |
int nrounds; |
374be101 |
|
89d5207b |
cli_dbgmsg("aes_decrypt: key length: %d, data length: %zu\n", key_n, *length); |
374be101 |
if (key_n > 32) { |
89d5207b |
cli_dbgmsg("aes_decrypt: key length is %d!\n", key_n*8); |
15a8a022 |
return; |
374be101 |
} |
15a8a022 |
|
374be101 |
if (len < 32) { |
89d5207b |
cli_dbgmsg("aes_decrypt: len is <32: %zu\n", len);
noisy_warnmsg("aes_decrypt: len is <32: %zu\n", len); |
15a8a022 |
return; |
374be101 |
} |
15a8a022 |
|
bbfad9ba |
if (has_iv) { |
15a8a022 |
memcpy(iv, in, 16);
in += 16;
len -= 16;
} else {
memset(iv, 0, sizeof(iv));
} |
374be101 |
|
22ee81d0 |
cli_dbgmsg("aes_decrypt: Calling rijndaelSetupDecrypt\n"); |
cd94be7a |
nrounds = rijndaelSetupDecrypt(rk, (const unsigned char *)key, key_n*8); |
1cea6b05 |
if (!nrounds) { |
89d5207b |
cli_dbgmsg("aes_decrypt: nrounds = 0\n"); |
53cbdee3 |
return; |
1cea6b05 |
} |
22ee81d0 |
cli_dbgmsg("aes_decrypt: Beginning rijndaelDecrypt\n"); |
15a8a022 |
|
374be101 |
while (len >= 16) { |
15a8a022 |
unsigned i;
rijndaelDecrypt(rk, nrounds, in, q);
for (i=0;i<16;i++)
q[i] ^= iv[i];
memcpy(iv, in, 16);
q += 16;
in += 16;
len -= 16; |
374be101 |
} |
bbfad9ba |
if (has_iv) { |
15a8a022 |
len += 16;
pad = q[-1];
if (pad > 0x10) { |
89d5207b |
cli_dbgmsg("aes_decrypt: bad pad: %x (extra len: %zu)\n", pad, len-16);
noisy_warnmsg("aes_decrypt: bad pad: %x (extra len: %zu)\n", pad, len-16); |
15a8a022 |
*length -= len;
return;
}
q -= pad;
for (i=1;i<pad;i++) {
if (q[i] != pad) { |
89d5207b |
cli_dbgmsg("aes_decrypt: bad pad: %x != %x\n",q[i],pad);
noisy_warnmsg("aes_decrypt: bad pad: %x != %x\n",q[i],pad); |
15a8a022 |
*length -= len;
return;
}
}
len += pad; |
374be101 |
} |
15a8a022 |
|
374be101 |
*length -= len; |
15a8a022 |
|
89d5207b |
cli_dbgmsg("aes_decrypt: length is %zu\n", *length); |
374be101 |
}
|
e09d8843 |
char *decrypt_any(struct pdf_struct *pdf, uint32_t id, const char *in, size_t *length, enum enc_method enc_method) |
374be101 |
{
unsigned char *key, *q, result[16];
unsigned n;
struct arc4_state arc4;
|
4956690d |
if (!length || !*length || !in) { |
89d5207b |
noisy_warnmsg("decrypt_any: decrypt failed for obj %u %u\n", id>>8, id&0xff); |
15a8a022 |
return NULL; |
4956690d |
} |
15a8a022 |
|
374be101 |
n = pdf->keylen + 5; |
bcc68567 |
if (enc_method == ENC_AESV2) |
15a8a022 |
n += 4;
|
374be101 |
key = cli_malloc(n); |
4956690d |
if (!key) { |
15a8a022 |
noisy_warnmsg("decrypt_any: malloc failed\n");
return NULL; |
4956690d |
} |
374be101 |
memcpy(key, pdf->key, pdf->keylen);
q = key + pdf->keylen;
*q++ = id >> 8;
*q++ = id >> 16;
*q++ = id >> 24;
*q++ = id;
*q++ = 0; |
bcc68567 |
if (enc_method == ENC_AESV2) |
15a8a022 |
memcpy(q, "sAlT", 4);
|
b2e7c931 |
cl_hash_data("md5", key, n, result, NULL); |
bbfad9ba |
free(key);
|
374be101 |
n = pdf->keylen + 5;
if (n > 16) |
15a8a022 |
n = 16; |
374be101 |
|
66c53a53 |
q = cli_calloc(*length, sizeof(char)); |
4956690d |
if (!q) { |
15a8a022 |
noisy_warnmsg("decrypt_any: malloc failed\n");
return NULL; |
4956690d |
} |
374be101 |
|
bcc68567 |
switch (enc_method) { |
15a8a022 |
case ENC_V2:
cli_dbgmsg("cli_pdf: enc is v2\n");
memcpy(q, in, *length);
arc4_init(&arc4, result, n); |
e09d8843 |
arc4_apply(&arc4, q, (unsigned)*length); /* TODO: may truncate for very large lengths */ |
15a8a022 |
|
89d5207b |
noisy_msg(pdf, "decrypt_any: decrypted ARC4 data\n"); |
15a8a022 |
break;
case ENC_AESV2:
cli_dbgmsg("cli_pdf: enc is aesv2\n"); |
cd94be7a |
aes_decrypt((const unsigned char *)in, length, q, (char *)result, n, 1); |
15a8a022 |
|
89d5207b |
noisy_msg(pdf, "decrypt_any: decrypted AES(v2) data\n"); |
15a8a022 |
break;
case ENC_AESV3: |
89d5207b |
cli_dbgmsg("decrypt_any: enc is aesv3\n"); |
15a8a022 |
if (pdf->keylen == 0) { |
89d5207b |
cli_dbgmsg("decrypt_any: no key\n"); |
15a8a022 |
return NULL;
}
|
cd94be7a |
aes_decrypt((const unsigned char *)in, length, q, pdf->key, pdf->keylen, 1); |
15a8a022 |
noisy_msg(pdf, "decrypted AES(v3) data\n");
break;
case ENC_IDENTITY: |
89d5207b |
cli_dbgmsg("decrypt_any: enc is identity\n"); |
15a8a022 |
memcpy(q, in, *length);
|
89d5207b |
noisy_msg(pdf, "decrypt_any: identity encryption\n"); |
15a8a022 |
break;
case ENC_NONE: |
89d5207b |
cli_dbgmsg("decrypt_any: enc is none\n"); |
15a8a022 |
noisy_msg(pdf, "encryption is none\n");
free(q);
return NULL;
case ENC_UNKNOWN: |
89d5207b |
cli_dbgmsg("decrypt_any: enc is unknown\n"); |
15a8a022 |
free(q);
noisy_warnmsg("decrypt_any: unknown encryption method for obj %u %u\n",
id>>8,id&0xff);
return NULL; |
374be101 |
} |
15a8a022 |
|
cd94be7a |
return (char *)q; |
374be101 |
}
|
e2b1880f |
enum enc_method get_enc_method(struct pdf_struct *pdf, struct pdf_obj *obj) |
bcc68567 |
{
if (obj->flags & (1 << OBJ_EMBEDDED_FILE)) |
15a8a022 |
return pdf->enc_method_embeddedfile;
|
bcc68567 |
if (obj->flags & (1 << OBJ_STREAM)) |
15a8a022 |
return pdf->enc_method_stream;
|
bcc68567 |
return pdf->enc_method_string;
}
|
7719760b |
enum cstate {
CSTATE_NONE,
CSTATE_TJ,
CSTATE_TJ_PAROPEN
};
static void process(struct text_norm_state *s, enum cstate *st, const char *buf, int length, int fout)
{
do { |
15a8a022 |
switch (*st) {
case CSTATE_NONE:
if (*buf == '[') {
*st = CSTATE_TJ;
} else {
const char *nl = memchr(buf, '\n', length);
if (!nl)
return;
length -= nl - buf;
buf = nl;
}
break;
case CSTATE_TJ:
if (*buf == '(')
*st = CSTATE_TJ_PAROPEN;
break;
case CSTATE_TJ_PAROPEN:
if (*buf == ')') {
*st = CSTATE_TJ;
} else { |
cd94be7a |
if (text_normalize_buffer(s, (const unsigned char *)buf, 1) != 1) { |
15a8a022 |
cli_writen(fout, s->out, s->out_pos);
text_normalize_reset(s);
}
}
break;
}
buf++;
length--; |
7719760b |
} while (length > 0);
}
static int pdf_scan_contents(int fd, struct pdf_struct *pdf)
{
struct text_norm_state s;
char fullname[1024];
char outbuff[BUFSIZ];
char inbuf[BUFSIZ]; |
89d5207b |
int fout, n;
cl_error_t rc; |
7719760b |
enum cstate st = CSTATE_NONE;
snprintf(fullname, sizeof(fullname), "%s"PATHSEP"pdf%02u_c", pdf->dir, (pdf->files-1));
fout = open(fullname,O_RDWR|O_CREAT|O_EXCL|O_TRUNC|O_BINARY, 0600);
if (fout < 0) { |
15a8a022 |
char err[128];
|
89d5207b |
cli_errmsg("pdf_scan_contents: can't create temporary file %s: %s\n", fullname, cli_strerror(errno, err, sizeof(err))); |
15a8a022 |
return CL_ETMPFILE; |
7719760b |
}
|
cd94be7a |
text_normalize_init(&s, (unsigned char *)outbuff, sizeof(outbuff)); |
7719760b |
while (1) { |
15a8a022 |
n = cli_readn(fd, inbuf, sizeof(inbuf));
if (n <= 0)
break;
process(&s, &st, inbuf, n, fout); |
7719760b |
} |
15a8a022 |
|
7719760b |
cli_writen(fout, s.out, s.out_pos);
|
4956690d |
lseek(fout, 0, SEEK_SET); |
d39cb658 |
rc = cli_magic_scandesc(fout, fullname, pdf->ctx); |
7719760b |
close(fout); |
15a8a022 |
|
4956690d |
if (!pdf->ctx->engine->keeptmp) |
15a8a022 |
if (cli_unlink(fullname) && rc != CL_VIRUS)
rc = CL_EUNLINK;
|
4956690d |
return rc; |
7719760b |
}
|
930b9395 |
int pdf_extract_obj(struct pdf_struct *pdf, struct pdf_obj *obj, uint32_t flags) |
3643f3d2 |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx; |
ab564992 |
char fullname[NAME_MAX + 1]; |
d77b8ae0 |
int fout = -1;
size_t sum = 0; |
89d5207b |
cl_error_t rc = CL_SUCCESS; |
dc200c6b |
int dump = 1; |
ab564992 |
|
5b574c47 |
cli_dbgmsg("pdf_extract_obj: obj %u %u\n", obj->id>>8, obj->id&0xff);
|
d77b8ae0 |
if (obj->objstm) { |
89d5207b |
cli_dbgmsg("pdf_extract_obj: extracting obj found in objstm.\n"); |
d77b8ae0 |
if (obj->objstm->streambuf == NULL) {
cli_warnmsg("pdf_extract_obj: object in object stream has null stream buffer!\n");
return CL_EFORMAT;
}
} |
89d5207b |
|
9acc81d6 |
/* TODO: call bytecode hook here, allow override dumpability */ |
15a8a022 |
if ((!(obj->flags & (1 << OBJ_STREAM)) || (obj->flags & (1 << OBJ_HASFILTERS))) && !(obj->flags & DUMP_MASK)) {
/* don't dump all streams */
dump = 0; |
ab564992 |
} |
15a8a022 |
if ((obj->flags & (1 << OBJ_IMAGE)) && !(obj->flags & (1 << OBJ_FILTER_DCT))) {
/* don't dump / scan non-JPG images */
dump = 0; |
9acc81d6 |
} |
15a8a022 |
|
dc200c6b |
if (obj->flags & (1 << OBJ_FORCEDUMP)) { |
15a8a022 |
/* bytecode can force dump by setting this flag */
dump = 1; |
dc200c6b |
} |
15a8a022 |
|
dc200c6b |
if (!dump) |
15a8a022 |
return CL_CLEAN;
|
89d5207b |
cli_dbgmsg("pdf_extract_obj: dumping obj %u %u\n", obj->id>>8, obj->id&0xff); |
15a8a022 |
|
ab564992 |
snprintf(fullname, sizeof(fullname), "%s"PATHSEP"pdf%02u", pdf->dir, pdf->files++);
fout = open(fullname,O_RDWR|O_CREAT|O_EXCL|O_TRUNC|O_BINARY, 0600);
if (fout < 0) { |
15a8a022 |
char err[128]; |
89d5207b |
cli_errmsg("pdf_extract_obj: can't create temporary file %s: %s\n", fullname, cli_strerror(errno, err, sizeof(err))); |
15a8a022 |
return CL_ETMPFILE; |
ab564992 |
}
|
1412b807 |
if (!(flags & PDF_EXTRACT_OBJ_SCAN))
obj->path = strdup(fullname);
|
e010ed37 |
if ((NULL == obj->objstm) &&
(obj->flags & (1 << OBJ_STREAM))) {
/*
* Object contains a stream. Parse this now.
*/
cli_dbgmsg("pdf_extract_obj: parsing a stream in obj %u %u\n", obj->id>>8, obj->id&0xff); |
15a8a022 |
|
e010ed37 |
const char *start = pdf->map + obj->start;
size_t length;
size_t orig_length;
int dict_len = obj->stream - start; /* Dictionary should end where the stream begins */
const char *pstr;
struct pdf_dict *dparams = NULL;
struct objstm_struct *objstm = NULL;
int xref = 0;
/* Find and interpret the length dictionary value */
length = find_length(pdf, obj, start, dict_len);
if (length < 0)
length = 0;
orig_length = length;
if (length > obj->stream_size) {
cli_dbgmsg("cli_pdf: Stream length exceeds object length by %zu bytes. Length truncated to %zu bytes\n", length - obj->stream_size, obj->stream_size);
noisy_warnmsg("Stream length exceeds object length by %zu bytes. Length truncated to %zu bytes\n", length - obj->stream_size, obj->stream_size);
length = obj->stream_size;
}
if (!(obj->flags & (1 << OBJ_FILTER_FLATE)) && (length <= 0)) {
/*
* If the length is unknown and this doesn't contain a FLATE encoded filter...
* Calculate the length using the stream size, and trimming
* off any newline/carriage returns from the end of the stream.
*/
const char *q = start + obj->stream_size;
length = obj->stream_size;
q--;
if (*q == '\n') {
q--;
length--;
if (*q == '\r')
length--;
} else if (*q == '\r') {
length--; |
89d5207b |
}
|
e010ed37 |
if (length < 0)
length = 0; |
fb0c9fa2 |
|
e010ed37 |
cli_dbgmsg("pdf_extract_obj: calculated length %lld\n", (long long)length);
} else {
if (obj->stream_size > (size_t)length + 2) {
cli_dbgmsg("cli_pdf: calculated length %zu < %zu\n",
(size_t)length, obj->stream_size);
length = obj->stream_size;
}
} |
15a8a022 |
|
e010ed37 |
if ((0 != orig_length) && (obj->stream_size > (size_t)orig_length + 20)) {
cli_dbgmsg("pdf_extract_obj: orig length: %lld, length: %lld, size: %zu\n",
(long long)orig_length, (long long)length, obj->stream_size);
pdfobj_flag(pdf, obj, BAD_STREAMLEN);
} |
15a8a022 |
|
e010ed37 |
if (0 == length) {
length = obj->stream_size;
if (0 == length) {
cli_dbgmsg("pdf_extract_obj: Alleged or calculated stream length and stream buffer size both 0\n");
goto done; /* Empty stream, nothing to scan */
}
} |
15a8a022 |
|
e010ed37 |
/* Check if XRef is enabled */
if (cli_memstr(start, dict_len, "/XRef", strlen("/XRef"))) {
xref = 1;
} |
15a8a022 |
|
e010ed37 |
cli_dbgmsg("-------------EXPERIMENTAL-------------\n"); |
15a8a022 |
|
e010ed37 |
/*
* Identify the DecodeParms, if available.
*/
if (NULL != (pstr = pdf_getdict(start, &dict_len, "/DecodeParms")))
{
cli_dbgmsg("pdf_extract_obj: Found /DecodeParms\n");
}
else if (NULL != (pstr = pdf_getdict(start, &dict_len, "/DP")))
{
cli_dbgmsg("pdf_extract_obj: Found /DP\n");
} |
15a8a022 |
|
e010ed37 |
if (pstr) {
/* shift pstr left to "<<" for pdf_parse_dict */
while ((*pstr == '<') && (pstr > start)) {
pstr--;
dict_len++;
} |
15a8a022 |
|
e010ed37 |
/* shift pstr right to "<<" for pdf_parse_dict */
while ((*pstr != '<') && (dict_len > 0)) {
pstr++;
dict_len--;
} |
a081b3e9 |
|
e010ed37 |
if (dict_len > 4)
dparams = pdf_parse_dict(pdf, obj, obj->size, (char *)pstr, NULL);
else
cli_dbgmsg("pdf_extract_obj: failed to locate DecodeParms dictionary start\n");
} |
15a8a022 |
|
e010ed37 |
/*
* Go back to the start of the dictionary and check to see if the stream
* is an object stream. If so, collect the relevant info.
*/
dict_len = obj->stream - start;
if (NULL != (pstr = pdf_getdict(start, &dict_len, "/Type/ObjStm")))
{
int32_t objstm_first = -1;
int32_t objstm_length = -1;
int32_t objstm_n = -1;
cli_dbgmsg("pdf_extract_obj: Found /Type/ObjStm\n");
dict_len = obj->stream - start;
if ((-1 == (objstm_first = pdf_readint(start, dict_len, "/First"))))
{
cli_warnmsg("pdf_extract_obj: Failed to find offset of first object in object stream\n");
}
else if ((-1 == (objstm_length = pdf_readint(start, dict_len, "/Length"))))
{
cli_warnmsg("pdf_extract_obj: Failed to find length of object stream\n");
}
else if ((-1 == (objstm_n = pdf_readint(start, dict_len, "/N"))))
{
cli_warnmsg("pdf_extract_obj: Failed to find num objects in object stream\n");
}
else
{
/* Add objstm to pdf struct, so it can be freed eventually */
pdf->nobjstms++;
pdf->objstms = cli_realloc2(pdf->objstms, sizeof(struct objstm_struct*) * pdf->nobjstms);
if (!pdf->objstms) {
cli_warnmsg("pdf_extract_obj: out of memory parsing object stream (%u)\n", pdf->nobjstms);
pdf_free_dict(dparams);
return CL_EMEM; |
89d5207b |
} |
e010ed37 |
objstm = malloc(sizeof(struct objstm_struct));
if (!objstm) {
cli_warnmsg("pdf_extract_obj: out of memory parsing object stream (%u)\n", pdf->nobjstms);
pdf_free_dict(dparams);
return CL_EMEM; |
89d5207b |
} |
e010ed37 |
pdf->objstms[pdf->nobjstms-1] = objstm; |
15a8a022 |
|
e010ed37 |
memset(objstm, 0, sizeof(*objstm)); |
15a8a022 |
|
e010ed37 |
objstm->first = (uint32_t)objstm_first;
objstm->current = (uint32_t)objstm_first;
objstm->current_pair = 0;
objstm->length = (uint32_t)objstm_length;
objstm->n = (uint32_t)objstm_n; |
8da5f4c8 |
|
e010ed37 |
cli_dbgmsg("pdf_extract_obj: ObjStm first obj at offset %d\n", objstm->first);
cli_dbgmsg("pdf_extract_obj: ObjStm length is %d bytes\n", objstm->length);
cli_dbgmsg("pdf_extract_obj: ObjStm should contain %d objects\n", objstm->n);
}
} |
15a8a022 |
|
e010ed37 |
sum = pdf_decodestream(pdf, obj, dparams, obj->stream, (uint32_t)length, xref, fout, &rc, objstm);
if ((CL_SUCCESS != rc) && (CL_VIRUS != rc)) {
cli_dbgmsg("Error decoding stream! Error code: %d\n", rc);
/* It's ok if we couldn't decode the stream,
* make a best effort to keep parsing. */
if (CL_EPARSE == rc)
rc = CL_SUCCESS; |
89d5207b |
|
e010ed37 |
if (NULL != objstm) { |
89d5207b |
/* |
e010ed37 |
* If we were expecting an objstm and there was a failure...
* discard the memory for last object stream. |
89d5207b |
*/ |
e010ed37 |
if (NULL != pdf->objstms) {
if (NULL != pdf->objstms[pdf->nobjstms - 1]) {
if (NULL != pdf->objstms[pdf->nobjstms - 1]->streambuf) {
free(pdf->objstms[pdf->nobjstms - 1]->streambuf);
pdf->objstms[pdf->nobjstms - 1]->streambuf = NULL; |
89d5207b |
} |
e010ed37 |
free(pdf->objstms[pdf->nobjstms - 1]);
pdf->objstms[pdf->nobjstms - 1] = NULL; |
89d5207b |
}
|
e010ed37 |
/* Pop the objstm off the end of the pdf->objstms array. */
if (pdf->nobjstms > 0) {
pdf->nobjstms--;
if (0 == pdf->nobjstms) {
free(pdf->objstms);
pdf->objstms = NULL;
} else {
pdf->objstms = cli_realloc2(pdf->objstms, sizeof(struct objstm_struct*) * pdf->nobjstms); |
89d5207b |
|
e010ed37 |
if (!pdf->objstms) {
cli_warnmsg("pdf_extract_obj: out of memory when shrinking down objstm array\n");
return CL_EMEM; |
89d5207b |
}
} |
e010ed37 |
} else {
/* hm.. this shouldn't happen */
cli_warnmsg("pdf_extract_obj: Failure counting objstms.\n"); |
89d5207b |
} |
13882281 |
} |
e010ed37 |
}
} |
15a8a022 |
|
e010ed37 |
if (dparams)
pdf_free_dict(dparams); |
15a8a022 |
|
e010ed37 |
if ((rc == CL_VIRUS) && !SCAN_ALLMATCHES) {
sum = 0; /* prevents post-filter scan */
goto done;
} |
15a8a022 |
|
e010ed37 |
cli_dbgmsg("-------------EXPERIMENTAL-------------\n"); |
fb0c9fa2 |
|
e010ed37 |
} else if (obj->flags & (1 << OBJ_JAVASCRIPT)) {
const char *q2;
const char *q = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf)
: (const char *)(obj->start + pdf->map); |
89d5207b |
|
e010ed37 |
/* TODO: get obj-endobj size */
off_t bytesleft = obj->size; |
89d5207b |
|
e010ed37 |
if (bytesleft < 0) {
goto done;
} |
15a8a022 |
|
e010ed37 |
do {
char *js = NULL;
size_t js_len = 0;
const char *q3; |
15a8a022 |
|
e010ed37 |
q2 = cli_memstr(q, bytesleft, "/JavaScript", 11);
if (!q2)
break; |
15a8a022 |
|
e010ed37 |
bytesleft -= q2 - q + 11;
q = q2 + 11; |
15a8a022 |
|
e010ed37 |
js = pdf_readstring(q, bytesleft, "/JS", NULL, &q2, !(pdf->flags & (1<<DECRYPTABLE_PDF)));
bytesleft -= q2 - q;
q = q2; |
15a8a022 |
|
e010ed37 |
if (js) {
char *decrypted = NULL;
const char *out = js;
js_len = strlen(js);
if (pdf->flags & (1 << DECRYPTABLE_PDF)) {
cli_dbgmsg("pdf_extract_obj: encrypted string\n");
decrypted = decrypt_any(pdf, obj->id, js, &js_len, pdf->enc_method_string); |
15a8a022 |
|
e010ed37 |
if (decrypted) {
noisy_msg(pdf, "pdf_extract_obj: decrypted Javascript string from obj %u %u\n", obj->id>>8,obj->id&0xff);
out = decrypted; |
15a8a022 |
} |
e010ed37 |
} |
15a8a022 |
|
e010ed37 |
if (filter_writen(pdf, obj, fout, out, js_len, (size_t*)&sum) != js_len) {
rc = CL_EWRITE;
free(js);
break;
} |
15a8a022 |
|
e010ed37 |
free(decrypted);
free(js);
cli_dbgmsg("pdf_extract_obj: bytesleft: %d\n", (int)bytesleft); |
15a8a022 |
|
e010ed37 |
if (bytesleft > 0) {
q2 = pdf_nextobject(q, bytesleft);
if (!q2)
q2 = q + bytesleft - 1; |
15a8a022 |
|
e010ed37 |
/* non-conforming PDFs that don't escape ) properly */
q3 = memchr(q, ')', bytesleft);
if (q3 && q3 < q2)
q2 = q3; |
15a8a022 |
|
e010ed37 |
while (q2 > q && q2[-1] == ' ')
q2--; |
15a8a022 |
|
e010ed37 |
if (q2 > q) {
q--;
filter_writen(pdf, obj, fout, q, q2 - q, (size_t*)&sum);
q++; |
15a8a022 |
}
} |
e010ed37 |
} |
15a8a022 |
|
e010ed37 |
} while (bytesleft > 0);
} else {
off_t bytesleft = obj->size;
if (bytesleft < 0)
rc = CL_EFORMAT;
else {
if (obj->objstm) {
if (filter_writen(pdf, obj, fout , obj->objstm->streambuf + obj->start, bytesleft, (size_t*)&sum) != (size_t)bytesleft)
rc = CL_EWRITE;
} else {
if (filter_writen(pdf, obj, fout , pdf->map + obj->start, bytesleft, (size_t*)&sum) != (size_t)bytesleft)
rc = CL_EWRITE; |
9280b4ea |
} |
15a8a022 |
} |
e010ed37 |
}
done: |
15a8a022 |
|
89d5207b |
cli_dbgmsg("pdf_extract_obj: extracted %td bytes %u %u obj\n", sum, obj->id>>8, obj->id&0xff);
cli_dbgmsg("pdf_extract_obj: ... to %s\n", fullname); |
15a8a022 |
|
1412b807 |
if (flags & PDF_EXTRACT_OBJ_SCAN && sum) { |
15a8a022 |
int rc2;
cli_updatelimits(pdf->ctx, sum);
/* TODO: invoke bytecode on this pdf obj with metainformation associated */
lseek(fout, 0, SEEK_SET); |
d39cb658 |
rc2 = cli_magic_scandesc(fout, fullname, pdf->ctx); |
15a8a022 |
if (rc2 == CL_VIRUS || rc == CL_SUCCESS)
rc = rc2;
|
d7979d4f |
if ((rc == CL_CLEAN) || ((rc == CL_VIRUS) && SCAN_ALLMATCHES)) { |
89d5207b |
unsigned int dumpid = 0;
for (dumpid = 0; dumpid < pdf->nobjs; dumpid++) {
if (pdf->objs[dumpid] == obj)
break;
}
rc2 = run_pdf_hooks(pdf, PDF_PHASE_POSTDUMP, fout, dumpid); |
15a8a022 |
if (rc2 == CL_VIRUS)
rc = rc2;
}
|
d7979d4f |
if (((rc == CL_CLEAN) || ((rc == CL_VIRUS) && SCAN_ALLMATCHES)) && (obj->flags & (1 << OBJ_CONTENTS))) { |
15a8a022 |
lseek(fout, 0, SEEK_SET); |
89d5207b |
cli_dbgmsg("pdf_extract_obj: dumping contents %u %u\n", obj->id>>8, obj->id&0xff); |
15a8a022 |
rc2 = pdf_scan_contents(fout, pdf);
if (rc2 == CL_VIRUS)
rc = rc2;
|
89d5207b |
noisy_msg(pdf, "pdf_extract_obj: extracted text from obj %u %u\n", obj->id>>8, obj->id&0xff); |
15a8a022 |
} |
dc200c6b |
} |
15a8a022 |
|
ab564992 |
close(fout); |
15a8a022 |
|
1412b807 |
if (flags & PDF_EXTRACT_OBJ_SCAN && !pdf->ctx->engine->keeptmp) |
15a8a022 |
if (cli_unlink(fullname) && rc != CL_VIRUS)
rc = CL_EUNLINK;
|
3643f3d2 |
return rc;
}
|
6c135eb4 |
enum objstate {
STATE_NONE,
STATE_S,
STATE_FILTER, |
3643f3d2 |
STATE_JAVASCRIPT, |
eb270d5a |
STATE_OPENACTION, |
b835a528 |
STATE_LINEARIZED, |
c16b3abb |
STATE_LAUNCHACTION, |
7719760b |
STATE_CONTENTS, |
6c135eb4 |
STATE_ANY /* for actions table below */
};
|
f7f9b88c |
#define NAMEFLAG_NONE 0x0
#define NAMEFLAG_HEURISTIC 0x1
|
6c135eb4 |
struct pdfname_action {
const char *pdfname; |
dc200c6b |
enum pdf_objflags set_objflag;/* OBJ_DICT is noop */ |
6c135eb4 |
enum objstate from_state;/* STATE_NONE is noop */
enum objstate to_state; |
f7f9b88c |
uint32_t nameflags; |
63803da5 |
#if HAVE_JSON |
224d1c4d |
void (*pdf_stats_cb)(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act); |
63803da5 |
#endif |
6c135eb4 |
};
|
63803da5 |
#if HAVE_JSON |
6c135eb4 |
static struct pdfname_action pdfname_actions[] = { |
f7f9b88c |
{"ASCIIHexDecode", OBJ_FILTER_AH, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, ASCIIHexDecode_cb},
{"ASCII85Decode", OBJ_FILTER_A85, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, ASCII85Decode_cb},
{"A85", OBJ_FILTER_A85, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, ASCII85Decode_cb},
{"AHx", OBJ_FILTER_AH, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, ASCIIHexDecode_cb},
{"EmbeddedFile", OBJ_EMBEDDED_FILE, STATE_NONE, STATE_NONE, NAMEFLAG_HEURISTIC, EmbeddedFile_cb},
{"FlateDecode", OBJ_FILTER_FLATE, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, FlateDecode_cb},
{"Fl", OBJ_FILTER_FLATE, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, FlateDecode_cb},
{"Image", OBJ_IMAGE, STATE_NONE, STATE_NONE, NAMEFLAG_HEURISTIC, Image_cb},
{"LZWDecode", OBJ_FILTER_LZW, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, LZWDecode_cb},
{"LZW", OBJ_FILTER_LZW, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, LZWDecode_cb},
{"RunLengthDecode", OBJ_FILTER_RL, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, RunLengthDecode_cb},
{"RL", OBJ_FILTER_RL, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, RunLengthDecode_cb},
{"CCITTFaxDecode", OBJ_FILTER_FAX, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, CCITTFaxDecode_cb},
{"CCF", OBJ_FILTER_FAX, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, CCITTFaxDecode_cb},
{"JBIG2Decode", OBJ_FILTER_DCT, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, JBIG2Decode_cb},
{"DCTDecode", OBJ_FILTER_DCT, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, DCTDecode_cb},
{"DCT", OBJ_FILTER_DCT, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, DCTDecode_cb},
{"JPXDecode", OBJ_FILTER_JPX, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, JPXDecode_cb},
{"Crypt", OBJ_FILTER_CRYPT, STATE_FILTER, STATE_NONE, NAMEFLAG_HEURISTIC, Crypt_cb},
{"Standard", OBJ_FILTER_STANDARD, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC, Standard_cb},
{"Sig", OBJ_SIGNED, STATE_ANY, STATE_NONE, NAMEFLAG_HEURISTIC, Sig_cb},
{"V", OBJ_SIGNED, STATE_ANY, STATE_NONE, NAMEFLAG_HEURISTIC, NULL},
{"R", OBJ_SIGNED, STATE_ANY, STATE_NONE, NAMEFLAG_HEURISTIC, NULL},
{"Linearized", OBJ_DICT, STATE_NONE, STATE_LINEARIZED, NAMEFLAG_HEURISTIC, NULL},
{"Filter", OBJ_HASFILTERS, STATE_ANY, STATE_FILTER, NAMEFLAG_HEURISTIC, NULL},
{"JavaScript", OBJ_JAVASCRIPT, STATE_S, STATE_JAVASCRIPT, NAMEFLAG_HEURISTIC, JavaScript_cb},
{"Length", OBJ_DICT, STATE_FILTER, STATE_NONE, NAMEFLAG_HEURISTIC, NULL},
{"S", OBJ_DICT, STATE_NONE, STATE_S, NAMEFLAG_HEURISTIC, NULL},
{"Type", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_HEURISTIC, NULL},
{"OpenAction", OBJ_OPENACTION, STATE_ANY, STATE_OPENACTION, NAMEFLAG_HEURISTIC, OpenAction_cb},
{"Launch", OBJ_LAUNCHACTION, STATE_ANY, STATE_LAUNCHACTION, NAMEFLAG_HEURISTIC, Launch_cb},
{"Page", OBJ_PAGE, STATE_NONE, STATE_NONE, NAMEFLAG_HEURISTIC, Page_cb},
{"Contents", OBJ_CONTENTS, STATE_NONE, STATE_CONTENTS, NAMEFLAG_HEURISTIC, NULL},
{"Author", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, Author_cb},
{"Producer", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, Producer_cb},
{"CreationDate", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, CreationDate_cb},
{"ModDate", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, ModificationDate_cb},
{"Creator", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, Creator_cb},
{"Title", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, Title_cb},
{"Keywords", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, Keywords_cb},
{"Subject", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, Subject_cb},
{"Pages", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, Pages_cb},
{"Colors", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, Colors_cb},
{"RichMedia", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, RichMedia_cb},
{"AcroForm", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, AcroForm_cb},
{"XFA", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_NONE, XFA_cb} |
6c135eb4 |
}; |
63803da5 |
#else
static struct pdfname_action pdfname_actions[] = { |
f7f9b88c |
{"ASCIIHexDecode", OBJ_FILTER_AH, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"ASCII85Decode", OBJ_FILTER_A85, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"A85", OBJ_FILTER_A85, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"AHx", OBJ_FILTER_AH, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"EmbeddedFile", OBJ_EMBEDDED_FILE, STATE_NONE, STATE_NONE, NAMEFLAG_HEURISTIC},
{"FlateDecode", OBJ_FILTER_FLATE, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"Fl", OBJ_FILTER_FLATE, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"Image", OBJ_IMAGE, STATE_NONE, STATE_NONE, NAMEFLAG_HEURISTIC},
{"LZWDecode", OBJ_FILTER_LZW, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"LZW", OBJ_FILTER_LZW, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"RunLengthDecode", OBJ_FILTER_RL, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"RL", OBJ_FILTER_RL, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"CCITTFaxDecode", OBJ_FILTER_FAX, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"CCF", OBJ_FILTER_FAX, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"JBIG2Decode", OBJ_FILTER_DCT, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"DCTDecode", OBJ_FILTER_DCT, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"DCT", OBJ_FILTER_DCT, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"JPXDecode", OBJ_FILTER_JPX, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"Crypt", OBJ_FILTER_CRYPT, STATE_FILTER, STATE_NONE, NAMEFLAG_HEURISTIC},
{"Standard", OBJ_FILTER_STANDARD, STATE_FILTER, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"Sig", OBJ_SIGNED, STATE_ANY, STATE_NONE, NAMEFLAG_HEURISTIC},
{"V", OBJ_SIGNED, STATE_ANY, STATE_NONE, NAMEFLAG_HEURISTIC},
{"R", OBJ_SIGNED, STATE_ANY, STATE_NONE, NAMEFLAG_HEURISTIC},
{"Linearized", OBJ_DICT, STATE_NONE, STATE_LINEARIZED, NAMEFLAG_HEURISTIC},
{"Filter", OBJ_HASFILTERS, STATE_ANY, STATE_FILTER, NAMEFLAG_HEURISTIC},
{"JavaScript", OBJ_JAVASCRIPT, STATE_S, STATE_JAVASCRIPT, NAMEFLAG_HEURISTIC},
{"Length", OBJ_DICT, STATE_FILTER, STATE_NONE, NAMEFLAG_HEURISTIC},
{"S", OBJ_DICT, STATE_NONE, STATE_S, NAMEFLAG_HEURISTIC},
{"Type", OBJ_DICT, STATE_NONE, STATE_NONE, NAMEFLAG_HEURISTIC},
{"OpenAction", OBJ_OPENACTION, STATE_ANY, STATE_OPENACTION, NAMEFLAG_HEURISTIC},
{"Launch", OBJ_LAUNCHACTION, STATE_ANY, STATE_LAUNCHACTION, NAMEFLAG_HEURISTIC},
{"Page", OBJ_PAGE, STATE_NONE, STATE_NONE, NAMEFLAG_HEURISTIC},
{"Contents", OBJ_CONTENTS, STATE_NONE, STATE_CONTENTS, NAMEFLAG_HEURISTIC} |
63803da5 |
};
#endif |
6c135eb4 |
|
edeb59b3 |
#define KNOWN_FILTERS ((1 << OBJ_FILTER_AH) | (1 << OBJ_FILTER_RL) | (1 << OBJ_FILTER_A85) | (1 << OBJ_FILTER_FLATE) | (1 << OBJ_FILTER_LZW) | (1 << OBJ_FILTER_FAX) | (1 << OBJ_FILTER_DCT) | (1 << OBJ_FILTER_JPX) | (1 << OBJ_FILTER_CRYPT)) |
eb270d5a |
|
15a8a022 |
static void handle_pdfname(struct pdf_struct *pdf, struct pdf_obj *obj, const char *pdfname, int escapes, enum objstate *state) |
6c135eb4 |
{
struct pdfname_action *act = NULL;
unsigned j; |
15a8a022 |
|
5091689d |
obj->statsflags |= OBJ_FLAG_PDFNAME_DONE;
|
6c135eb4 |
for (j=0;j<sizeof(pdfname_actions)/sizeof(pdfname_actions[0]);j++) { |
15a8a022 |
if (!strcmp(pdfname, pdfname_actions[j].pdfname)) {
act = &pdfname_actions[j];
break;
} |
6c135eb4 |
} |
15a8a022 |
|
eb270d5a |
if (!act) { |
15a8a022 |
/* these are digital signature objects, filter doesn't matter,
* we don't need them anyway */
if (*state == STATE_FILTER && !(obj->flags & (1 << OBJ_SIGNED)) && !(obj->flags & KNOWN_FILTERS)) { |
89d5207b |
cli_dbgmsg("handle_pdfname: unknown filter %s\n", pdfname); |
15a8a022 |
obj->flags |= 1 << OBJ_FILTER_UNKNOWN;
}
return; |
eb270d5a |
} |
15a8a022 |
|
a9584bfe |
/* record filter order */ |
c8ba4ae2 |
if (obj->numfilters < PDF_FILTERLIST_MAX && (*state == STATE_FILTER) && ((1 << act->set_objflag) & KNOWN_FILTERS)) |
a9584bfe |
obj->filterlist[obj->numfilters++] = act->set_objflag;
|
f7f9b88c |
if ((act->nameflags & NAMEFLAG_HEURISTIC) && escapes) {
/* if a commonly used PDF name is escaped that is certainly
suspicious. */ |
89d5207b |
cli_dbgmsg("handle_pdfname: pdfname %s is escaped\n", pdfname); |
f7f9b88c |
pdfobj_flag(pdf, obj, ESCAPED_COMMON_PDFNAME);
}
|
63803da5 |
#if HAVE_JSON |
49bc4992 |
if ((act->pdf_stats_cb))
act->pdf_stats_cb(pdf, obj, act); |
63803da5 |
#endif |
49bc4992 |
|
15a8a022 |
if (act->from_state == *state || act->from_state == STATE_ANY) {
*state = act->to_state;
|
c8ba4ae2 |
if (*state == STATE_FILTER && act->set_objflag != OBJ_DICT && (obj->flags & (1 << act->set_objflag))) { |
89d5207b |
cli_dbgmsg("handle_pdfname: duplicate stream filter %s\n", pdfname); |
15a8a022 |
pdfobj_flag(pdf, obj, BAD_STREAM_FILTERS);
}
obj->flags |= 1 << act->set_objflag; |
6c135eb4 |
} else { |
15a8a022 |
/* auto-reset states */
switch (*state) {
case STATE_S:
*state = STATE_NONE;
break;
default:
break;
} |
6c135eb4 |
}
}
|
bbfad9ba |
static void pdf_parse_encrypt(struct pdf_struct *pdf, const char *enc, int len)
{
const char *q, *q2; |
bf6e777f |
unsigned long objid;
unsigned long genid; |
8ad8d01b |
long temp_long; |
bbfad9ba |
if (len >= 16 && !strncmp(enc, "/EncryptMetadata", 16)) { |
15a8a022 |
q = cli_memstr(enc+16, len-16, "/Encrypt", 8);
if (!q)
return;
len -= q - enc;
enc = q; |
bbfad9ba |
} |
15a8a022 |
|
bbfad9ba |
q = enc + 8;
len -= 8;
q2 = pdf_nextobject(q, len);
if (!q2 || !isdigit(*q2)) |
15a8a022 |
return; |
bbfad9ba |
len -= q2 - q;
q = q2; |
53cbdee3 |
|
8ad8d01b |
if (CL_SUCCESS != cli_strntol_wrap(q2, (size_t)len, 0, 10, &temp_long)) { |
89d5207b |
cli_dbgmsg("pdf_parse_encrypt: Found Encrypt dictionary but failed to parse objid\n"); |
bf6e777f |
return; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("pdf_parse_encrypt: Encountered invalid negative objid (%ld).\n", temp_long);
return; |
bf6e777f |
} |
8ad8d01b |
objid = (unsigned long)temp_long;
|
bf6e777f |
objid = objid << 8; |
bbfad9ba |
q2 = pdf_nextobject(q, len);
if (!q2 || !isdigit(*q2)) |
15a8a022 |
return; |
bbfad9ba |
len -= q2 - q;
q = q2; |
53cbdee3 |
|
8ad8d01b |
if (CL_SUCCESS != cli_strntol_wrap(q2, (size_t)len, 0, 10, &temp_long)) { |
89d5207b |
cli_dbgmsg("pdf_parse_encrypt: Found Encrypt dictionary but failed to parse genid\n"); |
bf6e777f |
return; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("pdf_parse_encrypt: Encountered invalid negative genid (%ld).\n", temp_long);
return; |
bf6e777f |
} |
8ad8d01b |
genid = (unsigned long)temp_long;
|
e010ed37 |
objid |= genid & 0xff; |
bbfad9ba |
q2 = pdf_nextobject(q, len);
if (!q2 || *q2 != 'R') |
15a8a022 |
return;
|
89d5207b |
cli_dbgmsg("pdf_parse_encrypt: Encrypt dictionary in obj %lu %lu\n", objid>>8, objid&0xff); |
15a8a022 |
|
bbfad9ba |
pdf->enc_objid = objid;
}
static void pdf_parse_trailer(struct pdf_struct *pdf, const char *s, long length)
{
const char *enc; |
15a8a022 |
|
bbfad9ba |
enc = cli_memstr(s, length, "/Encrypt", 8);
if (enc) { |
15a8a022 |
char *newID;
pdf->flags |= 1 << ENCRYPTED_PDF;
pdf_parse_encrypt(pdf, enc, s + length - enc);
newID = pdf_readstring(s, length, "/ID", &pdf->fileIDlen, NULL, 0);
if (newID) {
free(pdf->fileID);
pdf->fileID = newID;
} |
bbfad9ba |
}
}
|
930b9395 |
void pdf_parseobj(struct pdf_struct *pdf, struct pdf_obj *obj) |
6c135eb4 |
{
/* enough to hold common pdf names, we don't need all the names */
char pdfname[64]; |
8d1ef133 |
const char *q2, *q3; |
c9a070c9 |
const char *nextobj = NULL, *nextopen = NULL, *nextclose = NULL; |
89d5207b |
const char *q = NULL;
const char *dict = NULL, *enddict = NULL, *start = NULL; |
e010ed37 |
off_t dict_length = 0, full_dict_length = 0, bytesleft = 0; |
89d5207b |
size_t i = 0;
unsigned filters = 0, blockopens = 0; |
6c135eb4 |
enum objstate objstate = STATE_NONE; |
fdfd5814 |
#if HAVE_JSON
json_object *pdfobj=NULL, *jsonobj=NULL;
#endif |
6c135eb4 |
|
8ad8d01b |
if (NULL == pdf || NULL == obj) {
cli_warnmsg("pdf_parseobj: invalid arguments\n");
return;
}
|
e010ed37 |
cli_dbgmsg("pdf_parseobj: Parsing object %u %u\n", obj->id >> 8, obj->id & 0xff);
|
8965a5af |
if (obj->objstm) {
if ((size_t)obj->start > obj->objstm->streambuf_len) {
cli_dbgmsg("pdf_parseobj: %u %u obj: obj start (%u) is greater than size of object stream (%zu).\n",
obj->id >> 8, obj->id & 0xff, obj->start, obj->objstm->streambuf_len);
return;
}
q = (const char *)(obj->start + obj->objstm->streambuf);
} else {
if ((size_t)obj->start > pdf->size) {
cli_dbgmsg("pdf_parseobj: %u %u obj: obj start (%u) is greater than size of PDF (%lld).\n",
obj->id >> 8, obj->id & 0xff, obj->start, (long long)pdf->size);
return;
}
q = (const char *)(obj->start + pdf->map);
}
start = q; |
89d5207b |
|
e010ed37 |
if (obj->size <= 0) |
15a8a022 |
return;
|
8965a5af |
if (obj->objstm) { |
e010ed37 |
bytesleft = MIN(obj->size, obj->objstm->streambuf_len - obj->start); |
8965a5af |
} else { |
e010ed37 |
bytesleft = MIN(obj->size, pdf->size - obj->start);
}
/* For objects that aren't already in an object stream^, check if they contain a stream.
* ^Objects in object streams aren't supposed to contain streams, so we don't check them. */
if (NULL == obj->objstm) {
/* Check if object contains stream */
cl_error_t has_stream;
const char* stream = NULL;
size_t stream_size = 0;
has_stream = find_stream_bounds(
start,
obj->size,
&stream,
&stream_size,
(pdf->enc_method_stream <= ENC_IDENTITY) && (pdf->enc_method_embeddedfile <= ENC_IDENTITY));
if ((CL_SUCCESS == has_stream) ||
(CL_EFORMAT == has_stream)) {
/* Stream found. Store this fact and the stream bounds. */
cli_dbgmsg("pdf_parseobj: %u %u contains stream, size: %zu\n", obj->id>>8, obj->id&0xff, stream_size);
obj->flags |= (1 << OBJ_STREAM);
obj->stream = stream;
obj->stream_size = stream_size;
} |
8965a5af |
} |
8d1ef133 |
|
6c135eb4 |
/* find start of dictionary */
do { |
15a8a022 |
nextobj = pdf_nextobject(q, bytesleft);
bytesleft -= nextobj -q;
if (!nextobj || bytesleft < 0) { |
89d5207b |
cli_dbgmsg("pdf_parseobj: %u %u obj: no dictionary\n", obj->id>>8, obj->id&0xff); |
fdfd5814 |
#if HAVE_JSON
if (!(pdfobj) && pdf->ctx->wrkproperty != NULL) {
pdfobj = cli_jsonobj(pdf->ctx->wrkproperty, "PDFStats");
if (!(pdfobj))
return;
}
if (pdfobj) {
if (!(jsonobj))
jsonobj = cli_jsonarray(pdfobj, "ObjectsWithoutDictionaries");
if (jsonobj)
cli_jsonint_array(jsonobj, obj->id>>8);
}
#endif |
15a8a022 |
return;
}
|
8ad8d01b |
/*
* Opening `<` for object's dictionary may be back 1 character,
* provided q is not at the start of the buffer (it shouldn't be).
*/
if (obj->objstm) {
if (obj->objstm->streambuf == q) {
q3 = memchr(q, '<', nextobj - q);
} else { |
15a8a022 |
q3 = memchr(q-1, '<', nextobj-q+1); |
8ad8d01b |
}
} else {
if (pdf->map == q) {
q3 = memchr(q, '<', nextobj - q);
} else {
q3 = memchr(q - 1, '<', nextobj - q + 1);
}
} |
15a8a022 |
nextobj++;
bytesleft--;
q = nextobj; |
6c135eb4 |
} while (!q3 || q3[1] != '<');
dict = q3+2;
q = dict; |
8d1ef133 |
blockopens++; |
e010ed37 |
bytesleft = obj->size - (q - start); |
4c19109d |
enddict = q + bytesleft - 1; |
8d1ef133 |
/* find end of dictionary block */ |
4c19109d |
if (bytesleft < 0) { |
89d5207b |
cli_dbgmsg("pdf_parseobj: %u %u obj: broken dictionary\n", obj->id>>8, obj->id&0xff); |
fdfd5814 |
#if HAVE_JSON
if (!(pdfobj) && pdf->ctx->wrkproperty != NULL) {
pdfobj = cli_jsonobj(pdf->ctx->wrkproperty, "PDFStats");
if (!(pdfobj))
return;
}
if (pdfobj) {
if (!(jsonobj))
jsonobj = cli_jsonarray(pdfobj, "ObjectsWithBrokenDictionaries");
if (jsonobj)
cli_jsonint_array(jsonobj, obj->id>>8);
}
#endif |
4c19109d |
return;
} |
8d1ef133 |
|
4c19109d |
/* while still looking ... */
while ((q < enddict-1) && (blockopens > 0)) {
/* find next close */ |
bc52aff8 |
nextclose = memchr(q, '>', enddict-q); |
4c19109d |
if (nextclose && (nextclose[1] == '>')) {
/* check for nested open */ |
374b5aea |
while ((nextopen = memchr(q-1, '<', nextclose-q+1)) != NULL) { |
4c19109d |
if (nextopen[1] == '<') {
/* nested open */
blockopens++;
q = nextopen + 2; |
8d1ef133 |
}
else { |
4c19109d |
/* unmatched < before next close */
q = nextopen + 2; |
8d1ef133 |
}
} |
4c19109d |
/* close block */
blockopens--;
q = nextclose + 2; |
8d1ef133 |
} |
4c19109d |
else if (nextclose) {
/* found one > but not two */
q = nextclose + 2;
}
else {
/* next closing not found */ |
bc52aff8 |
break; |
4c19109d |
}
} |
8d1ef133 |
|
4c19109d |
/* Was end of dictionary found? */ |
bc52aff8 |
if (blockopens) {
/* probably truncated */ |
89d5207b |
cli_dbgmsg("pdf_parseobj: %u %u obj broken dictionary\n", obj->id>>8, obj->id&0xff); |
fdfd5814 |
#if HAVE_JSON
if (!(pdfobj) && pdf->ctx->wrkproperty != NULL) {
pdfobj = cli_jsonobj(pdf->ctx->wrkproperty, "PDFStats");
if (!(pdfobj))
return;
}
if (pdfobj) {
if (!(jsonobj))
jsonobj = cli_jsonarray(pdfobj, "ObjectsWithBrokenDictionaries");
if (jsonobj)
cli_jsonint_array(jsonobj, obj->id>>8);
}
#endif |
4c19109d |
return; |
bc52aff8 |
} |
15a8a022 |
|
8d1ef133 |
enddict = nextclose; |
6c135eb4 |
obj->flags |= 1 << OBJ_DICT; |
8d1ef133 |
full_dict_length = dict_length = enddict - dict;
/* This code prints the dictionary content.
{
char * dictionary = malloc(dict_length + 1);
if (dictionary) { |
4c19109d |
for (i = 0; i < dict_length; i++) { |
115b6306 |
if (dict[i] == '\r')
dictionary[i] = '\n';
else if (isprint(dict[i]) || isspace(dict[i])) |
4c19109d |
dictionary[i] = dict[i];
else
dictionary[i] = '*';
} |
8d1ef133 |
dictionary[dict_length] = '\0'; |
89d5207b |
cli_dbgmsg("pdf_parseobj: dictionary is <<%s>>\n", dictionary); |
8d1ef133 |
free(dictionary);
}
}
*/ |
6c135eb4 |
|
9c617dbe |
/* process pdf names */ |
5af966d3 |
for (q = dict;dict_length > 0;) { |
15a8a022 |
int escapes = 0, breakout=0;
q2 = memchr(q, '/', dict_length);
if (!q2)
break;
dict_length -= q2 - q;
q = q2;
/* normalize PDF names */
for (i = 0;dict_length > 0 && (i < sizeof(pdfname)-1); i++) {
q++;
dict_length--;
if (*q == '#') {
if (cli_hex2str_to(q+1, pdfname+i, 2) == -1)
break;
q += 2;
dict_length -= 2;
escapes = 1;
continue;
}
switch (*q) {
case ' ':
case '\t':
case '\r':
case '\n':
case '/':
case '>':
case '[':
case ']':
case '<':
case '(':
breakout = 1;
}
if (breakout)
break;
pdfname[i] = *q;
}
pdfname[i] = '\0';
handle_pdfname(pdf, obj, pdfname, escapes, &objstate);
if (objstate == STATE_LINEARIZED) {
long trailer_end, trailer;
pdfobj_flag(pdf, obj, LINEARIZED_PDF);
objstate = STATE_NONE;
trailer_end = pdf_readint(dict, full_dict_length, "/H"); |
e010ed37 |
if ((trailer_end > 0) && ((size_t)trailer_end < pdf->size)) { |
15a8a022 |
trailer = trailer_end - 1024;
if (trailer < 0)
trailer = 0;
q2 = pdf->map + trailer; |
89d5207b |
cli_dbgmsg("pdf_parseobj: looking for trailer in linearized pdf: %ld - %ld\n", trailer, trailer_end); |
15a8a022 |
pdf_parse_trailer(pdf, q2, trailer_end - trailer);
if (pdf->fileID) |
89d5207b |
cli_dbgmsg("pdf_parseobj: found fileID\n"); |
15a8a022 |
}
}
if (objstate == STATE_LAUNCHACTION)
pdfobj_flag(pdf, obj, HAS_LAUNCHACTION);
if (dict_length > 0 && (objstate == STATE_JAVASCRIPT || objstate == STATE_OPENACTION || objstate == STATE_CONTENTS)) { |
69b4a223 |
off_t dict_remaining = dict_length;
|
15a8a022 |
if (objstate == STATE_OPENACTION)
pdfobj_flag(pdf, obj, HAS_OPENACTION);
|
69b4a223 |
q2 = pdf_nextobject(q, dict_remaining); |
15a8a022 |
if (q2 && isdigit(*q2)) { |
69b4a223 |
const char * q2_old = NULL; |
bf6e777f |
unsigned long objid;
unsigned long genid; |
8ad8d01b |
long temp_long; |
bf6e777f |
|
69b4a223 |
dict_remaining -= (off_t)(q2 - q);
|
8ad8d01b |
if (CL_SUCCESS != cli_strntol_wrap(q2, (size_t)dict_remaining, 0, 10, &temp_long)) { |
89d5207b |
cli_dbgmsg("pdf_parseobj: failed to parse object objid\n"); |
bf6e777f |
return; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("pdf_parseobj: Encountered invalid negative genid (%ld).\n", temp_long);
return; |
bf6e777f |
} |
8ad8d01b |
objid = (unsigned long)temp_long;
|
bf6e777f |
objid = objid << 8;
|
c1e7159d |
while ((dict_remaining > 0) && isdigit(*q2)) { |
15a8a022 |
q2++; |
c1e7159d |
dict_remaining--;
} |
15a8a022 |
|
69b4a223 |
q2_old = q2;
q2 = pdf_nextobject(q2, dict_remaining); |
15a8a022 |
if (q2 && isdigit(*q2)) { |
69b4a223 |
dict_remaining -= (off_t)(q2 - q2_old); |
8ad8d01b |
if (CL_SUCCESS != cli_strntol_wrap(q2, (size_t)dict_remaining, 0, 10, &temp_long)) { |
89d5207b |
cli_dbgmsg("pdf_parseobj: failed to parse object genid\n"); |
bf6e777f |
return; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("pdf_parseobj: Encountered invalid negative genid (%ld).\n", temp_long);
return; |
bf6e777f |
} |
8ad8d01b |
genid = (unsigned long)temp_long;
|
bf6e777f |
objid |= genid & 0xff; |
15a8a022 |
|
69b4a223 |
q2 = pdf_nextobject(q2, dict_remaining); |
15a8a022 |
if (q2 && *q2 == 'R') {
struct pdf_obj *obj2;
|
89d5207b |
cli_dbgmsg("pdf_parseobj: found %s stored in indirect object %lu %lu\n", pdfname, objid >> 8, objid&0xff); |
15a8a022 |
obj2 = find_obj(pdf, obj, objid);
if (obj2) {
enum pdf_objflags flag =
objstate == STATE_JAVASCRIPT ? OBJ_JAVASCRIPT :
objstate == STATE_OPENACTION ? OBJ_OPENACTION :
OBJ_CONTENTS;
obj2->flags |= 1 << flag;
obj->flags &= ~(1 << flag);
} else {
pdfobj_flag(pdf, obj, BAD_INDOBJ);
}
}
}
}
objstate = STATE_NONE;
} |
6c135eb4 |
} |
15a8a022 |
|
9acc81d6 |
for (i=0;i<sizeof(pdfname_actions)/sizeof(pdfname_actions[0]);i++) { |
15a8a022 |
const struct pdfname_action *act = &pdfname_actions[i];
if ((obj->flags & (1 << act->set_objflag)) &&
act->from_state == STATE_FILTER &&
act->to_state == STATE_FILTER &&
act->set_objflag != OBJ_FILTER_CRYPT &&
act->set_objflag != OBJ_FILTER_STANDARD) {
filters++;
} |
9acc81d6 |
} |
15a8a022 |
if (filters > 2) {
/* more than 2 non-crypt filters */
pdfobj_flag(pdf, obj, MANY_FILTERS); |
9acc81d6 |
} |
15a8a022 |
|
b835a528 |
if (obj->flags & ((1 << OBJ_SIGNED) | KNOWN_FILTERS)) |
15a8a022 |
obj->flags &= ~(1 << OBJ_FILTER_UNKNOWN);
|
b835a528 |
if (obj->flags & (1 << OBJ_FILTER_UNKNOWN)) |
15a8a022 |
pdfobj_flag(pdf, obj, UNKNOWN_FILTER);
|
89d5207b |
cli_dbgmsg("pdf_parseobj: %u %u obj flags: %02x\n", obj->id>>8, obj->id&0xff, obj->flags); |
6c135eb4 |
}
|
a79be759 |
/**
* @brief Given a pointer to a dictionary object and a key, get the key's value.
*
* @param q0 Offset of the start of the dictionary.
* @param[in,out] len In: The number of bytes in the dictionary.
* Out: The number of bytes remaining from the start
* of the value to the end of the dict
* @param key Null terminated 'key' to search for.
* @return const char* Address of the dictionary key's 'value'.
*/ |
7606789f |
static const char *pdf_getdict(const char *q0, int* len, const char *key)
{
const char *q;
|
82c0e6bc |
if (*len <= 0) { |
89d5207b |
cli_dbgmsg("pdf_getdict: bad length %d\n", *len); |
4c19109d |
return NULL;
} |
15a8a022 |
if (!q0)
return NULL;
|
a79be759 |
/* find the key */ |
7606789f |
q = cli_memstr(q0, *len, key, strlen(key));
if (!q) { |
89d5207b |
cli_dbgmsg("pdf_getdict: %s not found in dict\n", key); |
15a8a022 |
return NULL; |
7606789f |
} |
15a8a022 |
|
7606789f |
*len -= q - q0;
q0 = q; |
a79be759 |
/* find the start of the value object */ |
7606789f |
q = pdf_nextobject(q0 + 1, *len - 1);
if (!q) { |
89d5207b |
cli_dbgmsg("pdf_getdict: %s is invalid in dict\n", key); |
15a8a022 |
return NULL; |
7606789f |
} |
15a8a022 |
|
a79be759 |
/* if the value is a dictionary object, include the < > brackets.*/ |
7606789f |
if (q[-1] == '<') |
15a8a022 |
q--;
|
7606789f |
*len -= q - q0;
return q;
}
|
fb0c9fa2 |
static char *pdf_readstring(const char *q0, int len, const char *key, unsigned *slen, const char **qend, int noescape) |
7606789f |
{
char *s, *s0;
const char *start, *q, *end;
if (slen) |
15a8a022 |
*slen = 0;
|
fb0c9fa2 |
if (qend)
*qend = q0; |
15a8a022 |
|
7606789f |
q = pdf_getdict(q0, &len, key); |
a79be759 |
if (!q || len <= 0) |
15a8a022 |
return NULL;
|
7606789f |
if (*q == '(') { |
15a8a022 |
int paren = 1;
start = ++q; |
a79be759 |
len--; |
15a8a022 |
for (;paren > 0 && len > 0; q++,len--) {
switch (*q) {
case '(':
paren++;
break;
case ')':
paren--;
break;
case '\\':
q++;
len--;
break;
default:
break;
}
}
|
a79be759 |
if (len <= 0) {
cli_errmsg("pdf_readstring: Invalid, truncated dictionary.\n");
return NULL;
}
|
fb0c9fa2 |
if (qend)
*qend = q; |
15a8a022 |
q--;
len = q - start;
s0 = s = cli_malloc(len + 1);
if (!s) {
cli_errmsg("pdf_readstring: Unable to allocate buffer\n");
return NULL;
}
end = start + len; |
fb0c9fa2 |
if (noescape) {
memcpy(s0, start, len);
s = s0 + len;
} else { |
15a8a022 |
for (q = start;q < end;q++) {
if (*q != '\\') {
*s++ = *q;
} else {
q++;
switch (*q) {
case 'n':
*s++ = '\n';
break;
case 'r':
*s++ = '\r';
break;
case 't':
*s++ = '\t';
break;
case 'b':
*s++ = '\b';
break;
case 'f':
*s++ = '\f';
break;
case '(':/* fall-through */
case ')':/* fall-through */
case '\\':
*s++ = *q;
break;
case '\n':
/* ignore */
break;
case '\r':
/* ignore */
if (q+1 < end && q[1] == '\n')
q++;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
/* octal escape */
if (q+2 < end)
q++;
*s++ = 64*(q[0] - '0') + 8*(q[1] - '0') + (q[2] - '0');
break;
default:
/* ignore */ |
fb0c9fa2 |
*s++ = '\\';
q--; |
15a8a022 |
break;
}
}
} |
fb0c9fa2 |
} |
15a8a022 |
*s++ = '\0';
if (slen)
*slen = s - s0 - 1;
return s0; |
7606789f |
} |
15a8a022 |
|
d7979d4f |
if ((*q == '<') && (len >= 3)) { |
15a8a022 |
start = ++q; |
d7979d4f |
len -= 1;
q = memchr(q+1, '>', len-1); |
15a8a022 |
if (!q)
return NULL;
|
fb0c9fa2 |
if (qend)
*qend = q; |
15a8a022 |
s = cli_malloc((q - start)/2 + 1);
if (s == NULL) { /* oops, couldn't allocate memory */ |
89d5207b |
cli_dbgmsg("pdf_readstring: unable to allocate memory...\n"); |
15a8a022 |
return NULL;
}
if (cli_hex2str_to(start, s, q - start)) { |
89d5207b |
cli_dbgmsg("pdf_readstring: %s has bad hex value\n", key); |
15a8a022 |
free(s);
return NULL;
}
s[(q-start)/2] = '\0';
if (slen)
*slen = (q - start)/2;
return s; |
7606789f |
} |
15a8a022 |
|
89d5207b |
cli_dbgmsg("pdf_readstring: %s is invalid string in dict\n", key); |
7606789f |
return NULL;
}
|
374be101 |
static char *pdf_readval(const char *q, int len, const char *key)
{
const char *end;
char *s; |
09365897 |
int origlen = len; |
374be101 |
q = pdf_getdict(q, &len, key);
if (!q || len <= 0) |
15a8a022 |
return NULL;
while (len > 0 && *q && *q == ' ') {
q++;
len--;
}
|
374be101 |
if (*q != '/') |
15a8a022 |
return NULL;
|
374be101 |
q++;
len--;
end = q; |
15a8a022 |
|
374be101 |
while (len > 0 && *end && !(*end == '/' || (len > 1 && end[0] == '>' && end[1] == '>'))) { |
15a8a022 |
end++;
len--; |
374be101 |
} |
15a8a022 |
|
09365897 |
/* end-of-buffer whitespace trimming */
while (len < origlen && isspace(*(end-1))) {
end--;
len++;
}
|
374be101 |
s = cli_malloc(end - q + 1);
if (!s) |
15a8a022 |
return NULL;
|
374be101 |
memcpy(s, q, end-q);
s[end-q] = '\0'; |
15a8a022 |
|
374be101 |
return s;
}
|
7606789f |
static int pdf_readint(const char *q0, int len, const char *key)
{ |
bf6e777f |
long value = 0; |
7606789f |
const char *q = pdf_getdict(q0, &len, key); |
15a8a022 |
|
bf6e777f |
if (q == NULL) {
value = -1;
}
else if (CL_SUCCESS != cli_strntol_wrap(q, (size_t)len, 0, 10, &value)) {
value = -1;
}
return value; |
7606789f |
}
static int pdf_readbool(const char *q0, int len, const char *key, int Default)
{
const char *q = pdf_getdict(q0, &len, key); |
15a8a022 |
|
7606789f |
if (!q || len < 5) |
15a8a022 |
return Default;
|
7606789f |
if (!strncmp(q, "true", 4)) |
15a8a022 |
return 1;
|
7606789f |
if (!strncmp(q, "false", 5)) |
15a8a022 |
return 0;
|
89d5207b |
cli_dbgmsg("pdf_readbool: invalid value for %s bool\n", key); |
15a8a022 |
|
7606789f |
return Default;
}
static const char *key_padding =
"\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4e\x56\xff\xfa\x01\x08"
"\x2e\x2e\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A";
static void dbg_printhex(const char *msg, const char *hex, unsigned len)
{
if (cli_debug_flag) { |
15a8a022 |
char *kh = cli_str2hex(hex, len);
cli_dbgmsg("cli_pdf: %s: %s\n", msg, kh);
free(kh); |
7606789f |
}
}
static void check_user_password(struct pdf_struct *pdf, int R, const char *O, |
53cbdee3 |
const char *U, int32_t P, int EM,
const char *UE,
unsigned length, unsigned oulen) |
7606789f |
{
unsigned i;
uint8_t result[16];
char data[32];
struct arc4_state arc4;
unsigned password_empty = 0;
|
cd94be7a |
UNUSEDPARAM(oulen);
|
7606789f |
dbg_printhex("U: ", U, 32);
dbg_printhex("O: ", O, 32);
if (R == 5) { |
15a8a022 |
uint8_t result2[32];
/* supplement to ISO3200, 3.5.2 Algorithm 3.11 */
/* user validation salt */
cl_sha256(U+32, 8, result2, NULL); |
cd94be7a |
dbg_printhex("Computed U", (const char *)result2, 32); |
15a8a022 |
if (!memcmp(result2, U, 32)) { |
e09d8843 |
size_t UE_len; |
15a8a022 |
/* Algorithm 3.2a could be used to recover encryption key */
password_empty = 1;
cl_sha256(U+40, 8, result2, NULL); |
e09d8843 |
UE_len = UE ? strlen(UE) : 0;
if (UE_len != 32) { |
89d5207b |
cli_dbgmsg("check_user_password: UE length is not 32: %zu\n", UE_len);
noisy_warnmsg("check_user_password: UE length is not 32: %zu\n", UE_len); |
15a8a022 |
} else {
pdf->keylen = 32;
pdf->key = cli_malloc(32);
if (!pdf->key) {
cli_errmsg("check_user_password: Cannot allocate memory for pdf->key\n");
return;
}
|
e09d8843 |
aes_decrypt((const unsigned char *)UE, &UE_len, (unsigned char *)(pdf->key), (char *)result2, 32, 0); |
89d5207b |
dbg_printhex("check_user_password: Candidate encryption key", pdf->key, pdf->keylen); |
15a8a022 |
} |
241e7eb1 |
} |
374b5aea |
} else if ((R >= 2) && (R <= 4)) { |
b2e7c931 |
unsigned char *d;
size_t sz = 68 + pdf->fileIDlen + (R >= 4 && !EM ? 4 : 0);
d = calloc(1, sz);
if (!(d))
return;
memcpy(d, key_padding, 32);
memcpy(d+32, O, 32);
P = le32_to_host(P);
memcpy(d+64, &P, 4);
memcpy(d+68, pdf->fileID, pdf->fileIDlen);
|
15a8a022 |
/* 7.6.3.3 Algorithm 2 */
/* empty password, password == padding */
if (R >= 4 && !EM) {
uint32_t v = 0xFFFFFFFF;
memcpy(d+68+pdf->fileIDlen, &v, 4);
}
cl_hash_data("md5", d, sz, result, NULL);
free(d);
if (length > 128)
length = 128;
if (R >= 3) {
/* Yes, this really is on purpose */
for (i=0;i<50;i++)
cl_hash_data("md5", result, length/8, result, NULL);
}
if (R == 2)
length = 40;
pdf->keylen = length / 8;
pdf->key = cli_malloc(pdf->keylen);
if (!pdf->key) |
b2e7c931 |
return;
|
15a8a022 |
memcpy(pdf->key, result, pdf->keylen); |
cd94be7a |
dbg_printhex("md5", (const char *)result, 16); |
15a8a022 |
dbg_printhex("Candidate encryption key", pdf->key, pdf->keylen);
/* 7.6.3.3 Algorithm 6 */
if (R == 2) {
/* 7.6.3.3 Algorithm 4 */
memcpy(data, key_padding, 32); |
cd94be7a |
arc4_init(&arc4, (const uint8_t *)(pdf->key), pdf->keylen);
arc4_apply(&arc4, (uint8_t *)data, 32); |
15a8a022 |
dbg_printhex("computed U (R2)", data, 32);
if (!memcmp(data, U, 32))
password_empty = 1;
} else if (R >= 3) {
unsigned len = pdf->keylen;
unsigned char *d;
d = calloc(1, 32 + pdf->fileIDlen);
if (!(d))
return;
/* 7.6.3.3 Algorithm 5 */
memcpy(d, key_padding, 32);
memcpy(d+32, pdf->fileID, pdf->fileIDlen);
cl_hash_data("md5", d, 32 + pdf->fileIDlen, result, NULL);
memcpy(data, pdf->key, len);
|
cd94be7a |
arc4_init(&arc4, (const uint8_t *)data, len); |
15a8a022 |
arc4_apply(&arc4, result, 16);
for (i=1;i<=19;i++) {
unsigned j;
for (j=0;j<len;j++)
data[j] = pdf->key[j] ^ i;
|
cd94be7a |
arc4_init(&arc4, (const uint8_t *)data, len); |
15a8a022 |
arc4_apply(&arc4, result, 16);
}
dbg_printhex("fileID", pdf->fileID, pdf->fileIDlen); |
cd94be7a |
dbg_printhex("computed U (R>=3)", (const char *)result, 16); |
15a8a022 |
if (!memcmp(result, U, 16))
password_empty = 1; |
15fce6e0 |
free(d); |
15a8a022 |
} else { |
89d5207b |
cli_dbgmsg("check_user_password: invalid revision %d\n", R);
noisy_warnmsg("check_user_password: invalid revision %d\n", R); |
15a8a022 |
}
} else {
/* Supported R is in {2,3,4,5} */ |
89d5207b |
cli_dbgmsg("check_user_password: R value out of range\n");
noisy_warnmsg("check_user_password: R value out of range\n"); |
15a8a022 |
return; |
374b5aea |
} |
15a8a022 |
|
7606789f |
if (password_empty) { |
89d5207b |
cli_dbgmsg("check_user_password: user password is empty\n");
noisy_msg(pdf, "check_user_password: encrypted PDF found, user password is empty, will attempt to decrypt\n"); |
15a8a022 |
/* The key we computed above is the key used to encrypt the streams.
* We could decrypt it now if we wanted to */
pdf->flags |= 1 << DECRYPTABLE_PDF; |
7606789f |
} else { |
15a8a022 |
/* the key is not valid, we would need the user or the owner password to decrypt */ |
89d5207b |
cli_dbgmsg("check_user_password: user/owner password would be required for decryption\n");
noisy_warnmsg("check_user_password: encrypted PDF found, user password is NOT empty, cannot decrypt!\n"); |
7606789f |
}
}
|
1d0cdc67 |
enum enc_method parse_enc_method(const char *dict, unsigned len, const char *key, enum enc_method def) |
bcc68567 |
{
const char *q;
char *CFM = NULL; |
884b2e73 |
enum enc_method ret = ENC_UNKNOWN; |
15a8a022 |
|
bcc68567 |
if (!key) |
15a8a022 |
return def;
|
bcc68567 |
if (!strcmp(key, "Identity")) |
15a8a022 |
return ENC_IDENTITY;
|
cd94be7a |
q = pdf_getdict(dict, (int *)(&len), key); |
bcc68567 |
if (!q) |
15a8a022 |
return def;
|
bcc68567 |
CFM = pdf_readval(q, len, "/CFM");
if (CFM) { |
89d5207b |
cli_dbgmsg("parse_enc_method: %s CFM: %s\n", key, CFM); |
15a8a022 |
if (!strncmp(CFM,"V2", 2))
ret = ENC_V2;
else if (!strncmp(CFM,"AESV2",5))
ret = ENC_AESV2;
else if (!strncmp(CFM,"AESV3",5))
ret = ENC_AESV3;
else if (!strncmp(CFM,"None",4))
ret = ENC_NONE;
free(CFM); |
bcc68567 |
} |
15a8a022 |
|
884b2e73 |
return ret; |
bcc68567 |
}
|
e2b1880f |
void pdf_handle_enc(struct pdf_struct *pdf) |
7606789f |
{
struct pdf_obj *obj; |
cd94be7a |
uint32_t len, n, R, P, length, EM = 1, i, oulen; |
bcc68567 |
char *O, *U, *UE, *StmF, *StrF, *EFF; |
7606789f |
const char *q, *q2;
|
3f8016ce |
if (pdf->enc_objid == ~0u) |
15a8a022 |
return; |
3f8016ce |
if (!pdf->fileID) { |
89d5207b |
cli_dbgmsg("pdf_handle_enc: no file ID\n");
noisy_warnmsg("pdf_handle_enc: no file ID\n"); |
15a8a022 |
return; |
3f8016ce |
} |
15a8a022 |
|
89d5207b |
obj = find_obj(pdf, pdf->objs[0], pdf->enc_objid); |
3f8016ce |
if (!obj) { |
89d5207b |
cli_dbgmsg("pdf_handle_enc: can't find encrypted object %d %d\n", pdf->enc_objid>>8, pdf->enc_objid&0xff);
noisy_warnmsg("pdf_handle_enc: can't find encrypted object %d %d\n", pdf->enc_objid>>8, pdf->enc_objid&0xff); |
15a8a022 |
return; |
3f8016ce |
} |
15a8a022 |
|
e010ed37 |
len = obj->size; |
9280b4ea |
q = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf)
: (const char *)(obj->start + pdf->map); |
7606789f |
|
bcc68567 |
O = U = UE = StmF = StrF = EFF = NULL; |
7606789f |
do { |
374be101 |
|
15a8a022 |
pdf->enc_method_string = ENC_UNKNOWN;
pdf->enc_method_stream = ENC_UNKNOWN;
pdf->enc_method_embeddedfile = ENC_UNKNOWN;
P = pdf_readint(q, len, "/P");
if (P == ~0u) { |
89d5207b |
cli_dbgmsg("pdf_handle_enc: invalid P\n");
noisy_warnmsg("pdf_handle_enc: invalid P\n"); |
15a8a022 |
break;
} |
7606789f |
|
15a8a022 |
q2 = cli_memstr(q, len, "/Standard", 9);
if (!q2) { |
89d5207b |
cli_dbgmsg("pdf_handle_enc: /Standard not found\n");
noisy_warnmsg("pdf_handle_enc: /Standard not found\n"); |
15a8a022 |
break;
} |
7606789f |
|
15a8a022 |
/* we can have both of these:
* /AESV2/Length /Standard/Length
* /Length /Standard
* make sure we don't mistake AES's length for Standard's */
length = pdf_readint(q2, len - (q2 - q), "/Length");
if (length == ~0u)
length = pdf_readint(q, len, "/Length");
if (length < 40) { |
89d5207b |
cli_dbgmsg("pdf_handle_enc: invalid length: %d\n", length); |
15a8a022 |
length = 40;
} |
7606789f |
|
15a8a022 |
R = pdf_readint(q, len, "/R");
if (R == ~0u) { |
89d5207b |
cli_dbgmsg("pdf_handle_enc: invalid R\n");
noisy_warnmsg("pdf_handle_enc: invalid R\n"); |
15a8a022 |
break;
} |
7606789f |
|
15a8a022 |
if ((R > 5) || (R < 2)) { |
89d5207b |
cli_dbgmsg("pdf_handle_enc: R value outside supported range [2..5]\n");
noisy_warnmsg("pdf_handle_enc: R value outside supported range [2..5]\n"); |
15a8a022 |
break;
}
if (R < 5)
oulen = 32;
else
oulen = 48;
if (R == 2 || R == 3) {
pdf->enc_method_stream = ENC_V2;
pdf->enc_method_string = ENC_V2;
pdf->enc_method_embeddedfile = ENC_V2;
} else if (R == 4 || R == 5) {
EM = pdf_readbool(q, len, "/EncryptMetadata", 1);
StmF = pdf_readval(q, len, "/StmF");
StrF = pdf_readval(q, len, "/StrF");
EFF = pdf_readval(q, len, "/EFF");
n = len; |
cd94be7a |
pdf->CF = pdf_getdict(q, (int *)(&n), "/CF"); |
15a8a022 |
pdf->CF_n = n;
if (StmF) |
89d5207b |
cli_dbgmsg("pdf_handle_enc: StmF: %s\n", StmF); |
15a8a022 |
if (StrF) |
89d5207b |
cli_dbgmsg("pdf_handle_enc: StrF: %s\n", StrF); |
15a8a022 |
if (EFF) |
89d5207b |
cli_dbgmsg("pdf_handle_enc: EFF: %s\n", EFF); |
15a8a022 |
pdf->enc_method_stream = parse_enc_method(pdf->CF, n, StmF, ENC_IDENTITY);
pdf->enc_method_string = parse_enc_method(pdf->CF, n, StrF, ENC_IDENTITY);
pdf->enc_method_embeddedfile = parse_enc_method(pdf->CF, n, EFF, pdf->enc_method_stream);
free(StmF);
free(StrF);
free(EFF);
|
89d5207b |
cli_dbgmsg("pdf_handle_enc: EncryptMetadata: %s\n", EM ? "true" : "false"); |
15a8a022 |
if (R == 4) {
length = 128;
} else {
n = 0;
UE = pdf_readstring(q, len, "/UE", &n, NULL, 0);
length = 256;
}
}
if (length == ~0u)
length = 40;
n = 0;
O = pdf_readstring(q, len, "/O", &n, NULL, 0);
if (!O || n < oulen) { |
89d5207b |
cli_dbgmsg("pdf_handle_enc: invalid O: %d\n", n);
cli_dbgmsg("pdf_handle_enc: invalid O: %d\n", n); |
15a8a022 |
if (O)
dbg_printhex("invalid O", O, n);
break;
}
if (n > oulen) {
for (i=oulen;i<n;i++)
if (O[i])
break;
if (i != n) { |
89d5207b |
dbg_printhex("pdf_handle_enc: too long O", O, n);
noisy_warnmsg("pdf_handle_enc: too long O: %u", n); |
15a8a022 |
break;
}
}
n = 0;
U = pdf_readstring(q, len, "/U", &n, NULL, 0);
if (!U || n < oulen) { |
89d5207b |
cli_dbgmsg("pdf_handle_enc: invalid U: %u\n", n);
noisy_warnmsg("pdf_handle_enc: invalid U: %u\n", n); |
15a8a022 |
if (U)
dbg_printhex("invalid U", U, n);
break;
}
if (n > oulen) {
for (i=oulen;i<n;i++)
if (U[i])
break;
if (i != n) {
dbg_printhex("too long U", U, n);
break;
}
}
|
89d5207b |
cli_dbgmsg("pdf_handle_enc: Encrypt R: %d, P %x, length: %u\n", R, P, length); |
15a8a022 |
if (length % 8) { |
89d5207b |
cli_dbgmsg("pdf_handle_enc: wrong key length, not multiple of 8\n");
noisy_warnmsg("pdf_handle_enc: wrong key length, not multiple of 8\n"); |
15a8a022 |
break;
}
check_user_password(pdf, R, O, U, P, EM, UE, length, oulen); |
7606789f |
} while (0); |
15a8a022 |
|
7606789f |
free(O);
free(U); |
bbfad9ba |
free(UE); |
7606789f |
}
|
89d5207b |
/** |
e010ed37 |
* @brief Search pdf buffer for objects. Parse each.
* |
89d5207b |
* Newly found objects will be extracted after completion when the extraction for loop continues. |
e010ed37 |
*
* @param pdf Pdf struct that keeps track of all information found in the PDF. |
89d5207b |
* @param objstm Pointer to an object stream to parse. |
e010ed37 |
* |
89d5207b |
* @return cl_error_t Error code.
*/
cl_error_t pdf_find_and_parse_objs_in_objstm(struct pdf_struct *pdf, struct objstm_struct *objstm)
{
cl_error_t status = CL_EFORMAT;
cl_error_t retval = CL_EPARSE; |
e010ed37 |
int32_t alerts = 0; |
89d5207b |
uint32_t badobjects = 0;
size_t i = 0;
struct pdf_obj* obj = NULL;
|
8ad8d01b |
if ((NULL == objstm) || (NULL == objstm->streambuf)) {
status = CL_EARG;
goto done;
}
|
e010ed37 |
if ((0 == objstm->first) ||
(0 == objstm->streambuf_len) || |
89d5207b |
(0 == objstm->n))
{
cli_dbgmsg("pdf_find_and_parse_objs_in_objstm: Empty object stream.\n");
goto done;
}
if (objstm->first >= objstm->streambuf_len)
{
cli_dbgmsg("pdf_find_and_parse_objs_in_objstm: Invalid objstm values. Offset of first obj greater than stream length.\n");
goto done;
}
/* Process each object */
for (i = 0; i < objstm->n; i++)
{
obj = NULL;
if (cli_checktimelimit(pdf->ctx) != CL_SUCCESS) {
cli_errmsg("Timeout reached in the PDF parser while parsing object stream.\n");
status = CL_ETIMEOUT;
goto done;
}
/* Find object */
retval = pdf_findobj_in_objstm(pdf, objstm, &obj); |
e010ed37 |
|
89d5207b |
if (retval != CL_SUCCESS)
{
cli_dbgmsg("pdf_find_and_parse_objs_in_objstm: Fewer objects in stream than expected: %u found, %u expected.\n",
objstm->nobjs_found, objstm->n);
badobjects++;
pdf->stats.ninvalidobjs++;
break;
}
cli_dbgmsg("pdf_find_and_parse_objs_in_objstm: Found object %u %u in object stream at offset: %u\n", obj->id >> 8, obj->id & 0xff, obj->start);
if (cli_checktimelimit(pdf->ctx) != CL_SUCCESS) {
cli_errmsg("Timeout reached in the PDF parser while parsing object stream.\n");
status = CL_ETIMEOUT;
goto done;
}
/* Parse object */
pdf_parseobj(pdf, obj);
}
if (alerts) {
status = CL_VIRUS;
goto done;
}
else if (badobjects) {
status = CL_EFORMAT;
goto done;
} |
e010ed37 |
|
89d5207b |
status = CL_SUCCESS;
done:
return status;
}
/**
* @brief Search pdf buffer for objects. Parse each and then extract each. |
e010ed37 |
* |
89d5207b |
* @param pdf Pdf struct that keeps track of all information found in the PDF.
* @param alerts[in/out] The number of alerts, relevant in ALLMATCH mode. |
e010ed37 |
* |
89d5207b |
* @return cl_error_t Error code.
*/
cl_error_t pdf_find_and_extract_objs(struct pdf_struct *pdf, uint32_t *alerts)
{
cl_error_t status = CL_SUCCESS;
int32_t rv = 0; |
e010ed37 |
unsigned int i = 0; |
89d5207b |
uint32_t badobjects = 0; |
d7979d4f |
cli_ctx *ctx = pdf->ctx; |
89d5207b |
/* parse PDF and find obj offsets */
while (CL_BREAK != (rv = pdf_findobj(pdf))) {
if (rv == CL_EMEM) {
break;
}
}
if (rv == -1)
pdf->flags |= 1 << BAD_PDF_TOOMANYOBJS;
/* must parse after finding all objs, so we can flag indirect objects */
for (i=0; i < pdf->nobjs; i++) {
struct pdf_obj *obj = pdf->objs[i];
if (cli_checktimelimit(pdf->ctx) != CL_SUCCESS) {
cli_errmsg("pdf_find_and_extract_objs: Timeout reached in the PDF parser while parsing objects.\n");
status = CL_ETIMEOUT;
goto done;
}
pdf_parseobj(pdf, obj);
}
pdf_handle_enc(pdf);
if (pdf->flags & (1 << ENCRYPTED_PDF))
cli_dbgmsg("pdf_find_and_extract_objs: encrypted pdf found, %s!\n",
(pdf->flags & (1 << DECRYPTABLE_PDF)) ?
"decryptable" : "not decryptable, stream will probably fail to decompress");
|
f61e92da |
if (SCAN_HEURISTIC_ENCRYPTED_DOC && |
89d5207b |
(pdf->flags & (1 << ENCRYPTED_PDF)) &&
!(pdf->flags & (1 << DECRYPTABLE_PDF)))
{
/* It is encrypted, and a password/key needs to be supplied to decrypt.
* This doesn't trigger for PDFs that are encrypted but don't need
* a password to decrypt */
status = cli_append_virus(pdf->ctx, "Heuristics.Encrypted.PDF"); |
e010ed37 |
if (status == CL_VIRUS) { |
89d5207b |
alerts++; |
d7979d4f |
if (SCAN_ALLMATCHES) |
89d5207b |
status = CL_CLEAN;
}
}
if (!status) {
status = run_pdf_hooks(pdf, PDF_PHASE_PARSED, -1, -1);
cli_dbgmsg("pdf_find_and_extract_objs: (parsed hooks) returned %d\n", status);
if (status == CL_VIRUS) {
alerts++; |
d7979d4f |
if (SCAN_ALLMATCHES) { |
89d5207b |
status = CL_CLEAN;
}
}
}
/* extract PDF objs */
for (i=0; !status && i < pdf->nobjs; i++) {
struct pdf_obj *obj = pdf->objs[i];
if (cli_checktimelimit(pdf->ctx) != CL_SUCCESS) {
cli_errmsg("pdf_find_and_extract_objs: Timeout reached in the PDF parser while extracting objects.\n");
status = CL_ETIMEOUT;
goto done;
}
status = pdf_extract_obj(pdf, obj, PDF_EXTRACT_OBJ_SCAN);
switch (status) {
case CL_EFORMAT:
/* Don't halt on one bad object */
cli_dbgmsg("pdf_find_and_extract_objs: Format error when extracting object, skipping to the next object.\n");
badobjects++;
pdf->stats.ninvalidobjs++;
status = CL_CLEAN;
break;
case CL_VIRUS:
alerts++; |
d7979d4f |
if (SCAN_ALLMATCHES) { |
89d5207b |
status = CL_CLEAN;
}
break;
default:
break;
}
}
done:
if (!status && badobjects) {
status = CL_EFORMAT;
}
return status;
}
/**
* @brief Primary function for parsing and scanning a PDF. |
e010ed37 |
* |
89d5207b |
* @param dir Filepath for temp file. |
e010ed37 |
* @param ctx clam scan context structure. |
89d5207b |
* @param offset offset of pdf in ctx->fmap |
e010ed37 |
* |
89d5207b |
* @return int Returns cl_error_t status value.
*/ |
e7a27135 |
int cli_pdf(const char *dir, cli_ctx *ctx, off_t offset)
{ |
89d5207b |
cl_error_t rc = CL_SUCCESS; |
e7a27135 |
struct pdf_struct pdf;
fmap_t *map = *ctx->fmap;
size_t size = map->len - offset;
off_t versize = size > 1032 ? 1032 : size;
off_t map_off, bytesleft; |
f842e965 |
unsigned long xref; |
8ad8d01b |
long temp_long; |
53c957a9 |
const char *pdfver, *tmp, *start, *eofmap, *q, *eof; |
693757a1 |
unsigned i, alerts = 0; |
89d5207b |
unsigned int objs_found = 0; |
930b9395 |
#if HAVE_JSON
json_object *pdfobj=NULL; |
8ec930c3 |
char *begin, *end, *p1; |
930b9395 |
#endif |
e7a27135 |
cli_dbgmsg("in cli_pdf(%s)\n", dir);
memset(&pdf, 0, sizeof(pdf)); |
3643f3d2 |
pdf.ctx = ctx;
pdf.dir = dir; |
7606789f |
pdf.enc_objid = ~0u; |
e7a27135 |
pdfver = start = fmap_need_off_once(map, offset, versize);
/* Check PDF version */
if (!pdfver) { |
15a8a022 |
cli_errmsg("cli_pdf: mmap() failed (1)\n"); |
89d5207b |
rc = CL_EMAP;
goto done; |
e7a27135 |
} |
15a8a022 |
|
930b9395 |
#if HAVE_JSON
if (ctx->wrkproperty)
pdfobj = cli_jsonobj(ctx->wrkproperty, "PDFStats");
#endif
|
e7a27135 |
/* offset is 0 when coming from filetype2 */ |
53c957a9 |
tmp = cli_memstr(pdfver, versize, "%PDF-", 5);
if (!tmp) { |
15a8a022 |
cli_dbgmsg("cli_pdf: no PDF- header found\n");
noisy_warnmsg("cli_pdf: no PDF- header found\n"); |
63803da5 |
#if HAVE_JSON |
69080de3 |
pdf_export_json(&pdf); |
63803da5 |
#endif |
89d5207b |
rc = CL_SUCCESS;
goto done; |
e7a27135 |
} |
15a8a022 |
|
53c957a9 |
versize -= tmp - pdfver;
pdfver = tmp;
if (versize < 8) { |
89d5207b |
rc = CL_EFORMAT;
goto done; |
53c957a9 |
}
|
15a8a022 |
/* Check for PDF-1.[0-9]. Although 1.7 is highest now, allow for future versions */ |
e7a27135 |
if (pdfver[5] != '1' || pdfver[6] != '.' || |
15a8a022 |
pdfver[7] < '1' || pdfver[7] > '9') {
pdf.flags |= 1 << BAD_PDF_VERSION;
cli_dbgmsg("cli_pdf: bad pdf version: %.8s\n", pdfver); |
930b9395 |
#if HAVE_JSON
if (pdfobj)
cli_jsonbool(pdfobj, "BadVersion", 1);
#endif |
8ec930c3 |
} else {
#if HAVE_JSON
if (pdfobj) {
begin = (char *)(pdfver+5);
end = begin+2;
strtoul(end, &end, 10);
p1 = cli_calloc((end - begin) + 2, 1);
if (p1) {
strncpy(p1, begin, end - begin);
p1[end - begin] = '\0';
cli_jsonstr(pdfobj, "PDFVersion", p1);
free(p1);
}
}
#endif |
e7a27135 |
} |
15a8a022 |
|
e7a27135 |
if (pdfver != start || offset) { |
15a8a022 |
pdf.flags |= 1 << BAD_PDF_HEADERPOS; |
31064b37 |
cli_dbgmsg("cli_pdf: PDF header is not at position 0: %lld\n", (long long)(pdfver - start + offset)); |
930b9395 |
#if HAVE_JSON
if (pdfobj)
cli_jsonbool(pdfobj, "BadVersionLocation", 1);
#endif |
e7a27135 |
} |
15a8a022 |
|
e7a27135 |
offset += pdfver - start;
/* find trailer and xref, don't fail if not found */ |
8f6bf9fc |
map_off = (off_t)map->len - 2048; |
e7a27135 |
if (map_off < 0) |
15a8a022 |
map_off = 0;
|
e7a27135 |
bytesleft = map->len - map_off; |
15a8a022 |
|
e7a27135 |
eofmap = fmap_need_off_once(map, map_off, bytesleft);
if (!eofmap) { |
15a8a022 |
cli_errmsg("cli_pdf: mmap() failed (2)\n"); |
89d5207b |
rc = CL_EMAP;
goto done; |
e7a27135 |
} |
15a8a022 |
|
e7a27135 |
eof = eofmap + bytesleft;
for (q=&eofmap[bytesleft-5]; q > eofmap; q--) { |
15a8a022 |
if (memcmp(q, "%%EOF", 5) == 0)
break; |
e7a27135 |
} |
15a8a022 |
|
e7a27135 |
if (q <= eofmap) { |
15a8a022 |
pdf.flags |= 1 << BAD_PDF_TRAILER;
cli_dbgmsg("cli_pdf: %%%%EOF not found\n"); |
c7fd0220 |
#if HAVE_JSON
if (pdfobj)
cli_jsonbool(pdfobj, "NoEOF", 1);
#endif |
e7a27135 |
} else { |
15a8a022 |
const char *t;
/*size = q - eofmap + map_off;*/
q -= 9;
for (;q > eofmap;q--) {
if (memcmp(q, "startxref", 9) == 0)
break;
}
if (q <= eofmap) {
pdf.flags |= 1 << BAD_PDF_TRAILER;
cli_dbgmsg("cli_pdf: startxref not found\n"); |
c7fd0220 |
#if HAVE_JSON
if (pdfobj)
cli_jsonbool(pdfobj, "NoXREF", 1);
#endif |
15a8a022 |
} else {
for (t=q;t > eofmap; t--) {
if (memcmp(t,"trailer",7) == 0)
break;
}
pdf_parse_trailer(&pdf, eofmap, eof - eofmap);
q += 9;
while (q < eof && (*q == ' ' || *q == '\n' || *q == '\r')) { q++; }
|
8ad8d01b |
if (CL_SUCCESS != cli_strntol_wrap(q, q - eofmap + map_off, 0, 10, &temp_long)) { |
bf6e777f |
cli_dbgmsg("cli_pdf: failed to parse PDF trailer xref\n"); |
15a8a022 |
pdf.flags |= 1 << BAD_PDF_TRAILER; |
8ad8d01b |
} else if (temp_long < 0) {
cli_dbgmsg("cli_pdf: Encountered invalid negative PDF trailer xref (%ld).\n", temp_long);
pdf.flags |= 1 << BAD_PDF_TRAILER;
} else {
xref = (unsigned long)temp_long; |
bf6e777f |
bytesleft = map->len - offset - xref;
if (bytesleft > 4096)
bytesleft = 4096;
q = fmap_need_off_once(map, offset + xref, bytesleft);
if (!q || xrefCheck(q, q+bytesleft) == -1) {
cli_dbgmsg("cli_pdf: did not find valid xref\n");
pdf.flags |= 1 << BAD_PDF_TRAILER;
}
} |
15a8a022 |
} |
e7a27135 |
}
|
15a8a022 |
size -= offset; |
e7a27135 |
pdf.size = size; |
dc200c6b |
pdf.map = fmap_need_off(map, offset, size); |
e7a27135 |
if (!pdf.map) { |
15a8a022 |
cli_errmsg("cli_pdf: mmap() failed (3)\n"); |
89d5207b |
rc = CL_EMAP;
goto done; |
e7a27135 |
} |
15a8a022 |
pdf.startoff = offset;
|
f73212dc |
rc = run_pdf_hooks(&pdf, PDF_PHASE_PRE, -1, -1); |
d7979d4f |
if ((rc == CL_VIRUS) && SCAN_ALLMATCHES) { |
693757a1 |
cli_dbgmsg("cli_pdf: (pre hooks) returned %d\n", rc);
alerts++;
rc = CL_CLEAN; |
15a8a022 |
} else if (rc) {
cli_dbgmsg("cli_pdf: (pre hooks) returning %d\n", rc); |
9e60856f |
|
89d5207b |
rc = rc == CL_BREAK ? CL_CLEAN : rc;
goto done; |
693757a1 |
}
|
89d5207b |
/* |
e010ed37 |
* Find and extract all objects in the PDF. |
89d5207b |
* New experimental recursive methodology that adds objects from object streams.
*/
objs_found = pdf.nobjs;
rc = pdf_find_and_extract_objs(&pdf, &alerts); |
9e60856f |
|
89d5207b |
if (pdf.nobjs <= objs_found) {
cli_dbgmsg("cli_pdf: pdf_find_and_extract_objs did not find any new objects!\n");
} else {
cli_dbgmsg("cli_pdf: pdf_find_and_extract_objs found %d new objects.\n", pdf.nobjs - objs_found); |
3643f3d2 |
}
|
f984f75b |
if (pdf.flags & (1 << ENCRYPTED_PDF)) |
15a8a022 |
pdf.flags &= ~ ((1 << BAD_FLATESTART) | (1 << BAD_STREAMSTART) | (1 << BAD_ASCIIDECODE)); |
f984f75b |
|
89d5207b |
if (pdf.flags && !rc) { |
15a8a022 |
cli_dbgmsg("cli_pdf: flags 0x%02x\n", pdf.flags);
rc = run_pdf_hooks(&pdf, PDF_PHASE_END, -1, -1); |
693757a1 |
if (rc == CL_VIRUS) {
alerts++; |
d7979d4f |
if (SCAN_ALLMATCHES) { |
693757a1 |
rc = CL_CLEAN;
}
} |
15a8a022 |
|
d7979d4f |
if (!rc && SCAN_HEURISTICS && (ctx->dconf->other & OTHER_CONF_PDFNAMEOBJ)) { |
693757a1 |
if (pdf.flags & (1 << ESCAPED_COMMON_PDFNAME)) {
/* for example /Fl#61te#44#65#63#6f#64#65 instead of /FlateDecode */ |
cbf5017a |
cli_append_possibly_unwanted(ctx, "Heuristics.PDF.ObfuscatedNameObject"); |
693757a1 |
}
} |
2a599782 |
#if 0 |
53cbdee3 |
/* TODO: find both trailers, and /Encrypt settings */
if (pdf.flags & (1 << LINEARIZED_PDF))
pdf.flags &= ~ (1 << BAD_ASCIIDECODE);
if (pdf.flags & (1 << MANY_FILTERS))
pdf.flags &= ~ (1 << BAD_ASCIIDECODE);
if (!rc && (pdf.flags &
((1 << BAD_PDF_TOOMANYOBJS) | (1 << BAD_STREAM_FILTERS) |
(1<<BAD_FLATE) | (1<<BAD_ASCIIDECODE)|
(1<<UNTERMINATED_OBJ_DICT) | (1<<UNKNOWN_FILTER)))) {
rc = CL_EUNPACK;
} |
eb270d5a |
#endif |
ab564992 |
} |
f4819816 |
|
89d5207b |
done: |
693757a1 |
if (alerts) {
rc = CL_VIRUS;
} |
89d5207b |
else if (!rc && pdf.stats.ninvalidobjs > 0) { |
f4819816 |
rc = CL_EFORMAT;
}
|
63803da5 |
#if HAVE_JSON |
ebcca55f |
pdf_export_json(&pdf); |
63803da5 |
#endif |
ebcca55f |
|
89d5207b |
if (pdf.objstms) {
for (i = 0; i < pdf.nobjstms; i++) {
if (pdf.objstms[i]) {
if (pdf.objstms[i]->streambuf) {
free(pdf.objstms[i]->streambuf);
pdf.objstms[i]->streambuf = NULL;
}
free(pdf.objstms[i]);
pdf.objstms[i] = NULL;
}
}
free(pdf.objstms);
pdf.objstms = NULL;
}
if (NULL != pdf.objs) {
for (i = 0; i < pdf.nobjs; i++) {
if (NULL != pdf.objs[i]) {
free(pdf.objs[i]);
pdf.objs[i] = NULL;
}
}
free(pdf.objs);
pdf.objs = NULL;
}
if (pdf.fileID) {
free(pdf.fileID);
pdf.fileID = NULL;
}
if (pdf.key) {
free(pdf.key);
pdf.key = NULL;
} |
f4819816 |
|
a95d300f |
/* PDF hooks may abort, don't return CL_BREAK to caller! */ |
89d5207b |
rc = (rc == CL_BREAK) ? CL_CLEAN : rc;
cli_dbgmsg("cli_pdf: returning %d\n", rc);
return rc; |
e7a27135 |
}
|
89d5207b |
/**
* @brief Skip the rest of the current line, and find the start of the next line. |
e010ed37 |
* |
89d5207b |
* @param ptr Current offset into buffer. |
e010ed37 |
* @param len Remaining bytes in buffer.
* |
89d5207b |
* @return const char* Address of next line, or NULL if no next line in buffer. |
bce73fe9 |
*/
static const char *
pdf_nextlinestart(const char *ptr, size_t len)
{ |
c1e7159d |
if (!ptr || (0 == len)) {
/* Invalid args */
return NULL;
}
|
15a8a022 |
while(strchr("\r\n", *ptr) == NULL) {
if(--len == 0L)
return NULL;
ptr++;
}
while(strchr("\r\n", *ptr) != NULL) {
if(--len == 0L)
return NULL;
ptr++;
}
return ptr; |
bce73fe9 |
} |
9be10a55 |
|
89d5207b |
/**
* @brief Return the start of the next PDF object. |
e010ed37 |
* |
ef8219b8 |
* This assumes that we're not in a stream. |
e010ed37 |
* |
89d5207b |
* @param ptr Current offset into buffer. |
e010ed37 |
* @param len Remaining bytes in buffer.
*
* @return const char* Address of next object in the buffer, or NULL if there is none in the buffer. |
ef8219b8 |
*/
static const char *
pdf_nextobject(const char *ptr, size_t len)
{ |
15a8a022 |
const char *p;
int inobject = 1;
while(len) {
switch(*ptr) {
case '\n':
case '\r':
case '%': /* comment */
p = pdf_nextlinestart(ptr, len);
if(p == NULL)
return NULL;
len -= (size_t)(p - ptr);
ptr = p;
inobject = 0;
break;
case ' ':
case '\t':
case '[': /* Start of an array object */
case '\v':
case '\f':
case '<': /* Start of a dictionary object */
inobject = 0;
ptr++;
len--;
break;
case '/': /* Start of a name object */
return ptr;
case '(': /* start of JS */
return ptr;
default:
if(!inobject) {
/* TODO: parse and return object type */
return ptr;
}
ptr++;
len--;
}
}
return NULL; |
9be10a55 |
} |
49bc4992 |
/* PDF statistics */ |
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void ASCIIHexDecode_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.nasciihexdecode++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void ASCII85Decode_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.nascii85decode++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void EmbeddedFile_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.nembeddedfile++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void FlateDecode_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.nflate++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Image_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.nimage++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void LZWDecode_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.nlzw++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void RunLengthDecode_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.nrunlengthdecode++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void CCITTFaxDecode_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.nfaxdecode++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void JBIG2Decode_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx; |
cd94be7a |
struct json_object *pdfobj, *jbig2arr;
UNUSEDPARAM(obj);
UNUSEDPARAM(act); |
49bc4992 |
|
084707b3 |
if (!(pdf))
return;
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA)) |
0bed896b |
return;
|
084707b3 |
if (!(pdf->ctx->wrkproperty))
return;
pdfobj = cli_jsonobj(pdf->ctx->wrkproperty, "PDFStats");
if (!(pdfobj))
return;
jbig2arr = cli_jsonarray(pdfobj, "JBIG2Objects");
if (!(jbig2arr))
return;
cli_jsonint_array(jbig2arr, obj->id>>8);
pdf->stats.njbig2decode++; |
49bc4992 |
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void DCTDecode_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.ndctdecode++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void JPXDecode_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.njpxdecode++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Crypt_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.ncrypt++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Standard_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.nstandard++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Sig_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.nsigned++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void JavaScript_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx; |
cd94be7a |
struct json_object *pdfobj, *jbig2arr;
UNUSEDPARAM(act); |
cd1d52d1 |
|
49bc4992 |
if (!(pdf))
return;
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA)) |
0bed896b |
return;
|
cd1d52d1 |
if (!(pdf->ctx->wrkproperty))
return;
pdfobj = cli_jsonobj(pdf->ctx->wrkproperty, "PDFStats");
if (!(pdfobj))
return;
jbig2arr = cli_jsonarray(pdfobj, "JavascriptObjects");
if (!(jbig2arr))
return;
cli_jsonint_array(jbig2arr, obj->id>>8);
|
49bc4992 |
pdf->stats.njs++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void OpenAction_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.nopenaction++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Launch_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.nlaunch++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Page_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
49bc4992 |
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
49bc4992 |
if (!(pdf))
return;
pdf->stats.npage++;
} |
63803da5 |
#endif |
49bc4992 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Author_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
063f0d25 |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx;
|
cd94be7a |
UNUSEDPARAM(act);
|
063f0d25 |
if (!(pdf))
return;
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA)) |
0bed896b |
return;
|
9d33052f |
if (!(pdf->stats.author)) { |
89d5207b |
const char *objstart = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf)
: (const char *)(obj->start + pdf->map);
|
9d33052f |
pdf->stats.author = cli_calloc(1, sizeof(struct pdf_stats_entry));
if (!(pdf->stats.author))
return; |
e010ed37 |
pdf->stats.author->data = pdf_parse_string(pdf, obj, objstart, obj->size, "/Author", NULL, &(pdf->stats.author->meta)); |
9d33052f |
} |
063f0d25 |
} |
63803da5 |
#endif |
063f0d25 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Creator_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
063f0d25 |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx;
|
cd94be7a |
UNUSEDPARAM(act);
|
063f0d25 |
if (!(pdf))
return;
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA)) |
0bed896b |
return;
|
9d33052f |
if (!(pdf->stats.creator)) { |
89d5207b |
const char *objstart = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf)
: (const char *)(obj->start + pdf->map);
|
9d33052f |
pdf->stats.creator = cli_calloc(1, sizeof(struct pdf_stats_entry));
if (!(pdf->stats.creator))
return; |
e010ed37 |
pdf->stats.creator->data = pdf_parse_string(pdf, obj, objstart, obj->size, "/Creator", NULL, &(pdf->stats.creator->meta)); |
9d33052f |
} |
063f0d25 |
} |
63803da5 |
#endif |
063f0d25 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void ModificationDate_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
063f0d25 |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx;
|
cd94be7a |
UNUSEDPARAM(act);
|
063f0d25 |
if (!(pdf))
return;
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA)) |
0bed896b |
return;
|
9d33052f |
if (!(pdf->stats.modificationdate)) { |
89d5207b |
const char *objstart = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf)
: (const char *)(obj->start + pdf->map);
|
9d33052f |
pdf->stats.modificationdate = cli_calloc(1, sizeof(struct pdf_stats_entry));
if (!(pdf->stats.modificationdate))
return; |
e010ed37 |
pdf->stats.modificationdate->data = pdf_parse_string(pdf, obj, objstart, obj->size, "/ModDate", NULL, &(pdf->stats.modificationdate->meta)); |
9d33052f |
} |
063f0d25 |
} |
63803da5 |
#endif |
063f0d25 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void CreationDate_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
063f0d25 |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx;
|
cd94be7a |
UNUSEDPARAM(act);
|
063f0d25 |
if (!(pdf))
return;
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA)) |
0bed896b |
return;
|
9d33052f |
if (!(pdf->stats.creationdate)) { |
89d5207b |
const char *objstart = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf)
: (const char *)(obj->start + pdf->map);
|
9d33052f |
pdf->stats.creationdate = cli_calloc(1, sizeof(struct pdf_stats_entry));
if (!(pdf->stats.creationdate))
return; |
e010ed37 |
pdf->stats.creationdate->data = pdf_parse_string(pdf, obj, objstart, obj->size, "/CreationDate", NULL, &(pdf->stats.creationdate->meta)); |
9d33052f |
} |
063f0d25 |
} |
63803da5 |
#endif |
063f0d25 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Producer_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
063f0d25 |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx;
|
cd94be7a |
UNUSEDPARAM(act);
|
063f0d25 |
if (!(pdf))
return;
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA)) |
0bed896b |
return;
|
9d33052f |
if (!(pdf->stats.producer)) { |
89d5207b |
const char *objstart = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf)
: (const char *)(obj->start + pdf->map);
|
9d33052f |
pdf->stats.producer = cli_calloc(1, sizeof(struct pdf_stats_entry));
if (!(pdf->stats.producer))
return; |
e010ed37 |
pdf->stats.producer->data = pdf_parse_string(pdf, obj, objstart, obj->size, "/Producer", NULL, &(pdf->stats.producer->meta)); |
9d33052f |
} |
063f0d25 |
} |
63803da5 |
#endif |
063f0d25 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Title_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
754f976a |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx;
|
cd94be7a |
UNUSEDPARAM(act);
|
754f976a |
if (!(pdf))
return;
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA)) |
0bed896b |
return;
|
9d33052f |
if (!(pdf->stats.title)) { |
89d5207b |
const char *objstart = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf)
: (const char *)(obj->start + pdf->map);
|
9d33052f |
pdf->stats.title = cli_calloc(1, sizeof(struct pdf_stats_entry));
if (!(pdf->stats.title))
return; |
e010ed37 |
pdf->stats.title->data = pdf_parse_string(pdf, obj, objstart, obj->size, "/Title", NULL, &(pdf->stats.title->meta)); |
9d33052f |
} |
754f976a |
} |
63803da5 |
#endif |
754f976a |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Keywords_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
754f976a |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx;
|
cd94be7a |
UNUSEDPARAM(act);
|
754f976a |
if (!(pdf))
return;
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA)) |
0bed896b |
return;
|
9d33052f |
if (!(pdf->stats.keywords)) { |
89d5207b |
const char *objstart = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf)
: (const char *)(obj->start + pdf->map);
|
9d33052f |
pdf->stats.keywords = cli_calloc(1, sizeof(struct pdf_stats_entry));
if (!(pdf->stats.keywords))
return; |
e010ed37 |
pdf->stats.keywords->data = pdf_parse_string(pdf, obj, objstart, obj->size, "/Keywords", NULL, &(pdf->stats.keywords->meta)); |
9d33052f |
} |
754f976a |
} |
63803da5 |
#endif |
754f976a |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Subject_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
754f976a |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx;
|
cd94be7a |
UNUSEDPARAM(act);
|
754f976a |
if (!(pdf))
return;
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA)) |
0bed896b |
return;
|
9d33052f |
if (!(pdf->stats.subject)) { |
89d5207b |
const char *objstart = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf)
: (const char *)(obj->start + pdf->map);
|
9d33052f |
pdf->stats.subject = cli_calloc(1, sizeof(struct pdf_stats_entry));
if (!(pdf->stats.subject))
return; |
e010ed37 |
pdf->stats.subject->data = pdf_parse_string(pdf, obj, objstart, obj->size, "/Subject", NULL, &(pdf->stats.subject->meta)); |
9d33052f |
} |
754f976a |
} |
63803da5 |
#endif |
754f976a |
|
63803da5 |
#if HAVE_JSON |
09ff1409 |
static void RichMedia_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act)
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
09ff1409 |
if (!(pdf))
return;
pdf->stats.nrichmedia++;
} |
63803da5 |
#endif |
09ff1409 |
|
63803da5 |
#if HAVE_JSON |
09ff1409 |
static void AcroForm_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act)
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
09ff1409 |
if (!(pdf))
return;
pdf->stats.nacroform++;
} |
63803da5 |
#endif |
09ff1409 |
|
63803da5 |
#if HAVE_JSON |
09ff1409 |
static void XFA_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act)
{ |
cd94be7a |
UNUSEDPARAM(obj);
UNUSEDPARAM(act);
|
09ff1409 |
if (!(pdf))
return;
pdf->stats.nxfa++;
} |
63803da5 |
#endif |
09ff1409 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Pages_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
ca78e3b3 |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx; |
440f1fff |
struct pdf_array *array; |
89d5207b |
const char *objstart = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf)
: (const char *)(obj->start + pdf->map); |
440f1fff |
const char *begin;
unsigned long npages=0, count; |
8ad8d01b |
long temp_long; |
440f1fff |
struct pdf_array_node *node;
json_object *pdfobj; |
9280b4ea |
size_t countsize = 0; |
440f1fff |
|
cd94be7a |
UNUSEDPARAM(act);
|
440f1fff |
if (!(pdf) || !(pdf->ctx->wrkproperty))
return;
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA)) |
0bed896b |
return;
|
440f1fff |
pdfobj = cli_jsonobj(pdf->ctx->wrkproperty, "PDFStats");
if (!(pdfobj))
return;
|
e010ed37 |
begin = cli_memstr(objstart, obj->size, "/Kids", 5); |
440f1fff |
if (!(begin))
return;
begin += 5;
|
e010ed37 |
array = pdf_parse_array(pdf, obj, obj->size, (char *)begin, NULL); |
dd101bee |
if (!(array)) {
cli_jsonbool(pdfobj, "IncorrectPagesCount", 1); |
7a98488d |
return; |
dd101bee |
} |
7a98488d |
for (node = array->nodes; node != NULL; node = node->next)
if (node->datasz) |
dd101bee |
if (strchr((char *)(node->data), 'R')) |
7a98488d |
npages++; |
440f1fff |
|
e010ed37 |
begin = cli_memstr(objstart, obj->size, "/Count", 6); |
440f1fff |
if (!(begin)) {
cli_jsonbool(pdfobj, "IncorrectPagesCount", 1);
goto cleanup;
}
begin += 6; |
e010ed37 |
while (((size_t)(begin - objstart) < obj->size) && isspace(begin[0])) |
440f1fff |
begin++;
|
e010ed37 |
if ((size_t)(begin - objstart) >= obj->size) { |
440f1fff |
goto cleanup;
}
|
e010ed37 |
countsize = (obj->objstm) ? (size_t)(obj->start + obj->objstm->streambuf + obj->size - begin)
: (size_t)(obj->start + pdf->map + obj->size - begin); |
9280b4ea |
|
8ad8d01b |
if (CL_SUCCESS != cli_strntol_wrap(begin, countsize, 0, 10, &temp_long)) {
cli_jsonbool(pdfobj, "IncorrectPagesCount", 1);
} else if (temp_long < 0) {
cli_jsonbool(pdfobj, "IncorrectPagesCount", 1);
} else {
count = (unsigned long)temp_long;
if (count != npages) { |
440f1fff |
cli_jsonbool(pdfobj, "IncorrectPagesCount", 1); |
bf6e777f |
} |
8ad8d01b |
} |
440f1fff |
cleanup:
pdf_free_array(array); |
ca78e3b3 |
} |
63803da5 |
#endif |
ca78e3b3 |
|
63803da5 |
#if HAVE_JSON |
224d1c4d |
static void Colors_cb(struct pdf_struct *pdf, struct pdf_obj *obj, struct pdfname_action *act) |
ca78e3b3 |
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx; |
ca78e3b3 |
json_object *colorsobj, *pdfobj;
unsigned long ncolors; |
8ad8d01b |
long temp_long; |
89d5207b |
char *p1;
const char *objstart = (obj->objstm) ? (const char *)(obj->start + obj->objstm->streambuf)
: (const char *)(obj->start + pdf->map); |
cd94be7a |
UNUSEDPARAM(act); |
ca78e3b3 |
if (!(pdf) || !(pdf->ctx) || !(pdf->ctx->wrkproperty))
return;
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA)) |
0bed896b |
return;
|
e010ed37 |
p1 = (char *)cli_memstr(objstart, obj->size, "/Colors", 7); |
ca78e3b3 |
if (!(p1))
return;
p1 += 7;
/* Ensure that we have at least one whitespace character plus at least one number */ |
e010ed37 |
if (obj->size - (size_t)(p1 - objstart) < 2) |
ca78e3b3 |
return;
|
e010ed37 |
while (((size_t)(p1 - objstart) < obj->size) && isspace(p1[0])) |
ca78e3b3 |
p1++;
|
e010ed37 |
if ((size_t)(p1 - objstart) == obj->size) |
ca78e3b3 |
return;
|
e010ed37 |
if (CL_SUCCESS != cli_strntol_wrap(p1, (size_t)((p1 - objstart) - obj->size), 0, 10, &temp_long)) { |
8ad8d01b |
return;
} else if (temp_long < 0) { |
bf6e777f |
return; |
8ad8d01b |
}
ncolors = (unsigned long)temp_long; |
ca78e3b3 |
/* We only care if the number of colors > 2**24 */
if (ncolors < 1<<24)
return;
pdfobj = cli_jsonobj(pdf->ctx->wrkproperty, "PDFStats");
if (!(pdfobj))
return;
colorsobj = cli_jsonarray(pdfobj, "BigColors");
if (!(colorsobj))
return;
cli_jsonint_array(colorsobj, obj->id>>8);
} |
63803da5 |
#endif |
ca78e3b3 |
|
63803da5 |
#if HAVE_JSON |
ebcca55f |
static void pdf_export_json(struct pdf_struct *pdf)
{ |
d7979d4f |
cli_ctx *ctx = pdf->ctx; |
ebcca55f |
json_object *pdfobj; |
c7fd0220 |
unsigned long i; |
ebcca55f |
if (!(pdf))
return;
|
a5570b79 |
if (!(pdf->ctx)) {
goto cleanup;
} |
ebcca55f |
|
d7979d4f |
if (!(SCAN_COLLECT_METADATA) || !(pdf->ctx->wrkproperty)) { |
a5570b79 |
goto cleanup;
} |
ebcca55f |
|
084707b3 |
pdfobj = cli_jsonobj(pdf->ctx->wrkproperty, "PDFStats"); |
a5570b79 |
if (!(pdfobj)) {
goto cleanup;
} |
ebcca55f |
|
0e7442f1 |
if (pdf->stats.author) { |
9d33052f |
if (!pdf->stats.author->meta.success) {
char *out = pdf_finalize_string(pdf, pdf->stats.author->meta.obj, pdf->stats.author->data, pdf->stats.author->meta.length);
if (out) {
free(pdf->stats.author->data);
pdf->stats.author->data = out;
pdf->stats.author->meta.length = strlen(out);
pdf->stats.author->meta.success = 1; |
24db616f |
} |
0e7442f1 |
} |
9d33052f |
if (pdf->stats.author->meta.success && cli_isutf8(pdf->stats.author->data, pdf->stats.author->meta.length)) {
cli_jsonstr(pdfobj, "Author", pdf->stats.author->data); |
d010b117 |
} else if (pdf->stats.author->data && pdf->stats.author->meta.length) { |
5f31c9b4 |
char *b64 = cl_base64_encode(pdf->stats.author->data, pdf->stats.author->meta.length); |
9d33052f |
cli_jsonstr(pdfobj, "Author", b64);
cli_jsonbool(pdfobj, "Author_base64", 1);
free(b64); |
5f31c9b4 |
} else {
cli_jsonstr(pdfobj, "Author", ""); |
9d33052f |
} |
0e7442f1 |
}
if (pdf->stats.creator) { |
9d33052f |
if (!pdf->stats.creator->meta.success) {
char *out = pdf_finalize_string(pdf, pdf->stats.creator->meta.obj, pdf->stats.creator->data, pdf->stats.creator->meta.length);
if (out) {
free(pdf->stats.creator->data);
pdf->stats.creator->data = out;
pdf->stats.creator->meta.length = strlen(out);
pdf->stats.creator->meta.success = 1; |
24db616f |
} |
0e7442f1 |
} |
9d33052f |
if (pdf->stats.creator->meta.success && cli_isutf8(pdf->stats.creator->data, pdf->stats.creator->meta.length)) {
cli_jsonstr(pdfobj, "Creator", pdf->stats.creator->data); |
d010b117 |
} else if (pdf->stats.creator->data && pdf->stats.creator->meta.length) { |
5f31c9b4 |
char *b64 = cl_base64_encode(pdf->stats.creator->data, pdf->stats.creator->meta.length); |
9d33052f |
cli_jsonstr(pdfobj, "Creator", b64);
cli_jsonbool(pdfobj, "Creator_base64", 1);
free(b64); |
5f31c9b4 |
} else {
cli_jsonstr(pdfobj, "Creator", ""); |
9d33052f |
} |
0e7442f1 |
}
if (pdf->stats.producer) { |
9d33052f |
if (!pdf->stats.producer->meta.success) {
char *out = pdf_finalize_string(pdf, pdf->stats.producer->meta.obj, pdf->stats.producer->data, pdf->stats.producer->meta.length);
if (out) {
free(pdf->stats.producer->data);
pdf->stats.producer->data = out;
pdf->stats.producer->meta.length = strlen(out);
pdf->stats.producer->meta.success = 1; |
24db616f |
} |
0e7442f1 |
} |
9d33052f |
if (pdf->stats.producer->meta.success && cli_isutf8(pdf->stats.producer->data, pdf->stats.producer->meta.length)) {
cli_jsonstr(pdfobj, "Producer", pdf->stats.producer->data); |
d010b117 |
} else if (pdf->stats.producer->data && pdf->stats.producer->meta.length) { |
5f31c9b4 |
char *b64 = cl_base64_encode(pdf->stats.producer->data, pdf->stats.producer->meta.length); |
9d33052f |
cli_jsonstr(pdfobj, "Producer", b64);
cli_jsonbool(pdfobj, "Producer_base64", 1);
free(b64); |
5f31c9b4 |
} else {
cli_jsonstr(pdfobj, "Producer", ""); |
9d33052f |
} |
0e7442f1 |
}
if (pdf->stats.modificationdate) { |
9d33052f |
if (!pdf->stats.modificationdate->meta.success) {
char *out = pdf_finalize_string(pdf, pdf->stats.modificationdate->meta.obj, pdf->stats.modificationdate->data, pdf->stats.modificationdate->meta.length);
if (out) {
free(pdf->stats.modificationdate->data);
pdf->stats.modificationdate->data = out;
pdf->stats.modificationdate->meta.length = strlen(out);
pdf->stats.modificationdate->meta.success = 1; |
24db616f |
} |
0e7442f1 |
} |
9d33052f |
if (pdf->stats.modificationdate->meta.success && cli_isutf8(pdf->stats.modificationdate->data, pdf->stats.modificationdate->meta.length)) {
cli_jsonstr(pdfobj, "ModificationDate", pdf->stats.modificationdate->data); |
d010b117 |
} else if (pdf->stats.modificationdate->data && pdf->stats.modificationdate->meta.length) { |
5f31c9b4 |
char *b64 = cl_base64_encode(pdf->stats.modificationdate->data, pdf->stats.modificationdate->meta.length); |
9d33052f |
cli_jsonstr(pdfobj, "ModificationDate", b64);
cli_jsonbool(pdfobj, "ModificationDate_base64", 1);
free(b64); |
5f31c9b4 |
} else {
cli_jsonstr(pdfobj, "ModificationDate", ""); |
9d33052f |
} |
0e7442f1 |
}
if (pdf->stats.creationdate) { |
9d33052f |
if (!pdf->stats.creationdate->meta.success) {
char *out = pdf_finalize_string(pdf, pdf->stats.creationdate->meta.obj, pdf->stats.creationdate->data, pdf->stats.creationdate->meta.length);
if (out) {
free(pdf->stats.creationdate->data);
pdf->stats.creationdate->data = out;
pdf->stats.creationdate->meta.length = strlen(out);
pdf->stats.creationdate->meta.success = 1; |
24db616f |
} |
0e7442f1 |
} |
9d33052f |
if (pdf->stats.creationdate->meta.success && cli_isutf8(pdf->stats.creationdate->data, pdf->stats.creationdate->meta.length)) {
cli_jsonstr(pdfobj, "CreationDate", pdf->stats.creationdate->data); |
d010b117 |
} else if (pdf->stats.creationdate->data && pdf->stats.creationdate->meta.length) { |
5f31c9b4 |
char *b64 = cl_base64_encode(pdf->stats.creationdate->data, pdf->stats.creationdate->meta.length); |
9d33052f |
cli_jsonstr(pdfobj, "CreationDate", b64);
cli_jsonbool(pdfobj, "CreationDate_base64", 1);
free(b64); |
5f31c9b4 |
} else {
cli_jsonstr(pdfobj, "CreationDate", ""); |
9d33052f |
} |
0e7442f1 |
}
if (pdf->stats.title) { |
9d33052f |
if (!pdf->stats.title->meta.success) {
char *out = pdf_finalize_string(pdf, pdf->stats.title->meta.obj, pdf->stats.title->data, pdf->stats.title->meta.length);
if (out) {
free(pdf->stats.title->data);
pdf->stats.title->data = out;
pdf->stats.title->meta.length = strlen(out);
pdf->stats.title->meta.success = 1; |
24db616f |
} |
0e7442f1 |
} |
9d33052f |
if (pdf->stats.title->meta.success && cli_isutf8(pdf->stats.title->data, pdf->stats.title->meta.length)) {
cli_jsonstr(pdfobj, "Title", pdf->stats.title->data); |
d010b117 |
} else if (pdf->stats.title->data && pdf->stats.title->meta.length) { |
5f31c9b4 |
char *b64 = cl_base64_encode(pdf->stats.title->data, pdf->stats.title->meta.length); |
9d33052f |
cli_jsonstr(pdfobj, "Title", b64);
cli_jsonbool(pdfobj, "Title_base64", 1);
free(b64); |
5f31c9b4 |
} else {
cli_jsonstr(pdfobj, "Title", ""); |
9d33052f |
} |
0e7442f1 |
}
if (pdf->stats.subject) { |
9d33052f |
if (!pdf->stats.subject->meta.success) {
char *out = pdf_finalize_string(pdf, pdf->stats.subject->meta.obj, pdf->stats.subject->data, pdf->stats.subject->meta.length);
if (out) {
free(pdf->stats.subject->data);
pdf->stats.subject->data = out;
pdf->stats.subject->meta.length = strlen(out);
pdf->stats.subject->meta.success = 1; |
24db616f |
} |
0e7442f1 |
} |
9d33052f |
if (pdf->stats.subject->meta.success && cli_isutf8(pdf->stats.subject->data, pdf->stats.subject->meta.length)) {
cli_jsonstr(pdfobj, "Subject", pdf->stats.subject->data); |
d010b117 |
} else if (pdf->stats.subject->data && pdf->stats.subject->meta.length) { |
5f31c9b4 |
char *b64 = cl_base64_encode(pdf->stats.subject->data, pdf->stats.subject->meta.length); |
9d33052f |
cli_jsonstr(pdfobj, "Subject", b64);
cli_jsonbool(pdfobj, "Subject_base64", 1);
free(b64); |
5f31c9b4 |
} else {
cli_jsonstr(pdfobj, "Subject", ""); |
9d33052f |
} |
0e7442f1 |
}
if (pdf->stats.keywords) { |
9d33052f |
if (!pdf->stats.keywords->meta.success) {
char *out = pdf_finalize_string(pdf, pdf->stats.keywords->meta.obj, pdf->stats.keywords->data, pdf->stats.keywords->meta.length);
if (out) {
free(pdf->stats.keywords->data);
pdf->stats.keywords->data = out;
pdf->stats.keywords->meta.length = strlen(out);
pdf->stats.keywords->meta.success = 1; |
24db616f |
} |
0e7442f1 |
} |
9d33052f |
if (pdf->stats.keywords->meta.success && cli_isutf8(pdf->stats.keywords->data, pdf->stats.keywords->meta.length)) {
cli_jsonstr(pdfobj, "Keywords", pdf->stats.keywords->data); |
d010b117 |
} else if (pdf->stats.keywords->data && pdf->stats.keywords->meta.length) { |
5f31c9b4 |
char *b64 = cl_base64_encode(pdf->stats.keywords->data, pdf->stats.keywords->meta.length); |
9d33052f |
cli_jsonstr(pdfobj, "Keywords", b64);
cli_jsonbool(pdfobj, "Keywords_base64", 1);
free(b64); |
5f31c9b4 |
} else { |
188e40ae |
cli_jsonstr(pdfobj, "Keywords", ""); |
9d33052f |
} |
0e7442f1 |
} |
ebcca55f |
if (pdf->stats.ninvalidobjs)
cli_jsonint(pdfobj, "InvalidObjectCount", pdf->stats.ninvalidobjs);
if (pdf->stats.njs)
cli_jsonint(pdfobj, "JavaScriptObjectCount", pdf->stats.njs);
if (pdf->stats.nflate)
cli_jsonint(pdfobj, "DeflateObjectCount", pdf->stats.nflate);
if (pdf->stats.nactivex)
cli_jsonint(pdfobj, "ActiveXObjectCount", pdf->stats.nactivex);
if (pdf->stats.nflash)
cli_jsonint(pdfobj, "FlashObjectCount", pdf->stats.nflash);
if (pdf->stats.ncolors)
cli_jsonint(pdfobj, "ColorCount", pdf->stats.ncolors);
if (pdf->stats.nasciihexdecode)
cli_jsonint(pdfobj, "AsciiHexDecodeObjectCount", pdf->stats.nasciihexdecode);
if (pdf->stats.nascii85decode)
cli_jsonint(pdfobj, "Ascii85DecodeObjectCount", pdf->stats.nascii85decode);
if (pdf->stats.nembeddedfile)
cli_jsonint(pdfobj, "EmbeddedFileCount", pdf->stats.nembeddedfile);
if (pdf->stats.nimage)
cli_jsonint(pdfobj, "ImageCount", pdf->stats.nimage);
if (pdf->stats.nlzw)
cli_jsonint(pdfobj, "LZWCount", pdf->stats.nlzw);
if (pdf->stats.nrunlengthdecode)
cli_jsonint(pdfobj, "RunLengthDecodeCount", pdf->stats.nrunlengthdecode);
if (pdf->stats.nfaxdecode)
cli_jsonint(pdfobj, "FaxDecodeCount", pdf->stats.nfaxdecode);
if (pdf->stats.njbig2decode)
cli_jsonint(pdfobj, "JBIG2DecodeCount", pdf->stats.njbig2decode);
if (pdf->stats.ndctdecode)
cli_jsonint(pdfobj, "DCTDecodeCount", pdf->stats.ndctdecode);
if (pdf->stats.njpxdecode)
cli_jsonint(pdfobj, "JPXDecodeCount", pdf->stats.njpxdecode);
if (pdf->stats.ncrypt)
cli_jsonint(pdfobj, "CryptCount", pdf->stats.ncrypt);
if (pdf->stats.nstandard)
cli_jsonint(pdfobj, "StandardCount", pdf->stats.nstandard);
if (pdf->stats.nsigned)
cli_jsonint(pdfobj, "SignedCount", pdf->stats.nsigned);
if (pdf->stats.nopenaction)
cli_jsonint(pdfobj, "OpenActionCount", pdf->stats.nopenaction);
if (pdf->stats.nlaunch)
cli_jsonint(pdfobj, "LaunchCount", pdf->stats.nlaunch);
if (pdf->stats.npage)
cli_jsonint(pdfobj, "PageCount", pdf->stats.npage); |
09ff1409 |
if (pdf->stats.nrichmedia)
cli_jsonint(pdfobj, "RichMediaCount", pdf->stats.nrichmedia);
if (pdf->stats.nacroform)
cli_jsonint(pdfobj, "AcroFormCount", pdf->stats.nacroform);
if (pdf->stats.nxfa)
cli_jsonint(pdfobj, "XFACount", pdf->stats.nxfa); |
cfeac6cd |
if (pdf->flags & (1 << BAD_PDF_VERSION))
cli_jsonbool(pdfobj, "BadVersion", 1);
if (pdf->flags & (1 << BAD_PDF_HEADERPOS))
cli_jsonbool(pdfobj, "BadHeaderPosition", 1);
if (pdf->flags & (1 << BAD_PDF_TRAILER))
cli_jsonbool(pdfobj, "BadTrailer", 1);
if (pdf->flags & (1 << BAD_PDF_TOOMANYOBJS))
cli_jsonbool(pdfobj, "TooManyObjects", 1);
if (pdf->flags & (1 << ENCRYPTED_PDF)) {
cli_jsonbool(pdfobj, "Encrypted", 1);
if (pdf->flags & (1 << DECRYPTABLE_PDF))
cli_jsonbool(pdfobj, "Decryptable", 1); |
fc84532e |
else
cli_jsonbool(pdfobj, "Decryptable", 0); |
cfeac6cd |
} |
a5570b79 |
|
c7fd0220 |
for (i=0; i < pdf->nobjs; i++) { |
89d5207b |
if (pdf->objs[i]->flags & (1<<OBJ_TRUNCATED)) { |
c7fd0220 |
json_object *truncobj;
truncobj = cli_jsonarray(pdfobj, "TruncatedObjects");
if (!(truncobj))
continue;
|
89d5207b |
cli_jsonint_array(truncobj, pdf->objs[i]->id >> 8); |
c7fd0220 |
}
}
|
a5570b79 |
cleanup:
if ((pdf->stats.author)) { |
9d33052f |
if (pdf->stats.author->data)
free(pdf->stats.author->data); |
a5570b79 |
free(pdf->stats.author);
pdf->stats.author = NULL;
}
if (pdf->stats.creator) { |
9d33052f |
if (pdf->stats.creator->data)
free(pdf->stats.creator->data); |
a5570b79 |
free(pdf->stats.creator);
pdf->stats.creator = NULL;
}
if (pdf->stats.producer) { |
9d33052f |
if (pdf->stats.producer->data)
free(pdf->stats.producer->data); |
a5570b79 |
free(pdf->stats.producer);
pdf->stats.producer = NULL;
}
if (pdf->stats.modificationdate) { |
9d33052f |
if (pdf->stats.modificationdate->data)
free(pdf->stats.modificationdate->data); |
a5570b79 |
free(pdf->stats.modificationdate);
pdf->stats.modificationdate = NULL;
}
if (pdf->stats.creationdate) { |
9d33052f |
if (pdf->stats.creationdate->data)
free(pdf->stats.creationdate->data); |
a5570b79 |
free(pdf->stats.creationdate);
pdf->stats.creationdate = NULL;
} |
754f976a |
if (pdf->stats.title) { |
9d33052f |
if (pdf->stats.title->data)
free(pdf->stats.title->data); |
754f976a |
free(pdf->stats.title);
pdf->stats.title = NULL;
}
if (pdf->stats.subject) { |
9d33052f |
if (pdf->stats.subject->data)
free(pdf->stats.subject->data); |
754f976a |
free(pdf->stats.subject);
pdf->stats.subject = NULL;
}
if (pdf->stats.keywords) { |
9d33052f |
if (pdf->stats.keywords->data)
free(pdf->stats.keywords->data); |
754f976a |
free(pdf->stats.keywords);
pdf->stats.keywords = NULL;
} |
ebcca55f |
} |
63803da5 |
#endif |