blob: b20d78578dea4f4b01d63e4c1a1b27e83e64f79e [file] [log] [blame]
//========================================================================
//
// Form.cc
//
// This file is licensed under the GPLv2 or later
//
// Copyright 2006-2008 Julien Rebetez <julienr@svn.gnome.org>
// Copyright 2007-2012, 2015-2023 Albert Astals Cid <aacid@kde.org>
// Copyright 2007-2008, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright 2007, 2013, 2016, 2019, 2022 Adrian Johnson <ajohnson@redneon.com>
// Copyright 2007 Iñigo Martínez <inigomartinez@gmail.com>
// Copyright 2008, 2011 Pino Toscano <pino@kde.org>
// Copyright 2008 Michael Vrable <mvrable@cs.ucsd.edu>
// Copyright 2009 Matthias Drochner <M.Drochner@fz-juelich.de>
// Copyright 2009 KDAB via Guillermo Amaral <gamaral@amaral.com.mx>
// Copyright 2010, 2012 Mark Riedesel <mark@klowner.com>
// Copyright 2012 Fabio D'Urso <fabiodurso@hotmail.it>
// Copyright 2015 André Guerreiro <aguerreiro1985@gmail.com>
// Copyright 2015 André Esser <bepandre@hotmail.com>
// Copyright 2017 Hans-Ulrich Jüttner <huj@froreich-bioscientia.de>
// Copyright 2017 Bernd Kuhls <berndkuhls@hotmail.com>
// Copyright 2018 Andre Heinecke <aheinecke@intevation.de>
// Copyright 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
// Copyright 2018 Chinmoy Ranjan Pradhan <chinmoyrp65@protonmail.com>
// Copyright 2018 Adam Reichold <adam.reichold@t-online.de>
// Copyright 2018-2022 Nelson Benítez León <nbenitezl@gmail.com>
// Copyright 2019, 2020 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright 2019 Tomoyuki Kubota <himajin100000@gmail.com>
// Copyright 2019 João Netto <joaonetto901@gmail.com>
// Copyright 2020-2022 Marek Kasik <mkasik@redhat.com>
// Copyright 2020 Thorsten Behrens <Thorsten.Behrens@CIB.de>
// Copyright 2020, 2023 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by Technische Universität Dresden
// Copyright 2021 Georgiy Sgibnev <georgiy@sgibnev.com>. Work sponsored by lab50.net.
// Copyright 2021 Theofilos Intzoglou <int.teo@gmail.com>
// Copyright 2021 Even Rouault <even.rouault@spatialys.com>
// Copyright 2022 Alexander Sulfrian <asulfrian@zedat.fu-berlin.de>
// Copyright 2022, 2024 Erich E. Hoover <erich.e.hoover@gmail.com>
// Copyright 2023 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
//
//========================================================================
#include <config.h>
#include <array>
#include <set>
#include <limits>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include "goo/ft_utils.h"
#include "goo/gmem.h"
#include "goo/gfile.h"
#include "goo/GooString.h"
#include "Error.h"
#include "ErrorCodes.h"
#include "CharCodeToUnicode.h"
#include "Object.h"
#include "Array.h"
#include "Dict.h"
#include "Gfx.h"
#include "GfxFont.h"
#include "GlobalParams.h"
#include "Form.h"
#include "PDFDoc.h"
#include "DateInfo.h"
#include "CryptoSignBackend.h"
#include "SignatureInfo.h"
#include "CertificateInfo.h"
#include "XRef.h"
#include "PDFDocEncoding.h"
#include "Annot.h"
#include "Link.h"
#include "Lexer.h"
#include "Parser.h"
#include "CIDFontsWidthsBuilder.h"
#include "fofi/FoFiTrueType.h"
#include "fofi/FoFiIdentifier.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include <unordered_set>
// helper for using std::visit to get a dependent false for static_asserts
// to help get compile errors if one ever extends variants
template<class>
inline constexpr bool always_false_v = false;
// return a newly allocated char* containing an UTF16BE string of size length
char *pdfDocEncodingToUTF16(const std::string &orig, int *length)
{
// double size, a unicode char takes 2 char, add 2 for the unicode marker
*length = 2 + 2 * orig.size();
char *result = new char[(*length)];
const char *cstring = orig.c_str();
// unicode marker
result[0] = '\xfe';
result[1] = '\xff';
// convert to utf16
for (int i = 2, j = 0; i < (*length); i += 2, j++) {
Unicode u = pdfDocEncoding[(unsigned int)((unsigned char)cstring[j])] & 0xffff;
result[i] = (u >> 8) & 0xff;
result[i + 1] = u & 0xff;
}
return result;
}
static GooString *convertToUtf16(GooString *pdfDocEncodingString)
{
int tmp_length;
char *tmp_str = pdfDocEncodingToUTF16(pdfDocEncodingString->toStr(), &tmp_length);
delete pdfDocEncodingString;
pdfDocEncodingString = new GooString(tmp_str + 2, tmp_length - 2); // Remove the unicode BOM
delete[] tmp_str;
return pdfDocEncodingString;
}
FormWidget::FormWidget(PDFDoc *docA, Object *aobj, unsigned num, Ref aref, FormField *fieldA)
{
ref = aref;
ID = 0;
childNum = num;
doc = docA;
xref = doc->getXRef();
obj = aobj->copy();
type = formUndef;
field = fieldA;
widget = nullptr;
}
FormWidget::~FormWidget()
{
if (widget) {
widget->decRefCnt();
}
}
void FormWidget::print(int indent)
{
printf("%*s+ (%d %d): [widget]\n", indent, "", ref.num, ref.gen);
}
void FormWidget::createWidgetAnnotation()
{
if (widget) {
return;
}
Object obj1(ref);
widget = new AnnotWidget(doc, &obj, &obj1, field);
}
bool FormWidget::inRect(double x, double y) const
{
return widget ? widget->inRect(x, y) : false;
}
void FormWidget::getRect(double *x1, double *y1, double *x2, double *y2) const
{
if (widget) {
widget->getRect(x1, y1, x2, y2);
}
}
bool FormWidget::isReadOnly() const
{
return field->isReadOnly();
}
void FormWidget::setReadOnly(bool value)
{
field->setReadOnly(value);
}
int FormWidget::encodeID(unsigned pageNum, unsigned fieldNum)
{
return (pageNum << 4 * sizeof(unsigned)) + fieldNum;
}
void FormWidget::decodeID(unsigned id, unsigned *pageNum, unsigned *fieldNum)
{
*pageNum = id >> 4 * sizeof(unsigned);
*fieldNum = (id << 4 * sizeof(unsigned)) >> 4 * sizeof(unsigned);
}
const GooString *FormWidget::getPartialName() const
{
return field->getPartialName();
}
void FormWidget::setPartialName(const GooString &name)
{
field->setPartialName(name);
}
const GooString *FormWidget::getAlternateUiName() const
{
return field->getAlternateUiName();
}
const GooString *FormWidget::getMappingName() const
{
return field->getMappingName();
}
GooString *FormWidget::getFullyQualifiedName()
{
return field->getFullyQualifiedName();
}
LinkAction *FormWidget::getActivationAction()
{
return widget ? widget->getAction() : nullptr;
}
std::unique_ptr<LinkAction> FormWidget::getAdditionalAction(Annot::FormAdditionalActionsType t)
{
return widget ? widget->getFormAdditionalAction(t) : nullptr;
}
bool FormWidget::setAdditionalAction(Annot::FormAdditionalActionsType t, const std::string &js)
{
if (!widget) {
return false;
}
return widget->setFormAdditionalAction(t, js);
}
FormWidgetButton::FormWidgetButton(PDFDoc *docA, Object *dictObj, unsigned num, Ref refA, FormField *p) : FormWidget(docA, dictObj, num, refA, p)
{
type = formButton;
onStr = nullptr;
// Find the name of the ON state in the AP dictionary
// The reference say the Off state, if it exists, _must_ be stored in the AP dict under the name /Off
// The "on" state may be stored under any other name
Object obj1 = obj.dictLookup("AP");
if (obj1.isDict()) {
Object obj2 = obj1.dictLookup("N");
if (obj2.isDict()) {
for (int i = 0; i < obj2.dictGetLength(); i++) {
const char *key = obj2.dictGetKey(i);
if (strcmp(key, "Off") != 0) {
onStr = new GooString(key);
break;
}
}
}
}
}
const char *FormWidgetButton::getOnStr() const
{
if (onStr) {
return onStr->c_str();
}
// 12.7.4.2.3 Check Boxes
// Yes should be used as the name for the on state
return parent()->getButtonType() == formButtonCheck ? "Yes" : nullptr;
}
FormWidgetButton::~FormWidgetButton()
{
delete onStr;
}
FormButtonType FormWidgetButton::getButtonType() const
{
return parent()->getButtonType();
}
void FormWidgetButton::setAppearanceState(const char *state)
{
if (!widget) {
return;
}
widget->setAppearanceState(state);
}
void FormWidgetButton::updateWidgetAppearance()
{
// The appearance stream must NOT be regenerated for this widget type
}
void FormWidgetButton::setState(bool astate)
{
// pushButtons don't have state
if (parent()->getButtonType() == formButtonPush) {
return;
}
// Silently return if can't set ON state
if (astate && !getOnStr()) {
return;
}
parent()->setState(astate ? getOnStr() : (char *)"Off");
// Parent will call setAppearanceState()
// Now handle standAlone fields which are related to this one by having the same
// fully qualified name. This is *partially* by spec, as seen in "Field names"
// section inside "8.6.2 Field Dictionaries" in 1.7 PDF spec. Issue #1034
if (!astate) { // We're only interested when this field is being set to ON,
return; // to check if it has related fields and then set them OFF
}
unsigned this_page_num, this_field_num;
decodeID(getID(), &this_page_num, &this_field_num);
Page *this_page = doc->getCatalog()->getPage(this_page_num);
const FormField *this_field = getField();
if (!this_page->hasStandaloneFields() || this_field == nullptr) {
return;
}
auto this_page_widgets = this_page->getFormWidgets();
const FormButtonType this_button_type = getButtonType();
const int tot = this_page_widgets->getNumWidgets();
for (int i = 0; i < tot; i++) {
bool found_related = false;
FormWidget *wid = this_page_widgets->getWidget(i);
const bool same_fqn = wid->getFullyQualifiedName()->cmp(getFullyQualifiedName()) == 0;
const bool same_button_type = wid->getType() == formButton && static_cast<const FormWidgetButton *>(wid)->getButtonType() == this_button_type;
if (same_fqn && same_button_type) {
if (this_field->isStandAlone()) {
//'this_field' is standAlone, so we need to search in both standAlone fields and normal fields
if (this_field != wid->getField()) { // so take care to not choose our same field
found_related = true;
}
} else {
//'this_field' is not standAlone, so we just need to search in standAlone fields
if (wid->getField()->isStandAlone()) {
found_related = true;
}
}
}
if (found_related) {
FormFieldButton *ffb = static_cast<FormFieldButton *>(wid->getField());
if (ffb == nullptr) {
error(errInternal, -1, "FormWidgetButton::setState : FormFieldButton expected\n");
continue;
}
ffb->setState((char *)"Off", true);
}
}
}
bool FormWidgetButton::getState() const
{
return getOnStr() ? parent()->getState(getOnStr()) : false;
}
FormFieldButton *FormWidgetButton::parent() const
{
return static_cast<FormFieldButton *>(field);
}
FormWidgetText::FormWidgetText(PDFDoc *docA, Object *dictObj, unsigned num, Ref refA, FormField *p) : FormWidget(docA, dictObj, num, refA, p)
{
type = formText;
}
const GooString *FormWidgetText::getContent() const
{
return parent()->getContent();
}
void FormWidgetText::updateWidgetAppearance()
{
if (widget) {
widget->updateAppearanceStream();
}
}
bool FormWidgetText::isMultiline() const
{
return parent()->isMultiline();
}
bool FormWidgetText::isPassword() const
{
return parent()->isPassword();
}
bool FormWidgetText::isFileSelect() const
{
return parent()->isFileSelect();
}
bool FormWidgetText::noSpellCheck() const
{
return parent()->noSpellCheck();
}
bool FormWidgetText::noScroll() const
{
return parent()->noScroll();
}
bool FormWidgetText::isComb() const
{
return parent()->isComb();
}
bool FormWidgetText::isRichText() const
{
return parent()->isRichText();
}
int FormWidgetText::getMaxLen() const
{
return parent()->getMaxLen();
}
double FormWidgetText::getTextFontSize()
{
return parent()->getTextFontSize();
}
void FormWidgetText::setTextFontSize(int fontSize)
{
parent()->setTextFontSize(fontSize);
}
void FormWidgetText::setContent(const GooString *new_content)
{
parent()->setContentCopy(new_content);
}
void FormWidgetText::setAppearanceContent(const GooString *new_content)
{
parent()->setAppearanceContentCopy(new_content);
}
FormFieldText *FormWidgetText::parent() const
{
return static_cast<FormFieldText *>(field);
}
FormWidgetChoice::FormWidgetChoice(PDFDoc *docA, Object *dictObj, unsigned num, Ref refA, FormField *p) : FormWidget(docA, dictObj, num, refA, p)
{
type = formChoice;
}
FormWidgetChoice::~FormWidgetChoice() { }
bool FormWidgetChoice::_checkRange(int i) const
{
if (i < 0 || i >= parent()->getNumChoices()) {
error(errInternal, -1, "FormWidgetChoice::_checkRange i out of range : {0:d}", i);
return false;
}
return true;
}
void FormWidgetChoice::select(int i)
{
if (!_checkRange(i)) {
return;
}
parent()->select(i);
}
void FormWidgetChoice::toggle(int i)
{
if (!_checkRange(i)) {
return;
}
parent()->toggle(i);
}
void FormWidgetChoice::deselectAll()
{
parent()->deselectAll();
}
const GooString *FormWidgetChoice::getEditChoice() const
{
if (!hasEdit()) {
error(errInternal, -1, "FormFieldChoice::getEditChoice called on a non-editable choice\n");
return nullptr;
}
return parent()->getEditChoice();
}
void FormWidgetChoice::updateWidgetAppearance()
{
if (widget) {
widget->updateAppearanceStream();
}
}
bool FormWidgetChoice::isSelected(int i) const
{
if (!_checkRange(i)) {
return false;
}
return parent()->isSelected(i);
}
void FormWidgetChoice::setEditChoice(const GooString *new_content)
{
if (!hasEdit()) {
error(errInternal, -1, "FormFieldChoice::setEditChoice : trying to edit an non-editable choice\n");
return;
}
parent()->setEditChoice(new_content);
}
int FormWidgetChoice::getNumChoices() const
{
return parent()->getNumChoices();
}
const GooString *FormWidgetChoice::getChoice(int i) const
{
return parent()->getChoice(i);
}
const GooString *FormWidgetChoice::getExportVal(int i) const
{
return parent()->getExportVal(i);
}
bool FormWidgetChoice::isCombo() const
{
return parent()->isCombo();
}
bool FormWidgetChoice::hasEdit() const
{
return parent()->hasEdit();
}
bool FormWidgetChoice::isMultiSelect() const
{
return parent()->isMultiSelect();
}
bool FormWidgetChoice::noSpellCheck() const
{
return parent()->noSpellCheck();
}
bool FormWidgetChoice::commitOnSelChange() const
{
return parent()->commitOnSelChange();
}
bool FormWidgetChoice::isListBox() const
{
return parent()->isListBox();
}
FormFieldChoice *FormWidgetChoice::parent() const
{
return static_cast<FormFieldChoice *>(field);
}
FormWidgetSignature::FormWidgetSignature(PDFDoc *docA, Object *dictObj, unsigned num, Ref refA, FormField *p) : FormWidget(docA, dictObj, num, refA, p)
{
type = formSignature;
}
const GooString *FormWidgetSignature::getSignature() const
{
return static_cast<FormFieldSignature *>(field)->getSignature();
}
SignatureInfo *FormWidgetSignature::validateSignature(bool doVerifyCert, bool forceRevalidation, time_t validationTime, bool ocspRevocationCheck, bool enableAIA)
{
return static_cast<FormFieldSignature *>(field)->validateSignature(doVerifyCert, forceRevalidation, validationTime, ocspRevocationCheck, enableAIA);
}
// update hash with the specified range of data from the file
static bool hashFileRange(FILE *f, CryptoSign::SigningInterface *handler, Goffset start, Goffset end)
{
if (!handler) {
return false;
}
const int BUF_SIZE = 65536;
unsigned char *buf = new unsigned char[BUF_SIZE];
while (start < end) {
if (Gfseek(f, start, SEEK_SET) != 0) {
delete[] buf;
return false;
}
int len = BUF_SIZE;
if (end - start < len) {
len = static_cast<int>(end - start);
}
if (fread(buf, 1, len, f) != static_cast<size_t>(len)) {
delete[] buf;
return false;
}
handler->addData(buf, len);
start += len;
}
delete[] buf;
return true;
}
bool FormWidgetSignature::signDocument(const std::string &saveFilename, const std::string &certNickname, const std::string &password, const GooString *reason, const GooString *location, const std::optional<GooString> &ownerPassword,
const std::optional<GooString> &userPassword)
{
auto backend = CryptoSign::Factory::createActive();
if (!backend) {
return false;
}
if (certNickname.empty()) {
fprintf(stderr, "signDocument: Empty nickname\n");
return false;
}
auto sigHandler = backend->createSigningHandler(certNickname, HashAlgorithm::Sha256);
FormFieldSignature *signatureField = static_cast<FormFieldSignature *>(field);
std::unique_ptr<X509CertificateInfo> certInfo = sigHandler->getCertificateInfo();
if (!certInfo) {
fprintf(stderr, "signDocument: error getting signature info\n");
return false;
}
const std::string signerName = certInfo->getSubjectInfo().commonName;
signatureField->setCertificateInfo(certInfo);
updateWidgetAppearance(); // add visible signing info to appearance
Object vObj(new Dict(xref));
Ref vref = xref->addIndirectObject(vObj);
if (!createSignature(vObj, vref, GooString(signerName), CryptoSign::maxSupportedSignatureSize, reason, location)) {
return false;
}
// Incremental save to avoid breaking any existing signatures
const GooString fname(saveFilename);
if (doc->saveAs(fname, writeForceIncremental) != errNone) {
fprintf(stderr, "signDocument: error saving to file \"%s\"\n", saveFilename.c_str());
return false;
}
// Get start/end offset of signature object in the saved PDF
Goffset objStart, objEnd;
if (!getObjectStartEnd(fname, vref.num, &objStart, &objEnd, ownerPassword, userPassword)) {
fprintf(stderr, "signDocument: unable to get signature object offsets\n");
return false;
}
// Update byte range of signature in the saved PDF
Goffset sigStart, sigEnd, fileSize;
FILE *file = openFile(saveFilename.c_str(), "r+b");
if (!updateOffsets(file, objStart, objEnd, &sigStart, &sigEnd, &fileSize)) {
fprintf(stderr, "signDocument: unable update byte range\n");
fclose(file);
return false;
}
// compute hash of byte ranges
if (!hashFileRange(file, sigHandler.get(), 0LL, sigStart)) {
fclose(file);
return false;
}
if (!hashFileRange(file, sigHandler.get(), sigEnd, fileSize)) {
fclose(file);
return false;
}
// and sign it
auto signature = sigHandler->signDetached(password);
if (!signature) {
fclose(file);
return false;
}
if (signature->getLength() > CryptoSign::maxSupportedSignatureSize) {
fclose(file);
return false;
}
// pad with zeroes to placeholder length
auto length = signature->getLength();
signature->append(std::string(CryptoSign::maxSupportedSignatureSize - length, '\0'));
// write signature to saved file
if (!updateSignature(file, sigStart, sigEnd, signature.value())) {
fprintf(stderr, "signDocument: unable update signature\n");
fclose(file);
return false;
}
signatureField->setSignature(*signature);
fclose(file);
return true;
}
static std::tuple<double, double> calculateDxDy(int rot, const PDFRectangle *rect)
{
switch (rot) {
case 90:
return { rect->y2 - rect->y1, rect->x2 - rect->x1 };
case 180:
return { rect->x2 - rect->y2, rect->y2 - rect->y1 };
case 270:
return { rect->y2 - rect->y1, rect->x2 - rect->x1 };
default: // assume rot == 0
return { rect->x2 - rect->x1, rect->y2 - rect->y1 };
}
}
bool FormWidgetSignature::signDocumentWithAppearance(const std::string &saveFilename, const std::string &certNickname, const std::string &password, const GooString *reason, const GooString *location,
const std::optional<GooString> &ownerPassword, const std::optional<GooString> &userPassword, const GooString &signatureText, const GooString &signatureTextLeft, double fontSize,
double leftFontSize, std::unique_ptr<AnnotColor> &&fontColor, double borderWidth, std::unique_ptr<AnnotColor> &&borderColor, std::unique_ptr<AnnotColor> &&backgroundColor)
{
// Set the appearance
GooString *aux = getField()->getDefaultAppearance();
std::string originalDefaultAppearance = aux ? aux->toStr() : std::string();
Form *form = doc->getCatalog()->getCreateForm();
const std::string pdfFontName = form->findPdfFontNameToUseForSigning();
if (pdfFontName.empty()) {
return false;
}
std::shared_ptr<GfxFont> font = form->getDefaultResources()->lookupFont(pdfFontName.c_str());
double x1, y1, x2, y2;
getRect(&x1, &y1, &x2, &y2);
const PDFRectangle rect(x1, y1, x2, y2);
std::unique_ptr<AnnotAppearanceCharacs> origAppearCharacs = getWidgetAnnotation()->getAppearCharacs() ? getWidgetAnnotation()->getAppearCharacs()->copy() : nullptr;
const int rot = origAppearCharacs ? origAppearCharacs->getRotation() : 0;
const auto dxdy = calculateDxDy(rot, &rect);
const double dx = std::get<0>(dxdy);
const double dy = std::get<1>(dxdy);
const double wMax = dx - 2 * borderWidth - 4;
if (fontSize == 0) {
fontSize = Annot::calculateFontSize(form, font.get(), &signatureText, wMax / 2.0, dy);
}
if (leftFontSize == 0) {
leftFontSize = Annot::calculateFontSize(form, font.get(), &signatureTextLeft, wMax / 2.0, dy);
}
const DefaultAppearance da { { objName, pdfFontName.c_str() }, fontSize, std::move(fontColor) };
getField()->setDefaultAppearance(da.toAppearanceString());
auto appearCharacs = std::make_unique<AnnotAppearanceCharacs>(nullptr);
appearCharacs->setBorderColor(std::move(borderColor));
appearCharacs->setBackColor(std::move(backgroundColor));
getWidgetAnnotation()->setAppearCharacs(std::move(appearCharacs));
std::unique_ptr<AnnotBorder> origBorderCopy = getWidgetAnnotation()->getBorder() ? getWidgetAnnotation()->getBorder()->copy() : nullptr;
std::unique_ptr<AnnotBorder> border(new AnnotBorderArray());
border->setWidth(borderWidth);
getWidgetAnnotation()->setBorder(std::move(border));
getWidgetAnnotation()->generateFieldAppearance();
getWidgetAnnotation()->updateAppearanceStream();
form->ensureFontsForAllCharacters(&signatureText, pdfFontName);
form->ensureFontsForAllCharacters(&signatureTextLeft, pdfFontName);
::FormFieldSignature *ffs = static_cast<::FormFieldSignature *>(getField());
ffs->setCustomAppearanceContent(signatureText);
ffs->setCustomAppearanceLeftContent(signatureTextLeft);
ffs->setCustomAppearanceLeftFontSize(leftFontSize);
// say that there a now signatures and that we should append only
doc->getCatalog()->getAcroForm()->dictSet("SigFlags", Object(3));
const bool success = signDocument(saveFilename, certNickname, password, reason, location, ownerPassword, userPassword);
// Now bring back the annotation appearance back to what it was
ffs->setDefaultAppearance(originalDefaultAppearance);
ffs->setCustomAppearanceContent({});
ffs->setCustomAppearanceLeftContent({});
getWidgetAnnotation()->setAppearCharacs(std::move(origAppearCharacs));
getWidgetAnnotation()->setBorder(std::move(origBorderCopy));
getWidgetAnnotation()->generateFieldAppearance();
getWidgetAnnotation()->updateAppearanceStream();
return success;
}
// Get start and end file position of objNum in the PDF named filename.
bool FormWidgetSignature::getObjectStartEnd(const GooString &filename, int objNum, Goffset *objStart, Goffset *objEnd, const std::optional<GooString> &ownerPassword, const std::optional<GooString> &userPassword)
{
PDFDoc newDoc(std::unique_ptr<GooString>(filename.copy()), ownerPassword, userPassword);
if (!newDoc.isOk()) {
return false;
}
XRef *newXref = newDoc.getXRef();
XRefEntry *entry = newXref->getEntry(objNum);
if (entry->type != xrefEntryUncompressed) {
return false;
}
*objStart = entry->offset;
newXref->fetch(objNum, entry->gen, 0, objEnd);
return true;
}
// find next offset containing the dummy offset '9999999999' and overwrite with offset
static char *setNextOffset(char *start, Goffset offset)
{
char buf[50];
sprintf(buf, "%lld", offset);
strcat(buf, " "); // add some padding
char *p = strstr(start, "9999999999");
if (p) {
memcpy(p, buf, 10); // overwrite exact size.
p += 10;
} else {
return nullptr;
}
return p;
}
// Updates the ByteRange array of the signature object in the file.
// Returns start/end of signature string and file size.
bool FormWidgetSignature::updateOffsets(FILE *f, Goffset objStart, Goffset objEnd, Goffset *sigStart, Goffset *sigEnd, Goffset *fileSize)
{
if (Gfseek(f, 0, SEEK_END) != 0) {
return false;
}
*fileSize = Gftell(f);
if (objEnd > *fileSize) {
objEnd = *fileSize;
}
// sanity check object offsets
if (objEnd <= objStart || (objEnd - objStart >= INT_MAX)) {
return false;
}
const size_t bufSize = static_cast<size_t>(objEnd - objStart);
if (Gfseek(f, objStart, SEEK_SET) != 0) {
return false;
}
std::vector<char> buf(bufSize + 1);
if (fread(buf.data(), 1, bufSize, f) != bufSize) {
return false;
}
buf[bufSize] = 0; // prevent string functions from searching past the end
// search for the Contents field which contains the signature placeholder
// which always must start with hex digits 000
*sigStart = -1;
*sigEnd = -1;
for (size_t i = 0; i < bufSize - 14; i++) {
if (buf[i] == '/' && strncmp(&buf[i], "/Contents <000", 14) == 0) {
*sigStart = objStart + i + 10;
char *p = strchr(&buf[i], '>');
if (p) {
*sigEnd = objStart + (p - buf.data()) + 1;
}
break;
}
}
if (*sigStart == -1 || *sigEnd == -1) {
return false;
}
// Search for ByteRange array and update offsets
for (size_t i = 0; i < bufSize - 10; i++) {
if (buf[i] == '/' && strncmp(&buf[i], "/ByteRange", 10) == 0) {
// update range
char *p = setNextOffset(&buf[i], *sigStart);
if (!p) {
return false;
}
p = setNextOffset(p, *sigEnd);
if (!p) {
return false;
}
p = setNextOffset(p, *fileSize - *sigEnd);
if (!p) {
return false;
}
break;
}
}
// write buffer back to disk
if (Gfseek(f, objStart, SEEK_SET) != 0) {
return false;
}
fwrite(buf.data(), bufSize, 1, f);
return true;
}
// Overwrite signature string in the file with new signature
bool FormWidgetSignature::updateSignature(FILE *f, Goffset sigStart, Goffset sigEnd, const GooString &signature)
{
if (signature.getLength() * 2 + 2 != sigEnd - sigStart) {
return false;
}
if (Gfseek(f, sigStart, SEEK_SET) != 0) {
return false;
}
const char *c = signature.c_str();
fprintf(f, "<");
for (int i = 0; i < signature.getLength(); i++) {
unsigned char value = *(c + i) & 0x000000ff;
fprintf(f, "%2.2x", value);
}
fprintf(f, "> ");
return true;
}
bool FormWidgetSignature::createSignature(Object &vObj, Ref vRef, const GooString &name, int placeholderLength, const GooString *reason, const GooString *location)
{
vObj.dictAdd("Type", Object(objName, "Sig"));
vObj.dictAdd("Filter", Object(objName, "Adobe.PPKLite"));
vObj.dictAdd("SubFilter", Object(objName, "adbe.pkcs7.detached"));
vObj.dictAdd("Name", Object(name.copy()));
GooString *date = timeToDateString(nullptr);
vObj.dictAdd("M", Object(date));
if (reason && (reason->getLength() > 0)) {
vObj.dictAdd("Reason", Object(reason->copy()));
}
if (location && (location->getLength() > 0)) {
vObj.dictAdd("Location", Object(location->copy()));
}
vObj.dictAdd("Contents", Object(objHexString, new GooString(std::string(placeholderLength, '\0'))));
Object bObj(new Array(xref));
// reserve space in byte range for maximum number of bytes
bObj.arrayAdd(Object(static_cast<long long>(0LL)));
bObj.arrayAdd(Object(static_cast<long long>(9999999999LL)));
bObj.arrayAdd(Object(static_cast<long long>(9999999999LL)));
bObj.arrayAdd(Object(static_cast<long long>(9999999999LL)));
vObj.dictAdd("ByteRange", bObj.copy());
obj.dictSet("V", Object(vRef));
xref->setModifiedObject(&obj, ref);
return true;
}
std::vector<Goffset> FormWidgetSignature::getSignedRangeBounds() const
{
return static_cast<FormFieldSignature *>(field)->getSignedRangeBounds();
}
std::optional<GooString> FormWidgetSignature::getCheckedSignature(Goffset *checkedFileSize)
{
return static_cast<FormFieldSignature *>(field)->getCheckedSignature(checkedFileSize);
}
void FormWidgetSignature::updateWidgetAppearance()
{
if (widget) {
widget->updateAppearanceStream();
}
}
//========================================================================
// FormField
//========================================================================
FormField::FormField(PDFDoc *docA, Object &&aobj, const Ref aref, FormField *parentA, std::set<int> *usedParents, FormFieldType ty)
{
doc = docA;
xref = doc->getXRef();
obj = std::move(aobj);
Dict *dict = obj.getDict();
ref = aref;
type = ty;
parent = parentA;
numChildren = 0;
children = nullptr;
terminal = false;
widgets = nullptr;
readOnly = false;
defaultAppearance = nullptr;
fullyQualifiedName = nullptr;
quadding = VariableTextQuadding::leftJustified;
hasQuadding = false;
standAlone = false;
// childs
Object obj1 = dict->lookup("Kids");
if (obj1.isArray()) {
// Load children
for (int i = 0; i < obj1.arrayGetLength(); i++) {
Ref childRef;
Object childObj = obj1.getArray()->get(i, &childRef);
if (childRef == Ref::INVALID()) {
error(errSyntaxError, -1, "Invalid form field renference");
continue;
}
if (!childObj.isDict()) {
error(errSyntaxError, -1, "Form field child is not a dictionary");
continue;
}
if (usedParents->find(childRef.num) == usedParents->end()) {
// Field child: it could be a form field or a widget or composed dict
const Object &objParent = childObj.dictLookupNF("Parent");
Object obj3 = childObj.dictLookup("Parent");
if (objParent.isRef() || obj3.isDict()) {
// Child is a form field or composed dict
// We create the field, if it's composed
// it will create the widget as a child
std::set<int> usedParentsAux = *usedParents;
usedParentsAux.insert(childRef.num);
if (terminal) {
error(errSyntaxWarning, -1, "Field can't have both Widget AND Field as kids\n");
continue;
}
numChildren++;
children = (FormField **)greallocn(children, numChildren, sizeof(FormField *));
children[numChildren - 1] = Form::createFieldFromDict(std::move(childObj), doc, childRef, this, &usedParentsAux);
} else {
Object obj2 = childObj.dictLookup("Subtype");
if (obj2.isName("Widget")) {
// Child is a widget annotation
if (!terminal && numChildren > 0) {
error(errSyntaxWarning, -1, "Field can't have both Widget AND Field as kids\n");
continue;
}
_createWidget(&childObj, childRef);
}
}
}
}
} else {
// No children, if it's a composed dict, create the child widget
obj1 = dict->lookup("Subtype");
if (obj1.isName("Widget")) {
_createWidget(&obj, ref);
}
}
// flags
obj1 = Form::fieldLookup(dict, "Ff");
if (obj1.isInt()) {
int flags = obj1.getInt();
if (flags & 0x1) { // 1 -> ReadOnly
readOnly = true;
}
if (flags & 0x2) { // 2 -> Required
// TODO
}
if (flags & 0x4) { // 3 -> NoExport
// TODO
}
}
// Variable Text
obj1 = Form::fieldLookup(dict, "DA");
if (obj1.isString()) {
defaultAppearance = obj1.getString()->copy();
}
obj1 = Form::fieldLookup(dict, "Q");
if (obj1.isInt()) {
const VariableTextQuadding aux = static_cast<VariableTextQuadding>(obj1.getInt());
hasQuadding = aux == VariableTextQuadding::leftJustified || aux == VariableTextQuadding::centered || aux == VariableTextQuadding::rightJustified;
if (likely(hasQuadding)) {
quadding = static_cast<VariableTextQuadding>(aux);
}
}
obj1 = dict->lookup("T");
if (obj1.isString()) {
partialName = obj1.getString()->copy();
} else {
partialName = nullptr;
}
obj1 = dict->lookup("TU");
if (obj1.isString()) {
alternateUiName = obj1.getString()->copy();
} else {
alternateUiName = nullptr;
}
obj1 = dict->lookup("TM");
if (obj1.isString()) {
mappingName = obj1.getString()->copy();
} else {
mappingName = nullptr;
}
}
void FormField::setDefaultAppearance(const std::string &appearance)
{
delete defaultAppearance;
defaultAppearance = new GooString(appearance);
}
void FormField::setPartialName(const GooString &name)
{
delete partialName;
partialName = name.copy();
obj.getDict()->set("T", Object(name.copy()));
xref->setModifiedObject(&obj, ref);
}
FormField::~FormField()
{
if (!terminal) {
if (children) {
for (int i = 0; i < numChildren; i++) {
delete children[i];
}
gfree(children);
}
} else {
for (int i = 0; i < numChildren; ++i) {
delete widgets[i];
}
gfree(widgets);
}
delete defaultAppearance;
delete partialName;
delete alternateUiName;
delete mappingName;
delete fullyQualifiedName;
}
void FormField::print(int indent)
{
printf("%*s- (%d %d): [container] terminal: %s children: %d\n", indent, "", ref.num, ref.gen, terminal ? "Yes" : "No", numChildren);
}
void FormField::printTree(int indent)
{
print(indent);
if (terminal) {
for (int i = 0; i < numChildren; i++) {
widgets[i]->print(indent + 4);
}
} else {
for (int i = 0; i < numChildren; i++) {
children[i]->printTree(indent + 4);
}
}
}
void FormField::fillChildrenSiblingsID()
{
if (terminal) {
return;
}
for (int i = 0; i < numChildren; i++) {
children[i]->fillChildrenSiblingsID();
}
}
void FormField::createWidgetAnnotations()
{
if (terminal) {
for (int i = 0; i < numChildren; i++) {
widgets[i]->createWidgetAnnotation();
}
} else {
for (int i = 0; i < numChildren; i++) {
children[i]->createWidgetAnnotations();
}
}
}
void FormField::_createWidget(Object *objA, Ref aref)
{
terminal = true;
numChildren++;
widgets = (FormWidget **)greallocn(widgets, numChildren, sizeof(FormWidget *));
// ID = index in "widgets" table
switch (type) {
case formButton:
widgets[numChildren - 1] = new FormWidgetButton(doc, objA, numChildren - 1, aref, this);
break;
case formText:
widgets[numChildren - 1] = new FormWidgetText(doc, objA, numChildren - 1, aref, this);
break;
case formChoice:
widgets[numChildren - 1] = new FormWidgetChoice(doc, objA, numChildren - 1, aref, this);
break;
case formSignature:
widgets[numChildren - 1] = new FormWidgetSignature(doc, objA, numChildren - 1, aref, this);
break;
default:
error(errSyntaxWarning, -1, "SubType on non-terminal field, invalid document?");
numChildren--;
}
}
FormWidget *FormField::findWidgetByRef(Ref aref)
{
if (terminal) {
for (int i = 0; i < numChildren; i++) {
if (widgets[i]->getRef() == aref) {
return widgets[i];
}
}
} else {
for (int i = 0; i < numChildren; i++) {
FormWidget *result = children[i]->findWidgetByRef(aref);
if (result) {
return result;
}
}
}
return nullptr;
}
GooString *FormField::getFullyQualifiedName()
{
Object parentObj;
const GooString *parent_name;
bool unicode_encoded = false;
if (fullyQualifiedName) {
return fullyQualifiedName;
}
fullyQualifiedName = new GooString();
std::set<int> parsedRefs;
Ref parentRef;
parentObj = obj.getDict()->lookup("Parent", &parentRef);
if (parentRef != Ref::INVALID()) {
parsedRefs.insert(parentRef.num);
}
while (parentObj.isDict()) {
Object obj2 = parentObj.dictLookup("T");
if (obj2.isString()) {
parent_name = obj2.getString();
if (unicode_encoded) {
fullyQualifiedName->insert(0, "\0.", 2); // 2-byte unicode period
if (parent_name->hasUnicodeMarker()) {
fullyQualifiedName->insert(0, parent_name->c_str() + 2, parent_name->getLength() - 2); // Remove the unicode BOM
} else {
int tmp_length;
char *tmp_str = pdfDocEncodingToUTF16(parent_name->toStr(), &tmp_length);
fullyQualifiedName->insert(0, tmp_str + 2, tmp_length - 2); // Remove the unicode BOM
delete[] tmp_str;
}
} else {
fullyQualifiedName->insert(0, '.'); // 1-byte ascii period
if (parent_name->hasUnicodeMarker()) {
unicode_encoded = true;
fullyQualifiedName = convertToUtf16(fullyQualifiedName);
fullyQualifiedName->insert(0, parent_name->c_str() + 2, parent_name->getLength() - 2); // Remove the unicode BOM
} else {
fullyQualifiedName->insert(0, parent_name);
}
}
}
parentObj = parentObj.getDict()->lookup("Parent", &parentRef);
if (parentRef != Ref::INVALID() && !parsedRefs.insert(parentRef.num).second) {
error(errSyntaxError, -1, "FormField: Loop while trying to look for Parents\n");
return fullyQualifiedName;
}
}
if (partialName) {
if (unicode_encoded) {
if (partialName->hasUnicodeMarker()) {
fullyQualifiedName->append(partialName->c_str() + 2, partialName->getLength() - 2); // Remove the unicode BOM
} else {
int tmp_length;
char *tmp_str = pdfDocEncodingToUTF16(partialName->toStr(), &tmp_length);
fullyQualifiedName->append(tmp_str + 2, tmp_length - 2); // Remove the unicode BOM
delete[] tmp_str;
}
} else {
if (partialName->hasUnicodeMarker()) {
unicode_encoded = true;
fullyQualifiedName = convertToUtf16(fullyQualifiedName);
fullyQualifiedName->append(partialName->c_str() + 2, partialName->getLength() - 2); // Remove the unicode BOM
} else {
fullyQualifiedName->append(partialName);
}
}
} else {
int len = fullyQualifiedName->getLength();
// Remove the last period
if (unicode_encoded) {
if (len > 1) {
fullyQualifiedName->del(len - 2, 2);
}
} else {
if (len > 0) {
fullyQualifiedName->del(len - 1, 1);
}
}
}
if (unicode_encoded) {
fullyQualifiedName->prependUnicodeMarker();
}
return fullyQualifiedName;
}
void FormField::updateChildrenAppearance()
{
// Recursively update each child's appearance
if (terminal) {
for (int i = 0; i < numChildren; i++) {
widgets[i]->updateWidgetAppearance();
}
} else {
for (int i = 0; i < numChildren; i++) {
children[i]->updateChildrenAppearance();
}
}
}
void FormField::setReadOnly(bool value)
{
if (value == readOnly) {
return;
}
readOnly = value;
Dict *dict = obj.getDict();
const Object obj1 = Form::fieldLookup(dict, "Ff");
int flags = 0;
if (obj1.isInt()) {
flags = obj1.getInt();
}
if (value) {
flags |= 1;
} else {
flags &= ~1;
}
dict->set("Ff", Object(flags));
xref->setModifiedObject(&obj, ref);
updateChildrenAppearance();
}
void FormField::reset(const std::vector<std::string> &excludedFields)
{
resetChildren(excludedFields);
}
void FormField::resetChildren(const std::vector<std::string> &excludedFields)
{
if (!terminal) {
for (int i = 0; i < numChildren; i++) {
children[i]->reset(excludedFields);
}
}
}
bool FormField::isAmongExcludedFields(const std::vector<std::string> &excludedFields)
{
Ref fieldRef;
for (const std::string &field : excludedFields) {
if (field.compare(field.size() - 2, 2, " R") == 0) {
if (sscanf(field.c_str(), "%d %d R", &fieldRef.num, &fieldRef.gen) == 2 && fieldRef == getRef()) {
return true;
}
} else {
if (field == getFullyQualifiedName()->toStr()) {
return true;
}
}
}
return false;
}
FormField *FormField::findFieldByRef(Ref aref)
{
if (terminal) {
if (this->getRef() == aref) {
return this;
}
} else {
for (int i = 0; i < numChildren; i++) {
FormField *result = children[i]->findFieldByRef(aref);
if (result) {
return result;
}
}
}
return nullptr;
}
FormField *FormField::findFieldByFullyQualifiedName(const std::string &name)
{
if (terminal) {
if (getFullyQualifiedName()->cmp(name.c_str()) == 0) {
return this;
}
} else {
for (int i = 0; i < numChildren; i++) {
FormField *result = children[i]->findFieldByFullyQualifiedName(name);
if (result) {
return result;
}
}
}
return nullptr;
}
//------------------------------------------------------------------------
// FormFieldButton
//------------------------------------------------------------------------
FormFieldButton::FormFieldButton(PDFDoc *docA, Object &&dictObj, const Ref refA, FormField *parentA, std::set<int> *usedParents) : FormField(docA, std::move(dictObj), refA, parentA, usedParents, formButton)
{
Dict *dict = obj.getDict();
active_child = -1;
noAllOff = false;
siblings = nullptr;
numSiblings = 0;
appearanceState.setToNull();
defaultAppearanceState.setToNull();
btype = formButtonCheck;
Object obj1 = Form::fieldLookup(dict, "Ff");
if (obj1.isInt()) {
int flags = obj1.getInt();
if (flags & 0x10000) { // 17 -> push button
btype = formButtonPush;
} else if (flags & 0x8000) { // 16 -> radio button
btype = formButtonRadio;
if (flags & 0x4000) { // 15 -> noToggleToOff
noAllOff = true;
}
}
if (flags & 0x1000000) { // 26 -> radiosInUnison
error(errUnimplemented, -1, "FormFieldButton:: radiosInUnison flag unimplemented, please report a bug with a testcase\n");
}
}
bool isChildRadiobutton = btype == formButtonRadio && terminal && parent && parent->getType() == formButton;
// Ignore "V" for child radiobuttons, so FormFieldButton::getState() does not use it and instead uses the
// "V" of the parent, which is the real value indicating the active field in the radio group. Issue #159
if (btype != formButtonPush && !isChildRadiobutton) {
// Even though V is inheritable we are interested in the value of this
// field, if not present it's probably because it's a button in a set.
appearanceState = dict->lookup("V");
defaultAppearanceState = Form::fieldLookup(dict, "DV");
}
}
static const char *_getButtonType(FormButtonType type)
{
switch (type) {
case formButtonPush:
return "push";
case formButtonCheck:
return "check";
case formButtonRadio:
return "radio";
default:
break;
}
return "unknown";
}
void FormFieldButton::print(int indent)
{
printf("%*s- (%d %d): [%s] terminal: %s children: %d\n", indent, "", ref.num, ref.gen, _getButtonType(btype), terminal ? "Yes" : "No", numChildren);
}
void FormFieldButton::setNumSiblings(int num)
{
numSiblings = num;
siblings = (FormFieldButton **)greallocn(siblings, numSiblings, sizeof(FormFieldButton *));
}
void FormFieldButton::fillChildrenSiblingsID()
{
if (!terminal) {
for (int i = 0; i < numChildren; i++) {
FormFieldButton *child = dynamic_cast<FormFieldButton *>(children[i]);
if (child != nullptr) {
// Fill the siblings of this node childs
child->setNumSiblings(numChildren - 1);
for (int j = 0, counter = 0; j < numChildren; j++) {
FormFieldButton *otherChild = dynamic_cast<FormFieldButton *>(children[j]);
if (i == j) {
continue;
}
if (child == otherChild) {
continue;
}
child->setSibling(counter, otherChild);
counter++;
}
// now call ourselves on the child
// to fill its children data
child->fillChildrenSiblingsID();
}
}
}
}
bool FormFieldButton::setState(const char *state, bool ignoreToggleOff)
{
// A check button could behave as a radio button
// when it's in a set of more than 1 buttons
if (btype != formButtonRadio && btype != formButtonCheck) {
return false;
}
if (terminal && parent && parent->getType() == formButton && appearanceState.isNull()) {
// It's button in a set, set state on parent
if (static_cast<FormFieldButton *>(parent)->setState(state)) {
return true;
}
return false;
}
bool isOn = strcmp(state, "Off") != 0;
if (!isOn && noAllOff && !ignoreToggleOff) {
return false; // Don't allow to set all radio to off
}
const char *current = getAppearanceState();
bool currentFound = false, newFound = false;
for (int i = 0; i < numChildren; i++) {
FormWidgetButton *widget;
// If radio button is a terminal field we want the widget at i, but
// if it's not terminal, the child widget is a composed dict, so
// we want the ony child widget of the children at i
if (terminal) {
widget = static_cast<FormWidgetButton *>(widgets[i]);
} else {
widget = static_cast<FormWidgetButton *>(children[i]->getWidget(0));
}
if (!widget->getOnStr()) {
continue;
}
const char *onStr = widget->getOnStr();
if (current && strcmp(current, onStr) == 0) {
widget->setAppearanceState("Off");
if (!isOn) {
break;
}
currentFound = true;
}
if (isOn && strcmp(state, onStr) == 0) {
widget->setAppearanceState(state);
newFound = true;
}
if (currentFound && newFound) {
break;
}
}
updateState(state);
return true;
}
bool FormFieldButton::getState(const char *state) const
{
if (appearanceState.isName(state)) {
return true;
}
return (parent && parent->getType() == formButton) ? static_cast<FormFieldButton *>(parent)->getState(state) : false;
}
void FormFieldButton::updateState(const char *state)
{
appearanceState = Object(objName, state);
obj.getDict()->set("V", appearanceState.copy());
xref->setModifiedObject(&obj, ref);
}
FormFieldButton::~FormFieldButton()
{
if (siblings) {
gfree(siblings);
}
}
void FormFieldButton::reset(const std::vector<std::string> &excludedFields)
{
if (!isAmongExcludedFields(excludedFields)) {
if (getDefaultAppearanceState()) {
setState(getDefaultAppearanceState());
} else {
obj.getDict()->remove("V");
// Clear check button if it doesn't have default value.
// This behaviour is what Adobe Reader does, it is not written in specification.
if (btype == formButtonCheck) {
setState("Off");
}
}
}
resetChildren(excludedFields);
}
//------------------------------------------------------------------------
// FormFieldText
//------------------------------------------------------------------------
FormFieldText::FormFieldText(PDFDoc *docA, Object &&dictObj, const Ref refA, FormField *parentA, std::set<int> *usedParents) : FormField(docA, std::move(dictObj), refA, parentA, usedParents, formText)
{
Dict *dict = obj.getDict();
Object obj1;
content = nullptr;
internalContent = nullptr;
defaultContent = nullptr;
multiline = password = fileSelect = doNotSpellCheck = doNotScroll = comb = richText = false;
maxLen = 0;
obj1 = Form::fieldLookup(dict, "Ff");
if (obj1.isInt()) {
int flags = obj1.getInt();
if (flags & 0x1000) { // 13 -> Multiline
multiline = true;
}
if (flags & 0x2000) { // 14 -> Password
password = true;
}
if (flags & 0x100000) { // 21 -> FileSelect
fileSelect = true;
}
if (flags & 0x400000) { // 23 -> DoNotSpellCheck
doNotSpellCheck = true;
}
if (flags & 0x800000) { // 24 -> DoNotScroll
doNotScroll = true;
}
if (flags & 0x1000000) { // 25 -> Comb
comb = true;
}
if (flags & 0x2000000) { // 26 -> RichText
richText = true;
}
}
obj1 = Form::fieldLookup(dict, "MaxLen");
if (obj1.isInt()) {
maxLen = obj1.getInt();
}
fillContent(fillDefaultValue);
fillContent(fillValue);
}
void FormFieldText::fillContent(FillValueType fillType)
{
Dict *dict = obj.getDict();
Object obj1;
obj1 = Form::fieldLookup(dict, fillType == fillDefaultValue ? "DV" : "V");
if (obj1.isString()) {
if (obj1.getString()->hasUnicodeMarker()) {
if (obj1.getString()->getLength() > 2) {
if (fillType == fillDefaultValue) {
defaultContent = obj1.getString()->copy();
} else {
content = obj1.getString()->copy();
}
}
} else if (obj1.getString()->getLength() > 0) {
// non-unicode string -- assume pdfDocEncoding and try to convert to UTF16BE
int tmp_length;
char *tmp_str = pdfDocEncodingToUTF16(obj1.getString()->toStr(), &tmp_length);
if (fillType == fillDefaultValue) {
defaultContent = new GooString(tmp_str, tmp_length);
} else {
content = new GooString(tmp_str, tmp_length);
}
delete[] tmp_str;
}
}
}
void FormFieldText::print(int indent)
{
printf("%*s- (%d %d): [text] terminal: %s children: %d\n", indent, "", ref.num, ref.gen, terminal ? "Yes" : "No", numChildren);
}
void FormFieldText::setContentCopy(const GooString *new_content)
{
delete content;
content = nullptr;
if (new_content) {
content = new_content->copy();
// append the unicode marker <FE FF> if needed
if (!content->hasUnicodeMarker()) {
content->prependUnicodeMarker();
}
Form *form = doc->getCatalog()->getForm();
if (form) {
DefaultAppearance da(defaultAppearance);
if (da.getFontName().isName()) {
const std::string fontName = da.getFontName().getName();
if (!fontName.empty()) {
// Use the field resource dictionary if it exists
Object fieldResourcesDictObj = obj.dictLookup("DR");
if (fieldResourcesDictObj.isDict()) {
GfxResources fieldResources(doc->getXRef(), fieldResourcesDictObj.getDict(), form->getDefaultResources());
const std::vector<Form::AddFontResult> newFonts = form->ensureFontsForAllCharacters(content, fontName, &fieldResources);
// If we added new fonts to the Form object default resuources we also need to add them (we only add the ref so this is cheap)
// to the field DR dictionary
for (const Form::AddFontResult &afr : newFonts) {
fieldResourcesDictObj.dictLookup("Font").dictAdd(afr.fontName.c_str(), Object(afr.ref));
}
} else {
form->ensureFontsForAllCharacters(content, fontName);
}
}
} else {
// This is wrong, there has to be a Tf in DA
}
}
}
obj.getDict()->set("V", Object(content ? content->copy() : new GooString("")));
xref->setModifiedObject(&obj, ref);
updateChildrenAppearance();
}
void FormFieldText::setAppearanceContentCopy(const GooString *new_content)
{
delete internalContent;
internalContent = nullptr;
if (new_content) {
internalContent = new_content->copy();
}
updateChildrenAppearance();
}
FormFieldText::~FormFieldText()
{
delete content;
delete internalContent;
delete defaultContent;
}
void FormFieldText::reset(const std::vector<std::string> &excludedFields)
{
if (!isAmongExcludedFields(excludedFields)) {
setContentCopy(defaultContent);
if (defaultContent == nullptr) {
obj.getDict()->remove("V");
}
}
resetChildren(excludedFields);
}
double FormFieldText::getTextFontSize()
{
std::vector<std::string> daToks;
int idx = parseDA(&daToks);
double fontSize = -1;
if (idx >= 0) {
char *p = nullptr;
fontSize = strtod(daToks[idx].c_str(), &p);
if (!p || *p) {
fontSize = -1;
}
}
return fontSize;
}
void FormFieldText::setTextFontSize(int fontSize)
{
if (fontSize > 0 && obj.isDict()) {
std::vector<std::string> daToks;
int idx = parseDA(&daToks);
if (idx == -1) {
error(errSyntaxError, -1, "FormFieldText:: invalid DA object\n");
return;
}
if (defaultAppearance) {
delete defaultAppearance;
}
defaultAppearance = new GooString;
for (std::size_t i = 0; i < daToks.size(); ++i) {
if (i > 0) {
defaultAppearance->append(' ');
}
if (i == (std::size_t)idx) {
defaultAppearance->appendf("{0:d}", fontSize);
} else {
defaultAppearance->append(daToks[i]);
}
}
obj.dictSet("DA", Object(defaultAppearance->copy()));
xref->setModifiedObject(&obj, ref);
updateChildrenAppearance();
}
}
int FormFieldText::tokenizeDA(const std::string &da, std::vector<std::string> *daToks, const char *searchTok)
{
int idx = -1;
if (daToks) {
size_t i = 0;
size_t j = 0;
while (i < da.size()) {
while (i < da.size() && Lexer::isSpace(da[i])) {
++i;
}
if (i < da.size()) {
for (j = i + 1; j < da.size() && !Lexer::isSpace(da[j]); ++j) { }
std::string tok(da, i, j - i);
if (searchTok && tok == searchTok) {
idx = daToks->size();
}
daToks->emplace_back(std::move(tok));
i = j;
}
}
}
return idx;
}
int FormFieldText::parseDA(std::vector<std::string> *daToks)
{
int idx = -1;
if (obj.isDict()) {
Object objDA(obj.dictLookup("DA"));
if (objDA.isString()) {
const GooString *da = objDA.getString();
idx = tokenizeDA(da->toStr(), daToks, "Tf") - 1;
}
}
return idx;
}
//------------------------------------------------------------------------
// FormFieldChoice
//------------------------------------------------------------------------
FormFieldChoice::FormFieldChoice(PDFDoc *docA, Object &&aobj, const Ref refA, FormField *parentA, std::set<int> *usedParents) : FormField(docA, std::move(aobj), refA, parentA, usedParents, formChoice)
{
numChoices = 0;
choices = nullptr;
defaultChoices = nullptr;
editedChoice = nullptr;
topIdx = 0;
Dict *dict = obj.getDict();
Object obj1;
combo = edit = multiselect = doNotSpellCheck = doCommitOnSelChange = false;
obj1 = Form::fieldLookup(dict, "Ff");
if (obj1.isInt()) {
int flags = obj1.getInt();
if (flags & 0x20000) { // 18 -> Combo
combo = true;
}
if (flags & 0x40000) { // 19 -> Edit
edit = true;
}
if (flags & 0x200000) { // 22 -> MultiSelect
multiselect = true;
}
if (flags & 0x400000) { // 23 -> DoNotSpellCheck
doNotSpellCheck = true;
}
if (flags & 0x4000000) { // 27 -> CommitOnSelChange
doCommitOnSelChange = true;
}
}
obj1 = dict->lookup("TI");
if (obj1.isInt()) {
topIdx = obj1.getInt();
if (topIdx < 0) {
error(errSyntaxError, -1, "FormFieldChoice:: invalid topIdx entry\n");
topIdx = 0;
}
}
obj1 = Form::fieldLookup(dict, "Opt");
if (obj1.isArray()) {
numChoices = obj1.arrayGetLength();
choices = new ChoiceOpt[numChoices];
memset(choices, 0, sizeof(ChoiceOpt) * numChoices);
for (int i = 0; i < numChoices; i++) {
Object obj2 = obj1.arrayGet(i);
if (obj2.isString()) {
choices[i].optionName = obj2.getString()->copy();
} else if (obj2.isArray()) { // [Export_value, Displayed_text]
if (obj2.arrayGetLength() < 2) {
error(errSyntaxError, -1, "FormWidgetChoice:: invalid Opt entry -- array's length < 2\n");
continue;
}
Object obj3 = obj2.arrayGet(0);
if (obj3.isString()) {
choices[i].exportVal = obj3.getString()->copy();
} else {
error(errSyntaxError, -1, "FormWidgetChoice:: invalid Opt entry -- exported value not a string\n");
}
obj3 = obj2.arrayGet(1);
if (obj3.isString()) {
choices[i].optionName = obj3.getString()->copy();
} else {
error(errSyntaxError, -1, "FormWidgetChoice:: invalid Opt entry -- choice name not a string\n");
}
} else {
error(errSyntaxError, -1, "FormWidgetChoice:: invalid {0:d} Opt entry\n", i);
}
}
} else {
// empty choice
}
// Find selected items
// Note: PDF specs say that /V has precedence over /I, but acroread seems to
// do the opposite. We do the same.
obj1 = Form::fieldLookup(dict, "I");
if (obj1.isArray()) {
for (int i = 0; i < obj1.arrayGetLength(); i++) {
Object obj2 = obj1.arrayGet(i);
if (obj2.isInt() && obj2.getInt() >= 0 && obj2.getInt() < numChoices) {
choices[obj2.getInt()].selected = true;
}
}
} else {
// Note: According to PDF specs, /V should *never* contain the exportVal.
// However, if /Opt is an array of (exportVal,optionName) pairs, acroread
// seems to expect the exportVal instead of the optionName and so we do too.
fillChoices(fillValue);
}
fillChoices(fillDefaultValue);
}
void FormFieldChoice::fillChoices(FillValueType fillType)
{
const char *key = fillType == fillDefaultValue ? "DV" : "V";
Dict *dict = obj.getDict();
Object obj1;
obj1 = Form::fieldLookup(dict, key);
if (obj1.isString() || obj1.isArray()) {
if (fillType == fillDefaultValue) {
defaultChoices = new bool[numChoices];
memset(defaultChoices, 0, sizeof(bool) * numChoices);
}
if (obj1.isString()) {
bool optionFound = false;
for (int i = 0; i < numChoices; i++) {
if (choices[i].exportVal) {
if (choices[i].exportVal->cmp(obj1.getString()) == 0) {
optionFound = true;
}
} else if (choices[i].optionName) {
if (choices[i].optionName->cmp(obj1.getString()) == 0) {
optionFound = true;
}
}
if (optionFound) {
if (fillType == fillDefaultValue) {
defaultChoices[i] = true;
} else {
choices[i].selected = true;
}
break; // We've determined that this option is selected. No need to keep on scanning
}
}
// Set custom value if /V doesn't refer to any predefined option and the field is user-editable
if (fillType == fillValue && !optionFound && edit) {
editedChoice = obj1.getString()->copy();
}
} else if (obj1.isArray()) {
for (int i = 0; i < numChoices; i++) {
for (int j = 0; j < obj1.arrayGetLength(); j++) {
const Object obj2 = obj1.arrayGet(j);
if (!obj2.isString()) {
error(errSyntaxError, -1, "FormWidgetChoice:: {0:s} array contains a non string object", key);
continue;
}
bool matches = false;
if (choices[i].exportVal) {
if (choices[i].exportVal->cmp(obj2.getString()) == 0) {
matches = true;
}
} else if (choices[i].optionName) {
if (choices[i].optionName->cmp(obj2.getString()) == 0) {
matches = true;
}
}
if (matches) {
if (fillType == fillDefaultValue) {
defaultChoices[i] = true;
} else {
choices[i].selected = true;
}
break; // We've determined that this option is selected. No need to keep on scanning
}
}
}
}
}
}
FormFieldChoice::~FormFieldChoice()
{
for (int i = 0; i < numChoices; i++) {
delete choices[i].exportVal;
delete choices[i].optionName;
}
delete[] choices;
delete[] defaultChoices;
delete editedChoice;
}
void FormFieldChoice::print(int indent)
{
printf("%*s- (%d %d): [choice] terminal: %s children: %d\n", indent, "", ref.num, ref.gen, terminal ? "Yes" : "No", numChildren);
}
void FormFieldChoice::updateSelection()
{
Object objV;
Object objI(objNull);
if (edit && editedChoice) {
// This is an editable combo-box with user-entered text
objV = Object(editedChoice->copy());
} else {
const int numSelected = getNumSelected();
// Create /I array only if multiple selection is allowed (as per PDF spec)
if (multiselect) {
objI = Object(new Array(xref));
}
if (numSelected == 0) {
// No options are selected
objV = Object(new GooString(""));
} else if (numSelected == 1) {
// Only one option is selected
for (int i = 0; i < numChoices; i++) {
if (choices[i].selected) {
if (multiselect) {
objI.arrayAdd(Object(i));
}
if (choices[i].exportVal) {
objV = Object(choices[i].exportVal->copy());
} else if (choices[i].optionName) {
objV = Object(choices[i].optionName->copy());
}
break; // We've just written the selected option. No need to keep on scanning
}
}
} else {
// More than one option is selected
objV = Object(new Array(xref));
for (int i = 0; i < numChoices; i++) {
if (choices[i].selected) {
if (multiselect) {
objI.arrayAdd(Object(i));
}
if (choices[i].exportVal) {
objV.arrayAdd(Object(choices[i].exportVal->copy()));
} else if (choices[i].optionName) {
objV.arrayAdd(Object(choices[i].optionName->copy()));
}
}
}
}
}
obj.getDict()->set("V", std::move(objV));
obj.getDict()->set("I", std::move(objI));
xref->setModifiedObject(&obj, ref);
updateChildrenAppearance();
}
void FormFieldChoice::unselectAll()
{
for (int i = 0; i < numChoices; i++) {
choices[i].selected = false;
}
}
void FormFieldChoice::deselectAll()
{
delete editedChoice;
editedChoice = nullptr;
unselectAll();
updateSelection();
}
void FormFieldChoice::toggle(int i)
{
delete editedChoice;
editedChoice = nullptr;
choices[i].selected = !choices[i].selected;
updateSelection();
}
void FormFieldChoice::select(int i)
{
delete editedChoice;
editedChoice = nullptr;
if (!multiselect) {
unselectAll();
}
choices[i].selected = true;
updateSelection();
}
void FormFieldChoice::setEditChoice(const GooString *new_content)
{
delete editedChoice;
editedChoice = nullptr;
unselectAll();
if (new_content) {
editedChoice = new_content->copy();
// append the unicode marker <FE FF> if needed
if (!editedChoice->hasUnicodeMarker()) {
editedChoice->prependUnicodeMarker();
}
}
updateSelection();
}
const GooString *FormFieldChoice::getEditChoice() const
{
return editedChoice;
}
int FormFieldChoice::getNumSelected()
{
int cnt = 0;
for (int i = 0; i < numChoices; i++) {
if (choices[i].selected) {
cnt++;
}
}
return cnt;
}
const GooString *FormFieldChoice::getSelectedChoice() const
{
if (edit && editedChoice) {
return editedChoice;
}
for (int i = 0; i < numChoices; i++) {
if (choices[i].optionName && choices[i].selected) {
return choices[i].optionName;
}
}
return nullptr;
}
void FormFieldChoice::reset(const std::vector<std::string> &excludedFields)
{
if (!isAmongExcludedFields(excludedFields)) {
delete editedChoice;
editedChoice = nullptr;
if (defaultChoices) {
for (int i = 0; i < numChoices; i++) {
choices[i].selected = defaultChoices[i];
}
} else {
unselectAll();
}
}
resetChildren(excludedFields);
updateSelection();
}
//------------------------------------------------------------------------
// FormFieldSignature
//------------------------------------------------------------------------
FormFieldSignature::FormFieldSignature(PDFDoc *docA, Object &&dict, const Ref refA, FormField *parentA, std::set<int> *usedParents)
: FormField(docA, std::move(dict), refA, parentA, usedParents, formSignature), signature_type(unsigned_signature_field), signature(nullptr)
{
signature_info = new SignatureInfo();
parseInfo();
}
FormFieldSignature::~FormFieldSignature()
{
delete signature_info;
delete signature;
}
void FormFieldSignature::setSignature(const GooString &sig)
{
delete signature;
signature = sig.copy();
}
const GooString &FormFieldSignature::getCustomAppearanceContent() const
{
return customAppearanceContent;
}
void FormFieldSignature::setCustomAppearanceContent(const GooString &s)
{
customAppearanceContent = GooString(s.toStr());
}
const GooString &FormFieldSignature::getCustomAppearanceLeftContent() const
{
return customAppearanceLeftContent;
}
void FormFieldSignature::setCustomAppearanceLeftContent(const GooString &s)
{
customAppearanceLeftContent = GooString(s.toStr());
}
double FormFieldSignature::getCustomAppearanceLeftFontSize() const
{
return customAppearanceLeftFontSize;
}
void FormFieldSignature::setCustomAppearanceLeftFontSize(double size)
{
customAppearanceLeftFontSize = size;
}
Ref FormFieldSignature::getImageResource() const
{
return imageResource;
}
void FormFieldSignature::setImageResource(const Ref imageResourceA)
{
imageResource = imageResourceA;
}
void FormFieldSignature::setCertificateInfo(std::unique_ptr<X509CertificateInfo> &certInfo)
{
certificate_info.swap(certInfo);
}
FormWidget *FormFieldSignature::getCreateWidget()
{
::FormWidget *fw = getWidget(0);
if (!fw) {
error(errSyntaxError, 0, "FormFieldSignature: was asked for widget and didn't had one, creating it");
_createWidget(&obj, ref);
fw = getWidget(0);
fw->createWidgetAnnotation();
}
return fw;
}
void FormFieldSignature::parseInfo()
{
if (!obj.isDict()) {
return;
}
// retrieve PKCS#7
Object sig_dict = obj.dictLookup("V");
if (!sig_dict.isDict()) {
return;
}
Object contents_obj = sig_dict.dictLookup("Contents");
if (contents_obj.isString()) {
signature = contents_obj.getString()->copy();
}
byte_range = sig_dict.dictLookup("ByteRange");
const Object location_obj = sig_dict.dictLookup("Location");
if (location_obj.isString()) {
signature_info->setLocation(location_obj.getString());
}
const Object reason_obj = sig_dict.dictLookup("Reason");
if (reason_obj.isString()) {
signature_info->setReason(reason_obj.getString());
}
// retrieve SigningTime
Object time_of_signing = sig_dict.dictLookup("M");
if (time_of_signing.isString()) {
const GooString *time_str = time_of_signing.getString();
signature_info->setSigningTime(dateStringToTime(time_str)); // Put this information directly in SignatureInfo object
}
// check if subfilter is supported for signature validation, only detached signatures work for now
Object subfilterName = sig_dict.dictLookup("SubFilter");
if (subfilterName.isName("adbe.pkcs7.sha1")) {
signature_type = adbe_pkcs7_sha1;
signature_info->setSubFilterSupport(true);
} else if (subfilterName.isName("adbe.pkcs7.detached")) {
signature_type = adbe_pkcs7_detached;
signature_info->setSubFilterSupport(true);
} else if (subfilterName.isName("ETSI.CAdES.detached")) {
signature_type = ETSI_CAdES_detached;
signature_info->setSubFilterSupport(true);
} else {
signature_type = unknown_signature_type;
}
}
void FormFieldSignature::hashSignedDataBlock(CryptoSign::VerificationInterface *handler, Goffset block_len)
{
if (!handler) {
return;
}
const int BLOCK_SIZE = 4096;
unsigned char signed_data_buffer[BLOCK_SIZE];
Goffset i = 0;
while (i < block_len) {
Goffset bytes_left = block_len - i;
if (bytes_left < BLOCK_SIZE) {
doc->getBaseStream()->doGetChars(static_cast<int>(bytes_left), signed_data_buffer);
handler->addData(signed_data_buffer, static_cast<int>(bytes_left));
i = block_len;
} else {
doc->getBaseStream()->doGetChars(BLOCK_SIZE, signed_data_buffer);
handler->addData(signed_data_buffer, BLOCK_SIZE);
i += BLOCK_SIZE;
}
}
}
FormSignatureType FormWidgetSignature::signatureType() const
{
return static_cast<FormFieldSignature *>(field)->getSignatureType();
}
void FormWidgetSignature::setSignatureType(FormSignatureType fst)
{
static_cast<FormFieldSignature *>(field)->setSignatureType(fst);
}
SignatureInfo *FormFieldSignature::validateSignature(bool doVerifyCert, bool forceRevalidation, time_t validationTime, bool ocspRevocationCheck, bool enableAIA)
{
auto backend = CryptoSign::Factory::createActive();
if (!backend) {
return signature_info;
}
if (signature_info->getSignatureValStatus() != SIGNATURE_NOT_VERIFIED && !forceRevalidation) {
return signature_info;
}
if (signature == nullptr) {
error(errSyntaxError, 0, "Invalid or missing Signature string");
return signature_info;
}
if (!byte_range.isArray()) {
error(errSyntaxError, 0, "Invalid or missing ByteRange array");
return signature_info;
}
int arrayLen = byte_range.arrayGetLength();
if (arrayLen < 2) {
error(errSyntaxError, 0, "Too few elements in ByteRange array");
return signature_info;
}
const int signature_len = signature->getLength();
std::vector<unsigned char> signatureData(signature_len);
memcpy(signatureData.data(), signature->c_str(), signature_len);
auto signature_handler = backend->createVerificationHandler(std::move(signatureData));
Goffset fileLength = doc->getBaseStream()->getLength();
for (int i = 0; i < arrayLen / 2; i++) {
Object offsetObj = byte_range.arrayGet(i * 2);
Object lenObj = byte_range.arrayGet(i * 2 + 1);
if (!offsetObj.isIntOrInt64() || !lenObj.isIntOrInt64()) {
error(errSyntaxError, 0, "Illegal values in ByteRange array");
return signature_info;
}
Goffset offset = offsetObj.getIntOrInt64();
Goffset len = lenObj.getIntOrInt64();
if (offset < 0 || offset >= fileLength || len < 0 || len > fileLength || offset + len > fileLength) {
error(errSyntaxError, 0, "Illegal values in ByteRange array");
return signature_info;
}
doc->getBaseStream()->setPos(offset);
hashSignedDataBlock(signature_handler.get(), len);
}
if (!signature_info->isSubfilterSupported()) {
error(errUnimplemented, 0, "Unable to validate this type of signature");
return signature_info;
}
const SignatureValidationStatus sig_val_state = signature_handler->validateSignature();
signature_info->setSignatureValStatus(sig_val_state);
signature_info->setSignerName(signature_handler->getSignerName());
signature_info->setSubjectDN(signature_handler->getSignerSubjectDN());
signature_info->setHashAlgorithm(signature_handler->getHashAlgorithm());
// verify if signature contains a 'signing time' attribute
if (signature_handler->getSigningTime() != std::chrono::system_clock::time_point {}) {
signature_info->setSigningTime(std::chrono::system_clock::to_time_t(signature_handler->getSigningTime()));
}
signature_info->setCertificateInfo(signature_handler->getCertificateInfo());
if (sig_val_state != SIGNATURE_VALID || !doVerifyCert) {
return signature_info;
}
const CertificateValidationStatus cert_val_state = signature_handler->validateCertificate(std::chrono::system_clock::from_time_t(validationTime), ocspRevocationCheck, enableAIA);
signature_info->setCertificateValStatus(cert_val_state);
return signature_info;
}
std::vector<Goffset> FormFieldSignature::getSignedRangeBounds() const
{
std::vector<Goffset> range_vec;
if (byte_range.isArray()) {
if (byte_range.arrayGetLength() == 4) {
for (int i = 0; i < 2; ++i) {
const Object offsetObj(byte_range.arrayGet(2 * i));
const Object lenObj(byte_range.arrayGet(2 * i + 1));
if (offsetObj.isIntOrInt64() && lenObj.isIntOrInt64()) {
const Goffset offset = offsetObj.getIntOrInt64();
const Goffset len = lenObj.getIntOrInt64();
range_vec.push_back(offset);
range_vec.push_back(offset + len);
}
}
}
}
return range_vec;
}
std::optional<GooString> FormFieldSignature::getCheckedSignature(Goffset *checkedFileSize)
{
Goffset start = 0;
Goffset end = 0;
const std::vector<Goffset> ranges = getSignedRangeBounds();
if (ranges.size() == 4) {
start = ranges[1];
end = ranges[2];
}
if (end >= start + 6) {
BaseStream *stream = doc->getBaseStream();
*checkedFileSize = stream->getLength();
Goffset len = end - start;
stream->setPos(end - 1);
int c2 = stream->lookChar();
stream->setPos(start);
int c1 = stream->getChar();
// PDF signatures are first ASN1 DER, then hex encoded PKCS#7 structures,
// possibly padded with 0 characters and enclosed in '<' and '>'.
// The ASN1 DER encoding of a PKCS#7 structure must start with the tag 0x30
// for SEQUENCE. The next byte must be 0x80 for ASN1 DER indefinite length
// encoding or (0x80 + n) for ASN1 DER definite length encoding
// where n is the number of subsequent "length bytes" which big-endian
// encode the length of the content of the SEQUENCE following them.
if (len <= std::numeric_limits<int>::max() && *checkedFileSize > end && c1 == '<' && c2 == '>') {
GooString gstr;
++start;
--end;
len = end - start;
Goffset pos = 0;
do {
c1 = stream->getChar();
if (c1 == EOF) {
return {};
}
gstr.append(static_cast<char>(c1));
} while (++pos < len);
if (gstr.getChar(0) == '3' && gstr.getChar(1) == '0') {
if (gstr.getChar(2) == '8' && gstr.getChar(3) == '0') {
// ASN1 DER indefinite length encoding:
// We only check that all characters up to the enclosing '>'
// are hex characters and that there are two hex encoded 0 bytes
// just before the enclosing '>' marking the end of the indefinite
// length encoding.
int paddingCount = 0;
while (gstr.getChar(len - 1) == '0' && gstr.getChar(len - 2) == '0') {
++paddingCount;
len -= 2;
}
if (paddingCount < 2 || len % 2 == 1) {
len = 0;
}
} else if (gstr.getChar(2) == '8') {
// ASN1 DER definite length encoding:
// We calculate the length of the following bytes from the length bytes and
// check that after the length bytes and the following calculated number of
// bytes all bytes up to the enclosing '>' character are hex encoded 0 bytes.
int lenBytes = gstr.getChar(3) - '0';
if (lenBytes > 0 && lenBytes <= 4) {
int sigLen = 0;
for (int i = 0; i < 2 * lenBytes; ++i) {
sigLen <<= 4;
char c = gstr.getChar(i + 4);
if (isdigit(c)) {
sigLen += c - '0';
} else if (isxdigit(c) && c >= 'a') {
sigLen += c - 'a' + 10;
} else if (isxdigit(c) && c >= 'A') {
sigLen += c - 'A' + 10;
} else {
len = 0;
break;
}
}
if (sigLen > 0 && 2 * (sigLen + lenBytes) <= len - 4) {
for (Goffset i = 2 * (sigLen + lenBytes) + 4; i < len; ++i) {
if (gstr.getChar(i) != '0') {
len = 0;
break;
}
}
} else {
len = 0;
}
}
}
for (const char c : gstr.toStr()) {
if (!isxdigit(c)) {
len = 0;
}
}
if (len > 0) {
return GooString(&gstr, 0, len);
}
}
}
}
return {};
}
void FormFieldSignature::print(int indent)
{
printf("%*s- (%d %d): [signature] terminal: %s children: %d\n", indent, "", ref.num, ref.gen, terminal ? "Yes" : "No", numChildren);
}
//------------------------------------------------------------------------
// Form
//------------------------------------------------------------------------
Form::Form(PDFDoc *docA) : doc(docA)
{
Object obj1;
XRef *xref = doc->getXRef();
size = 0;
numFields = 0;
rootFields = nullptr;
quadding = VariableTextQuadding::leftJustified;
defaultAppearance = nullptr;
defaultResources = nullptr;
Object *acroForm = doc->getCatalog()->getAcroForm();
needAppearances = acroForm->dictLookup("NeedAppearances").getBoolWithDefaultValue(false);
obj1 = acroForm->dictLookup("DA");
if (obj1.isString()) {
defaultAppearance = obj1.getString()->copy();
}
obj1 = acroForm->dictLookup("Q");
if (obj1.isInt()) {
const VariableTextQuadding aux = static_cast<VariableTextQuadding>(obj1.getInt());
if (aux == VariableTextQuadding::leftJustified || aux == VariableTextQuadding::centered || aux == VariableTextQuadding::rightJustified) {
quadding = static_cast<VariableTextQuadding>(aux);
}
}
resDict = acroForm->dictLookup("DR");
if (resDict.isDict()) {
// At a minimum, this dictionary shall contain a Font entry
obj1 = resDict.dictLookup("Font");
if (obj1.isDict()) {
defaultResources = new GfxResources(xref, resDict.getDict(), nullptr);
}
}
if (!defaultResources) {
resDict.setToNull();
}
obj1 = acroForm->dictLookup("Fields");
if (obj1.isArray()) {
Array *array = obj1.getArray();
std::set<Ref> alreadyReadRefs;
for (int i = 0; i < array->getLength(); i++) {
Object obj2 = array->get(i);
const Object &oref = array->getNF(i);
if (!oref.isRef()) {
error(errSyntaxWarning, -1, "Direct object in rootFields");
continue;
}
if (!obj2.isDict()) {
error(errSyntaxWarning, -1, "Reference in Fields array to an invalid or non existent object");
continue;
}
if (alreadyReadRefs.find(oref.getRef()) != alreadyReadRefs.end()) {
continue;
}
alreadyReadRefs.insert(oref.getRef());
if (numFields >= size) {
size += 16;
rootFields = (FormField **)greallocn(rootFields, size, sizeof(FormField *));
}
std::set<int> usedParents;
rootFields[numFields++] = createFieldFromDict(std::move(obj2), doc, oref.getRef(), nullptr, &usedParents);
}
} else {
error(errSyntaxError, -1, "Can't get Fields array\n");
}
obj1 = acroForm->dictLookup("CO");
if (obj1.isArray()) {
Array *array = obj1.getArray();
calculateOrder.reserve(array->getLength());
for (int i = 0; i < array->getLength(); i++) {
const Object &oref = array->getNF(i);
if (!oref.isRef()) {
error(errSyntaxWarning, -1, "Direct object in CO");
continue;
}
calculateOrder.push_back(oref.getRef());
}
}
// for (int i = 0; i < numFields; i++)
// rootFields[i]->printTree();
}
Form::~Form()
{
int i;
for (i = 0; i < numFields; ++i) {
delete rootFields[i];
}
gfree(rootFields);
delete defaultAppearance;
delete defaultResources;
}
// Look up an inheritable field dictionary entry.
static Object fieldLookup(Dict *field, const char *key, std::set<int> *usedParents)
{
Dict *dict = field;
Object obj = dict->lookup(key);
if (!obj.isNull()) {
return obj;
}
const Object &parent = dict->lookupNF("Parent");
if (parent.isRef()) {
const Ref ref = parent.getRef();
if (usedParents->find(ref.num) == usedParents->end()) {
usedParents->insert(ref.num);
Object obj2 = parent.fetch(dict->getXRef());
if (obj2.isDict()) {
return fieldLookup(obj2.getDict(), key, usedParents);
}
}
} else if (parent.isDict()) {
return fieldLookup(parent.getDict(), key, usedParents);
}
return Object(objNull);
}
Object Form::fieldLookup(Dict *field, const char *key)
{
std::set<int> usedParents;
return ::fieldLookup(field, key, &usedParents);
}
FormField *Form::createFieldFromDict(Object &&obj, PDFDoc *docA, const Ref aref, FormField *parent, std::set<int> *usedParents)
{
FormField *field;
const Object obj2 = Form::fieldLookup(obj.getDict(), "FT");
if (obj2.isName("Btn")) {
field = new FormFieldButton(docA, std::move(obj), aref, parent, usedParents);
} else if (obj2.isName("Tx")) {
field = new FormFieldText(docA, std::move(obj), aref, parent, usedParents);
} else if (obj2.isName("Ch")) {
field = new FormFieldChoice(docA, std::move(obj), aref, parent, usedParents);
} else if (obj2.isName("Sig")) {
field = new FormFieldSignature(docA, std::move(obj), aref, parent, usedParents);
} else { // we don't have an FT entry => non-terminal field
field = new FormField(docA, std::move(obj), aref, parent, usedParents);
}
return field;
}
static const std::string kOurDictFontNamePrefix = "popplerfont";
std::string Form::findFontInDefaultResources(const std::string &fontFamily, const std::string &fontStyle) const
{
if (!resDict.isDict()) {
return {};
}
const std::string fontFamilyAndStyle = fontStyle.empty() ? fontFamily : fontFamily + " " + fontStyle;
Object fontDictObj = resDict.dictLookup("Font");
assert(fontDictObj.isDict());
const Dict *fontDict = fontDictObj.getDict();
for (int i = 0; i < fontDict->getLength(); ++i) {
const