#include "ppfilter.h" #include "pplib.h" ppstream * ppstream_create (ppdoc *pdf, ppdict *dict, size_t offset) { ppstream *stream; stream = (ppstream *)ppstruct_take(&pdf->heap, sizeof(ppstream)); stream->dict = dict; stream->offset = offset; //if (!ppdict_rget_uint(dict, "Length", &stream->length)) // may be indirect pointing PPNONE at this moment // stream->length = 0; stream->length = 0; stream->filespec = NULL; stream->filter.filters = NULL; stream->filter.params = NULL; stream->filter.count = 0; stream->input = &pdf->input; stream->I = NULL; stream->cryptkey = NULL; stream->flags = 0; return stream; } static iof * ppstream_predictor (ppdict *params, iof *N) { ppint predictor, rowsamples, components, samplebits; if (!ppdict_get_int(params, "Predictor", &predictor) || predictor <= 1) return N; if (!ppdict_get_int(params, "Columns", &rowsamples) || rowsamples == 0) // sanity, filter probably expects >0 rowsamples = 1;; if (!ppdict_get_int(params, "Colors", &components) || components == 0) // ditto components = 1; if (!ppdict_get_int(params, "BitsPerComponent", &samplebits) || samplebits == 0) samplebits = 8; return iof_filter_predictor_decoder(N, (int)predictor, (int)rowsamples, (int)components, (int)samplebits); } static iof * ppstream_decoder (ppstream *stream, ppstreamtp filtertype, ppdict *params, iof *N) { int flags; iof *F, *P; ppint earlychange; ppstring *cryptkey; switch (filtertype) { case PPSTREAM_BASE16: return iof_filter_base16_decoder(N); case PPSTREAM_BASE85: return iof_filter_base85_decoder(N); case PPSTREAM_RUNLENGTH: return iof_filter_runlength_decoder(N); case PPSTREAM_FLATE: if ((F = iof_filter_flate_decoder(N)) != NULL) { if (params != NULL) { if ((P = ppstream_predictor(params, F)) != NULL) return P; iof_close(F); break; } return F; } break; case PPSTREAM_LZW: flags = LZW_DECODER_DEFAULTS; if (params != NULL && ppdict_get_int(params, "EarlyChange", &earlychange) && earlychange == 0) // integer, not boolean flags &= ~LZW_EARLY_INDEX; if ((F = iof_filter_lzw_decoder(N, flags)) != NULL) { if (params != NULL) { if ((P = ppstream_predictor(params, F)) != NULL) return P; iof_close(F); break; } return F; } break; case PPSTREAM_CRYPT: if ((cryptkey = stream->cryptkey) == NULL) return N; // /Identity crypt if (stream->flags & PPSTREAM_ENCRYPTED_AES) return iof_filter_aes_decoder(N, cryptkey->data, cryptkey->size); if (stream->flags & PPSTREAM_ENCRYPTED_RC4) return iof_filter_rc4_decoder(N, cryptkey->data, cryptkey->size); return NULL; // if neither AES or RC4 but cryptkey present, something went wrong; see ppstream_info() case PPSTREAM_CCITT: case PPSTREAM_DCT: case PPSTREAM_JBIG2: case PPSTREAM_JPX: break; } return NULL; } #define ppstream_source(stream) iof_filter_stream_coreader((iof_file *)((stream)->input), (size_t)((stream)->offset), (size_t)((stream)->length)) #define ppstream_auxsource(filename) iof_filter_file_reader(filename) static ppname * ppstream_get_filter_name (ppobj *filterobj, size_t index) { if (filterobj->type == PPNAME) return index == 0 ? filterobj->name : NULL; if (filterobj->type == PPARRAY) return pparray_get_name(filterobj->array, index); return NULL; } static ppdict * ppstream_get_filter_params (ppobj *paramsobj, size_t index) { if (paramsobj->type == PPDICT) return index == 0 ? paramsobj->dict : NULL; if (paramsobj->type == PPARRAY) return pparray_rget_dict(paramsobj->array, index); return NULL; } static const char * ppstream_aux_filename (ppobj *filespec) { // mockup, here we should decode the string if (filespec->type == PPSTRING) { return (const char *)(filespec->string); } // else might be a dict - todo return NULL; } #define ppstream_image_filter(fcode) (fcode == PPSTREAM_DCT || fcode == PPSTREAM_CCITT || fcode == PPSTREAM_JBIG2 || fcode == PPSTREAM_JPX) iof * ppstream_read (ppstream *stream, int decode, int all) { iof *I, *F; ppstreamtp *filtertypes, filtertype; int owncrypt; ppdict **filterparams, *fparams; size_t index, filtercount; const char *filename; if (ppstream_iof(stream) != NULL) return NULL; // usage error if (stream->filespec != NULL) { filename = ppstream_aux_filename(stream->filespec); // mockup, basic support I = filename != NULL ? ppstream_auxsource(filename) : NULL; } else { I = ppstream_source(stream); } if (I == NULL) return NULL; /* If the stream is encrypted, decipher is the first to be applied */ owncrypt = (stream->flags & PPSTREAM_ENCRYPTED_OWN) != 0; if (!owncrypt) { if (stream->cryptkey != NULL && stream->filespec == NULL) { /* implied global crypt; does not apply to external files (pdf psec page 115), except for embedded file streams (not supported so far) */ if ((F = ppstream_decoder(stream, PPSTREAM_CRYPT, NULL, I)) == NULL) goto stream_error; I = F; } /* otherwise no crypt at all or /Identity */ } if (decode || owncrypt) { if ((filtercount = stream->filter.count) > 0) { filtertypes = stream->filter.filters; filterparams = stream->filter.params; for (index = 0; index < filtercount; ++index) { fparams = filterparams != NULL ? filterparams[index] : NULL; filtertype = filtertypes[index]; if ((F = ppstream_decoder(stream, filtertype, fparams, I)) != NULL) { I = F; if (owncrypt && !decode && filtertype == PPSTREAM_CRYPT) break; // /Crypt filter should always be first, so in practise we return decrypted but compressed continue; } if (!ppstream_image_filter(filtertype)) goto stream_error; // failed to create non-image filter, something unexpected break; } } } if (all) iof_load(I); else iof_input(I); stream->I = I; return I; stream_error: iof_close(I); return NULL; } uint8_t * ppstream_first (ppstream *stream, size_t *size, int decode) { iof *I; if ((I = ppstream_read(stream, decode, 0)) != NULL) { *size = (size_t)iof_left(I); return I->pos; } *size = 0; return NULL; } uint8_t * ppstream_next (ppstream *stream, size_t *size) { iof *I; if ((I = ppstream_iof(stream)) != NULL) { I->pos = I->end; if ((*size = iof_input(I)) > 0) return I->pos; } *size = 0; return NULL; } uint8_t * ppstream_all (ppstream *stream, size_t *size, int decode) { iof *I; if ((I = ppstream_read(stream, decode, 1)) != NULL) { *size = (size_t)iof_left(I); return I->pos; } *size = 0; return NULL; } void ppstream_done (ppstream *stream) { iof *I; if ((I = ppstream_iof(stream)) != NULL) { iof_close(I); stream->I = NULL; } } /* fetching stream info PJ20180916: revealed it makes sense to do a lilbit more just after parsing stream entry to simplify stream operations and extend ppstream api */ /* stream filters */ const char * ppstream_filter_name[] = { "ASCIIHexDecode", "ASCII85Decode", "RunLengthDecode", "FlateDecode", "LZWDecode", "CCITTFaxDecode", "DCTDecode", "JBIG2Decode", "JPXDecode", "Crypt" }; int ppstream_filter_type (ppname *name, ppstreamtp *filtertype) { switch (name->data[0]) { case 'A': if (ppname_is(name, "ASCIIHexDecode")) { *filtertype = PPSTREAM_BASE16; return 1; } if (ppname_is(name, "ASCII85Decode")) { *filtertype = PPSTREAM_BASE85; return 1; } break; case 'R': if (ppname_is(name, "RunLengthDecode")) { *filtertype = PPSTREAM_RUNLENGTH; return 1; } break; case 'F': if (ppname_is(name, "FlateDecode")) { *filtertype = PPSTREAM_FLATE; return 1; } break; case 'L': if (ppname_is(name, "LZWDecode")) { *filtertype = PPSTREAM_LZW; return 1; } break; case 'D': if (ppname_is(name, "DCTDecode")) { *filtertype = PPSTREAM_DCT; return 1; } break; case 'C': if (ppname_is(name, "CCITTFaxDecode")) { *filtertype = PPSTREAM_CCITT; return 1; } if (ppname_is(name, "Crypt")) { *filtertype = PPSTREAM_CRYPT; return 1; } break; case 'J': if (ppname_is(name, "JPXDecode")) { *filtertype = PPSTREAM_JPX; return 1; } if (ppname_is(name, "JBIG2Decode")) { *filtertype = PPSTREAM_JBIG2; return 1; } break; } return 0; } void ppstream_info (ppstream *stream, ppdoc *pdf) { // called in ppdoc_load_entries() for every stream, but after loading non-stream objects (eg. /Length..) ppdict *dict, *fparams; ppobj *fobj, *pobj; ppname *fname, *tname, *owncryptfilter = NULL; ppcrypt *crypt; ppref *ref; size_t i; int cflags; ppstreamtp *filtertypes = NULL, filtertype; ppdict **filterparams = NULL; size_t filtercount = 0, farraysize = 0; const char *filterkey, *paramskey; dict = stream->dict; ppdict_rget_uint(dict, "Length", &stream->length); if ((stream->filespec = ppdict_get_obj(dict, "F")) != NULL) { stream->flags |= PPSTREAM_NOT_SUPPORTED; filterkey = "FFilter", paramskey = "FDecodeParms"; } else filterkey = "Filter", paramskey = "DecodeParms"; if ((fobj = ppdict_rget_obj(dict, filterkey)) != NULL) { switch (fobj->type) { case PPNAME: farraysize = 1; break; case PPARRAY: farraysize = fobj->array->size; break; default: break; } if (farraysize > 0) { filtertypes = (ppstreamtp *)ppstruct_take(&pdf->heap, farraysize * sizeof(ppstreamtp)); if ((pobj = ppdict_rget_obj(dict, paramskey)) != NULL) { filterparams = (ppdict **)ppstruct_take(&pdf->heap, farraysize * sizeof(ppdict *)); } for (i = 0; i < farraysize; ++i) { if ((fname = ppstream_get_filter_name(fobj, i)) != NULL && ppstream_filter_type(fname, &filtertype)) { filtertypes[filtercount] = filtertype; if (pobj != NULL) { fparams = ppstream_get_filter_params(pobj, i); filterparams[filtercount] = fparams; } else fparams = NULL; switch (filtertype) { case PPSTREAM_BASE16: case PPSTREAM_BASE85: case PPSTREAM_RUNLENGTH: case PPSTREAM_FLATE: case PPSTREAM_LZW: stream->flags |= PPSTREAM_FILTER; break; case PPSTREAM_CCITT: case PPSTREAM_DCT: case PPSTREAM_JBIG2: case PPSTREAM_JPX: stream->flags |= PPSTREAM_IMAGE; break; case PPSTREAM_CRYPT: stream->flags |= PPSTREAM_ENCRYPTED_OWN; owncryptfilter = fparams != NULL ? ppdict_get_name(fparams, "Name") : NULL; // /Type /CryptFilterDecodeParms /Name ... if (i != 0) // we assume it is first stream->flags |= PPSTREAM_NOT_SUPPORTED; break; } ++filtercount; } else { stream->flags |= PPSTREAM_NOT_SUPPORTED; } } } } stream->filter.filters = filtertypes; stream->filter.params = filterparams; stream->filter.count = filtercount; if ((crypt = pdf->crypt) == NULL || (ref = crypt->ref) == NULL) return; if (stream->flags & PPSTREAM_ENCRYPTED_OWN) { /* Seems a common habit to use just /Crypt filter name with no params, which defaults to /Identity. A real example with uncompressed metadata: <> */ if (owncryptfilter != NULL && !ppname_is(owncryptfilter, "Identity") && stream->filespec == NULL) // ? { if (crypt->map != NULL && ppcrypt_type(crypt, owncryptfilter, NULL, &cflags)) { if (cflags & PPCRYPT_INFO_AES) stream->flags |= PPSTREAM_ENCRYPTED_AES; else if (cflags & PPCRYPT_INFO_RC4) stream->flags |= PPSTREAM_ENCRYPTED_RC4; } } } else { if ((crypt->flags & PPCRYPT_NO_METADATA) && (tname = ppdict_get_name(dict, "Type")) != NULL && ppname_is(tname, "Metadata")) ; /* special treatment of metadata stream; we assume that explicit /Filter /Crypt setup overrides document level setup of EncryptMetadata. */ else if (stream->filespec == NULL) /* external files are not encrypted, expect embedded files (not supported yet) */ { if (crypt->flags & PPCRYPT_STREAM_RC4) stream->flags |= PPSTREAM_ENCRYPTED_RC4; else if (crypt->flags & PPCRYPT_STREAM_AES) stream->flags |= PPSTREAM_ENCRYPTED_AES; } } /* finally, if the stream is encrypted with non-identity crypt (implicit or explicit), make and save the crypt key */ if (stream->flags & PPSTREAM_ENCRYPTED) stream->cryptkey = ppcrypt_stmkey(crypt, ref, ((stream->flags & PPSTREAM_ENCRYPTED_AES) != 0), &pdf->heap); } void ppstream_filter_info (ppstream *stream, ppstream_filter *info, int decode) { size_t from, index; ppstreamtp filtertype; ppdict *params; *info = stream->filter; if (info->count > 0) { from = (stream->flags & PPSTREAM_ENCRYPTED_OWN) && info->filters[0] == PPSTREAM_CRYPT ? 1 : 0; if (decode) { for (index = from; index < info->count; ++index) { filtertype = info->filters[index]; if (ppstream_image_filter(filtertype)) { break; } } } else { index = from; } if (index > 0) { info->count -= index; if (info->count > 0) { info->filters += index; if (info->params != NULL) { info->params += index; for (index = 0, params = NULL; index < info->count; ++index) if ((params = info->params[index]) != NULL) break; if (params == NULL) info->params = NULL; } } else { info->filters = NULL; info->params = NULL; } } } } /* */ void ppstream_init_buffers (void) { iof_filters_init(); } void ppstream_free_buffers (void) { iof_filters_free(); }