View Javadoc
1   /*
2    * Copyright (c) 2002-2017 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package com.gargoylesoftware.htmlunit.html;
16  
17  import java.applet.Applet;
18  import java.io.IOException;
19  import java.net.URL;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.LinkedList;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.commons.lang3.StringUtils;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  
30  import com.gargoylesoftware.htmlunit.AppletConfirmHandler;
31  import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
32  import com.gargoylesoftware.htmlunit.SgmlPage;
33  import com.gargoylesoftware.htmlunit.WebClient;
34  import com.gargoylesoftware.htmlunit.WebRequest;
35  import com.gargoylesoftware.htmlunit.WebResponse;
36  import com.gargoylesoftware.htmlunit.html.applets.AppletClassLoader;
37  import com.gargoylesoftware.htmlunit.html.applets.AppletStubImpl;
38  import com.gargoylesoftware.htmlunit.javascript.host.Window;
39  import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLObjectElement;
40  import com.gargoylesoftware.htmlunit.util.UrlUtils;
41  import com.gargoylesoftware.htmlunit.xml.XmlPage;
42  
43  /**
44   * Wrapper for the HTML element "object".
45   *
46   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
47   * @author David K. Taylor
48   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
49   * @author Ahmed Ashour
50   * @author Ronald Brill
51   * @author Frank Danek
52   */
53  public class HtmlObject extends HtmlElement {
54  
55      private static final Log LOG = LogFactory.getLog(HtmlObject.class);
56  
57      private static final String APPLET_TYPE = "application/x-java-applet";
58      private static final String ARCHIVE = "archive";
59      private static final String CACHE_ARCHIVE = "cache_archive";
60      private static final String CODEBASE = "codebase";
61  
62      /** The HTML tag represented by this element. */
63      public static final String TAG_NAME = "object";
64  
65      private Applet applet_;
66      private AppletClassLoader appletClassLoader_;
67      private List<URL> archiveUrls_;
68  
69      /**
70       * Creates an instance of HtmlObject
71       *
72       * @param qualifiedName the qualified name of the element type to instantiate
73       * @param page the HtmlPage that contains this element
74       * @param attributes the initial attributes
75       */
76      HtmlObject(final String qualifiedName, final SgmlPage page,
77              final Map<String, DomAttr> attributes) {
78          super(qualifiedName, page, attributes);
79      }
80  
81      /**
82       * Returns the value of the attribute {@code declare}. Refer to the
83       * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
84       * documentation for details on the use of this attribute.
85       *
86       * @return the value of the attribute {@code declare}
87       * or an empty string if that attribute isn't defined.
88       */
89      public final String getDeclareAttribute() {
90          return getAttribute("declare");
91      }
92  
93      /**
94       * Returns the value of the attribute {@code classid}. Refer to the
95       * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
96       * documentation for details on the use of this attribute.
97       *
98       * @return the value of the attribute {@code classid}
99       * or an empty string if that attribute isn't defined.
100      */
101     public final String getClassIdAttribute() {
102         return getAttribute("classid");
103     }
104 
105     /**
106      * Returns the value of the attribute "codebase". Refer to the
107      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
108      * documentation for details on the use of this attribute.
109      *
110      * @return the value of the attribute "codebase"
111      * or an empty string if that attribute isn't defined.
112      */
113     public final String getCodebaseAttribute() {
114         return getAttribute("codebase");
115     }
116 
117     /**
118      * Returns the value of the attribute {@code data}. Refer to the
119      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
120      * documentation for details on the use of this attribute.
121      *
122      * @return the value of the attribute {@code data}
123      * or an empty string if that attribute isn't defined.
124      */
125     public final String getDataAttribute() {
126         return getAttribute("data");
127     }
128 
129     /**
130      * Returns the value of the attribute {@code type}. Refer to the
131      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
132      * documentation for details on the use of this attribute.
133      *
134      * @return the value of the attribute {@code type}
135      * or an empty string if that attribute isn't defined.
136      */
137     public final String getTypeAttribute() {
138         return getAttribute("type");
139     }
140 
141     /**
142      * Returns the value of the attribute "codetype". Refer to the
143      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
144      * documentation for details on the use of this attribute.
145      *
146      * @return the value of the attribute "codetype"
147      * or an empty string if that attribute isn't defined.
148      */
149     public final String getCodeTypeAttribute() {
150         return getAttribute("codetype");
151     }
152 
153     /**
154      * Returns the value of the attribute {@code archive}. Refer to the
155      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
156      * documentation for details on the use of this attribute.
157      *
158      * @return the value of the attribute {@code archive}
159      * or an empty string if that attribute isn't defined.
160      */
161     public final String getArchiveAttribute() {
162         return getAttribute("archive");
163     }
164 
165     /**
166      * Returns the value of the attribute {@code standby}. Refer to the
167      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
168      * documentation for details on the use of this attribute.
169      *
170      * @return the value of the attribute {@code standby}
171      * or an empty string if that attribute isn't defined.
172      */
173     public final String getStandbyAttribute() {
174         return getAttribute("standby");
175     }
176 
177     /**
178      * Returns the value of the attribute {@code height}. Refer to the
179      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
180      * documentation for details on the use of this attribute.
181      *
182      * @return the value of the attribute {@code height}
183      * or an empty string if that attribute isn't defined.
184      */
185     public final String getHeightAttribute() {
186         return getAttribute("height");
187     }
188 
189     /**
190      * Returns the value of the attribute {@code width}. Refer to the
191      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
192      * documentation for details on the use of this attribute.
193      *
194      * @return the value of the attribute {@code width}
195      * or an empty string if that attribute isn't defined.
196      */
197     public final String getWidthAttribute() {
198         return getAttribute("width");
199     }
200 
201     /**
202      * Returns the value of the attribute {@code usemap}. Refer to the
203      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
204      * documentation for details on the use of this attribute.
205      *
206      * @return the value of the attribute {@code usemap}
207      * or an empty string if that attribute isn't defined.
208      */
209     public final String getUseMapAttribute() {
210         return getAttribute("usemap");
211     }
212 
213     /**
214      * Returns the value of the attribute {@code name}. Refer to the
215      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
216      * documentation for details on the use of this attribute.
217      *
218      * @return the value of the attribute {@code name}
219      * or an empty string if that attribute isn't defined.
220      */
221     public final String getNameAttribute() {
222         return getAttribute("name");
223     }
224 
225     /**
226      * Returns the value of the attribute {@code tabindex}. Refer to the
227      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
228      * documentation for details on the use of this attribute.
229      *
230      * @return the value of the attribute {@code tabindex}
231      * or an empty string if that attribute isn't defined.
232      */
233     public final String getTabIndexAttribute() {
234         return getAttribute("tabindex");
235     }
236 
237     /**
238      * Returns the value of the attribute {@code align}. Refer to the
239      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
240      * documentation for details on the use of this attribute.
241      *
242      * @return the value of the attribute {@code align}
243      * or an empty string if that attribute isn't defined.
244      */
245     public final String getAlignAttribute() {
246         return getAttribute("align");
247     }
248 
249     /**
250      * Returns the value of the attribute {@code border}. Refer to the
251      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
252      * documentation for details on the use of this attribute.
253      *
254      * @return the value of the attribute {@code border}
255      * or an empty string if that attribute isn't defined.
256      */
257     public final String getBorderAttribute() {
258         return getAttribute("border");
259     }
260 
261     /**
262      * Returns the value of the attribute {@code hspace}. Refer to the
263      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
264      * documentation for details on the use of this attribute.
265      *
266      * @return the value of the attribute {@code hspace}
267      * or an empty string if that attribute isn't defined.
268      */
269     public final String getHspaceAttribute() {
270         return getAttribute("hspace");
271     }
272 
273     /**
274      * Returns the value of the attribute {@code vspace}. Refer to the
275      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
276      * documentation for details on the use of this attribute.
277      *
278      * @return the value of the attribute {@code vspace}
279      * or an empty string if that attribute isn't defined.
280      */
281     public final String getVspaceAttribute() {
282         return getAttribute("vspace");
283     }
284 
285     /**
286      * Initialize the ActiveX(Mock).
287      * {@inheritDoc}
288      */
289     @Override
290     protected void onAllChildrenAddedToPage(final boolean postponed) {
291         if (getOwnerDocument() instanceof XmlPage) {
292             return;
293         }
294 
295         if (LOG.isDebugEnabled()) {
296             LOG.debug("Object node added: " + asXml());
297         }
298 
299         final String clsId = getClassIdAttribute();
300         if (ATTRIBUTE_NOT_DEFINED != clsId) {
301             ((HTMLObjectElement) getScriptableObject()).setClassid(clsId);
302         }
303     }
304 
305     /**
306      * {@inheritDoc}
307      */
308     @Override
309     public DisplayStyle getDefaultStyleDisplay() {
310         return DisplayStyle.INLINE;
311     }
312 
313     /**
314      * Gets the applet referenced by this tag. Instantiates it if necessary.
315      *
316      * @return the applet or null, if the installed AppletConfirmHandler
317      * prohibits this applet
318      * @throws IOException in case of problem
319      */
320     public Applet getApplet() throws IOException {
321         setupAppletIfNeeded();
322         return applet_;
323     }
324 
325     /**
326      * Download the associated content specified in the code attribute.
327      *
328      * @throws IOException if an error occurs while downloading the content
329      */
330     @SuppressWarnings("unchecked")
331     private synchronized void setupAppletIfNeeded() throws IOException {
332         if (applet_ != null) {
333             return;
334         }
335 
336         if (StringUtils.isBlank(getTypeAttribute()) || !getTypeAttribute().startsWith(APPLET_TYPE)) {
337             return;
338         }
339 
340         final HashMap<String, String> params = new HashMap<>();
341         params.put("name", getNameAttribute());
342 
343         params.put("height", getHeightAttribute());
344         params.put("width", getWidthAttribute());
345 
346         final DomNodeList<HtmlElement> paramTags = getElementsByTagName("param");
347         for (final HtmlElement paramTag : paramTags) {
348             final HtmlParameter parameter = (HtmlParameter) paramTag;
349             params.put(parameter.getNameAttribute(), parameter.getValueAttribute());
350         }
351 
352         if (StringUtils.isEmpty(params.get(CODEBASE)) && StringUtils.isNotEmpty(getCodebaseAttribute())) {
353             params.put(CODEBASE, getCodebaseAttribute());
354         }
355         final String codebaseProperty = params.get(CODEBASE);
356 
357         if (StringUtils.isEmpty(params.get(ARCHIVE)) && StringUtils.isNotEmpty(getArchiveAttribute())) {
358             params.put(ARCHIVE, getArchiveAttribute());
359         }
360 
361         final HtmlPage page = (HtmlPage) getPage();
362         final WebClient webclient = page.getWebClient();
363 
364         final AppletConfirmHandler handler = webclient.getAppletConfirmHandler();
365         if (null != handler && !handler.confirm(this)) {
366             return;
367         }
368 
369         String appletClassName = getAttribute("code");
370         if (StringUtils.isEmpty(appletClassName)) {
371             appletClassName = params.get("code");
372         }
373         if (appletClassName.endsWith(".class")) {
374             appletClassName = appletClassName.substring(0, appletClassName.length() - 6);
375         }
376 
377         appletClassLoader_ = new AppletClassLoader((Window) getPage().getEnclosingWindow().getScriptableObject(),
378                                             Thread.currentThread().getContextClassLoader());
379 
380         final String documentUrl = page.getUrl().toExternalForm();
381         String baseUrl = UrlUtils.resolveUrl(documentUrl, ".");
382         if (StringUtils.isNotEmpty(codebaseProperty)) {
383             // codebase can be relative to the page
384             baseUrl = UrlUtils.resolveUrl(baseUrl, codebaseProperty);
385         }
386         if (!baseUrl.endsWith("/")) {
387             baseUrl = baseUrl + "/";
388         }
389 
390         // check archive
391         archiveUrls_ = new LinkedList<>();
392         String[] archives = StringUtils.split(params.get(ARCHIVE), ',');
393         if (null != archives) {
394             for (int i = 0; i < archives.length; i++) {
395                 final String tmpArchive = archives[i].trim();
396                 final String tempUrl = UrlUtils.resolveUrl(baseUrl, tmpArchive);
397                 final URL archiveUrl = UrlUtils.toUrlUnsafe(tempUrl);
398 
399                 appletClassLoader_.addArchiveToClassPath(archiveUrl);
400                 archiveUrls_.add(archiveUrl);
401             }
402         }
403         archives = StringUtils.split(params.get(CACHE_ARCHIVE), ',');
404         if (null != archives) {
405             for (int i = 0; i < archives.length; i++) {
406                 final String tmpArchive = archives[i].trim();
407                 final String tempUrl = UrlUtils.resolveUrl(baseUrl, tmpArchive);
408                 final URL archiveUrl = UrlUtils.toUrlUnsafe(tempUrl);
409 
410                 appletClassLoader_.addArchiveToClassPath(archiveUrl);
411                 archiveUrls_.add(archiveUrl);
412             }
413         }
414         archiveUrls_ = Collections.unmodifiableList(archiveUrls_);
415 
416         // no archive attribute, single class
417         if (archiveUrls_.isEmpty()) {
418             final String tempUrl = UrlUtils.resolveUrl(baseUrl, getAttribute("code"));
419             final URL classUrl = UrlUtils.toUrlUnsafe(tempUrl);
420 
421             final WebResponse response = webclient.loadWebResponse(new WebRequest(classUrl));
422             try {
423                 webclient.throwFailingHttpStatusCodeExceptionIfNecessary(response);
424                 appletClassLoader_.addClassToClassPath(appletClassName, response);
425             }
426             catch (final FailingHttpStatusCodeException e) {
427                 // that is what the browser does, the applet only fails, if
428                 // the main class is not loadable
429                 LOG.error(e.getMessage(), e);
430             }
431         }
432 
433         try {
434             final Class<Applet> appletClass = (Class<Applet>) appletClassLoader_.loadClass(appletClassName);
435             applet_ = appletClass.newInstance();
436             applet_.setStub(new AppletStubImpl(getHtmlPageOrNull(), params,
437                     UrlUtils.toUrlUnsafe(baseUrl), UrlUtils.toUrlUnsafe(documentUrl)));
438             applet_.init();
439             applet_.start();
440         }
441         catch (final ClassNotFoundException e) {
442             throw new RuntimeException(e);
443         }
444         catch (final InstantiationException e) {
445             throw new RuntimeException(e);
446         }
447         catch (final IllegalAccessException e) {
448             throw new RuntimeException(e);
449         }
450     }
451 }