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

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