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

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