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

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