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 static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.APPLET_INLINE_BLOCK;
18  
19  import java.applet.Applet;
20  import java.io.IOException;
21  import java.net.URL;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  
32  import com.gargoylesoftware.htmlunit.AppletConfirmHandler;
33  import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
34  import com.gargoylesoftware.htmlunit.SgmlPage;
35  import com.gargoylesoftware.htmlunit.WebClient;
36  import com.gargoylesoftware.htmlunit.WebRequest;
37  import com.gargoylesoftware.htmlunit.WebResponse;
38  import com.gargoylesoftware.htmlunit.html.applets.AppletClassLoader;
39  import com.gargoylesoftware.htmlunit.html.applets.AppletStubImpl;
40  import com.gargoylesoftware.htmlunit.javascript.host.Window;
41  import com.gargoylesoftware.htmlunit.util.UrlUtils;
42  
43  /**
44   * Wrapper for the HTML element "applet".
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 Marc Guillemot
51   * @author Ronald Brill
52   * @author Frank Danek
53   */
54  public class HtmlApplet extends HtmlElement {
55  
56      private static final Log LOG = LogFactory.getLog(HtmlApplet.class);
57  
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 = "applet";
64  
65      private Applet applet_;
66      private AppletClassLoader appletClassLoader_;
67      private List<URL> archiveUrls_;
68  
69      /**
70       * Creates a new instance.
71       *
72       * @param qualifiedName the qualified name of the element type to instantiate
73       * @param page the page that contains this element
74       * @param attributes the initial attributes
75       */
76      HtmlApplet(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 "codebase". 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 "codebase" or an empty string if that attribute isn't defined
87       */
88      public final String getCodebaseAttribute() {
89          return getAttribute(CODEBASE);
90      }
91  
92      /**
93       * Returns the value of the attribute {@code archive}. Refer to the
94       * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
95       * documentation for details on the use of this attribute.
96       *
97       * @return the value of the attribute {@code archive} or an empty string if that attribute isn't defined
98       */
99      public final String getArchiveAttribute() {
100         return getAttribute(ARCHIVE);
101     }
102 
103     /**
104      * Returns the value of the attribute "code". Refer to the
105      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
106      * documentation for details on the use of this attribute.
107      *
108      * @return the value of the attribute "code" or an empty string if that attribute isn't defined
109      */
110     public final String getCodeAttribute() {
111         return getAttribute("code");
112     }
113 
114     /**
115      * Returns the value of the attribute {@code object}. Refer to the
116      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
117      * documentation for details on the use of this attribute.
118      *
119      * @return the value of the attribute {@code object} or an empty string if that attribute isn't defined
120      */
121     public final String getObjectAttribute() {
122         return getAttribute("object");
123     }
124 
125     /**
126      * Returns the value of the attribute {@code alt}. Refer to the
127      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
128      * documentation for details on the use of this attribute.
129      *
130      * @return the value of the attribute {@code alt} or an empty string if that attribute isn't defined
131      */
132     public final String getAltAttribute() {
133         return getAttribute("alt");
134     }
135 
136     /**
137      * Returns the value of the attribute {@code name}. Refer to the
138      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
139      * documentation for details on the use of this attribute.
140      *
141      * @return the value of the attribute {@code name} or an empty string if that attribute isn't defined
142      */
143     public final String getNameAttribute() {
144         return getAttribute("name");
145     }
146 
147     /**
148      * Returns the value of the attribute {@code width}. Refer to the
149      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
150      * documentation for details on the use of this attribute.
151      *
152      * @return the value of the attribute {@code width} or an empty string if that attribute isn't defined
153      */
154     public final String getWidthAttribute() {
155         return getAttribute("width");
156     }
157 
158     /**
159      * Returns the value of the attribute {@code height}. Refer to the
160      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
161      * documentation for details on the use of this attribute.
162      *
163      * @return the value of the attribute {@code height} or an empty string if that attribute isn't defined
164      */
165     public final String getHeightAttribute() {
166         return getAttribute("height");
167     }
168 
169     /**
170      * Returns the value of the attribute {@code align}. Refer to the
171      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
172      * documentation for details on the use of this attribute.
173      *
174      * @return the value of the attribute {@code align} or an empty string if that attribute isn't defined
175      */
176     public final String getAlignAttribute() {
177         return getAttribute("align");
178     }
179 
180     /**
181      * Returns the value of the attribute {@code hspace}. Refer to the
182      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
183      * documentation for details on the use of this attribute.
184      *
185      * @return the value of the attribute {@code hspace} or an empty string if that attribute isn't defined
186      */
187     public final String getHspaceAttribute() {
188         return getAttribute("hspace");
189     }
190 
191     /**
192      * Returns the value of the attribute {@code vspace}. Refer to the
193      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
194      * documentation for details on the use of this attribute.
195      *
196      * @return the value of the attribute {@code vspace} or an empty string if that attribute isn't defined
197      */
198     public final String getVspaceAttribute() {
199         return getAttribute("vspace");
200     }
201 
202     /**
203      * Gets the applet referenced by this tag. Instantiates it if necessary.
204      *
205      * @return the applet or null, if the installed AppletConfirmHandler
206      * prohibits this applet
207      * @throws IOException in case of problem
208      */
209     public Applet getApplet() throws IOException {
210         setupAppletIfNeeded();
211         return applet_;
212     }
213 
214     /**
215      * Download the associated content specified in the code attribute.
216      *
217      * @throws IOException if an error occurs while downloading the content
218      */
219     @SuppressWarnings("unchecked")
220     private synchronized void setupAppletIfNeeded() throws IOException {
221         if (applet_ != null) {
222             return;
223         }
224 
225         final HashMap<String, String> params = new HashMap<>();
226         params.put("name", getNameAttribute());
227 
228         params.put("object", getObjectAttribute());
229         params.put("align", getAlignAttribute());
230         params.put("alt", getAltAttribute());
231         params.put("height", getHeightAttribute());
232         params.put("hspace", getHspaceAttribute());
233         params.put("vspace", getVspaceAttribute());
234         params.put("width", getWidthAttribute());
235 
236         final DomNodeList<HtmlElement> paramTags = getElementsByTagName("param");
237         for (final HtmlElement paramTag : paramTags) {
238             final HtmlParameter parameter = (HtmlParameter) paramTag;
239             params.put(parameter.getNameAttribute(), parameter.getValueAttribute());
240         }
241 
242         if (StringUtils.isEmpty(params.get(CODEBASE)) && StringUtils.isNotEmpty(getCodebaseAttribute())) {
243             params.put(CODEBASE, getCodebaseAttribute());
244         }
245         final String codebaseProperty = params.get(CODEBASE);
246 
247         if (StringUtils.isEmpty(params.get(ARCHIVE)) && StringUtils.isNotEmpty(getArchiveAttribute())) {
248             params.put(ARCHIVE, getArchiveAttribute());
249         }
250 
251         final HtmlPage page = (HtmlPage) getPage();
252         final WebClient webclient = page.getWebClient();
253 
254         final AppletConfirmHandler handler = webclient.getAppletConfirmHandler();
255         if (null != handler && !handler.confirm(this)) {
256             return;
257         }
258 
259         String appletClassName = getCodeAttribute();
260         if (appletClassName.endsWith(".class")) {
261             appletClassName = appletClassName.substring(0, appletClassName.length() - 6);
262         }
263 
264         appletClassLoader_ = new AppletClassLoader((Window) getPage().getEnclosingWindow().getScriptableObject(),
265                                             Thread.currentThread().getContextClassLoader());
266 
267         final String documentUrl = page.getUrl().toExternalForm();
268         String baseUrl = UrlUtils.resolveUrl(documentUrl, ".");
269         if (StringUtils.isNotEmpty(codebaseProperty)) {
270             // codebase can be relative to the page
271             baseUrl = UrlUtils.resolveUrl(baseUrl, codebaseProperty);
272         }
273         if (!baseUrl.endsWith("/")) {
274             baseUrl = baseUrl + "/";
275         }
276 
277         // check archive
278         final List<URL> archiveUrls = new LinkedList<>();
279         String[] archives = StringUtils.split(params.get(ARCHIVE), ',');
280         if (null != archives) {
281             for (int i = 0; i < archives.length; i++) {
282                 final String tmpArchive = archives[i].trim();
283                 final String tempUrl = UrlUtils.resolveUrl(baseUrl, tmpArchive);
284                 final URL archiveUrl = UrlUtils.toUrlUnsafe(tempUrl);
285 
286                 appletClassLoader_.addArchiveToClassPath(archiveUrl);
287                 archiveUrls.add(archiveUrl);
288             }
289         }
290         archives = StringUtils.split(params.get(CACHE_ARCHIVE), ',');
291         if (null != archives) {
292             for (int i = 0; i < archives.length; i++) {
293                 final String tmpArchive = archives[i].trim();
294                 final String tempUrl = UrlUtils.resolveUrl(baseUrl, tmpArchive);
295                 final URL archiveUrl = UrlUtils.toUrlUnsafe(tempUrl);
296 
297                 appletClassLoader_.addArchiveToClassPath(archiveUrl);
298                 archiveUrls.add(archiveUrl);
299             }
300         }
301         archiveUrls_ = Collections.unmodifiableList(archiveUrls);
302 
303         // no archive attribute, single class
304         if (archiveUrls_.isEmpty()) {
305             final String tempUrl = UrlUtils.resolveUrl(baseUrl, getCodeAttribute());
306             final URL classUrl = UrlUtils.toUrlUnsafe(tempUrl);
307 
308             final WebResponse response = webclient.loadWebResponse(new WebRequest(classUrl));
309             try {
310                 webclient.throwFailingHttpStatusCodeExceptionIfNecessary(response);
311                 appletClassLoader_.addClassToClassPath(appletClassName, response);
312             }
313             catch (final FailingHttpStatusCodeException e) {
314                 // that is what the browser does, the applet only fails, if
315                 // the main class is not loadable
316                 LOG.error(e.getMessage(), e);
317             }
318         }
319 
320         try {
321             final Class<Applet> appletClass = (Class<Applet>) appletClassLoader_.loadClass(appletClassName);
322             applet_ = appletClass.newInstance();
323             applet_.setStub(new AppletStubImpl(getHtmlPageOrNull(), params,
324                     UrlUtils.toUrlUnsafe(baseUrl), UrlUtils.toUrlUnsafe(documentUrl)));
325             applet_.init();
326             applet_.start();
327         }
328         catch (final ClassNotFoundException e) {
329             throw new RuntimeException(e);
330         }
331         catch (final InstantiationException e) {
332             throw new RuntimeException(e);
333         }
334         catch (final IllegalAccessException e) {
335             throw new RuntimeException(e);
336         }
337     }
338 
339     /**
340      * Returns the list of used jar file urls.
341      * This returns null, if the applet was not initialized before.
342      *
343      * @return the list of jar urls
344      */
345     public List<URL> getArchiveUrls() {
346         return archiveUrls_;
347     }
348 
349     /**
350      * {@inheritDoc}
351      */
352     @Override
353     public DisplayStyle getDefaultStyleDisplay() {
354         if (getPage().getWebClient().getBrowserVersion().hasFeature(APPLET_INLINE_BLOCK)) {
355             return DisplayStyle.INLINE_BLOCK;
356         }
357         return DisplayStyle.INLINE;
358     }
359 }