1 | //======================================================================== |
2 | // |
3 | // JSInfo.cc |
4 | // |
5 | // This file is licensed under the GPLv2 or later |
6 | // |
7 | // Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com> |
8 | // Copyright (C) 2017, 2020, 2021 Albert Astals Cid <aacid@kde.org> |
9 | // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich |
10 | // Copyright (C) 2020 Oliver Sander <oliver.sander@tu-dresden.de> |
11 | // Copyright (C) 2020 Nelson Benítez León <nbenitezl@gmail.com> |
12 | // Copyright (C) 2024 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk> |
13 | // |
14 | // To see a description of the changes please see the Changelog file that |
15 | // came with your tarball or type make ChangeLog if you are building from git |
16 | // |
17 | //======================================================================== |
18 | |
19 | #include "config.h" |
20 | #include <cstdio> |
21 | #include "Object.h" |
22 | #include "Dict.h" |
23 | #include "Annot.h" |
24 | #include "PDFDoc.h" |
25 | #include "JSInfo.h" |
26 | #include "Link.h" |
27 | #include "Form.h" |
28 | #include "UnicodeMap.h" |
29 | #include "UTF.h" |
30 | // #include "Win32Console.h" |
31 | |
32 | JSInfo::JSInfo(PDFDoc *docA, int firstPage) |
33 | { |
34 | doc = docA; |
35 | currentPage = firstPage + 1; |
36 | } |
37 | |
38 | JSInfo::~JSInfo() { } |
39 | |
40 | void JSInfo::printJS(const GooString *js) |
41 | { |
42 | char buf[8]; |
43 | |
44 | if (!js || !js->c_str()) { |
45 | return; |
46 | } |
47 | |
48 | std::vector<Unicode> u = TextStringToUCS4(textStr: js->toStr()); |
49 | for (auto &c : u) { |
50 | int n = uniMap->mapUnicode(u: c, buf, bufSize: sizeof(buf)); |
51 | fwrite(ptr: buf, size: 1, n: n, s: file); |
52 | } |
53 | } |
54 | |
55 | void JSInfo::scanLinkAction(LinkAction *link, const char *action) |
56 | { |
57 | if (!link) { |
58 | return; |
59 | } |
60 | |
61 | if (link->getKind() == actionJavaScript) { |
62 | hasJS = true; |
63 | if (print) { |
64 | LinkJavaScript *linkjs = static_cast<LinkJavaScript *>(link); |
65 | if (linkjs->isOk()) { |
66 | const std::string &s = linkjs->getScript(); |
67 | fprintf(stream: file, format: "%s:\n" , action); |
68 | GooString gooS = GooString(s); |
69 | printJS(js: &gooS); |
70 | fputs(s: "\n\n" , stream: file); |
71 | } |
72 | } |
73 | } |
74 | |
75 | if (link->getKind() == actionRendition) { |
76 | LinkRendition *linkr = static_cast<LinkRendition *>(link); |
77 | if (!linkr->getScript().empty()) { |
78 | hasJS = true; |
79 | if (print) { |
80 | fprintf(stream: file, format: "%s (Rendition):\n" , action); |
81 | const GooString s(linkr->getScript()); |
82 | printJS(js: &s); |
83 | fputs(s: "\n\n" , stream: file); |
84 | } |
85 | } |
86 | } |
87 | } |
88 | |
89 | void JSInfo::scanJS(int nPages) |
90 | { |
91 | print = false; |
92 | file = nullptr; |
93 | onlyFirstJS = false; |
94 | scan(nPages); |
95 | } |
96 | |
97 | void JSInfo::scanJS(int nPages, FILE *fout, const UnicodeMap *uMap) |
98 | { |
99 | print = true; |
100 | file = fout; |
101 | uniMap = uMap; |
102 | onlyFirstJS = false; |
103 | scan(nPages); |
104 | } |
105 | |
106 | void JSInfo::scanJS(int nPages, bool stopOnFirstJS) |
107 | { |
108 | print = false; |
109 | file = nullptr; |
110 | onlyFirstJS = stopOnFirstJS; |
111 | scan(nPages); |
112 | } |
113 | |
114 | void JSInfo::scan(int nPages) |
115 | { |
116 | Page *page; |
117 | Annots *annots; |
118 | int lastPage; |
119 | |
120 | hasJS = false; |
121 | |
122 | // Names |
123 | int numNames = doc->getCatalog()->numJS(); |
124 | if (numNames > 0) { |
125 | hasJS = true; |
126 | if (onlyFirstJS) { |
127 | return; |
128 | } |
129 | if (print) { |
130 | for (int i = 0; i < numNames; i++) { |
131 | fprintf(stream: file, format: "Name Dictionary \"%s\":\n" , doc->getCatalog()->getJSName(i)->c_str()); |
132 | GooString *js = doc->getCatalog()->getJS(i); |
133 | printJS(js); |
134 | delete js; |
135 | fputs(s: "\n\n" , stream: file); |
136 | } |
137 | } |
138 | } |
139 | |
140 | // document actions |
141 | scanLinkAction(link: doc->getCatalog()->getAdditionalAction(type: Catalog::actionCloseDocument).get(), action: "Before Close Document" ); |
142 | scanLinkAction(link: doc->getCatalog()->getAdditionalAction(type: Catalog::actionSaveDocumentStart).get(), action: "Before Save Document" ); |
143 | scanLinkAction(link: doc->getCatalog()->getAdditionalAction(type: Catalog::actionSaveDocumentFinish).get(), action: "After Save Document" ); |
144 | scanLinkAction(link: doc->getCatalog()->getAdditionalAction(type: Catalog::actionPrintDocumentStart).get(), action: "Before Print Document" ); |
145 | scanLinkAction(link: doc->getCatalog()->getAdditionalAction(type: Catalog::actionPrintDocumentFinish).get(), action: "After Print Document" ); |
146 | |
147 | if (onlyFirstJS && hasJS) { |
148 | return; |
149 | } |
150 | // form field actions |
151 | if (doc->getCatalog()->getFormType() == Catalog::AcroForm) { |
152 | Form *form = doc->getCatalog()->getForm(); |
153 | for (int i = 0; i < form->getNumFields(); i++) { |
154 | FormField *field = form->getRootField(i); |
155 | for (int j = 0; j < field->getNumWidgets(); j++) { |
156 | FormWidget *widget = field->getWidget(i: j); |
157 | scanLinkAction(link: widget->getActivationAction(), action: "Field Activated" ); |
158 | scanLinkAction(link: widget->getAdditionalAction(type: Annot::actionFieldModified).get(), action: "Field Modified" ); |
159 | scanLinkAction(link: widget->getAdditionalAction(type: Annot::actionFormatField).get(), action: "Format Field" ); |
160 | scanLinkAction(link: widget->getAdditionalAction(type: Annot::actionValidateField).get(), action: "Validate Field" ); |
161 | scanLinkAction(link: widget->getAdditionalAction(type: Annot::actionCalculateField).get(), action: "Calculate Field" ); |
162 | if (onlyFirstJS && hasJS) { |
163 | return; |
164 | } |
165 | } |
166 | } |
167 | } |
168 | |
169 | // scan pages |
170 | |
171 | if (currentPage > doc->getNumPages()) { |
172 | return; |
173 | } |
174 | |
175 | lastPage = currentPage + nPages; |
176 | if (lastPage > doc->getNumPages() + 1) { |
177 | lastPage = doc->getNumPages() + 1; |
178 | } |
179 | |
180 | for (int pg = currentPage; pg < lastPage; ++pg) { |
181 | page = doc->getPage(page: pg); |
182 | if (!page) { |
183 | continue; |
184 | } |
185 | |
186 | // page actions (open, close) |
187 | scanLinkAction(link: page->getAdditionalAction(type: Page::actionOpenPage).get(), action: "Page Open" ); |
188 | scanLinkAction(link: page->getAdditionalAction(type: Page::actionClosePage).get(), action: "Page Close" ); |
189 | |
190 | if (onlyFirstJS && hasJS) { |
191 | return; |
192 | } |
193 | // annotation actions (links, screen, widget) |
194 | annots = page->getAnnots(); |
195 | for (Annot *a : annots->getAnnots()) { |
196 | if (a->getType() == Annot::typeLink) { |
197 | AnnotLink *annot = static_cast<AnnotLink *>(a); |
198 | scanLinkAction(link: annot->getAction(), action: "Link Annotation Activated" ); |
199 | if (onlyFirstJS && hasJS) { |
200 | return; |
201 | } |
202 | } else if (a->getType() == Annot::typeScreen) { |
203 | AnnotScreen *annot = static_cast<AnnotScreen *>(a); |
204 | scanLinkAction(link: annot->getAction(), action: "Screen Annotation Activated" ); |
205 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionCursorEntering).get(), action: "Screen Annotation Cursor Enter" ); |
206 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionCursorLeaving).get(), action: "Screen Annotation Cursor Leave" ); |
207 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionMousePressed).get(), action: "Screen Annotation Mouse Pressed" ); |
208 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionMouseReleased).get(), action: "Screen Annotation Mouse Released" ); |
209 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionFocusIn).get(), action: "Screen Annotation Focus In" ); |
210 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionFocusOut).get(), action: "Screen Annotation Focus Out" ); |
211 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionPageOpening).get(), action: "Screen Annotation Page Open" ); |
212 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionPageClosing).get(), action: "Screen Annotation Page Close" ); |
213 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionPageVisible).get(), action: "Screen Annotation Page Visible" ); |
214 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionPageInvisible).get(), action: "Screen Annotation Page Invisible" ); |
215 | |
216 | if (onlyFirstJS && hasJS) { |
217 | return; |
218 | } |
219 | } else if (a->getType() == Annot::typeWidget) { |
220 | AnnotWidget *annot = static_cast<AnnotWidget *>(a); |
221 | scanLinkAction(link: annot->getAction(), action: "Widget Annotation Activated" ); |
222 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionCursorEntering).get(), action: "Widget Annotation Cursor Enter" ); |
223 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionCursorLeaving).get(), action: "Widget Annotation Cursor Leave" ); |
224 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionMousePressed).get(), action: "Widget Annotation Mouse Pressed" ); |
225 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionMouseReleased).get(), action: "Widget Annotation Mouse Released" ); |
226 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionFocusIn).get(), action: "Widget Annotation Focus In" ); |
227 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionFocusOut).get(), action: "Widget Annotation Focus Out" ); |
228 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionPageOpening).get(), action: "Widget Annotation Page Open" ); |
229 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionPageClosing).get(), action: "Widget Annotation Page Close" ); |
230 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionPageVisible).get(), action: "Widget Annotation Page Visible" ); |
231 | scanLinkAction(link: annot->getAdditionalAction(type: Annot::actionPageInvisible).get(), action: "Widget Annotation Page Invisible" ); |
232 | if (onlyFirstJS && hasJS) { |
233 | return; |
234 | } |
235 | } |
236 | } |
237 | } |
238 | |
239 | currentPage = lastPage; |
240 | } |
241 | |
242 | bool JSInfo::containsJS() |
243 | { |
244 | return hasJS; |
245 | } |
246 | |