// Copyright (C) 2020-2022 Jakub Melka
//
// This file is part of PDF4QT.
//
// PDF4QT is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// with the written consent of the copyright owner, any later version.
//
// PDF4QT 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDF4QT. If not, see .
#include "pdfjavascriptscanner.h"
#include "pdfaction.h"
#include "pdfform.h"
#include "pdfdbgheap.h"
namespace pdf
{
PDFJavaScriptScanner::PDFJavaScriptScanner(const PDFDocument* document) :
m_document(document)
{
}
PDFJavaScriptScanner::Entries PDFJavaScriptScanner::scan(const std::vector& pages, Options options) const
{
Entries result;
auto scanAction = [options, &result](PDFJavaScriptEntry::Type type, PDFInteger pageIndex, const PDFAction* action)
{
if (!result.empty() && options.testFlag(FindFirstOnly))
{
return;
}
if (action)
{
std::vector actions = action->getActionList();
for (const PDFAction* a : actions)
{
switch (a->getType())
{
case ActionType::JavaScript:
{
const PDFActionJavaScript* javascriptAction = dynamic_cast(a);
Q_ASSERT(javascriptAction);
result.emplace_back(type, pageIndex, javascriptAction->getJavaScript());
break;
}
case ActionType::Rendition:
{
const PDFActionRendition* renditionAction = dynamic_cast(a);
Q_ASSERT(renditionAction);
if (!renditionAction->getJavaScript().isEmpty())
{
result.emplace_back(type, pageIndex, renditionAction->getJavaScript());
}
break;
}
default:
break;
}
if (!result.empty() && options.testFlag(FindFirstOnly))
{
break;
}
}
}
};
auto scanContainer = [&scanAction](PDFJavaScriptEntry::Type type, PDFInteger pageIndex, const auto& container)
{
for (const PDFActionPtr& action : container)
{
scanAction(type, pageIndex, action.get());
}
};
const PDFCatalog* catalog = m_document->getCatalog();
if (options.testFlag(ScanDocument) && (result.empty() || !options.testFlag(FindFirstOnly)))
{
scanContainer(PDFJavaScriptEntry::Type::Document, -1, catalog->getDocumentActions());
}
if (options.testFlag(ScanNamed) && (result.empty() || !options.testFlag(FindFirstOnly)))
{
for (const auto& actionItem : catalog->getNamedJavaScriptActions())
{
scanAction(PDFJavaScriptEntry::Type::Named, -1, actionItem.second.get());
}
}
if (options.testFlag(ScanForm) && (result.empty() || !options.testFlag(FindFirstOnly)))
{
PDFForm form = PDFForm::parse(m_document, catalog->getFormObject());
if (form.isAcroForm() || form.isXFAForm())
{
auto fillActions = [&scanContainer](const PDFFormField* formField)
{
scanContainer(PDFJavaScriptEntry::Type::Form, -1, formField->getActions().getActions());
};
form.apply(fillActions);
}
}
if (options.testFlag(ScanPage) && (result.empty() || !options.testFlag(FindFirstOnly)))
{
std::vector scannedPages;
if (options.testFlag(AllPages))
{
scannedPages.resize(m_document->getCatalog()->getPageCount(), 0);
std::iota(scannedPages.begin(), scannedPages.end(), 0);
}
else
{
scannedPages = pages;
}
for (const PDFInteger pageIndex : scannedPages)
{
if (pageIndex < 0 || pageIndex >= PDFInteger(catalog->getPageCount()))
{
continue;
}
if (!result.empty() && options.testFlag(FindFirstOnly))
{
break;
}
PDFPageAdditionalActions pageActions = PDFPageAdditionalActions::parse(&m_document->getStorage(), catalog->getPage(pageIndex)->getAdditionalActions(&m_document->getStorage()));
scanContainer(PDFJavaScriptEntry::Type::Page, pageIndex, pageActions.getActions());
const std::vector& pageAnnotations = catalog->getPage(pageIndex)->getAnnotations();
for (PDFObjectReference annotationReference : pageAnnotations)
{
PDFAnnotationPtr annotationPtr = PDFAnnotation::parse(&m_document->getStorage(), annotationReference);
if (annotationPtr)
{
switch (annotationPtr->getType())
{
case AnnotationType::Link:
{
const PDFLinkAnnotation* linkAnnotation = dynamic_cast(annotationPtr.get());
Q_ASSERT(linkAnnotation);
scanAction(PDFJavaScriptEntry::Type::Annotation, pageIndex, linkAnnotation->getAction());
break;
}
case AnnotationType::Screen:
{
const PDFScreenAnnotation* screenAnnotation = dynamic_cast(annotationPtr.get());
Q_ASSERT(screenAnnotation);
scanAction(PDFJavaScriptEntry::Type::Annotation, pageIndex, screenAnnotation->getAction());
scanContainer(PDFJavaScriptEntry::Type::Annotation, pageIndex, screenAnnotation->getAdditionalActions().getActions());
break;
}
case AnnotationType::Widget:
{
const PDFWidgetAnnotation* widgetAnnotation = dynamic_cast(annotationPtr.get());
Q_ASSERT(widgetAnnotation);
scanAction(PDFJavaScriptEntry::Type::Annotation, pageIndex, widgetAnnotation->getAction());
scanContainer(PDFJavaScriptEntry::Type::Annotation, pageIndex, widgetAnnotation->getAdditionalActions().getActions());
break;
}
default:
break;
}
}
}
}
}
std::sort(result.begin(), result.end());
if (options.testFlag(Optimize))
{
result.erase(std::unique(result.begin(), result.end()), result.end());
}
return result;
}
bool PDFJavaScriptScanner::hasJavaScript() const
{
return !scan({ }, Options(AllPages | FindFirstOnly | ScanDocument | ScanNamed | ScanForm | ScanPage)).empty();
}
} // namespace pdf