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 55KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486
  1. /*
  2. * Copyright 2000-2016 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.File;
  19. import java.io.FileInputStream;
  20. import java.io.FileNotFoundException;
  21. import java.io.FileOutputStream;
  22. import java.io.IOException;
  23. import java.io.InputStream;
  24. import java.io.OutputStream;
  25. import java.io.OutputStreamWriter;
  26. import java.io.PrintWriter;
  27. import java.io.Serializable;
  28. import java.lang.reflect.Method;
  29. import java.net.MalformedURLException;
  30. import java.net.URISyntaxException;
  31. import java.net.URL;
  32. import java.net.URLConnection;
  33. import java.net.URLDecoder;
  34. import java.nio.charset.Charset;
  35. import java.util.ArrayList;
  36. import java.util.Arrays;
  37. import java.util.Collection;
  38. import java.util.Enumeration;
  39. import java.util.HashMap;
  40. import java.util.HashSet;
  41. import java.util.List;
  42. import java.util.Map;
  43. import java.util.Properties;
  44. import java.util.logging.Level;
  45. import java.util.logging.Logger;
  46. import javax.servlet.ServletContext;
  47. import javax.servlet.ServletException;
  48. import javax.servlet.http.HttpServlet;
  49. import javax.servlet.http.HttpServletRequest;
  50. import javax.servlet.http.HttpServletResponse;
  51. import com.vaadin.annotations.VaadinServletConfiguration;
  52. import com.vaadin.annotations.VaadinServletConfiguration.InitParameterName;
  53. import com.vaadin.sass.internal.ScssStylesheet;
  54. import com.vaadin.server.communication.ServletUIInitHandler;
  55. import com.vaadin.shared.JsonConstants;
  56. import com.vaadin.shared.Version;
  57. import com.vaadin.ui.UI;
  58. import com.vaadin.util.CurrentInstance;
  59. import elemental.json.Json;
  60. import elemental.json.JsonArray;
  61. import elemental.json.JsonObject;
  62. @SuppressWarnings("serial")
  63. public class VaadinServlet extends HttpServlet implements Constants {
  64. private class ScssCacheEntry implements Serializable {
  65. private final String css;
  66. private final List<String> sourceUris;
  67. private final long timestamp;
  68. private final String scssFileName;
  69. public ScssCacheEntry(String scssFileName, String css,
  70. List<String> sourceUris) {
  71. this.scssFileName = scssFileName;
  72. this.css = css;
  73. this.sourceUris = sourceUris;
  74. timestamp = getLastModified();
  75. }
  76. public ScssCacheEntry(JsonObject json) {
  77. css = json.getString("css");
  78. timestamp = Long.parseLong(json.getString("timestamp"));
  79. sourceUris = new ArrayList<>();
  80. JsonArray uris = json.getArray("uris");
  81. for (int i = 0; i < uris.length(); i++) {
  82. sourceUris.add(uris.getString(i));
  83. }
  84. // Not set for cache entries read from disk
  85. scssFileName = null;
  86. }
  87. public String asJson() {
  88. JsonArray uris = Json.createArray();
  89. for (String uri : sourceUris) {
  90. uris.set(uris.length(), uri);
  91. }
  92. JsonObject object = Json.createObject();
  93. object.put("version", Version.getFullVersion());
  94. object.put("timestamp", Long.toString(timestamp));
  95. object.put("uris", uris);
  96. object.put("css", css);
  97. return object.toJson();
  98. }
  99. public String getCss() {
  100. return css;
  101. }
  102. private long getLastModified() {
  103. long newest = 0;
  104. for (String uri : sourceUris) {
  105. File file = new File(uri);
  106. URL resource = getService().getClassLoader().getResource(uri);
  107. long lastModified = -1L;
  108. if (file.exists()) {
  109. lastModified = file.lastModified();
  110. } else if (resource != null
  111. && resource.getProtocol().equals("file")) {
  112. try {
  113. file = new File(resource.toURI());
  114. if (file.exists()) {
  115. lastModified = file.lastModified();
  116. }
  117. } catch (URISyntaxException e) {
  118. getLogger().log(Level.WARNING,
  119. "Could not resolve timestamp for " + resource,
  120. e);
  121. }
  122. }
  123. if (lastModified == -1L && resource == null) {
  124. /*
  125. * Ignore missing files found in the classpath, report
  126. * problem and abort for other files.
  127. */
  128. getLogger().log(Level.WARNING,
  129. "Could not resolve timestamp for {0}, Scss on the fly caching will be disabled",
  130. uri);
  131. // -1 means this cache entry will never be valid
  132. return -1;
  133. }
  134. newest = Math.max(newest, lastModified);
  135. }
  136. return newest;
  137. }
  138. public boolean isStillValid() {
  139. if (timestamp == -1) {
  140. /*
  141. * Don't ever bother checking anything if files used during the
  142. * compilation were gone before the cache entry was created.
  143. */
  144. return false;
  145. } else if (timestamp != getLastModified()) {
  146. /*
  147. * Would in theory still be valid if the last modification is
  148. * before the recorded timestamp, but that would still mean that
  149. * something has changed since we last checked, so let's
  150. * invalidate in that case as well to be on the safe side.
  151. */
  152. return false;
  153. } else {
  154. return true;
  155. }
  156. }
  157. public String getScssFileName() {
  158. return scssFileName;
  159. }
  160. }
  161. private VaadinServletService servletService;
  162. /**
  163. * Called by the servlet container to indicate to a servlet that the servlet
  164. * is being placed into service.
  165. *
  166. * @param servletConfig
  167. * the object containing the servlet's configuration and
  168. * initialization parameters
  169. * @throws ServletException
  170. * if an exception has occurred that interferes with the
  171. * servlet's normal operation.
  172. */
  173. @Override
  174. public void init(javax.servlet.ServletConfig servletConfig)
  175. throws ServletException {
  176. CurrentInstance.clearAll();
  177. super.init(servletConfig);
  178. Properties initParameters = new Properties();
  179. readUiFromEnclosingClass(initParameters);
  180. readConfigurationAnnotation(initParameters);
  181. // Read default parameters from server.xml
  182. final ServletContext context = servletConfig.getServletContext();
  183. for (final Enumeration<String> e = context.getInitParameterNames(); e
  184. .hasMoreElements();) {
  185. final String name = e.nextElement();
  186. initParameters.setProperty(name, context.getInitParameter(name));
  187. }
  188. // Override with application config from web.xml
  189. for (final Enumeration<String> e = servletConfig
  190. .getInitParameterNames(); e.hasMoreElements();) {
  191. final String name = e.nextElement();
  192. initParameters.setProperty(name,
  193. servletConfig.getInitParameter(name));
  194. }
  195. DeploymentConfiguration deploymentConfiguration = createDeploymentConfiguration(
  196. initParameters);
  197. try {
  198. servletService = createServletService(deploymentConfiguration);
  199. } catch (ServiceException e) {
  200. throw new ServletException("Could not initialize VaadinServlet", e);
  201. }
  202. // Sets current service even though there are no request and response
  203. servletService.setCurrentInstances(null, null);
  204. servletInitialized();
  205. CurrentInstance.clearAll();
  206. }
  207. private void readUiFromEnclosingClass(Properties initParameters) {
  208. Class<?> enclosingClass = getClass().getEnclosingClass();
  209. if (enclosingClass != null
  210. && UI.class.isAssignableFrom(enclosingClass)) {
  211. initParameters.put(VaadinSession.UI_PARAMETER,
  212. enclosingClass.getName());
  213. }
  214. }
  215. private void readConfigurationAnnotation(Properties initParameters)
  216. throws ServletException {
  217. VaadinServletConfiguration configAnnotation = UIProvider
  218. .getAnnotationFor(getClass(), VaadinServletConfiguration.class);
  219. if (configAnnotation != null) {
  220. Method[] methods = VaadinServletConfiguration.class
  221. .getDeclaredMethods();
  222. for (Method method : methods) {
  223. InitParameterName name = method
  224. .getAnnotation(InitParameterName.class);
  225. assert name != null : "All methods declared in VaadinServletConfiguration should have a @InitParameterName annotation";
  226. try {
  227. Object value = method.invoke(configAnnotation);
  228. String stringValue;
  229. if (value instanceof Class<?>) {
  230. stringValue = ((Class<?>) value).getName();
  231. } else {
  232. stringValue = value.toString();
  233. }
  234. if (VaadinServlet.PARAMETER_WIDGETSET.equals(name.value())
  235. && method.getDefaultValue().equals(stringValue)) {
  236. // Do not set the widgetset to anything so that the
  237. // framework can fallback to the default. Setting
  238. // anything to the init parameter will force that into
  239. // use and e.g. AppWidgetset will not be used even
  240. // though it is found.
  241. continue;
  242. }
  243. initParameters.setProperty(name.value(), stringValue);
  244. } catch (Exception e) {
  245. // This should never happen
  246. throw new ServletException(
  247. "Could not read @VaadinServletConfiguration value "
  248. + method.getName(),
  249. e);
  250. }
  251. }
  252. }
  253. }
  254. protected void servletInitialized() throws ServletException {
  255. // Empty by default
  256. }
  257. /**
  258. * Gets the currently used Vaadin servlet. The current servlet is
  259. * automatically defined when initializing the servlet and when processing
  260. * requests to the server and in threads started at a point when the current
  261. * servlet is defined (see {@link InheritableThreadLocal}). In other cases,
  262. * (e.g. from background threads started in some other way), the current
  263. * servlet is not automatically defined.
  264. * <p>
  265. * The current servlet is derived from the current service using
  266. * {@link VaadinService#getCurrent()}
  267. *
  268. * @return the current Vaadin servlet instance if available, otherwise
  269. * <code>null</code>
  270. *
  271. * @since 7.0
  272. */
  273. public static VaadinServlet getCurrent() {
  274. VaadinService vaadinService = CurrentInstance.get(VaadinService.class);
  275. if (vaadinService instanceof VaadinServletService) {
  276. VaadinServletService vss = (VaadinServletService) vaadinService;
  277. return vss.getServlet();
  278. } else {
  279. return null;
  280. }
  281. }
  282. protected DeploymentConfiguration createDeploymentConfiguration(
  283. Properties initParameters) {
  284. return new DefaultDeploymentConfiguration(getClass(), initParameters);
  285. }
  286. protected VaadinServletService createServletService(
  287. DeploymentConfiguration deploymentConfiguration)
  288. throws ServiceException {
  289. VaadinServletService service = new VaadinServletService(this,
  290. deploymentConfiguration);
  291. service.init();
  292. return service;
  293. }
  294. /**
  295. * Receives standard HTTP requests from the public service method and
  296. * dispatches them.
  297. *
  298. * @param request
  299. * the object that contains the request the client made of the
  300. * servlet.
  301. * @param response
  302. * the object that contains the response the servlet returns to
  303. * the client.
  304. * @throws ServletException
  305. * if an input or output error occurs while the servlet is
  306. * handling the TRACE request.
  307. * @throws IOException
  308. * if the request for the TRACE cannot be handled.
  309. */
  310. @Override
  311. protected void service(HttpServletRequest request,
  312. HttpServletResponse response) throws ServletException, IOException {
  313. // Handle context root request without trailing slash, see #9921
  314. if (handleContextRootWithoutSlash(request, response)) {
  315. return;
  316. }
  317. CurrentInstance.clearAll();
  318. VaadinServletRequest vaadinRequest = createVaadinRequest(request);
  319. VaadinServletResponse vaadinResponse = createVaadinResponse(response);
  320. if (!ensureCookiesEnabled(vaadinRequest, vaadinResponse)) {
  321. return;
  322. }
  323. if (isStaticResourceRequest(vaadinRequest)) {
  324. // Define current servlet and service, but no request and response
  325. getService().setCurrentInstances(null, null);
  326. try {
  327. serveStaticResources(vaadinRequest, vaadinResponse);
  328. return;
  329. } finally {
  330. CurrentInstance.clearAll();
  331. }
  332. }
  333. try {
  334. getService().handleRequest(vaadinRequest, vaadinResponse);
  335. } catch (ServiceException e) {
  336. throw new ServletException(e);
  337. }
  338. }
  339. /**
  340. * Invoked for every request to this servlet to potentially send a redirect
  341. * to avoid problems with requests to the context root with no trailing
  342. * slash.
  343. *
  344. * @param request
  345. * the processed request
  346. * @param response
  347. * the processed response
  348. * @return <code>true</code> if a redirect has been sent and the request
  349. * should not be processed further; <code>false</code> if the
  350. * request should be processed as usual
  351. * @throws IOException
  352. * If an input or output exception occurs
  353. */
  354. protected boolean handleContextRootWithoutSlash(HttpServletRequest request,
  355. HttpServletResponse response) throws IOException {
  356. // Query parameters like "?a=b" are handled by the servlet container but
  357. // path parameter (e.g. ;jsessionid=) needs to be handled here
  358. String location = request.getRequestURI();
  359. String lastPathParameter = getLastPathParameter(location);
  360. location = location.substring(0,
  361. location.length() - lastPathParameter.length());
  362. if ((request.getPathInfo() == null || "/".equals(request.getPathInfo()))
  363. && "".equals(request.getServletPath())
  364. && !location.endsWith("/")) {
  365. /*
  366. * Path info is for the root but request URI doesn't end with a
  367. * slash -> redirect to the same URI but with an ending slash.
  368. */
  369. location = location + "/" + lastPathParameter;
  370. String queryString = request.getQueryString();
  371. if (queryString != null) {
  372. // Prevent HTTP Response splitting in case the server doesn't
  373. queryString = queryString.replaceAll("[\\r\\n]", "");
  374. location += '?' + queryString;
  375. }
  376. response.sendRedirect(location);
  377. return true;
  378. } else {
  379. return false;
  380. }
  381. }
  382. /**
  383. * Finds any path parameter added to the last part of the uri. A path
  384. * parameter is any string separated by ";" from the path and ends in / or
  385. * at the end of the string.
  386. * <p>
  387. * For example the uri http://myhost.com/foo;a=1/bar;b=1 contains two path
  388. * parameters, {@literal a=1} related to {@literal /foo} and {@literal b=1}
  389. * related to /bar.
  390. * <p>
  391. * For http://myhost.com/foo;a=1/bar;b=1 this method will return ;b=1
  392. *
  393. * @since 7.2
  394. * @param uri
  395. * a URI
  396. * @return the last path parameter of the uri including the semicolon or an
  397. * empty string. Never null.
  398. */
  399. protected static String getLastPathParameter(String uri) {
  400. int lastPathStart = uri.lastIndexOf('/');
  401. if (lastPathStart == -1) {
  402. return "";
  403. }
  404. int semicolonPos = uri.indexOf(';', lastPathStart);
  405. if (semicolonPos < 0) {
  406. // No path parameter for the last part
  407. return "";
  408. } else {
  409. // This includes the semicolon.
  410. String semicolonString = uri.substring(semicolonPos);
  411. return semicolonString;
  412. }
  413. }
  414. private VaadinServletResponse createVaadinResponse(
  415. HttpServletResponse response) {
  416. return new VaadinServletResponse(response, getService());
  417. }
  418. /**
  419. * Create a Vaadin request for a http servlet request. This method can be
  420. * overridden if the Vaadin request should have special properties.
  421. *
  422. * @param request
  423. * the original http servlet request
  424. * @return a Vaadin request for the original request
  425. */
  426. protected VaadinServletRequest createVaadinRequest(
  427. HttpServletRequest request) {
  428. return new VaadinServletRequest(request, getService());
  429. }
  430. /**
  431. * Gets a the vaadin service for this servlet.
  432. *
  433. * @return the vaadin service
  434. */
  435. protected VaadinServletService getService() {
  436. return servletService;
  437. }
  438. /**
  439. * Check that cookie support is enabled in the browser. Only checks UIDL
  440. * requests.
  441. *
  442. * @param requestType
  443. * Type of the request as returned by
  444. * {@link #getRequestType(HttpServletRequest)}
  445. * @param request
  446. * The request from the browser
  447. * @param response
  448. * The response to which an error can be written
  449. * @return false if cookies are disabled, true otherwise
  450. * @throws IOException
  451. */
  452. private boolean ensureCookiesEnabled(VaadinServletRequest request,
  453. VaadinServletResponse response) throws IOException {
  454. if (ServletPortletHelper.isUIDLRequest(request)) {
  455. // In all other but the first UIDL request a cookie should be
  456. // returned by the browser.
  457. // This can be removed if cookieless mode (#3228) is supported
  458. if (request.getRequestedSessionId() == null) {
  459. // User has cookies disabled
  460. SystemMessages systemMessages = getService().getSystemMessages(
  461. ServletPortletHelper.findLocale(null, null, request),
  462. request);
  463. getService().writeStringResponse(response,
  464. JsonConstants.JSON_CONTENT_TYPE,
  465. VaadinService.createCriticalNotificationJSON(
  466. systemMessages.getCookiesDisabledCaption(),
  467. systemMessages.getCookiesDisabledMessage(),
  468. null, systemMessages.getCookiesDisabledURL()));
  469. return false;
  470. }
  471. }
  472. return true;
  473. }
  474. /**
  475. * Send a notification to client-side widgetset. Used to notify client of
  476. * critical errors, session expiration and more. Server has no knowledge of
  477. * what UI client refers to.
  478. *
  479. * @param request
  480. * the HTTP request instance.
  481. * @param response
  482. * the HTTP response to write to.
  483. * @param caption
  484. * the notification caption
  485. * @param message
  486. * to notification body
  487. * @param details
  488. * a detail message to show in addition to the message. Currently
  489. * shown directly below the message but could be hidden behind a
  490. * details drop down in the future. Mainly used to give
  491. * additional information not necessarily useful to the end user.
  492. * @param url
  493. * url to load when the message is dismissed. Null will reload
  494. * the current page.
  495. * @throws IOException
  496. * if the writing failed due to input/output error.
  497. *
  498. * @deprecated As of 7.0. This method is retained only for backwards
  499. * compatibility and for {@link GAEVaadinServlet}.
  500. */
  501. @Deprecated
  502. protected void criticalNotification(VaadinServletRequest request,
  503. VaadinServletResponse response, String caption, String message,
  504. String details, String url) throws IOException {
  505. if (ServletPortletHelper.isUIDLRequest(request)) {
  506. String output = VaadinService.createCriticalNotificationJSON(
  507. caption, message, details, url);
  508. getService().writeStringResponse(response,
  509. JsonConstants.JSON_CONTENT_TYPE, output);
  510. } else {
  511. // Create an HTML reponse with the error
  512. String output = "";
  513. if (url != null) {
  514. output += "<a href=\"" + url + "\">";
  515. }
  516. if (caption != null) {
  517. output += "<b>" + caption + "</b><br/>";
  518. }
  519. if (message != null) {
  520. output += message;
  521. output += "<br/><br/>";
  522. }
  523. if (details != null) {
  524. output += details;
  525. output += "<br/><br/>";
  526. }
  527. if (url != null) {
  528. output += "</a>";
  529. }
  530. getService().writeStringResponse(response,
  531. "text/html; charset=UTF-8", output);
  532. }
  533. }
  534. /**
  535. * Writes the response in {@code output} using the contentType given in
  536. * {@code contentType} to the provided {@link HttpServletResponse}
  537. *
  538. * @param response
  539. * @param contentType
  540. * @param output
  541. * Output to write (UTF-8 encoded)
  542. * @throws IOException
  543. */
  544. private void writeResponse(HttpServletResponse response, String contentType,
  545. String output) throws IOException {
  546. response.setContentType(contentType);
  547. final OutputStream out = response.getOutputStream();
  548. // Set the response type
  549. final PrintWriter outWriter = new PrintWriter(
  550. new BufferedWriter(new OutputStreamWriter(out, "UTF-8")));
  551. outWriter.print(output);
  552. outWriter.flush();
  553. outWriter.close();
  554. }
  555. /**
  556. * Gets resource path using different implementations. Required to
  557. * supporting different servlet container implementations (application
  558. * servers).
  559. *
  560. * @param servletContext
  561. * @param path
  562. * the resource path.
  563. * @return the resource path.
  564. *
  565. * @deprecated As of 7.0. Will likely change or be removed in a future
  566. * version
  567. */
  568. @Deprecated
  569. protected static String getResourcePath(ServletContext servletContext,
  570. String path) {
  571. String resultPath = null;
  572. resultPath = servletContext.getRealPath(path);
  573. if (resultPath != null) {
  574. return resultPath;
  575. } else {
  576. try {
  577. final URL url = servletContext.getResource(path);
  578. resultPath = url.getFile();
  579. } catch (final Exception e) {
  580. // FIXME: Handle exception
  581. getLogger().log(Level.INFO,
  582. "Could not find resource path " + path, e);
  583. }
  584. }
  585. return resultPath;
  586. }
  587. /**
  588. * A helper method to strip away characters that might somehow be used for
  589. * XSS attacks. Leaves at least alphanumeric characters intact. Also removes
  590. * e.g. '(' and ')', so values should be safe in javascript too.
  591. *
  592. * @param themeName
  593. * @return
  594. *
  595. * @deprecated As of 7.0. Will likely change or be removed in a future
  596. * version
  597. */
  598. @Deprecated
  599. public static String stripSpecialChars(String themeName) {
  600. StringBuilder sb = new StringBuilder();
  601. char[] charArray = themeName.toCharArray();
  602. for (int i = 0; i < charArray.length; i++) {
  603. char c = charArray[i];
  604. if (!CHAR_BLACKLIST.contains(c)) {
  605. sb.append(c);
  606. }
  607. }
  608. return sb.toString();
  609. }
  610. private static final Collection<Character> CHAR_BLACKLIST = new HashSet<>(
  611. Arrays.asList(new Character[] { '&', '"', '\'', '<', '>', '(', ')',
  612. ';' }));
  613. /**
  614. * Mutex for preventing to scss compilations to take place simultaneously.
  615. * This is a workaround needed as the scss compiler currently is not thread
  616. * safe (#10292).
  617. * <p>
  618. * In addition, this is also used to protect the cached compilation results.
  619. */
  620. private static final Object SCSS_MUTEX = new Object();
  621. /**
  622. * Global cache of scss compilation results. This map is protected from
  623. * concurrent access by {@link #SCSS_MUTEX}.
  624. */
  625. private final Map<String, ScssCacheEntry> scssCache = new HashMap<>();
  626. /**
  627. * Keeps track of whether a warning about not being able to persist cache
  628. * files has already been printed. The flag is protected from concurrent
  629. * access by {@link #SCSS_MUTEX}.
  630. */
  631. private static boolean scssCompileWarWarningEmitted = false;
  632. /**
  633. * Returns the default theme. Must never return null.
  634. *
  635. * @return
  636. */
  637. public static String getDefaultTheme() {
  638. return DEFAULT_THEME_NAME;
  639. }
  640. /**
  641. * Check if this is a request for a static resource and, if it is, serve the
  642. * resource to the client.
  643. *
  644. * @param request
  645. * @param response
  646. * @return true if a file was served and the request has been handled, false
  647. * otherwise.
  648. * @throws IOException
  649. * @throws ServletException
  650. */
  651. private boolean serveStaticResources(HttpServletRequest request,
  652. HttpServletResponse response) throws IOException, ServletException {
  653. String pathInfo = request.getPathInfo();
  654. if (pathInfo == null) {
  655. return false;
  656. }
  657. String decodedRequestURI = URLDecoder.decode(request.getRequestURI(),
  658. "UTF-8");
  659. if ((request.getContextPath() != null)
  660. && (decodedRequestURI.startsWith("/VAADIN/"))) {
  661. serveStaticResourcesInVAADIN(decodedRequestURI, request, response);
  662. return true;
  663. }
  664. String decodedContextPath = URLDecoder.decode(request.getContextPath(),
  665. "UTF-8");
  666. if (decodedRequestURI.startsWith(decodedContextPath + "/VAADIN/")) {
  667. serveStaticResourcesInVAADIN(
  668. decodedRequestURI.substring(decodedContextPath.length()),
  669. request, response);
  670. return true;
  671. }
  672. return false;
  673. }
  674. /**
  675. * Serve resources from VAADIN directory.
  676. *
  677. * @param filename
  678. * The filename to serve. Should always start with /VAADIN/.
  679. * @param request
  680. * @param response
  681. * @throws IOException
  682. * @throws ServletException
  683. */
  684. private void serveStaticResourcesInVAADIN(String filename,
  685. HttpServletRequest request, HttpServletResponse response)
  686. throws IOException, ServletException {
  687. final ServletContext sc = getServletContext();
  688. URL resourceUrl = findResourceURL(filename);
  689. if (resourceUrl == null) {
  690. // File not found, if this was a css request we still look for a
  691. // scss file with the same name
  692. if (serveOnTheFlyCompiledScss(filename, request, response, sc)) {
  693. return;
  694. } else {
  695. // cannot serve requested file
  696. getLogger().log(Level.INFO,
  697. "Requested resource [{0}] not found from filesystem or through class loader."
  698. + " Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder.",
  699. filename);
  700. response.setStatus(HttpServletResponse.SC_NOT_FOUND);
  701. }
  702. return;
  703. }
  704. // security check: do not permit navigation out of the VAADIN
  705. // directory
  706. if (!isAllowedVAADINResourceUrl(request, resourceUrl)) {
  707. getLogger().log(Level.INFO,
  708. "Requested resource [{0}] not accessible in the VAADIN directory or access to it is forbidden.",
  709. filename);
  710. response.setStatus(HttpServletResponse.SC_FORBIDDEN);
  711. return;
  712. }
  713. String cacheControl = "public, max-age=0, must-revalidate";
  714. int resourceCacheTime = getCacheTime(filename);
  715. if (resourceCacheTime > 0) {
  716. cacheControl = "max-age=" + String.valueOf(resourceCacheTime);
  717. }
  718. response.setHeader("Cache-Control", cacheControl);
  719. response.setDateHeader("Expires",
  720. System.currentTimeMillis() + (resourceCacheTime * 1000));
  721. // Find the modification timestamp
  722. long lastModifiedTime = 0;
  723. URLConnection connection = null;
  724. try {
  725. connection = resourceUrl.openConnection();
  726. lastModifiedTime = connection.getLastModified();
  727. // Remove milliseconds to avoid comparison problems (milliseconds
  728. // are not returned by the browser in the "If-Modified-Since"
  729. // header).
  730. lastModifiedTime = lastModifiedTime - lastModifiedTime % 1000;
  731. response.setDateHeader("Last-Modified", lastModifiedTime);
  732. if (browserHasNewestVersion(request, lastModifiedTime)) {
  733. response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
  734. return;
  735. }
  736. } catch (Exception e) {
  737. // Failed to find out last modified timestamp. Continue without it.
  738. getLogger().log(Level.FINEST,
  739. "Failed to find out last modified timestamp. Continuing without it.",
  740. e);
  741. } finally {
  742. try {
  743. // Explicitly close the input stream to prevent it
  744. // from remaining hanging
  745. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4257700
  746. InputStream is = connection.getInputStream();
  747. if (is != null) {
  748. is.close();
  749. }
  750. } catch (FileNotFoundException e) {
  751. // Not logging when the file does not exist.
  752. } catch (IOException e) {
  753. getLogger().log(Level.INFO,
  754. "Error closing URLConnection input stream", e);
  755. }
  756. }
  757. // Set type mime type if we can determine it based on the filename
  758. final String mimetype = sc.getMimeType(filename);
  759. if (mimetype != null) {
  760. response.setContentType(mimetype);
  761. }
  762. writeStaticResourceResponse(request, response, resourceUrl);
  763. }
  764. /**
  765. * Calculates the cache lifetime for the given filename in seconds. By
  766. * default filenames containing ".nocache." return 0, filenames containing
  767. * ".cache." return one year, all other return the value defined in the
  768. * web.xml using resourceCacheTime (defaults to 1 hour).
  769. *
  770. * @param filename
  771. * @return cache lifetime for the given filename in seconds
  772. */
  773. protected int getCacheTime(String filename) {
  774. /*
  775. * GWT conventions:
  776. *
  777. * - files containing .nocache. will not be cached.
  778. *
  779. * - files containing .cache. will be cached for one year.
  780. *
  781. * https://developers.google.com/web-toolkit/doc/latest/
  782. * DevGuideCompilingAndDebugging#perfect_caching
  783. */
  784. if (filename.contains(".nocache.")) {
  785. return 0;
  786. }
  787. if (filename.contains(".cache.")) {
  788. return 60 * 60 * 24 * 365;
  789. }
  790. /*
  791. * For all other files, the browser is allowed to cache for 1 hour
  792. * without checking if the file has changed. This forces browsers to
  793. * fetch a new version when the Vaadin version is updated. This will
  794. * cause more requests to the servlet than without this but for high
  795. * volume sites the static files should never be served through the
  796. * servlet.
  797. */
  798. return getService().getDeploymentConfiguration().getResourceCacheTime();
  799. }
  800. /**
  801. * Writes the contents of the given resourceUrl in the response. Can be
  802. * overridden to add/modify response headers and similar.
  803. *
  804. * @param request
  805. * The request for the resource
  806. * @param response
  807. * The response
  808. * @param resourceUrl
  809. * The url to send
  810. * @throws IOException
  811. */
  812. protected void writeStaticResourceResponse(HttpServletRequest request,
  813. HttpServletResponse response, URL resourceUrl) throws IOException {
  814. URLConnection connection = null;
  815. InputStream is = null;
  816. String urlStr = resourceUrl.toExternalForm();
  817. if (allowServePrecompressedResource(request, urlStr)) {
  818. // try to serve a precompressed version if available
  819. try {
  820. connection = new URL(urlStr + ".gz").openConnection();
  821. is = connection.getInputStream();
  822. // set gzip headers
  823. response.setHeader("Content-Encoding", "gzip");
  824. } catch (IOException e) {
  825. // NOP: will be still tried with non gzipped version
  826. } catch (Exception e) {
  827. getLogger().log(Level.FINE,
  828. "Unexpected exception looking for gzipped version of resource "
  829. + urlStr,
  830. e);
  831. }
  832. }
  833. if (is == null) {
  834. // precompressed resource not available, get non compressed
  835. connection = resourceUrl.openConnection();
  836. try {
  837. is = connection.getInputStream();
  838. } catch (FileNotFoundException e) {
  839. response.setStatus(HttpServletResponse.SC_NOT_FOUND);
  840. return;
  841. }
  842. }
  843. try {
  844. int length = connection.getContentLength();
  845. if (length >= 0) {
  846. response.setContentLength(length);
  847. }
  848. } catch (Throwable e) {
  849. // This can be ignored, content length header is not required.
  850. // Need to close the input stream because of
  851. // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4257700 to
  852. // prevent it from hanging, but that is done below.
  853. }
  854. try {
  855. streamContent(response, is);
  856. } finally {
  857. is.close();
  858. }
  859. }
  860. /**
  861. * Returns whether this servlet should attempt to serve a precompressed
  862. * version of the given static resource. If this method returns true, the
  863. * suffix {@code .gz} is appended to the URL and the corresponding resource
  864. * is served if it exists. It is assumed that the compression method used is
  865. * gzip. If this method returns false or a compressed version is not found,
  866. * the original URL is used.
  867. *
  868. * The base implementation of this method returns true if and only if the
  869. * request indicates that the client accepts gzip compressed responses and
  870. * the filename extension of the requested resource is .js, .css, or .html.
  871. *
  872. * @since 7.5.0
  873. *
  874. * @param request
  875. * the request for the resource
  876. * @param url
  877. * the URL of the requested resource
  878. * @return true if the servlet should attempt to serve a precompressed
  879. * version of the resource, false otherwise
  880. */
  881. protected boolean allowServePrecompressedResource(
  882. HttpServletRequest request, String url) {
  883. String accept = request.getHeader("Accept-Encoding");
  884. return accept != null && accept.contains("gzip") && (url.endsWith(".js")
  885. || url.endsWith(".css") || url.endsWith(".html"));
  886. }
  887. private void streamContent(HttpServletResponse response, InputStream is)
  888. throws IOException {
  889. final OutputStream os = response.getOutputStream();
  890. final byte buffer[] = new byte[DEFAULT_BUFFER_SIZE];
  891. int bytes;
  892. while ((bytes = is.read(buffer)) >= 0) {
  893. os.write(buffer, 0, bytes);
  894. }
  895. }
  896. /**
  897. * Finds the given resource from the web content folder or using the class
  898. * loader.
  899. *
  900. * @since 7.7
  901. * @param filename
  902. * The file to find, starting with a "/"
  903. * @return The URL to the given file, or null if the file was not found
  904. * @throws IOException
  905. * if there was a problem while locating the file
  906. */
  907. protected URL findResourceURL(String filename) throws IOException {
  908. URL resourceUrl = getServletContext().getResource(filename);
  909. if (resourceUrl == null) {
  910. // try if requested file is found from class loader
  911. // strip leading "/" otherwise stream from JAR wont work
  912. if (filename.startsWith("/")) {
  913. filename = filename.substring(1);
  914. }
  915. resourceUrl = getService().getClassLoader().getResource(filename);
  916. }
  917. return resourceUrl;
  918. }
  919. private boolean serveOnTheFlyCompiledScss(String filename,
  920. HttpServletRequest request, HttpServletResponse response,
  921. ServletContext sc) throws IOException {
  922. if (!filename.endsWith(".css")) {
  923. return false;
  924. }
  925. String scssFilename = filename.substring(0, filename.length() - 4)
  926. + ".scss";
  927. URL scssUrl = findResourceURL(scssFilename);
  928. if (scssUrl == null) {
  929. // Is a css request but no scss file was found
  930. return false;
  931. }
  932. // security check: do not permit navigation out of the VAADIN
  933. // directory
  934. if (!isAllowedVAADINResourceUrl(request, scssUrl)) {
  935. getLogger().log(Level.INFO,
  936. "Requested resource [{0}] not accessible in the VAADIN directory or access to it is forbidden.",
  937. filename);
  938. response.setStatus(HttpServletResponse.SC_FORBIDDEN);
  939. // Handled, return true so no further processing is done
  940. return true;
  941. }
  942. if (getService().getDeploymentConfiguration().isProductionMode()) {
  943. // This is not meant for production mode.
  944. getLogger().log(Level.INFO,
  945. "Request for {0} not handled by sass compiler while in production mode",
  946. filename);
  947. response.setStatus(HttpServletResponse.SC_NOT_FOUND);
  948. // Handled, return true so no further processing is done
  949. return true;
  950. }
  951. synchronized (SCSS_MUTEX) {
  952. ScssCacheEntry cacheEntry = scssCache.get(scssFilename);
  953. if (cacheEntry == null) {
  954. try {
  955. cacheEntry = loadPersistedScssCache(scssFilename, sc);
  956. } catch (Exception e) {
  957. getLogger().log(Level.WARNING,
  958. "Could not read persisted scss cache", e);
  959. }
  960. }
  961. if (cacheEntry == null || !cacheEntry.isStillValid()) {
  962. cacheEntry = compileScssOnTheFly(filename, scssFilename, sc);
  963. persistCacheEntry(cacheEntry);
  964. }
  965. scssCache.put(scssFilename, cacheEntry);
  966. if (cacheEntry == null) {
  967. // compilation did not produce any result, but logged a message
  968. return false;
  969. }
  970. // This is for development mode only so instruct the browser to
  971. // never cache it
  972. response.setHeader("Cache-Control", "no-cache");
  973. final String mimetype = getService().getMimeType(filename);
  974. writeResponse(response, mimetype, cacheEntry.getCss());
  975. return true;
  976. }
  977. }
  978. private ScssCacheEntry loadPersistedScssCache(String scssFilename,
  979. ServletContext sc) throws IOException {
  980. String realFilename = sc.getRealPath(scssFilename);
  981. File scssCacheFile = getScssCacheFile(new File(realFilename));
  982. if (!scssCacheFile.exists()) {
  983. return null;
  984. }
  985. String jsonString = readFile(scssCacheFile, Charset.forName("UTF-8"));
  986. JsonObject entryJson = Json.parse(jsonString);
  987. String cacheVersion = entryJson.getString("version");
  988. if (!Version.getFullVersion().equals(cacheVersion)) {
  989. // Compiled for some other Vaadin version, discard cache
  990. scssCacheFile.delete();
  991. return null;
  992. }
  993. return new ScssCacheEntry(entryJson);
  994. }
  995. private ScssCacheEntry compileScssOnTheFly(String filename,
  996. String scssFilename, ServletContext sc) throws IOException {
  997. String realFilename = sc.getRealPath(scssFilename);
  998. ScssStylesheet scss = ScssStylesheet.get(realFilename);
  999. if (scss == null) {
  1000. // Not a file in the file system (WebContent directory). Use the
  1001. // identifier directly (VAADIN/themes/.../styles.css) so
  1002. // ScssStylesheet will try using the class loader.
  1003. if (scssFilename.startsWith("/")) {
  1004. scssFilename = scssFilename.substring(1);
  1005. }
  1006. scss = ScssStylesheet.get(scssFilename);
  1007. }
  1008. if (scss == null) {
  1009. getLogger().log(Level.WARNING,
  1010. "Scss file {0} exists but ScssStylesheet was not able to find it",
  1011. scssFilename);
  1012. return null;
  1013. }
  1014. try {
  1015. getLogger().log(Level.FINE, "Compiling {0} for request to {1}",
  1016. new Object[] { realFilename, filename });
  1017. scss.compile();
  1018. } catch (Exception e) {
  1019. getLogger().log(Level.WARNING, "Scss compilation failed", e);
  1020. return null;
  1021. }
  1022. return new ScssCacheEntry(realFilename, scss.printState(),
  1023. scss.getSourceUris());
  1024. }
  1025. /**
  1026. * Check whether a URL obtained from a classloader refers to a valid static
  1027. * resource in the directory VAADIN.
  1028. *
  1029. * Warning: Overriding of this method is not recommended, but is possible to
  1030. * support non-default classloaders or servers that may produce URLs
  1031. * different from the normal ones. The method prototype may change in the
  1032. * future. Care should be taken not to expose class files or other resources
  1033. * outside the VAADIN directory if the method is overridden.
  1034. *
  1035. * @param request
  1036. * @param resourceUrl
  1037. * @return
  1038. *
  1039. * @since 6.6.7
  1040. *
  1041. * @deprecated As of 7.0. Will likely change or be removed in a future
  1042. * version
  1043. */
  1044. @Deprecated
  1045. protected boolean isAllowedVAADINResourceUrl(HttpServletRequest request,
  1046. URL resourceUrl) {
  1047. if ("jar".equals(resourceUrl.getProtocol())) {
  1048. // This branch is used for accessing resources directly from the
  1049. // Vaadin JAR in development environments and in similar cases.
  1050. // Inside a JAR, a ".." would mean a real directory named ".." so
  1051. // using it in paths should just result in the file not being found.
  1052. // However, performing a check in case some servers or class loaders
  1053. // try to normalize the path by collapsing ".." before the class
  1054. // loader sees it.
  1055. if (!resourceUrl.getPath().contains("!/VAADIN/")) {
  1056. getLogger().log(Level.INFO,
  1057. "Blocked attempt to access a JAR entry not starting with /VAADIN/: {0}",
  1058. resourceUrl);
  1059. return false;
  1060. }
  1061. getLogger().log(Level.FINE,
  1062. "Accepted access to a JAR entry using a class loader: {0}",
  1063. resourceUrl);
  1064. return true;
  1065. } else {
  1066. // Some servers such as GlassFish extract files from JARs (file:)
  1067. // and e.g. JBoss 5+ use protocols vsf: and vfsfile: .
  1068. // Check that the URL is in a VAADIN directory and does not contain
  1069. // "/../"
  1070. if (!resourceUrl.getPath().contains("/VAADIN/")
  1071. || resourceUrl.getPath().contains("/../")) {
  1072. getLogger().log(Level.INFO,
  1073. "Blocked attempt to access file: {0}", resourceUrl);
  1074. return false;
  1075. }
  1076. getLogger().log(Level.FINE,
  1077. "Accepted access to a file using a class loader: {0}",
  1078. resourceUrl);
  1079. return true;
  1080. }
  1081. }
  1082. /**
  1083. * Checks if the browser has an up to date cached version of requested
  1084. * resource. Currently the check is performed using the "If-Modified-Since"
  1085. * header. Could be expanded if needed.
  1086. *
  1087. * @param request
  1088. * The HttpServletRequest from the browser.
  1089. * @param resourceLastModifiedTimestamp
  1090. * The timestamp when the resource was last modified. 0 if the
  1091. * last modification time is unknown.
  1092. * @return true if the If-Modified-Since header tells the cached version in
  1093. * the browser is up to date, false otherwise
  1094. */
  1095. private boolean browserHasNewestVersion(HttpServletRequest request,
  1096. long resourceLastModifiedTimestamp) {
  1097. if (resourceLastModifiedTimestamp < 1) {
  1098. // We do not know when it was modified so the browser cannot have an
  1099. // up-to-date version
  1100. return false;
  1101. }
  1102. /*
  1103. * The browser can request the resource conditionally using an
  1104. * If-Modified-Since header. Check this against the last modification
  1105. * time.
  1106. */
  1107. try {
  1108. // If-Modified-Since represents the timestamp of the version cached
  1109. // in the browser
  1110. long headerIfModifiedSince = request
  1111. .getDateHeader("If-Modified-Since");
  1112. if (headerIfModifiedSince >= resourceLastModifiedTimestamp) {
  1113. // Browser has this an up-to-date version of the resource
  1114. return true;
  1115. }
  1116. } catch (Exception e) {
  1117. // Failed to parse header. Fail silently - the browser does not have
  1118. // an up-to-date version in its cache.
  1119. }
  1120. return false;
  1121. }
  1122. /**
  1123. *
  1124. * @author Vaadin Ltd
  1125. * @since 7.0
  1126. *
  1127. * @deprecated As of 7.0. This is no longer used and only provided for
  1128. * backwards compatibility. Each {@link RequestHandler} can
  1129. * individually decide whether it wants to handle a request or
  1130. * not.
  1131. */
  1132. @Deprecated
  1133. protected enum RequestType {
  1134. FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APP, PUBLISHED_FILE, HEARTBEAT;
  1135. }
  1136. /**
  1137. * @param request
  1138. * @return
  1139. *
  1140. * @deprecated As of 7.0. This is no longer used and only provided for
  1141. * backwards compatibility. Each {@link RequestHandler} can
  1142. * individually decide whether it wants to handle a request or
  1143. * not.
  1144. */
  1145. @Deprecated
  1146. protected RequestType getRequestType(VaadinServletRequest request) {
  1147. if (ServletPortletHelper.isFileUploadRequest(request)) {
  1148. return RequestType.FILE_UPLOAD;
  1149. } else if (ServletPortletHelper.isPublishedFileRequest(request)) {
  1150. return RequestType.PUBLISHED_FILE;
  1151. } else if (ServletUIInitHandler.isUIInitRequest(request)) {
  1152. return RequestType.BROWSER_DETAILS;
  1153. } else if (ServletPortletHelper.isUIDLRequest(request)) {
  1154. return RequestType.UIDL;
  1155. } else if (isStaticResourceRequest(request)) {
  1156. return RequestType.STATIC_FILE;
  1157. } else if (ServletPortletHelper.isAppRequest(request)) {
  1158. return RequestType.APP;
  1159. } else if (ServletPortletHelper.isHeartbeatRequest(request)) {
  1160. return RequestType.HEARTBEAT;
  1161. }
  1162. return RequestType.OTHER;
  1163. }
  1164. protected boolean isStaticResourceRequest(HttpServletRequest request) {
  1165. return request.getRequestURI()
  1166. .startsWith(request.getContextPath() + "/VAADIN/");
  1167. }
  1168. /**
  1169. * Remove any heading or trailing "what" from the "string".
  1170. *
  1171. * @param string
  1172. * @param what
  1173. * @return
  1174. */
  1175. static String removeHeadingOrTrailing(String string, String what) {
  1176. while (string.startsWith(what)) {
  1177. string = string.substring(1);
  1178. }
  1179. while (string.endsWith(what)) {
  1180. string = string.substring(0, string.length() - 1);
  1181. }
  1182. return string;
  1183. }
  1184. /**
  1185. * Write a redirect response to the main page of the application.
  1186. *
  1187. * @param request
  1188. * @param response
  1189. * @throws IOException
  1190. * if sending the redirect fails due to an input/output error or
  1191. * a bad application URL
  1192. */
  1193. private void redirectToApplication(HttpServletRequest request,
  1194. HttpServletResponse response) throws IOException {
  1195. String applicationUrl = getApplicationUrl(request).toExternalForm();
  1196. response.sendRedirect(response.encodeRedirectURL(applicationUrl));
  1197. }
  1198. /**
  1199. * Gets the current application URL from request.
  1200. *
  1201. * @param request
  1202. * the HTTP request.
  1203. * @throws MalformedURLException
  1204. * if the application is denied access to the persistent data
  1205. * store represented by the given URL.
  1206. *
  1207. * @deprecated As of 7.0. Will likely change or be removed in a future
  1208. * version
  1209. */
  1210. @Deprecated
  1211. protected URL getApplicationUrl(HttpServletRequest request)
  1212. throws MalformedURLException {
  1213. final URL reqURL = new URL((request.isSecure() ? "https://" : "http://")
  1214. + request.getServerName()
  1215. + ((request.isSecure() && request.getServerPort() == 443)
  1216. || (!request.isSecure()
  1217. && request.getServerPort() == 80) ? ""
  1218. : ":" + request.getServerPort())
  1219. + request.getRequestURI());
  1220. String servletPath = "";
  1221. if (request
  1222. .getAttribute("javax.servlet.include.servlet_path") != null) {
  1223. // this is an include request
  1224. servletPath = request
  1225. .getAttribute("javax.servlet.include.context_path")
  1226. .toString()
  1227. + request
  1228. .getAttribute("javax.servlet.include.servlet_path");
  1229. } else {
  1230. servletPath = request.getContextPath() + request.getServletPath();
  1231. }
  1232. if (servletPath.length() == 0
  1233. || servletPath.charAt(servletPath.length() - 1) != '/') {
  1234. servletPath = servletPath + "/";
  1235. }
  1236. URL u = new URL(reqURL, servletPath);
  1237. return u;
  1238. }
  1239. /*
  1240. * (non-Javadoc)
  1241. *
  1242. * @see javax.servlet.GenericServlet#destroy()
  1243. */
  1244. @Override
  1245. public void destroy() {
  1246. super.destroy();
  1247. getService().destroy();
  1248. }
  1249. private static void persistCacheEntry(ScssCacheEntry cacheEntry) {
  1250. String scssFileName = cacheEntry.getScssFileName();
  1251. if (scssFileName == null) {
  1252. if (!scssCompileWarWarningEmitted) {
  1253. getLogger().warning(
  1254. "Could not persist scss cache because no real file was found for the compiled scss file. "
  1255. + "This might happen e.g. if serving the scss file directly from a .war file.");
  1256. scssCompileWarWarningEmitted = true;
  1257. }
  1258. return;
  1259. }
  1260. File scssFile = new File(scssFileName);
  1261. File cacheFile = getScssCacheFile(scssFile);
  1262. String cacheEntryJsonString = cacheEntry.asJson();
  1263. try {
  1264. writeFile(cacheEntryJsonString, cacheFile,
  1265. Charset.forName("UTF-8"));
  1266. } catch (IOException e) {
  1267. getLogger().log(Level.WARNING,
  1268. "Error persisting scss cache " + cacheFile, e);
  1269. }
  1270. }
  1271. private static String readFile(File file, Charset charset)
  1272. throws IOException {
  1273. InputStream in = new FileInputStream(file);
  1274. try {
  1275. // no point in reading files over 2GB to a String
  1276. byte[] b = new byte[(int) file.length()];
  1277. int len = b.length;
  1278. int total = 0;
  1279. while (total < len) {
  1280. int result = in.read(b, total, len - total);
  1281. if (result == -1) {
  1282. break;
  1283. }
  1284. total += result;
  1285. }
  1286. return new String(b, charset);
  1287. } finally {
  1288. in.close();
  1289. }
  1290. }
  1291. private static void writeFile(String content, File file, Charset charset)
  1292. throws IOException {
  1293. FileOutputStream fos = new FileOutputStream(file);
  1294. try {
  1295. fos.write(content.getBytes(charset));
  1296. } finally {
  1297. fos.close();
  1298. }
  1299. }
  1300. private static File getScssCacheFile(File scssFile) {
  1301. return new File(scssFile.getParentFile(),
  1302. scssFile.getName() + ".cache");
  1303. }
  1304. /**
  1305. * Escapes characters to html entities. An exception is made for some "safe
  1306. * characters" to keep the text somewhat readable.
  1307. *
  1308. * @param unsafe
  1309. * @return a safe string to be added inside an html tag
  1310. *
  1311. * @deprecated As of 7.0. Will likely change or be removed in a future
  1312. * version
  1313. */
  1314. @Deprecated
  1315. public static final String safeEscapeForHtml(String unsafe) {
  1316. if (null == unsafe) {
  1317. return null;
  1318. }
  1319. StringBuilder safe = new StringBuilder();
  1320. char[] charArray = unsafe.toCharArray();
  1321. for (int i = 0; i < charArray.length; i++) {
  1322. char c = charArray[i];
  1323. if (isSafe(c)) {
  1324. safe.append(c);
  1325. } else {
  1326. safe.append("&#");
  1327. safe.append((int) c);
  1328. safe.append(";");
  1329. }
  1330. }
  1331. return safe.toString();
  1332. }
  1333. private static boolean isSafe(char c) {
  1334. return //
  1335. c > 47 && c < 58 || // alphanum
  1336. c > 64 && c < 91 || // A-Z
  1337. c > 96 && c < 123 // a-z
  1338. ;
  1339. }
  1340. private static final Logger getLogger() {
  1341. return Logger.getLogger(VaadinServlet.class.getName());
  1342. }
  1343. }