1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host;
16
17 import static java.nio.charset.StandardCharsets.UTF_8;
18 import static org.htmlunit.BrowserVersionFeatures.JS_NAVIGATOR_DO_NOT_TRACK_UNSPECIFIED;
19 import static org.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
20 import static org.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
21 import static org.htmlunit.javascript.configuration.SupportedBrowser.FF;
22 import static org.htmlunit.javascript.configuration.SupportedBrowser.FF_ESR;
23
24 import java.io.IOException;
25 import java.net.MalformedURLException;
26 import java.net.URL;
27 import java.util.ArrayList;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.htmlunit.FormEncodingType;
32 import org.htmlunit.HttpHeader;
33 import org.htmlunit.HttpMethod;
34 import org.htmlunit.WebClient;
35 import org.htmlunit.WebRequest;
36 import org.htmlunit.WebRequest.HttpHint;
37 import org.htmlunit.WebWindow;
38 import org.htmlunit.corejs.javascript.Scriptable;
39 import org.htmlunit.corejs.javascript.typedarrays.NativeArrayBufferView;
40 import org.htmlunit.html.HtmlPage;
41 import org.htmlunit.javascript.HtmlUnitScriptable;
42 import org.htmlunit.javascript.JavaScriptEngine;
43 import org.htmlunit.javascript.configuration.JsxClass;
44 import org.htmlunit.javascript.configuration.JsxConstructor;
45 import org.htmlunit.javascript.configuration.JsxFunction;
46 import org.htmlunit.javascript.configuration.JsxGetter;
47 import org.htmlunit.javascript.host.file.Blob;
48 import org.htmlunit.javascript.host.geo.Geolocation;
49 import org.htmlunit.javascript.host.media.MediaDevices;
50 import org.htmlunit.javascript.host.network.NetworkInformation;
51
52
53
54 import org.htmlunit.javascript.host.xml.FormData;
55 import org.htmlunit.util.StringUtils;
56 import org.htmlunit.util.UrlUtils;
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 @JsxClass
72 public class Navigator extends HtmlUnitScriptable {
73
74 private static final Log LOG = LogFactory.getLog(Navigator.class);
75
76 private PluginArray plugins_;
77 private MimeTypeArray mimeTypes_;
78 private MediaDevices mediaDevices_;
79
80
81
82
83 @JsxConstructor
84 public void jsConstructor() {
85
86 }
87
88
89
90
91
92
93 @JsxGetter
94 public String getAppCodeName() {
95 return getBrowserVersion().getApplicationCodeName();
96 }
97
98
99
100
101
102
103 @JsxGetter
104 public String getAppName() {
105 return getBrowserVersion().getApplicationName();
106 }
107
108
109
110
111
112
113 @JsxGetter
114 public String getAppVersion() {
115 return getBrowserVersion().getApplicationVersion();
116 }
117
118
119
120
121
122
123 @JsxGetter
124 public String getLanguage() {
125 return getBrowserVersion().getBrowserLanguage();
126 }
127
128
129
130
131
132
133 @JsxGetter
134 public Scriptable getLanguages() {
135 final String acceptLang = getBrowserVersion().getAcceptLanguageHeader();
136 if (StringUtils.isEmptyOrNull(acceptLang)) {
137 return JavaScriptEngine.newArray(getParentScope(), 0);
138 }
139
140 final ArrayList<String> res = new ArrayList<>();
141 final String[] parts = StringUtils.splitAtComma(acceptLang);
142 for (final String part : parts) {
143 if (!StringUtils.isEmptyOrNull(part)) {
144 final String lang = StringUtils.substringBefore(part, ";").trim();
145 if (!StringUtils.isEmptyOrNull(part)) {
146 res.add(lang);
147 }
148 }
149 }
150
151 return JavaScriptEngine.newArray(getParentScope(), res.toArray());
152 }
153
154
155
156
157
158
159 @JsxGetter
160 public boolean isCookieEnabled() {
161 return getWindow().getWebWindow().getWebClient().getCookieManager().isCookiesEnabled();
162 }
163
164
165
166
167
168
169 @JsxGetter
170 public boolean isOnLine() {
171 return getBrowserVersion().isOnLine();
172 }
173
174
175
176
177
178
179 @JsxGetter
180 public String getPlatform() {
181 return getBrowserVersion().getPlatform();
182 }
183
184
185
186
187
188
189 @JsxGetter
190 public String getProduct() {
191 return "Gecko";
192 }
193
194
195
196
197
198
199
200 @JsxGetter
201 public String getProductSub() {
202 return getBrowserVersion().getProductSub();
203 }
204
205
206
207
208
209
210 @JsxGetter
211 public String getUserAgent() {
212 return getBrowserVersion().getUserAgent();
213 }
214
215
216
217
218
219
220 @JsxGetter
221 public PluginArray getPlugins() {
222 initPluginsAndMimeTypes();
223 return plugins_;
224 }
225
226 private void initPluginsAndMimeTypes() {
227
228
229
230
231 if (plugins_ != null) {
232 return;
233 }
234 plugins_ = new PluginArray();
235 plugins_.setParentScope(getParentScope());
236 plugins_.setPrototype(getPrototype(PluginArray.class));
237
238 Plugin plugin = new Plugin("PDF Viewer", "Portable Document Format", "internal-pdf-viewer");
239 plugin.setParentScope(getParentScope());
240 plugin.setPrototype(getPrototype(Plugin.class));
241
242
243
244
245
246 mimeTypes_ = new MimeTypeArray();
247 mimeTypes_.setParentScope(getParentScope());
248 mimeTypes_.setPrototype(getPrototype(MimeTypeArray.class));
249
250 final MimeType mimeTypeAppPdf = new MimeType("application/pdf", "Portable Document Format", "pdf", plugin);
251 mimeTypeAppPdf.setParentScope(getParentScope());
252 mimeTypeAppPdf.setPrototype(getPrototype(MimeType.class));
253 mimeTypes_.add(mimeTypeAppPdf);
254
255 final MimeType mimeTypeTxtPdf = new MimeType("text/pdf", "Portable Document Format", "pdf", plugin);
256 mimeTypeTxtPdf.setParentScope(getParentScope());
257 mimeTypeTxtPdf.setPrototype(getPrototype(MimeType.class));
258 mimeTypes_.add(mimeTypeTxtPdf);
259
260 plugin.add(mimeTypeAppPdf);
261 plugin.add(mimeTypeTxtPdf);
262 plugins_.add(plugin);
263
264
265 plugin = new Plugin("Chrome PDF Viewer", "Portable Document Format", "internal-pdf-viewer");
266 plugin.setParentScope(getParentScope());
267 plugin.setPrototype(getPrototype(Plugin.class));
268 plugin.add(mimeTypeAppPdf);
269 plugin.add(mimeTypeTxtPdf);
270 plugins_.add(plugin);
271
272 plugin = new Plugin("Chromium PDF Viewer", "Portable Document Format", "internal-pdf-viewer");
273 plugin.setParentScope(getParentScope());
274 plugin.setPrototype(getPrototype(Plugin.class));
275 plugin.add(mimeTypeAppPdf);
276 plugin.add(mimeTypeTxtPdf);
277 plugins_.add(plugin);
278
279 plugin = new Plugin("Microsoft Edge PDF Viewer", "Portable Document Format", "internal-pdf-viewer");
280 plugin.setParentScope(getParentScope());
281 plugin.setPrototype(getPrototype(Plugin.class));
282 plugin.add(mimeTypeAppPdf);
283 plugin.add(mimeTypeTxtPdf);
284 plugins_.add(plugin);
285
286 plugin = new Plugin("WebKit built-in PDF", "Portable Document Format", "internal-pdf-viewer");
287 plugin.setParentScope(getParentScope());
288 plugin.setPrototype(getPrototype(Plugin.class));
289 plugin.add(mimeTypeAppPdf);
290 plugin.add(mimeTypeTxtPdf);
291 plugins_.add(plugin);
292 }
293
294
295
296
297
298
299 @JsxGetter
300 public MimeTypeArray getMimeTypes() {
301 initPluginsAndMimeTypes();
302 return mimeTypes_;
303 }
304
305
306
307
308
309
310 @JsxFunction
311 public boolean javaEnabled() {
312 return false;
313 }
314
315
316
317
318
319
320 @JsxFunction({FF, FF_ESR})
321 public boolean taintEnabled() {
322 return false;
323 }
324
325
326
327
328
329
330 @JsxGetter
331 public Geolocation getGeolocation() {
332 final Geolocation geolocation = new Geolocation();
333 geolocation.setParentScope(getParentScope());
334 geolocation.setPrototype(getPrototype(geolocation.getClass()));
335 return geolocation;
336 }
337
338
339
340
341
342
343 @JsxGetter
344 public boolean isPdfViewerEnabled() {
345 return true;
346 }
347
348
349
350
351
352
353 @JsxGetter({FF, FF_ESR})
354 public String getBuildID() {
355 return getBrowserVersion().getBuildId();
356 }
357
358
359
360
361
362
363 @JsxGetter
364 public String getVendor() {
365 return getBrowserVersion().getVendor();
366 }
367
368
369
370
371
372
373 @JsxGetter
374 public String getVendorSub() {
375 return "";
376 }
377
378
379
380
381
382
383 @JsxGetter
384 public Object getDoNotTrack() {
385 final WebClient client = getWindow().getWebWindow().getWebClient();
386 if (client.getOptions().isDoNotTrackEnabled()) {
387 return 1;
388 }
389 if (client.getBrowserVersion().hasFeature(JS_NAVIGATOR_DO_NOT_TRACK_UNSPECIFIED)) {
390 return "unspecified";
391 }
392 return null;
393 }
394
395
396
397
398
399
400 @JsxGetter({FF, FF_ESR})
401 public String getOscpu() {
402 return "Windows NT 6.1";
403 }
404
405
406
407
408
409
410 @JsxGetter({CHROME, EDGE})
411 public NetworkInformation getConnection() {
412 final NetworkInformation networkInformation = new NetworkInformation();
413 networkInformation.setPrototype(getPrototype(networkInformation.getClass()));
414 networkInformation.setParentScope(getParentScope());
415 return networkInformation;
416 }
417
418
419
420
421
422
423 @JsxGetter
424 public MediaDevices getMediaDevices() {
425 if (mediaDevices_ == null) {
426 mediaDevices_ = new MediaDevices();
427 mediaDevices_.setPrototype(getPrototype(mediaDevices_.getClass()));
428 mediaDevices_.setParentScope(getParentScope());
429 }
430 return mediaDevices_;
431 }
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452 @JsxFunction
453 public boolean sendBeacon(final String url, final Object data) {
454 final Window window = getWindow();
455 final WebWindow webWindow = window.getWebWindow();
456 final HtmlPage page = (HtmlPage) webWindow.getEnclosedPage();
457 final URL pageUrl = page.getUrl();
458
459 final URL targetUrl;
460 try {
461 targetUrl = page.getFullyQualifiedUrl(url);
462 }
463 catch (final MalformedURLException e) {
464 if (LOG.isInfoEnabled()) {
465 LOG.info("sendBeacon(): invalid url '" + url + "'", e);
466 }
467 return false;
468 }
469
470 final WebRequest request = new WebRequest(targetUrl, HttpMethod.POST);
471
472
473
474 try {
475 request.setRefererHeader(pageUrl);
476 request.setAdditionalHeader(HttpHeader.ORIGIN,
477 UrlUtils.getUrlWithProtocolAndAuthority(pageUrl).toExternalForm());
478 }
479 catch (final MalformedURLException e) {
480 if (LOG.isInfoEnabled()) {
481 LOG.info("sendBeacon(): invalid origin url '" + pageUrl + "'", e);
482 }
483 }
484
485
486
487 request.setFetchDestination(WebRequest.FetchDestination.EMPTY);
488 request.setFetchModeOverride(WebRequest.FetchMode.NO_CORS);
489 request.setRequestingUrl(pageUrl);
490
491 fillRequestBody(request, data);
492
493 final WebClient webClient = webWindow.getWebClient();
494 try {
495 webClient.loadWebResponse(request);
496 }
497 catch (final IOException e) {
498 if (LOG.isInfoEnabled()) {
499 LOG.info("sendBeacon(): request failed", e);
500 }
501
502 }
503 return true;
504 }
505
506
507
508
509
510
511
512
513
514
515 private static void fillRequestBody(final WebRequest request, final Object data) {
516 if (data == null || JavaScriptEngine.isUndefined(data)) {
517 return;
518 }
519
520 if (data instanceof FormData formData) {
521 formData.fillRequest(request);
522 }
523 else if (data instanceof URLSearchParams params) {
524 params.fillRequest(request);
525 request.setCharset(UTF_8);
526 request.addHint(HttpHint.IncludeCharsetInContentTypeHeader);
527 }
528 else if (data instanceof Blob blob) {
529 blob.fillRequest(request);
530 }
531 else if (data instanceof NativeArrayBufferView view) {
532 request.setRequestBody(new String(view.getBuffer().getBuffer(), UTF_8));
533 request.setEncodingType(null);
534 }
535 else {
536 final String body = JavaScriptEngine.toString(data);
537 if (!body.isEmpty()) {
538 request.setRequestBody(body);
539 request.setEncodingType(FormEncodingType.TEXT_PLAIN);
540 request.setCharset(UTF_8);
541 request.addHint(HttpHint.IncludeCharsetInContentTypeHeader);
542 }
543 }
544 }
545 }