Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

VaadinServlet.java 56KB

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