View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.lifecycle;
20  
21  import java.io.BufferedReader;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  import java.io.OutputStream;
26  import java.util.HashMap;
27  import java.util.Map;
28  import javax.faces.FacesException;
29  import javax.faces.application.Resource;
30  import javax.faces.context.ExternalContext;
31  import javax.faces.context.FacesContext;
32  import javax.faces.lifecycle.ClientWindow;
33  import javax.faces.render.ResponseStateManager;
34  import javax.servlet.http.Cookie;
35  import javax.servlet.http.HttpServletResponse;
36  
37  /**
38   *
39   * @author lu4242
40   */
41  public class CODIClientSideWindow extends ClientWindow
42  {
43      /**
44       * Key for storing the window-id e.g. in URLs
45       */
46      //private static final String WINDOW_CONTEXT_ID_PARAMETER_KEY = 
47      //        ResponseStateManager.CLIENT_WINDOW_URL_PARAM;
48  
49      /**
50       * Value which can be used as "window-id" by external clients which aren't aware of windows.
51       * It deactivates e.g. the redirect for the initial request.
52       */
53      private static final String AUTOMATED_ENTRY_POINT_PARAMETER_KEY = "automatedEntryPoint";    
54      
55      private static final long serialVersionUID = 5293942986187078113L;
56  
57      private static final String WINDOW_ID_COOKIE_PREFIX = "jfwid-";
58      private static final String CODI_REQUEST_TOKEN = "mfRid";
59  
60      private static final String UNINITIALIZED_WINDOW_ID_VALUE = "uninitializedWindowId";
61      private static final String WINDOW_ID_REPLACE_PATTERN = "$$windowIdValue$$";
62      private static final String NOSCRIPT_URL_REPLACE_PATTERN = "$$noscriptUrl$$";
63      private static final String NOSCRIPT_PARAMETER = "mfDirect";
64  
65      private final ClientConfig clientConfig;
66  
67      private final WindowContextConfig windowContextConfig;
68      
69      private final TokenGenerator clientWindowTokenGenerator;    
70      
71      private String windowId;
72      
73      private String unparsedWindowHandlerHtml = null;
74      
75      private Map<String,String> queryParamsMap;
76  
77      /*
78      protected CODIClientSideWindow()
79      {
80          // needed for proxying
81      }*/
82  
83      protected CODIClientSideWindow(TokenGenerator clientWindowTokenGenerator,
84              WindowContextConfig windowContextConfig,
85              ClientConfig clientConfig)
86      {
87          this.windowContextConfig = windowContextConfig;
88          this.clientConfig = clientConfig;
89          this.clientWindowTokenGenerator = clientWindowTokenGenerator;
90      }
91  
92      /**
93       * {@inheritDoc}
94       */
95      /*
96      public String restoreWindowId(ExternalContext externalContext)
97      {
98          if (this.clientConfig.isJavaScriptEnabled())
99          {
100             return (String) externalContext.getRequestMap().get(
101                     WINDOW_CONTEXT_ID_PARAMETER_KEY);
102         }
103         else
104         {
105             // fallback
106             //if(!this.useWindowAwareUrlEncoding)
107             //{
108             //    return null;
109             //}
110 
111             return externalContext.getRequestParameterMap().get(WINDOW_CONTEXT_ID_PARAMETER_KEY);
112         }
113     }*/
114 
115     /**
116      * {@inheritDoc}
117      */
118     //public void beforeLifecycleExecute(FacesContext facesContext)
119     public void decode(FacesContext facesContext)
120     {
121         if (facesContext.isPostback())
122         {
123             // In postback, we can safely ignore the query param, because it is not useful
124             if (getId() == null)
125             {
126                  setId(calculateWindowIdFromPost(facesContext));
127             }
128         }
129 
130         if (!isClientSideWindowHandlerRequest(facesContext))
131         {
132             return;
133         }
134         
135         ExternalContext externalContext = facesContext.getExternalContext();
136 
137         if (isNoscriptRequest(externalContext))
138         {
139             // the client has JavaScript disabled
140             clientConfig.setJavaScriptEnabled(false);
141             return;
142         }
143 
144         String windowId = getWindowIdFromCookie(externalContext);
145         if (windowId == null)
146         {
147             // GET request without windowId - send windowhandlerfilter.html to get the windowId
148             sendWindowHandlerHtml(facesContext, null);
149             facesContext.responseComplete();
150         }
151         else
152         {
153             if (AUTOMATED_ENTRY_POINT_PARAMETER_KEY.equals(windowId) ||
154                 (!windowContextConfig.isUnknownWindowIdsAllowed() /*&&
155                  !ConversationUtils.isWindowActive(this.windowContextManager, windowId)*/))
156             {
157                 // no or invalid windowId --> create new one
158                 // don't use createWindowId() the following call will ensure the max. window context count,...
159                 //windowId = this.windowContextManager.getCurrentWindowContext().getId();
160                 windowId = createWindowId(facesContext);
161 
162                 // GET request with NEW windowId - send windowhandlerfilter.html to set and re-get the windowId
163                 sendWindowHandlerHtml(facesContext, windowId);
164                 facesContext.responseComplete();
165             }
166             else
167             {
168                 // we have a valid windowId - set it and continue with the request
169                 // TODO only set internally and provide via restoreWindowId()? 
170                 //externalContext.getRequestMap().put(WINDOW_CONTEXT_ID_PARAMETER_KEY, windowId);
171                 setId(windowId);
172             }
173         }
174     }
175 
176     public String calculateWindowIdFromPost(FacesContext context)
177     {
178         //1. If it comes as parameter, it takes precedence over any other choice, because
179         //   no browser is capable to do a POST and create a new window at the same time.
180         String windowId = context.getExternalContext().getRequestParameterMap().get(
181                 ResponseStateManager.CLIENT_WINDOW_PARAM);
182         if (windowId != null)
183         {
184             return windowId;
185         }
186         return null;
187     }
188     
189     private boolean isClientSideWindowHandlerRequest(FacesContext facesContext)
190     {
191         // no POST request and javascript enabled
192         // NOTE that for POST-requests the windowId is saved in the state (see WindowContextIdHolderComponent)
193         return !facesContext.isPostback() && clientConfig.isClientSideWindowHandlerRequest(facesContext);
194     }
195 
196     private boolean isNoscriptRequest(ExternalContext externalContext)
197     {
198         String noscript = externalContext.getRequestParameterMap().get(NOSCRIPT_PARAMETER);
199 
200         return (noscript != null && "true".equals(noscript));
201     }
202 
203     private void sendWindowHandlerHtml(FacesContext facesContext, String windowId)
204     {
205         HttpServletResponse httpResponse = (HttpServletResponse) facesContext.getExternalContext().getResponse();
206 
207         try
208         {
209             httpResponse.setStatus(HttpServletResponse.SC_OK);
210             httpResponse.setContentType("text/html");
211 
212             if (unparsedWindowHandlerHtml == null)
213             {
214                 Resource resource = facesContext.getApplication().getResourceHandler().createResource(
215                         "windowhandler.html", "org.apache.myfaces.windowId");
216                 
217                 unparsedWindowHandlerHtml = convertStreamToString(resource.getInputStream());
218             }
219             
220             String windowHandlerHtml = unparsedWindowHandlerHtml;
221 
222             if (windowId == null)
223             {
224                 windowId = UNINITIALIZED_WINDOW_ID_VALUE;
225             }
226 
227             // set the windowId value in the javascript code
228             windowHandlerHtml = windowHandlerHtml.replace(WINDOW_ID_REPLACE_PATTERN, windowId);
229 
230             // set the noscript-URL for users with no JavaScript
231             windowHandlerHtml = windowHandlerHtml.replace(
232                     NOSCRIPT_URL_REPLACE_PATTERN, getNoscriptUrl(facesContext.getExternalContext()));
233 
234             OutputStream os = httpResponse.getOutputStream();
235             try
236             {
237                 os.write(windowHandlerHtml.getBytes());
238             }
239             finally
240             {
241                 os.close();
242             }
243         }
244         catch (IOException ioe)
245         {
246             throw new FacesException(ioe);
247         }
248     }
249     
250     private static String convertStreamToString(InputStream is)
251     {
252         StringBuilder sb = new StringBuilder();
253         try
254         {
255             BufferedReader reader = new BufferedReader(new InputStreamReader(is));
256             String line = null;
257             while ((line = reader.readLine()) != null)
258             {
259               sb.append(line + "\n");
260             }
261         }
262         catch (IOException e)
263         {
264             throw new FacesException(e);
265         }
266         finally
267         {
268             if (is != null)
269             {
270                 try
271                 {
272                     is.close();
273                 }
274                 catch (IOException e)
275                 {
276                     //No op
277                 }                
278             }
279         }
280         return sb.toString();
281     }    
282 
283     private String getNoscriptUrl(ExternalContext externalContext)
284     {
285         String url = externalContext.getRequestPathInfo();
286         if (url == null)
287         {
288             url = "";
289         }
290 
291         // only use the very last part of the url
292         int lastSlash = url.lastIndexOf('/');
293         if (lastSlash != -1)
294         {
295             url = url.substring(lastSlash + 1);
296         }
297 
298         // add request parameter
299         url = addParameters(externalContext, url, true, true, true);
300 
301         // add noscript parameter
302         if (url.contains("?"))
303         {
304             url = url + "&";
305         }
306         else
307         {
308             url = url + "?";
309         }
310         url = url + NOSCRIPT_PARAMETER + "=true";
311 
312         // NOTE that the url could contain data for an XSS attack
313         // like e.g. ?"></a><a href%3D"http://hacker.org/attack.html?a
314         // DO NOT REMOVE THE FOLLOWING LINES!
315         url = url.replace("\"", "");
316         url = url.replace("\'", "");
317 
318         return url;
319     }
320 
321     /**
322      * Adds the current request-parameters to the given url
323      * @param externalContext current external-context
324      * @param url current url
325      * @param addRequestParameter flag which indicates if the request params should be added or not
326      * @param addPageParameter flag which indicates if the view params should be added or not. See ViewParameter
327      * @param encodeValues flag which indicates if parameter values should be encoded or not
328      * @return url with request-parameters
329      */
330     public static String addParameters(ExternalContext externalContext, String url,
331                                        boolean addRequestParameter, boolean addPageParameter, boolean encodeValues)
332     {
333         StringBuilder finalUrl = new StringBuilder(url);
334         boolean existingParameters = url.contains("?");
335         boolean urlContainsWindowId = url.contains(ResponseStateManager.CLIENT_WINDOW_URL_PARAM + "=");
336 
337         /* TODO: implement me
338         for(RequestParameter requestParam :
339                 getParameters(externalContext, true, addRequestParameter, addPageParameter))
340         {
341             String key = requestParam.getKey();
342 
343             //TODO eval if we should also filter the other params
344             if(WindowContextManager.WINDOW_CONTEXT_ID_PARAMETER_KEY.equals(key) && urlContainsWindowId)
345             {
346                 continue;
347             }
348 
349             for(String parameterValue : requestParam.getValues())
350             {
351                 if(!url.contains(key + "=" + parameterValue) &&
352                         !url.contains(key + "=" + encodeURLParameterValue(parameterValue, externalContext)))
353                 {
354                     if(!existingParameters)
355                     {
356                         finalUrl.append("?");
357                         existingParameters = true;
358                     }
359                     else
360                     {
361                         finalUrl.append("&");
362                     }
363                     finalUrl.append(key);
364                     finalUrl.append("=");
365 
366                     if(encodeValues)
367                     {
368                         finalUrl.append(encodeURLParameterValue(parameterValue, externalContext));
369                     }
370                     else
371                     {
372                         finalUrl.append(parameterValue);
373                     }
374                 }
375             }
376         }
377         */
378         return finalUrl.toString();
379     }
380     
381     protected String createWindowId(FacesContext context)
382     {
383         String windowId = clientWindowTokenGenerator._getNextToken();
384         setId(windowId);
385         return windowId;
386     }
387     
388     private String getWindowIdFromCookie(ExternalContext externalContext)
389     {
390         String cookieName = WINDOW_ID_COOKIE_PREFIX + getRequestToken(externalContext);
391         Cookie cookie = (Cookie) externalContext.getRequestCookieMap().get(cookieName);
392 
393         if (cookie != null)
394         {
395             // manually blast the cookie away, otherwise it pollutes the
396             // cookie storage in some browsers. E.g. Firefox doesn't
397             // cleanup properly, even if the max-age is reached.
398             cookie.setMaxAge(0);
399 
400             return cookie.getValue();
401         }
402 
403         return null;
404     }
405 
406     private String getRequestToken(ExternalContext externalContext)
407     {
408         String requestToken = externalContext.getRequestParameterMap().get(CODI_REQUEST_TOKEN);
409         if (requestToken != null)
410         {
411             return requestToken;
412         }
413 
414         return "";
415     }
416 
417     @Override
418     public String getId()
419     {
420         return windowId;
421     }
422     
423     public void setId(String id)
424     {
425         windowId = id;
426         queryParamsMap = null;
427     }
428     
429     @Override
430     public Map<String, String> getQueryURLParameters(FacesContext context)
431     {
432         if (queryParamsMap == null)
433         {
434             String id = context.getExternalContext().getClientWindow().getId();
435             if (id != null)
436             {
437                 queryParamsMap = new HashMap<String, String>(2,1);
438                 queryParamsMap.put(ResponseStateManager.CLIENT_WINDOW_URL_PARAM, id);
439             }
440         }
441         return queryParamsMap;
442     }
443 }