libclamav/pdf.c
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