You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VaadinServlet.java 39KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044
  1. /*
  2. * Copyright 2000-2013 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.server;
  17. import java.io.BufferedWriter;
  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.io.OutputStream;
  21. import java.io.OutputStreamWriter;
  22. import java.io.PrintWriter;
  23. import java.net.MalformedURLException;
  24. import java.net.URL;
  25. import java.net.URLConnection;
  26. import java.util.Arrays;
  27. import java.util.Collection;
  28. import java.util.Enumeration;
  29. import java.util.HashSet;
  30. import java.util.Properties;
  31. import java.util.logging.Level;
  32. import java.util.logging.Logger;
  33. import javax.servlet.ServletContext;
  34. import javax.servlet.ServletException;
  35. import javax.servlet.http.HttpServlet;
  36. import javax.servlet.http.HttpServletRequest;
  37. import javax.servlet.http.HttpServletResponse;
  38. import com.vaadin.sass.internal.ScssStylesheet;
  39. import com.vaadin.server.communication.ServletUIInitHandler;
  40. import com.vaadin.shared.JsonConstants;
  41. import com.vaadin.util.CurrentInstance;
  42. @SuppressWarnings("serial")
  43. public class VaadinServlet extends HttpServlet implements Constants {
  44. private VaadinServletService servletService;
  45. /**
  46. * Called by the servlet container to indicate to a servlet that the servlet
  47. * is being placed into service.
  48. *
  49. * @param servletConfig
  50. * the object containing the servlet's configuration and
  51. * initialization parameters
  52. * @throws ServletException
  53. * if an exception has occurred that interferes with the
  54. * servlet's normal operation.
  55. */
  56. @Override
  57. public void init(javax.servlet.ServletConfig servletConfig)
  58. throws ServletException {
  59. CurrentInstance.clearAll();
  60. setCurrent(this);
  61. super.init(servletConfig);
  62. Properties initParameters = new Properties();
  63. // Read default parameters from server.xml
  64. final ServletContext context = servletConfig.getServletContext();
  65. for (final Enumeration<String> e = context.getInitParameterNames(); e
  66. .hasMoreElements();) {
  67. final String name = e.nextElement();
  68. initParameters.setProperty(name, context.getInitParameter(name));
  69. }
  70. // Override with application config from web.xml
  71. for (final Enumeration<String> e = servletConfig
  72. .getInitParameterNames(); e.hasMoreElements();) {
  73. final String name = e.nextElement();
  74. initParameters.setProperty(name,
  75. servletConfig.getInitParameter(name));
  76. }
  77. DeploymentConfiguration deploymentConfiguration = createDeploymentConfiguration(initParameters);
  78. servletService = createServletService(deploymentConfiguration);
  79. // Sets current service even though there are no request and response
  80. servletService.setCurrentInstances(null, null);
  81. servletInitialized();
  82. CurrentInstance.clearAll();
  83. }
  84. protected void servletInitialized() throws ServletException {
  85. // Empty by default
  86. }
  87. /**
  88. * Gets the currently used Vaadin servlet. The current servlet is
  89. * automatically defined when initializing the servlet and when processing
  90. * requests to the server and in threads started at a point when the current
  91. * servlet is defined (see {@link InheritableThreadLocal}). In other cases,
  92. * (e.g. from background threads started in some other way), the current
  93. * servlet is not automatically defined.
  94. *
  95. * @return the current Vaadin servlet instance if available, otherwise
  96. * <code>null</code>
  97. *
  98. * @see #setCurrent(VaadinServlet)
  99. *
  100. * @since 7.0
  101. */
  102. public static VaadinServlet getCurrent() {
  103. return CurrentInstance.get(VaadinServlet.class);
  104. }
  105. /**
  106. * Sets the current Vaadin servlet. This method is used by the framework to
  107. * set the current servlet whenever a new request is processed and it is
  108. * cleared when the request has been processed.
  109. * <p>
  110. * The application developer can also use this method to define the current
  111. * servlet outside the normal request handling, e.g. when initiating custom
  112. * background threads.
  113. * </p>
  114. *
  115. * @param servlet
  116. * the Vaadin servlet to register as the current servlet
  117. *
  118. * @see #getCurrent()
  119. * @see InheritableThreadLocal
  120. */
  121. public static void setCurrent(VaadinServlet servlet) {
  122. CurrentInstance.setInheritable(VaadinServlet.class, servlet);
  123. }
  124. protected DeploymentConfiguration createDeploymentConfiguration(
  125. Properties initParameters) {
  126. return new DefaultDeploymentConfiguration(getClass(), initParameters);
  127. }
  128. protected VaadinServletService createServletService(
  129. DeploymentConfiguration deploymentConfiguration) {
  130. return new VaadinServletService(this, deploymentConfiguration);
  131. }
  132. /**
  133. * Receives standard HTTP requests from the public service method and
  134. * dispatches them.
  135. *
  136. * @param request
  137. * the object that contains the request the client made of the
  138. * servlet.
  139. * @param response
  140. * the object that contains the response the servlet returns to
  141. * the client.
  142. * @throws ServletException
  143. * if an input or output error occurs while the servlet is
  144. * handling the TRACE request.
  145. * @throws IOException
  146. * if the request for the TRACE cannot be handled.
  147. */
  148. @Override
  149. protected void service(HttpServletRequest request,
  150. HttpServletResponse response) throws ServletException, IOException {
  151. // Handle context root request without trailing slash, see #9921
  152. if (handleContextRootWithoutSlash(request, response)) {
  153. return;
  154. }
  155. CurrentInstance.clearAll();
  156. setCurrent(this);
  157. VaadinServletRequest vaadinRequest = createVaadinRequest(request);
  158. VaadinServletResponse vaadinResponse = createVaadinResponse(response);
  159. if (!ensureCookiesEnabled(vaadinRequest, vaadinResponse)) {
  160. return;
  161. }
  162. if (isStaticResourceRequest(request)) {
  163. serveStaticResources(request, response);
  164. return;
  165. }
  166. try {
  167. getService().handleRequest(vaadinRequest, vaadinResponse);
  168. } catch (ServiceException e) {
  169. throw new ServletException(e);
  170. }
  171. }
  172. /**
  173. * Invoked for every request to this servlet to potentially send a redirect
  174. * to avoid problems with requests to the context root with no trailing
  175. * slash.
  176. *
  177. * @param request
  178. * the processed request
  179. * @param response
  180. * the processed response
  181. * @return <code>true</code> if a redirect has been sent and the request
  182. * should not be processed further; <code>false</code> if the
  183. * request should be processed as usual
  184. * @throws IOException
  185. * If an input or output exception occurs
  186. */
  187. protected boolean handleContextRootWithoutSlash(HttpServletRequest request,
  188. HttpServletResponse response) throws IOException {
  189. if ((request.getPathInfo() == null || "/".equals(request.getPathInfo()))
  190. && "".equals(request.getServletPath())
  191. && !request.getRequestURI().endsWith("/")) {
  192. /*
  193. * Path info is for the root but request URI doesn't end with a
  194. * slash -> redirect to the same URI but with an ending slash.
  195. */
  196. String location = request.getRequestURI() + "/";
  197. String queryString = request.getQueryString();
  198. if (queryString != null) {
  199. location += '?' + queryString;
  200. }
  201. response.sendRedirect(location);
  202. return true;
  203. } else {
  204. return false;
  205. }
  206. }
  207. private VaadinServletResponse createVaadinResponse(
  208. HttpServletResponse response) {
  209. return new VaadinServletResponse(response, getService());
  210. }
  211. /**
  212. * Create a Vaadin request for a http servlet request. This method can be
  213. * overridden if the Vaadin request should have special properties.
  214. *
  215. * @param request
  216. * the original http servlet request
  217. * @return a Vaadin request for the original request
  218. */
  219. protected VaadinServletRequest createVaadinRequest(
  220. HttpServletRequest request) {
  221. return new VaadinServletRequest(request, getService());
  222. }
  223. /**
  224. * Gets a the vaadin service for this servlet.
  225. *
  226. * @return the vaadin service
  227. */
  228. protected VaadinServletService getService() {
  229. return servletService;
  230. }
  231. /**
  232. * Check that cookie support is enabled in the browser. Only checks UIDL
  233. * requests.
  234. *
  235. * @param requestType
  236. * Type of the request as returned by
  237. * {@link #getRequestType(HttpServletRequest)}
  238. * @param request
  239. * The request from the browser
  240. * @param response
  241. * The response to which an error can be written
  242. * @return false if cookies are disabled, true otherwise
  243. * @throws IOException
  244. */
  245. private boolean ensureCookiesEnabled(VaadinServletRequest request,
  246. VaadinServletResponse response) throws IOException {
  247. if (ServletPortletHelper.isUIDLRequest(request)) {
  248. // In all other but the first UIDL request a cookie should be
  249. // returned by the browser.
  250. // This can be removed if cookieless mode (#3228) is supported
  251. if (request.getRequestedSessionId() == null) {
  252. // User has cookies disabled
  253. SystemMessages systemMessages = getService().getSystemMessages(
  254. ServletPortletHelper.findLocale(null, null, request),
  255. request);
  256. getService().writeStringResponse(
  257. response,
  258. JsonConstants.JSON_CONTENT_TYPE,
  259. VaadinService.createCriticalNotificationJSON(
  260. systemMessages.getCookiesDisabledCaption(),
  261. systemMessages.getCookiesDisabledMessage(),
  262. null, systemMessages.getCookiesDisabledURL()));
  263. return false;
  264. }
  265. }
  266. return true;
  267. }
  268. /**
  269. * Send a notification to client-side widgetset. Used to notify client of
  270. * critical errors, session expiration and more. Server has no knowledge of
  271. * what UI client refers to.
  272. *
  273. * @param request
  274. * the HTTP request instance.
  275. * @param response
  276. * the HTTP response to write to.
  277. * @param caption
  278. * the notification caption
  279. * @param message
  280. * to notification body
  281. * @param details
  282. * a detail message to show in addition to the message. Currently
  283. * shown directly below the message but could be hidden behind a
  284. * details drop down in the future. Mainly used to give
  285. * additional information not necessarily useful to the end user.
  286. * @param url
  287. * url to load when the message is dismissed. Null will reload
  288. * the current page.
  289. * @throws IOException
  290. * if the writing failed due to input/output error.
  291. *
  292. * @deprecated As of 7.0. This method is retained only for backwards
  293. * compatibility and for {@link GAEVaadinServlet}.
  294. */
  295. @Deprecated
  296. protected void criticalNotification(VaadinServletRequest request,
  297. VaadinServletResponse response, String caption, String message,
  298. String details, String url) throws IOException {
  299. if (ServletPortletHelper.isUIDLRequest(request)) {
  300. String output = VaadinService.createCriticalNotificationJSON(
  301. caption, message, details, url);
  302. getService().writeStringResponse(response,
  303. JsonConstants.JSON_CONTENT_TYPE, output);
  304. } else {
  305. // Create an HTML reponse with the error
  306. String output = "";
  307. if (url != null) {
  308. output += "<a href=\"" + url + "\">";
  309. }
  310. if (caption != null) {
  311. output += "<b>" + caption + "</b><br/>";
  312. }
  313. if (message != null) {
  314. output += message;
  315. output += "<br/><br/>";
  316. }
  317. if (details != null) {
  318. output += details;
  319. output += "<br/><br/>";
  320. }
  321. if (url != null) {
  322. output += "</a>";
  323. }
  324. getService().writeStringResponse(response,
  325. "text/html; charset=UTF-8", output);
  326. }
  327. }
  328. /**
  329. * Writes the response in {@code output} using the contentType given in
  330. * {@code contentType} to the provided {@link HttpServletResponse}
  331. *
  332. * @param response
  333. * @param contentType
  334. * @param output
  335. * Output to write (UTF-8 encoded)
  336. * @throws IOException
  337. */
  338. private void writeResponse(HttpServletResponse response,
  339. String contentType, String output) throws IOException {
  340. response.setContentType(contentType);
  341. final OutputStream out = response.getOutputStream();
  342. // Set the response type
  343. final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
  344. new OutputStreamWriter(out, "UTF-8")));
  345. outWriter.print(output);
  346. outWriter.flush();
  347. outWriter.close();
  348. out.flush();
  349. }
  350. /**
  351. * Gets resource path using different implementations. Required to
  352. * supporting different servlet container implementations (application
  353. * servers).
  354. *
  355. * @param servletContext
  356. * @param path
  357. * the resource path.
  358. * @return the resource path.
  359. *
  360. * @deprecated As of 7.0. Will likely change or be removed in a future
  361. * version
  362. */
  363. @Deprecated
  364. protected static String getResourcePath(ServletContext servletContext,
  365. String path) {
  366. String resultPath = null;
  367. resultPath = servletContext.getRealPath(path);
  368. if (resultPath != null) {
  369. return resultPath;
  370. } else {
  371. try {
  372. final URL url = servletContext.getResource(path);
  373. resultPath = url.getFile();
  374. } catch (final Exception e) {
  375. // FIXME: Handle exception
  376. getLogger().log(Level.INFO,
  377. "Could not find resource path " + path, e);
  378. }
  379. }
  380. return resultPath;
  381. }
  382. /**
  383. * A helper method to strip away characters that might somehow be used for
  384. * XSS attacs. Leaves at least alphanumeric characters intact. Also removes
  385. * eg. ( and ), so values should be safe in javascript too.
  386. *
  387. * @param themeName
  388. * @return
  389. *
  390. * @deprecated As of 7.0. Will likely change or be removed in a future
  391. * version
  392. */
  393. @Deprecated
  394. protected static String stripSpecialChars(String themeName) {
  395. StringBuilder sb = new StringBuilder();
  396. char[] charArray = themeName.toCharArray();
  397. for (int i = 0; i < charArray.length; i++) {
  398. char c = charArray[i];
  399. if (!CHAR_BLACKLIST.contains(c)) {
  400. sb.append(c);
  401. }
  402. }
  403. return sb.toString();
  404. }
  405. private static final Collection<Character> CHAR_BLACKLIST = new HashSet<Character>(
  406. Arrays.asList(new Character[] { '&', '"', '\'', '<', '>', '(', ')',
  407. ';' }));
  408. /**
  409. * Mutex for preventing to scss compilations to take place simultaneously.
  410. * This is a workaround needed as the scss compiler currently is not thread
  411. * safe (#10292).
  412. */
  413. private static final Object SCSS_MUTEX = new Object();
  414. /**
  415. * Returns the default theme. Must never return null.
  416. *
  417. * @return
  418. */
  419. public static String getDefaultTheme() {
  420. return DEFAULT_THEME_NAME;
  421. }
  422. private void handleServiceSecurityException(VaadinServletRequest request,
  423. VaadinServletResponse response) throws IOException,
  424. ServletException {
  425. try {
  426. /*
  427. * We might have a UI, but we don't want to leak any information in
  428. * this case so just use the info provided in the request.
  429. */
  430. SystemMessages ci = getService().getSystemMessages(
  431. request.getLocale(), request);
  432. if (ServletPortletHelper.isUIDLRequest(request)) {
  433. // send uidl redirect
  434. getService().writeStringResponse(
  435. response,
  436. JsonConstants.JSON_CONTENT_TYPE,
  437. VaadinService.createCriticalNotificationJSON(
  438. ci.getCommunicationErrorCaption(),
  439. ci.getCommunicationErrorMessage(),
  440. INVALID_SECURITY_KEY_MSG,
  441. ci.getCommunicationErrorURL()));
  442. } else if (ServletPortletHelper.isHeartbeatRequest(request)) {
  443. response.sendError(HttpServletResponse.SC_FORBIDDEN,
  444. "Forbidden");
  445. } else {
  446. // 'plain' http req - e.g. browser reload;
  447. // just go ahead redirect the browser
  448. response.sendRedirect(ci.getCommunicationErrorURL());
  449. }
  450. } catch (SystemMessageException ee) {
  451. throw new ServletException(ee);
  452. }
  453. log("Invalid security key received from " + request.getRemoteHost());
  454. }
  455. /**
  456. * Check if this is a request for a static resource and, if it is, serve the
  457. * resource to the client.
  458. *
  459. * @param request
  460. * @param response
  461. * @return true if a file was served and the request has been handled, false
  462. * otherwise.
  463. * @throws IOException
  464. * @throws ServletException
  465. */
  466. private boolean serveStaticResources(HttpServletRequest request,
  467. HttpServletResponse response) throws IOException, ServletException {
  468. String pathInfo = request.getPathInfo();
  469. if (pathInfo == null) {
  470. return false;
  471. }
  472. if ((request.getContextPath() != null)
  473. && (request.getRequestURI().startsWith("/VAADIN/"))) {
  474. serveStaticResourcesInVAADIN(request.getRequestURI(), request,
  475. response);
  476. return true;
  477. } else if (request.getRequestURI().startsWith(
  478. request.getContextPath() + "/VAADIN/")) {
  479. serveStaticResourcesInVAADIN(
  480. request.getRequestURI().substring(
  481. request.getContextPath().length()), request,
  482. response);
  483. return true;
  484. }
  485. return false;
  486. }
  487. /**
  488. * Serve resources from VAADIN directory.
  489. *
  490. * @param filename
  491. * The filename to serve. Should always start with /VAADIN/.
  492. * @param request
  493. * @param response
  494. * @throws IOException
  495. * @throws ServletException
  496. */
  497. private void serveStaticResourcesInVAADIN(String filename,
  498. HttpServletRequest request, HttpServletResponse response)
  499. throws IOException, ServletException {
  500. final ServletContext sc = getServletContext();
  501. URL resourceUrl = findResourceURL(filename, sc);
  502. if (resourceUrl == null) {
  503. // File not found, if this was a css request we still look for a
  504. // scss file with the same name
  505. if (serveOnTheFlyCompiledScss(filename, request, response, sc)) {
  506. return;
  507. } else {
  508. // cannot serve requested file
  509. getLogger()
  510. .log(Level.INFO,
  511. "Requested resource [{0}] not found from filesystem or through class loader."
  512. + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder.",
  513. filename);
  514. response.setStatus(HttpServletResponse.SC_NOT_FOUND);
  515. }
  516. return;
  517. }
  518. // security check: do not permit navigation out of the VAADIN
  519. // directory
  520. if (!isAllowedVAADINResourceUrl(request, resourceUrl)) {
  521. getLogger()
  522. .log(Level.INFO,
  523. "Requested resource [{0}] not accessible in the VAADIN directory or access to it is forbidden.",
  524. filename);
  525. response.setStatus(HttpServletResponse.SC_FORBIDDEN);
  526. return;
  527. }
  528. // Find the modification timestamp
  529. long lastModifiedTime = 0;
  530. URLConnection connection = null;
  531. try {
  532. connection = resourceUrl.openConnection();
  533. lastModifiedTime = connection.getLastModified();
  534. // Remove milliseconds to avoid comparison problems (milliseconds
  535. // are not returned by the browser in the "If-Modified-Since"
  536. // header).
  537. lastModifiedTime = lastModifiedTime - lastModifiedTime % 1000;
  538. if (browserHasNewestVersion(request, lastModifiedTime)) {
  539. response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
  540. return;
  541. }
  542. } catch (Exception e) {
  543. // Failed to find out last modified timestamp. Continue without it.
  544. getLogger()
  545. .log(Level.FINEST,
  546. "Failed to find out last modified timestamp. Continuing without it.",
  547. e);
  548. } finally {
  549. if (connection instanceof URLConnection) {
  550. try {
  551. // Explicitly close the input stream to prevent it
  552. // from remaining hanging
  553. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4257700
  554. InputStream is = connection.getInputStream();
  555. if (is != null) {
  556. is.close();
  557. }
  558. } catch (IOException e) {
  559. getLogger().log(Level.INFO,
  560. "Error closing URLConnection input stream", e);
  561. }
  562. }
  563. }
  564. // Set type mime type if we can determine it based on the filename
  565. final String mimetype = sc.getMimeType(filename);
  566. if (mimetype != null) {
  567. response.setContentType(mimetype);
  568. }
  569. // Provide modification timestamp to the browser if it is known.
  570. if (lastModifiedTime > 0) {
  571. response.setDateHeader("Last-Modified", lastModifiedTime);
  572. /*
  573. * The browser is allowed to cache for 1 hour without checking if
  574. * the file has changed. This forces browsers to fetch a new version
  575. * when the Vaadin version is updated. This will cause more requests
  576. * to the servlet than without this but for high volume sites the
  577. * static files should never be served through the servlet. The
  578. * cache timeout can be configured by setting the resourceCacheTime
  579. * parameter in web.xml
  580. */
  581. int resourceCacheTime = getService().getDeploymentConfiguration()
  582. .getResourceCacheTime();
  583. String cacheControl = "max-age="
  584. + String.valueOf(resourceCacheTime);
  585. if (filename.contains("nocache")) {
  586. cacheControl = "public, max-age=0, must-revalidate";
  587. }
  588. response.setHeader("Cache-Control", cacheControl);
  589. }
  590. writeStaticResourceResponse(request, response, resourceUrl);
  591. }
  592. /**
  593. * Writes the contents of the given resourceUrl in the response. Can be
  594. * overridden to add/modify response headers and similar.
  595. *
  596. * @param request
  597. * The request for the resource
  598. * @param response
  599. * The response
  600. * @param resourceUrl
  601. * The url to send
  602. * @throws IOException
  603. */
  604. protected void writeStaticResourceResponse(HttpServletRequest request,
  605. HttpServletResponse response, URL resourceUrl) throws IOException {
  606. // Write the resource to the client.
  607. final OutputStream os = response.getOutputStream();
  608. final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE];
  609. int bytes;
  610. InputStream is = resourceUrl.openStream();
  611. while ((bytes = is.read(buffer)) >= 0) {
  612. os.write(buffer, 0, bytes);
  613. }
  614. is.close();
  615. }
  616. private URL findResourceURL(String filename, ServletContext sc)
  617. throws MalformedURLException {
  618. URL resourceUrl = sc.getResource(filename);
  619. if (resourceUrl == null) {
  620. // try if requested file is found from classloader
  621. // strip leading "/" otherwise stream from JAR wont work
  622. if (filename.startsWith("/")) {
  623. filename = filename.substring(1);
  624. }
  625. resourceUrl = getService().getClassLoader().getResource(filename);
  626. }
  627. return resourceUrl;
  628. }
  629. private boolean serveOnTheFlyCompiledScss(String filename,
  630. HttpServletRequest request, HttpServletResponse response,
  631. ServletContext sc) throws IOException {
  632. if (getService().getDeploymentConfiguration().isProductionMode()) {
  633. // This is not meant for production mode.
  634. return false;
  635. }
  636. if (!filename.endsWith(".css")) {
  637. return false;
  638. }
  639. String scssFilename = filename.substring(0, filename.length() - 4)
  640. + ".scss";
  641. URL scssUrl = findResourceURL(scssFilename, sc);
  642. if (scssUrl == null) {
  643. // Is a css request but no scss file was found
  644. return false;
  645. }
  646. // security check: do not permit navigation out of the VAADIN
  647. // directory
  648. if (!isAllowedVAADINResourceUrl(request, scssUrl)) {
  649. getLogger()
  650. .log(Level.INFO,
  651. "Requested resource [{0}] not accessible in the VAADIN directory or access to it is forbidden.",
  652. filename);
  653. response.setStatus(HttpServletResponse.SC_FORBIDDEN);
  654. // Handled, return true so no further processing is done
  655. return true;
  656. }
  657. synchronized (SCSS_MUTEX) {
  658. String realFilename = sc.getRealPath(scssFilename);
  659. ScssStylesheet scss = ScssStylesheet.get(realFilename);
  660. if (scss == null) {
  661. // Not a file in the file system (WebContent directory). Use the
  662. // identifier directly (VAADIN/themes/.../styles.css) so
  663. // ScssStylesheet will try using the class loader.
  664. if (scssFilename.startsWith("/")) {
  665. scssFilename = scssFilename.substring(1);
  666. }
  667. scss = ScssStylesheet.get(scssFilename);
  668. }
  669. if (scss == null) {
  670. getLogger()
  671. .log(Level.WARNING,
  672. "Scss file {0} exists but ScssStylesheet was not able to find it",
  673. scssFilename);
  674. return false;
  675. }
  676. try {
  677. getLogger().log(Level.FINE, "Compiling {0} for request to {1}",
  678. new Object[] { realFilename, filename });
  679. scss.compile();
  680. } catch (Exception e) {
  681. e.printStackTrace();
  682. return false;
  683. }
  684. // This is for development mode only so instruct the browser to
  685. // never
  686. // cache it
  687. response.setHeader("Cache-Control", "no-cache");
  688. final String mimetype = getService().getMimeType(filename);
  689. writeResponse(response, mimetype, scss.toString());
  690. return true;
  691. }
  692. }
  693. /**
  694. * Check whether a URL obtained from a classloader refers to a valid static
  695. * resource in the directory VAADIN.
  696. *
  697. * Warning: Overriding of this method is not recommended, but is possible to
  698. * support non-default classloaders or servers that may produce URLs
  699. * different from the normal ones. The method prototype may change in the
  700. * future. Care should be taken not to expose class files or other resources
  701. * outside the VAADIN directory if the method is overridden.
  702. *
  703. * @param request
  704. * @param resourceUrl
  705. * @return
  706. *
  707. * @since 6.6.7
  708. *
  709. * @deprecated As of 7.0. Will likely change or be removed in a future
  710. * version
  711. */
  712. @Deprecated
  713. protected boolean isAllowedVAADINResourceUrl(HttpServletRequest request,
  714. URL resourceUrl) {
  715. if ("jar".equals(resourceUrl.getProtocol())) {
  716. // This branch is used for accessing resources directly from the
  717. // Vaadin JAR in development environments and in similar cases.
  718. // Inside a JAR, a ".." would mean a real directory named ".." so
  719. // using it in paths should just result in the file not being found.
  720. // However, performing a check in case some servers or class loaders
  721. // try to normalize the path by collapsing ".." before the class
  722. // loader sees it.
  723. if (!resourceUrl.getPath().contains("!/VAADIN/")) {
  724. getLogger()
  725. .log(Level.INFO,
  726. "Blocked attempt to access a JAR entry not starting with /VAADIN/: {0}",
  727. resourceUrl);
  728. return false;
  729. }
  730. getLogger().log(Level.FINE,
  731. "Accepted access to a JAR entry using a class loader: {0}",
  732. resourceUrl);
  733. return true;
  734. } else {
  735. // Some servers such as GlassFish extract files from JARs (file:)
  736. // and e.g. JBoss 5+ use protocols vsf: and vfsfile: .
  737. // Check that the URL is in a VAADIN directory and does not contain
  738. // "/../"
  739. if (!resourceUrl.getPath().contains("/VAADIN/")
  740. || resourceUrl.getPath().contains("/../")) {
  741. getLogger().log(Level.INFO,
  742. "Blocked attempt to access file: {0}", resourceUrl);
  743. return false;
  744. }
  745. getLogger().log(Level.FINE,
  746. "Accepted access to a file using a class loader: {0}",
  747. resourceUrl);
  748. return true;
  749. }
  750. }
  751. /**
  752. * Checks if the browser has an up to date cached version of requested
  753. * resource. Currently the check is performed using the "If-Modified-Since"
  754. * header. Could be expanded if needed.
  755. *
  756. * @param request
  757. * The HttpServletRequest from the browser.
  758. * @param resourceLastModifiedTimestamp
  759. * The timestamp when the resource was last modified. 0 if the
  760. * last modification time is unknown.
  761. * @return true if the If-Modified-Since header tells the cached version in
  762. * the browser is up to date, false otherwise
  763. */
  764. private boolean browserHasNewestVersion(HttpServletRequest request,
  765. long resourceLastModifiedTimestamp) {
  766. if (resourceLastModifiedTimestamp < 1) {
  767. // We do not know when it was modified so the browser cannot have an
  768. // up-to-date version
  769. return false;
  770. }
  771. /*
  772. * The browser can request the resource conditionally using an
  773. * If-Modified-Since header. Check this against the last modification
  774. * time.
  775. */
  776. try {
  777. // If-Modified-Since represents the timestamp of the version cached
  778. // in the browser
  779. long headerIfModifiedSince = request
  780. .getDateHeader("If-Modified-Since");
  781. if (headerIfModifiedSince >= resourceLastModifiedTimestamp) {
  782. // Browser has this an up-to-date version of the resource
  783. return true;
  784. }
  785. } catch (Exception e) {
  786. // Failed to parse header. Fail silently - the browser does not have
  787. // an up-to-date version in its cache.
  788. }
  789. return false;
  790. }
  791. /**
  792. *
  793. * @author Vaadin Ltd
  794. * @since 7.0
  795. *
  796. * @deprecated As of 7.0. This is no longer used and only provided for
  797. * backwards compatibility. Each {@link RequestHandler} can
  798. * individually decide whether it wants to handle a request or
  799. * not.
  800. */
  801. @Deprecated
  802. protected enum RequestType {
  803. FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APP, PUBLISHED_FILE, HEARTBEAT;
  804. }
  805. /**
  806. * @param request
  807. * @return
  808. *
  809. * @deprecated As of 7.0. This is no longer used and only provided for
  810. * backwards compatibility. Each {@link RequestHandler} can
  811. * individually decide whether it wants to handle a request or
  812. * not.
  813. */
  814. @Deprecated
  815. protected RequestType getRequestType(VaadinServletRequest request) {
  816. if (ServletPortletHelper.isFileUploadRequest(request)) {
  817. return RequestType.FILE_UPLOAD;
  818. } else if (ServletPortletHelper.isPublishedFileRequest(request)) {
  819. return RequestType.PUBLISHED_FILE;
  820. } else if (ServletUIInitHandler.isUIInitRequest(request)) {
  821. return RequestType.BROWSER_DETAILS;
  822. } else if (ServletPortletHelper.isUIDLRequest(request)) {
  823. return RequestType.UIDL;
  824. } else if (isStaticResourceRequest(request)) {
  825. return RequestType.STATIC_FILE;
  826. } else if (ServletPortletHelper.isAppRequest(request)) {
  827. return RequestType.APP;
  828. } else if (ServletPortletHelper.isHeartbeatRequest(request)) {
  829. return RequestType.HEARTBEAT;
  830. }
  831. return RequestType.OTHER;
  832. }
  833. protected boolean isStaticResourceRequest(HttpServletRequest request) {
  834. String pathInfo = request.getPathInfo();
  835. if (pathInfo == null) {
  836. return false;
  837. }
  838. if ((request.getContextPath() != null)
  839. && (request.getRequestURI().startsWith("/VAADIN/"))) {
  840. return true;
  841. } else if (request.getRequestURI().startsWith(
  842. request.getContextPath() + "/VAADIN/")) {
  843. return true;
  844. }
  845. return false;
  846. }
  847. /**
  848. * Remove any heading or trailing "what" from the "string".
  849. *
  850. * @param string
  851. * @param what
  852. * @return
  853. */
  854. static String removeHeadingOrTrailing(String string, String what) {
  855. while (string.startsWith(what)) {
  856. string = string.substring(1);
  857. }
  858. while (string.endsWith(what)) {
  859. string = string.substring(0, string.length() - 1);
  860. }
  861. return string;
  862. }
  863. /**
  864. * Write a redirect response to the main page of the application.
  865. *
  866. * @param request
  867. * @param response
  868. * @throws IOException
  869. * if sending the redirect fails due to an input/output error or
  870. * a bad application URL
  871. */
  872. private void redirectToApplication(HttpServletRequest request,
  873. HttpServletResponse response) throws IOException {
  874. String applicationUrl = getApplicationUrl(request).toExternalForm();
  875. response.sendRedirect(response.encodeRedirectURL(applicationUrl));
  876. }
  877. /**
  878. * Gets the current application URL from request.
  879. *
  880. * @param request
  881. * the HTTP request.
  882. * @throws MalformedURLException
  883. * if the application is denied access to the persistent data
  884. * store represented by the given URL.
  885. *
  886. * @deprecated As of 7.0. Will likely change or be removed in a future
  887. * version
  888. */
  889. @Deprecated
  890. protected URL getApplicationUrl(HttpServletRequest request)
  891. throws MalformedURLException {
  892. final URL reqURL = new URL(
  893. (request.isSecure() ? "https://" : "http://")
  894. + request.getServerName()
  895. + ((request.isSecure() && request.getServerPort() == 443)
  896. || (!request.isSecure() && request
  897. .getServerPort() == 80) ? "" : ":"
  898. + request.getServerPort())
  899. + request.getRequestURI());
  900. String servletPath = "";
  901. if (request.getAttribute("javax.servlet.include.servlet_path") != null) {
  902. // this is an include request
  903. servletPath = request.getAttribute(
  904. "javax.servlet.include.context_path").toString()
  905. + request
  906. .getAttribute("javax.servlet.include.servlet_path");
  907. } else {
  908. servletPath = request.getContextPath() + request.getServletPath();
  909. }
  910. if (servletPath.length() == 0
  911. || servletPath.charAt(servletPath.length() - 1) != '/') {
  912. servletPath = servletPath + "/";
  913. }
  914. URL u = new URL(reqURL, servletPath);
  915. return u;
  916. }
  917. /**
  918. * Escapes characters to html entities. An exception is made for some
  919. * "safe characters" to keep the text somewhat readable.
  920. *
  921. * @param unsafe
  922. * @return a safe string to be added inside an html tag
  923. *
  924. * @deprecated As of 7.0. Will likely change or be removed in a future
  925. * version
  926. */
  927. @Deprecated
  928. public static final String safeEscapeForHtml(String unsafe) {
  929. if (null == unsafe) {
  930. return null;
  931. }
  932. StringBuilder safe = new StringBuilder();
  933. char[] charArray = unsafe.toCharArray();
  934. for (int i = 0; i < charArray.length; i++) {
  935. char c = charArray[i];
  936. if (isSafe(c)) {
  937. safe.append(c);
  938. } else {
  939. safe.append("&#");
  940. safe.append((int) c);
  941. safe.append(";");
  942. }
  943. }
  944. return safe.toString();
  945. }
  946. private static boolean isSafe(char c) {
  947. return //
  948. c > 47 && c < 58 || // alphanum
  949. c > 64 && c < 91 || // A-Z
  950. c > 96 && c < 123 // a-z
  951. ;
  952. }
  953. private static final Logger getLogger() {
  954. return Logger.getLogger(VaadinServlet.class.getName());
  955. }
  956. }