javaserverfaces
  1. javaserverfaces
  2. JAVASERVERFACES-2441

URL encoding problems when using ?faces-redirect=true&includeViewParams=true

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Duplicate
    • Affects Version/s: 2.1.8
    • Fix Version/s: None
    • Component/s: None
    • Labels:
      None
    • Environment:

      linux 64bit, apache tomcat 7.0.27

      Description

      I want to use PRG (Post-Redirect-Get) Pattern in JSF, but it breaks when the user enters german umlauts in the input field.

      How to reproduce:

      1) Extract the attached file and create target/test.war using this command: mvn clean package
      2) Deploy target/test.war on tomcat 7.0.27.

      $CATALINA_HOME/conf/server.xml contains this line

      <Connector port="8080" protocol="HTTP/1.1"
      connectionTimeout="20000"
      redirectPort="8443" URIEncoding="UTF-8"
      useBodyEncodingForURI="false"/>

      3) Using chromium, navigate to http://localhost:8080/test/test.jsf, and make a form submit the word "Bücher" (german umlaut u) in the input field. Observe the URL in the browser location bar. This is what I expect (and you can get this by just clicking one of the links):

      http://localhost:8080/test/test.jsf?term=B%C3%BCcher

      This is what you get instead:

      http://localhost:8080/test/test.jsf?term=B%FCcher

      After clicking submit again, you get this:

      http://localhost:8080/test/test.jsf?term=B%3F

      so something is clearly wrong.

      I've tried some things to solve this:

      • There is <?xml version="1.0" encoding="UTF-8" ?> on top of page.
      • The h:form has the attribute acceptcharset="UTF-8" (I also tried ISO-8859-1).
      • There is a filter that does request.setCharacterEncoding("UTF-8");

      but nothing helped, and I suspect this might be a jsf bug.

      Greetings, Lars

      1. test_fixed.tgz
        4 kB
        bohl_-.
      2. test_v2.tgz
        4 kB
        bohl_-.
      3. test_v3_jetty_no_weld.tgz
        4 kB
        bohl_-.

        Activity

        Hide
        bohl_-. added a comment -

        Adding a jvm param -Dfile.encoding=utf-8 didn't help.
        Here's the output of the locale command in the console:

        LANG=en_US.UTF-8
        LANGUAGE=en_US.UTF-8
        LC_CTYPE="en_US.UTF-8"
        LC_NUMERIC="en_US.UTF-8"
        LC_TIME="en_US.UTF-8"
        LC_COLLATE="en_US.UTF-8"
        LC_MONETARY="en_US.UTF-8"
        LC_MESSAGES="en_US.UTF-8"
        LC_PAPER="en_US.UTF-8"
        LC_NAME="en_US.UTF-8"
        LC_ADDRESS="en_US.UTF-8"
        LC_TELEPHONE="en_US.UTF-8"
        LC_MEASUREMENT="en_US.UTF-8"
        LC_IDENTIFICATION="en_US.UTF-8"
        LC_ALL=en_US.UTF-8

        Show
        bohl_-. added a comment - Adding a jvm param -Dfile.encoding=utf-8 didn't help. Here's the output of the locale command in the console: LANG=en_US.UTF-8 LANGUAGE=en_US.UTF-8 LC_CTYPE="en_US.UTF-8" LC_NUMERIC="en_US.UTF-8" LC_TIME="en_US.UTF-8" LC_COLLATE="en_US.UTF-8" LC_MONETARY="en_US.UTF-8" LC_MESSAGES="en_US.UTF-8" LC_PAPER="en_US.UTF-8" LC_NAME="en_US.UTF-8" LC_ADDRESS="en_US.UTF-8" LC_TELEPHONE="en_US.UTF-8" LC_MEASUREMENT="en_US.UTF-8" LC_IDENTIFICATION="en_US.UTF-8" LC_ALL=en_US.UTF-8
        Hide
        bohl_-. added a comment - - edited

        Pasting some relevant files to make this a bit more accessible ...

        test.xhtml
        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
                "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
        <f:view locale="en" xmlns="http://www.w3.org/1999/xhtml" 
        	contentType="text/html"
        	xmlns:ui="http://java.sun.com/jsf/facelets"
        	xmlns:f="http://java.sun.com/jsf/core"
        	xmlns:h="http://java.sun.com/jsf/html">
        <html xmlns="http://www.w3.org/1999/xhtml">
        <h:head>
        <title>Foo</title>
        </h:head><h:body>
        
        	<f:metadata>
        		<f:viewParam name="categoryPath" value="#{foo['categoryPath']}"/>
        		<f:viewParam name="term" value="#{foo['term']}"/>
        		<f:event type="preRenderView" listener="#{foo['printParams']}" />
        	</f:metadata>	
        
        	<h:form acceptcharset="UTF-8">
        		<h:inputText value="#{foo['term']}"/>
        		<h:commandButton action="#{foo['prg']}" value="Post"/>
        	</h:form>
        	<p>	
        		<h:link outcome="test" value="set categoryPath=B&#xfc;cher">
        			<f:param name="categoryPath" value="B&#xfc;cher" />
        			<f:param name="term" value="#{foo['term']}" />
        		</h:link>
        	</p>
        	<p>	
        		<h:link outcome="test" value="set term=B&#xfc;cher">
        			<f:param name="categoryPath" value="#{foo['categoryPath']}" />
        			<f:param name="term" value="B&#xfc;cher" />
        		</h:link>
        	</p>
        	<p>
        		<h:outputText value="CategoryPath: #{foo['categoryPath']}" />
        	</p>
        	<p>
        		<h:outputText value="term: #{foo['term']}" />
        	</p>
        
        </h:body></html></f:view>
        
        Foo.java
        package foo;
        
        import java.util.Map;
        
        import javax.enterprise.context.RequestScoped;
        import javax.faces.context.FacesContext;
        import javax.inject.Named;
        
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        
        @Named
        @RequestScoped
        public class Foo {
        	
        	private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
        	
        	private String term;
        	private String categoryPath;
        	
        	@Override
        	public String toString() {
        		return "Foo [term=" + term + ", categoryPath=" + categoryPath + "]";
        	}
        	
        	public void printParams() {
        		if (FacesContext.getCurrentInstance().isPostback()) {
        			LOG.error("POST not allowed here");
        		} else {
        			Map<String, String> params = RequestUtil.getParams();
        			if (LOG.isDebugEnabled()) {
        				LOG.debug("Found params: "+params.toString());
        			}
        		}
        	}
        
        	/**
        	 * performs post-redirect-get
        	 */
        	public String prg() {
        		if (FacesContext.getCurrentInstance().isPostback()) {
        			final String result 
        				= String.format("%s?faces-redirect=true"
        				+ "&amp;includeViewParams=true", 
        				FacesContext.getCurrentInstance().getViewRoot().getViewId());
        			if (LOG.isDebugEnabled()) {
        				LOG.debug("Redirect to " + result);
        			}
        			return result;
        		} else {
        			LOG.error("GET not allowed here");
        			return null;
        		}
        	}
        
        	public String getTerm() {
        		return term;
        	}
        
        	public void setTerm(String term) {
        		this.term = term;
        	}
        
        	public String getCategoryPath() {
        		return categoryPath;
        	}
        
        	public void setCategoryPath(String categoryPath) {
        		this.categoryPath = categoryPath;
        	}
        
        }
        
        Show
        bohl_-. added a comment - - edited Pasting some relevant files to make this a bit more accessible ... test.xhtml <?xml version= "1.0" encoding= "UTF-8" ?> <!DOCTYPE html PUBLIC "- //W3C//DTD XHTML 1.0 Transitional//EN" "http: //www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" > <f:view locale= "en" xmlns= "http: //www.w3.org/1999/xhtml" contentType= "text/html" xmlns:ui= "http: //java.sun.com/jsf/facelets" xmlns:f= "http: //java.sun.com/jsf/core" xmlns:h= "http: //java.sun.com/jsf/html" > <html xmlns= "http: //www.w3.org/1999/xhtml" > <h:head> <title>Foo</title> </h:head><h:body> <f:metadata> <f:viewParam name= "categoryPath" value= "#{foo['categoryPath']}" /> <f:viewParam name= "term" value= "#{foo['term']}" /> <f:event type= "preRenderView" listener= "#{foo['printParams']}" /> </f:metadata> <h:form acceptcharset= "UTF-8" > <h:inputText value= "#{foo['term']}" /> <h:commandButton action= "#{foo['prg']}" value= "Post" /> </h:form> <p> <h:link outcome= "test" value= "set categoryPath=B&#xfc;cher" > <f:param name= "categoryPath" value= "B&#xfc;cher" /> <f:param name= "term" value= "#{foo['term']}" /> </h:link> </p> <p> <h:link outcome= "test" value= "set term=B&#xfc;cher" > <f:param name= "categoryPath" value= "#{foo['categoryPath']}" /> <f:param name= "term" value= "B&#xfc;cher" /> </h:link> </p> <p> <h:outputText value= "CategoryPath: #{foo['categoryPath']}" /> </p> <p> <h:outputText value= "term: #{foo['term']}" /> </p> </h:body></html></f:view> Foo.java package foo; import java.util.Map; import javax.enterprise.context.RequestScoped; import javax.faces.context.FacesContext; import javax.inject.Named; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Named @RequestScoped public class Foo { private static final Logger LOG = LoggerFactory.getLogger(Foo.class); private String term; private String categoryPath; @Override public String toString() { return "Foo [term=" + term + ", categoryPath=" + categoryPath + "]" ; } public void printParams() { if (FacesContext.getCurrentInstance().isPostback()) { LOG.error( "POST not allowed here" ); } else { Map< String , String > params = RequestUtil.getParams(); if (LOG.isDebugEnabled()) { LOG.debug( "Found params: " +params.toString()); } } } /** * performs post-redirect-get */ public String prg() { if (FacesContext.getCurrentInstance().isPostback()) { final String result = String .format( "%s?faces-redirect= true " + "&amp;includeViewParams= true " , FacesContext.getCurrentInstance().getViewRoot().getViewId()); if (LOG.isDebugEnabled()) { LOG.debug( "Redirect to " + result); } return result; } else { LOG.error( "GET not allowed here" ); return null ; } } public String getTerm() { return term; } public void setTerm( String term) { this .term = term; } public String getCategoryPath() { return categoryPath; } public void setCategoryPath( String categoryPath) { this .categoryPath = categoryPath; } }
        Hide
        bohl_-. added a comment -

        Did some research, see updated demo project in test_v2.tgz. test.xhtml now contains 2 forms, one <h:form>, and one plain html <form>. The <form> demonstrates that PRG to unicode URLs is possible with the servlet API, although it requires encoding the redirect URL manually in a servlet (see PRGServlet.java). Unfortunately I can't use this manual URLencoding with the JSF form, because I don't know to do control the response in this way with JSF.

        PRGServlet.java; mapped to /prg.do via web.xml
        public class PRGServlet extends HttpServlet {
        
        	private static final long serialVersionUID = 1L;
        	private static final Logger LOG = LoggerFactory.getLogger(PRGServlet.class);
        	
        	public void doPost(HttpServletRequest request, HttpServletResponse response) {
        		try {
        			final Map<String, String> params = RequestUtil.getParams(request);
        			StringBuilder sb = new StringBuilder();
        			sb.append(request.getContextPath());
        			sb.append("/test.jsf");
        			final String result;
        			if (!params.isEmpty()) {
        				sb.append("?");
        				for (Entry<String, String> e: params.entrySet()) {
        					sb.append(e.getKey());
        					sb.append("=");
        					sb.append(e.getValue());
        					sb.append("&");
        				}
        				result = sb.substring(0, sb.length() - 1);
        			} else {
        				result = sb.toString();
        			}
        			/* (sic) */
        			final String cleanResponse = result.replace("\u00fc", "%C3%BC");
        			if (LOG.isDebugEnabled()) {
        				LOG.debug("Redirecting to " + cleanResponse);
        			}
        			response.sendRedirect(cleanResponse);
        		} catch (IOException e) {
        			LOG.error("IO Error", e);
        		}
        	}
        
        }
        
        Fragment from test.xhtml
        <h2>Non-JSF Form</h2>
        <form action="prg.do" method="post" accept-charset="UTF-8" enctype="application/x-www-form-urlencoded">
        	<input type="text" name="term" value="#{foo['term']}"/>
        	<input type="hidden" name="categoryPath" value="#{foo['categoryPath']}"/>
        </form>
        
        Show
        bohl_-. added a comment - Did some research, see updated demo project in test_v2.tgz. test.xhtml now contains 2 forms, one <h:form>, and one plain html <form>. The <form> demonstrates that PRG to unicode URLs is possible with the servlet API, although it requires encoding the redirect URL manually in a servlet (see PRGServlet.java). Unfortunately I can't use this manual URLencoding with the JSF form, because I don't know to do control the response in this way with JSF. PRGServlet.java; mapped to /prg.do via web.xml public class PRGServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger LOG = LoggerFactory.getLogger(PRGServlet.class); public void doPost(HttpServletRequest request, HttpServletResponse response) { try { final Map< String , String > params = RequestUtil.getParams(request); StringBuilder sb = new StringBuilder(); sb.append(request.getContextPath()); sb.append( "/test.jsf" ); final String result; if (!params.isEmpty()) { sb.append( "?" ); for (Entry< String , String > e: params.entrySet()) { sb.append(e.getKey()); sb.append( "=" ); sb.append(e.getValue()); sb.append( "&" ); } result = sb.substring(0, sb.length() - 1); } else { result = sb.toString(); } /* (sic) */ final String cleanResponse = result.replace( "\u00fc" , "%C3%BC" ); if (LOG.isDebugEnabled()) { LOG.debug( "Redirecting to " + cleanResponse); } response.sendRedirect(cleanResponse); } catch (IOException e) { LOG.error( "IO Error" , e); } } } Fragment from test.xhtml <h2>Non-JSF Form</h2> <form action= "prg. do " method= "post" accept-charset= "UTF-8" enctype= "application/x-www-form-urlencoded" > <input type= "text" name= "term" value= "#{foo['term']}" /> <input type= "hidden" name= "categoryPath" value= "#{foo['categoryPath']}" /> </form>
        Hide
        bohl_-. added a comment -

        Forgot the submit button in the plain html form above, please submit it by hitting return while the focus is on the text input field.

        This is related:
        http://stackoverflow.com/questions/846409/configuring-tomcat-5-5-to-utf-8-encode-all-sendredirect-redirections

        so maybe this is a weakness in the servlet api rather than in jsf, but there is a workaround (manually encoding the redirect url), and it looks like this workaround is incompatible with ?faces-redirect=true&includeViewParams=true

        Show
        bohl_-. added a comment - Forgot the submit button in the plain html form above, please submit it by hitting return while the focus is on the text input field. This is related: http://stackoverflow.com/questions/846409/configuring-tomcat-5-5-to-utf-8-encode-all-sendredirect-redirections so maybe this is a weakness in the servlet api rather than in jsf, but there is a workaround (manually encoding the redirect url), and it looks like this workaround is incompatible with ?faces-redirect=true&includeViewParams=true
        Hide
        bohl_-. added a comment -

        Uploading a new version that doesn't use Weld and is preconfigured to run with mvn jetty:run-war.

        Show
        bohl_-. added a comment - Uploading a new version that doesn't use Weld and is preconfigured to run with mvn jetty:run-war.
        Hide
        Ed Burns added a comment -

        I just fixed JAVASERVERFACES-2440 last week, and it seems like this one might have been fixed by that also. Can you please try it using a nightly Mojarra 2.2.0 Snapshot release from maven.java.net. The GAV is: org.glassfish:javax.faces:2.2.0-SNAPSHOT

        Show
        Ed Burns added a comment - I just fixed JAVASERVERFACES-2440 last week, and it seems like this one might have been fixed by that also. Can you please try it using a nightly Mojarra 2.2.0 Snapshot release from maven.java.net. The GAV is: org.glassfish:javax.faces:2.2.0-SNAPSHOT
        Hide
        bohl_-. added a comment -

        The problem was gone after this modification in pom.xml:

        <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>javax.faces</artifactId>
        <version>2.2.0-SNAPSHOT</version>
        </dependency>
        <!--
        <dependency>
        <groupId>com.sun.faces</groupId>
        <artifactId>jsf-api</artifactId>
        <version>2.1.8</version>
        </dependency>
        <dependency>
        <groupId>com.sun.faces</groupId>
        <artifactId>jsf-impl</artifactId>
        <version>2.1.8</version>
        <scope>runtime</scope>
        </dependency>
        -->

        Show
        bohl_-. added a comment - The problem was gone after this modification in pom.xml: <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.faces</artifactId> <version>2.2.0-SNAPSHOT</version> </dependency> <!-- <dependency> <groupId>com.sun.faces</groupId> <artifactId>jsf-api</artifactId> <version>2.1.8</version> </dependency> <dependency> <groupId>com.sun.faces</groupId> <artifactId>jsf-impl</artifactId> <version>2.1.8</version> <scope>runtime</scope> </dependency> -->
        Hide
        Ed Burns added a comment -

        Are you ok waiting for 2.2 to come out? Do you need me to backport this to 2.1? I'd rather not, but if you really I need it, I can.

        Show
        Ed Burns added a comment - Are you ok waiting for 2.2 to come out? Do you need me to backport this to 2.1? I'd rather not, but if you really I need it, I can.
        Hide
        bohl_-. added a comment -

        Oh wow, no thanks no backport needed. In our case, a servlet does the job as well.

        Show
        bohl_-. added a comment - Oh wow, no thanks no backport needed. In our case, a servlet does the job as well.

          People

          • Assignee:
            Ed Burns
            Reporter:
            bohl_-.
          • Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: