From aedf85725413c0e58c893b3ae187092898f11910 Mon Sep 17 00:00:00 2001 From: Bauke Scholtz Date: Sat, 20 Jan 2024 12:40:44 -0400 Subject: [PATCH 1/4] Remove hardcoded string constant representing "UTF-8" where possible --- .../main/java/com/sun/faces/RIConstants.java | 4 ++- .../ConverterPropertyEditorFactory.java | 14 ++------ .../faces/context/ExternalContextImpl.java | 32 +++---------------- .../faces/context/PartialViewContextImpl.java | 11 ++----- .../com/sun/faces/context/flash/ELFlash.java | 22 +++---------- .../sun/faces/facelets/util/Classpath.java | 13 +++----- .../sun/faces/lifecycle/RestoreViewPhase.java | 13 ++------ .../com/sun/faces/renderkit/StateHelper.java | 12 ++----- .../html_basic/OutputLinkRenderer.java | 9 +++--- .../faces/context/PartialResponseWriter.java | 5 ++- 10 files changed, 35 insertions(+), 100 deletions(-) diff --git a/impl/src/main/java/com/sun/faces/RIConstants.java b/impl/src/main/java/com/sun/faces/RIConstants.java index 62b400c6b4..cffc4e0ec5 100644 --- a/impl/src/main/java/com/sun/faces/RIConstants.java +++ b/impl/src/main/java/com/sun/faces/RIConstants.java @@ -17,6 +17,8 @@ package com.sun.faces; +import java.nio.charset.StandardCharsets; + import com.sun.faces.config.manager.FacesSchema; import jakarta.faces.render.RenderKitFactory; @@ -58,7 +60,7 @@ public class RIConstants { public static final String APPLICATION_XML_CONTENT_TYPE = "application/xml"; public static final String TEXT_XML_CONTENT_TYPE = "text/xml"; public static final String ALL_MEDIA = "*/*"; - public static final String CHAR_ENCODING = "UTF-8"; + public static final String CHAR_ENCODING = StandardCharsets.UTF_8.name(); public static final String FACELETS_ENCODING_KEY = "facelets.Encoding"; public static final String DEFAULT_LIFECYCLE = FACES_PREFIX + "DefaultLifecycle"; public static final String DEFAULT_STATEMANAGER = FACES_PREFIX + "DefaultStateManager"; diff --git a/impl/src/main/java/com/sun/faces/application/ConverterPropertyEditorFactory.java b/impl/src/main/java/com/sun/faces/application/ConverterPropertyEditorFactory.java index 660e566abb..9f9d08b41d 100644 --- a/impl/src/main/java/com/sun/faces/application/ConverterPropertyEditorFactory.java +++ b/impl/src/main/java/com/sun/faces/application/ConverterPropertyEditorFactory.java @@ -16,6 +16,7 @@ package com.sun.faces.application; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINEST; import static java.util.logging.Level.WARNING; @@ -23,7 +24,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.security.AccessController; import java.security.PrivilegedAction; @@ -80,7 +80,6 @@ private static class Utf8InfoRef { int length; public Utf8InfoRef(int index, int length) { - super(); this.index = index; this.length = length; } @@ -102,7 +101,6 @@ private static class Utf8InfoReplacement implements Comparable c) { * @return the bytes for the UTF8Info constant pool entry, including the tag, length, and utf8 content. */ private static byte[] getUtf8InfoBytes(String text) { - byte[] utf8; - try { - utf8 = text.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - // The DM_DEFAULT_ENCODING warning is acceptable here - // because we explicitly *want* to use the Java runtime's - // default encoding. - utf8 = text.getBytes(); - } + byte[] utf8 = text.getBytes(UTF_8); byte[] info = new byte[utf8.length + 3]; info[0] = 1; info[1] = (byte) (utf8.length >> 8 & 0xff); diff --git a/impl/src/main/java/com/sun/faces/context/ExternalContextImpl.java b/impl/src/main/java/com/sun/faces/context/ExternalContextImpl.java index 13e505874b..935754bf91 100644 --- a/impl/src/main/java/com/sun/faces/context/ExternalContextImpl.java +++ b/impl/src/main/java/com/sun/faces/context/ExternalContextImpl.java @@ -654,9 +654,8 @@ public void redirect(String requestURI) throws IOException { pwriter = ctx.getPartialViewContext().getPartialResponseWriter(); } setResponseContentType(RIConstants.TEXT_XML_CONTENT_TYPE); - setResponseCharacterEncoding("UTF-8"); + setResponseCharacterEncoding(RIConstants.CHAR_ENCODING); addResponseHeader("Cache-Control", "no-cache"); -// pwriter.writePreamble("\n"); pwriter.startDocument(); pwriter.redirect(requestURI); pwriter.endDocument(); @@ -974,14 +973,7 @@ public boolean isSecure() { @Override public String encodeBookmarkableURL(String baseUrl, Map> parameters) { - FacesContext context = FacesContext.getCurrentInstance(); - String encodingFromContext = (String) context.getAttributes().get(RIConstants.FACELETS_ENCODING_KEY); - if (null == encodingFromContext) { - encodingFromContext = (String) context.getViewRoot().getAttributes().get(RIConstants.FACELETS_ENCODING_KEY); - } - - String currentResponseEncoding = null != encodingFromContext ? encodingFromContext : getResponseCharacterEncoding(); - + String currentResponseEncoding = Util.getResponseEncoding(FacesContext.getCurrentInstance()); UrlBuilder builder = new UrlBuilder(baseUrl, currentResponseEncoding); builder.addParameters(parameters); return builder.createUrl(); @@ -990,14 +982,7 @@ public String encodeBookmarkableURL(String baseUrl, Map> pa @Override public String encodeRedirectURL(String baseUrl, Map> parameters) { - FacesContext context = FacesContext.getCurrentInstance(); - String encodingFromContext = (String) context.getAttributes().get(RIConstants.FACELETS_ENCODING_KEY); - if (null == encodingFromContext) { - encodingFromContext = (String) context.getViewRoot().getAttributes().get(RIConstants.FACELETS_ENCODING_KEY); - } - - String currentResponseEncoding = null != encodingFromContext ? encodingFromContext : getResponseCharacterEncoding(); - + String currentResponseEncoding = Util.getResponseEncoding(FacesContext.getCurrentInstance()); UrlBuilder builder = new UrlBuilder(baseUrl, currentResponseEncoding); builder.addParameters(parameters); return builder.createUrl(); @@ -1013,14 +998,7 @@ public String encodePartialActionURL(String url) { String message = MessageUtils.getExceptionMessageString(MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "url"); throw new NullPointerException(message); } - FacesContext context = FacesContext.getCurrentInstance(); - String encodingFromContext = (String) context.getAttributes().get(RIConstants.FACELETS_ENCODING_KEY); - if (null == encodingFromContext) { - encodingFromContext = (String) context.getViewRoot().getAttributes().get(RIConstants.FACELETS_ENCODING_KEY); - } - - String currentResponseEncoding = null != encodingFromContext ? encodingFromContext : getResponseCharacterEncoding(); - + String currentResponseEncoding = Util.getResponseEncoding(FacesContext.getCurrentInstance()); UrlBuilder builder = new UrlBuilder(url, currentResponseEncoding); return ((HttpServletResponse) response).encodeURL(builder.createUrl()); } @@ -1078,7 +1056,7 @@ private void pushIfPossibleAndNecessary(String result) { private PushBuilder getPushBuilder(FacesContext context) { ExternalContext extContext = context.getExternalContext(); - + if (extContext.getRequest() instanceof HttpServletRequest) { HttpServletRequest hreq = (HttpServletRequest) extContext.getRequest(); diff --git a/impl/src/main/java/com/sun/faces/context/PartialViewContextImpl.java b/impl/src/main/java/com/sun/faces/context/PartialViewContextImpl.java index f9b379ff53..bbf59f37c8 100644 --- a/impl/src/main/java/com/sun/faces/context/PartialViewContextImpl.java +++ b/impl/src/main/java/com/sun/faces/context/PartialViewContextImpl.java @@ -19,9 +19,9 @@ import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.PARTIAL_EXECUTE_PARAM; import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.PARTIAL_RENDER_PARAM; import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.PARTIAL_RESET_VALUES_PARAM; +import static jakarta.faces.FactoryFinder.VISIT_CONTEXT_FACTORY; import static java.util.logging.Level.FINE; import static java.util.logging.Level.WARNING; -import static jakarta.faces.FactoryFinder.VISIT_CONTEXT_FACTORY; import java.io.IOException; import java.io.Writer; @@ -281,11 +281,6 @@ public void processPartial(PhaseId phaseId) { exContext.setResponseContentType(RIConstants.TEXT_XML_CONTENT_TYPE); exContext.addResponseHeader("Cache-Control", "no-cache"); -// String encoding = writer.getCharacterEncoding( ); -// if( encoding == null ) { -// encoding = "UTF-8"; -// } -// writer.writePreamble("\n"); writer.startDocument(); if (isResetValues()) { @@ -322,7 +317,7 @@ public void processPartial(PhaseId phaseId) { } } } - + private void doFlashPostPhaseActions(FacesContext ctx) { try { ctx.getExternalContext().getFlash().doPostPhaseActions(ctx); @@ -630,7 +625,7 @@ public DelayedInitPartialResponseWriter(PartialViewContextImpl ctx) { super(null); this.ctx = ctx; ExternalContext extCtx = ctx.ctx.getExternalContext(); - + if (extCtx.isResponseCommitted()) { LOGGER.log(WARNING, "Response is already committed - cannot reconfigure it anymore"); } diff --git a/impl/src/main/java/com/sun/faces/context/flash/ELFlash.java b/impl/src/main/java/com/sun/faces/context/flash/ELFlash.java index 85a728229f..c653e4627d 100644 --- a/impl/src/main/java/com/sun/faces/context/flash/ELFlash.java +++ b/impl/src/main/java/com/sun/faces/context/flash/ELFlash.java @@ -18,8 +18,8 @@ import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.EnableDistributable; import static com.sun.faces.config.WebConfiguration.BooleanWebContextInitParameter.ForceAlwaysWriteFlashCookie; +import static java.nio.charset.StandardCharsets.UTF_8; -import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.security.InvalidKeyException; @@ -640,7 +640,7 @@ public void doPostPhaseActions(FacesContext context) { * necessary because the call to extContext.flushBuffer() is too late, the response has already been committed by that * point. outgoingResponseIsRedirect is false. *

- * + * * @param context the involved faces context * @param outgoingResponseIsRedirect whether outgoing response is redirect */ @@ -1313,15 +1313,7 @@ void expireNext_MovePreviousToNext() { void decode(FacesContext context, ELFlash flash, Cookie cookie) throws InvalidKeyException { String temp; String value; - - String urlDecodedValue = null; - - try { - urlDecodedValue = URLDecoder.decode(cookie.getValue(), "UTF-8"); - } catch (UnsupportedEncodingException uee) { - urlDecodedValue = cookie.getValue(); - } - + String urlDecodedValue = URLDecoder.decode(cookie.getValue(), UTF_8); value = guard.decrypt(urlDecodedValue); try { @@ -1390,16 +1382,10 @@ void decode(FacesContext context, ELFlash flash, Cookie cookie) throws InvalidKe *

*/ Cookie encode() { - Cookie result = null; - String value = (null != previousRequestFlashInfo ? previousRequestFlashInfo.encode() : "") + "_" + (null != nextRequestFlashInfo ? nextRequestFlashInfo.encode() : ""); String encryptedValue = guard.encrypt(value); - try { - result = new Cookie(FLASH_COOKIE_NAME, URLEncoder.encode(encryptedValue, "UTF-8")); - } catch (UnsupportedEncodingException uee) { - result = new Cookie(FLASH_COOKIE_NAME, encryptedValue); - } + Cookie result = new Cookie(FLASH_COOKIE_NAME, URLEncoder.encode(encryptedValue, UTF_8)); if (1 == value.length()) { result.setMaxAge(0); diff --git a/impl/src/main/java/com/sun/faces/facelets/util/Classpath.java b/impl/src/main/java/com/sun/faces/facelets/util/Classpath.java index ac2bbce3b3..829cf0c20f 100644 --- a/impl/src/main/java/com/sun/faces/facelets/util/Classpath.java +++ b/impl/src/main/java/com/sun/faces/facelets/util/Classpath.java @@ -16,6 +16,8 @@ package com.sun.faces.facelets.util; +import static java.nio.charset.StandardCharsets.UTF_8; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -45,13 +47,6 @@ public final class Classpath { private static final String[] PREFIXES_TO_EXCLUDE = { "rar:", "sar:" }; private static final String[] EXTENSIONS_TO_EXCLUDE = { ".rar", ".sar" }; - /** - * - */ - public Classpath() { - super(); - } - public enum SearchAdvice { FirstMatchOnly, AllMatches } @@ -93,7 +88,7 @@ public static URL[] search(ClassLoader cl, String prefix, String suffix, SearchA if (jarFile != null) { searchJar(cl, all, jarFile, prefix, suffix, advice); } else { - boolean searchDone = searchDir(all, new File(URLDecoder.decode(url.getFile(), "UTF-8")), suffix); + boolean searchDone = searchDir(all, new File(URLDecoder.decode(url.getFile(), UTF_8)), suffix); if (!searchDone) { searchFromURL(all, prefix, suffix, url); } @@ -244,7 +239,7 @@ static JarFile getAlternativeJarFile(String urlFile) throws IOException { // And trim off any "file:" prefix. if (jarFileUrl.startsWith("file:")) { jarFileUrl = jarFileUrl.substring("file:".length()); - jarFileUrl = URLDecoder.decode(jarFileUrl, "UTF-8"); + jarFileUrl = URLDecoder.decode(jarFileUrl, UTF_8); } boolean foundExclusion = false; for (int i = 0; i < PREFIXES_TO_EXCLUDE.length; i++) { diff --git a/impl/src/main/java/com/sun/faces/lifecycle/RestoreViewPhase.java b/impl/src/main/java/com/sun/faces/lifecycle/RestoreViewPhase.java index 2e335c0190..74af50b913 100644 --- a/impl/src/main/java/com/sun/faces/lifecycle/RestoreViewPhase.java +++ b/impl/src/main/java/com/sun/faces/lifecycle/RestoreViewPhase.java @@ -29,10 +29,10 @@ import static jakarta.faces.event.PhaseId.RESTORE_VIEW; import static jakarta.faces.render.ResponseStateManager.NON_POSTBACK_VIEW_TOKEN_PARAM; import static jakarta.faces.view.ViewMetadata.hasMetadata; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.logging.Level.FINE; import static java.util.logging.Level.SEVERE; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; @@ -249,16 +249,7 @@ private void maybeTakeProtectedViewAction(FacesContext context, ViewHandler view String incomingSecretKeyValue = extContext.getRequestParameterMap().get(NON_POSTBACK_VIEW_TOKEN_PARAM); if (incomingSecretKeyValue != null) { - try { - incomingSecretKeyValue = URLEncoder.encode(incomingSecretKeyValue, "UTF-8"); - } catch (UnsupportedEncodingException e) { - if (LOGGER.isLoggable(SEVERE)) { - LOGGER.log(SEVERE, - "Unable to re-encode value of request parameter " + NON_POSTBACK_VIEW_TOKEN_PARAM + ":" - + incomingSecretKeyValue, e); - } - incomingSecretKeyValue = null; - } + incomingSecretKeyValue = URLEncoder.encode(incomingSecretKeyValue, UTF_8); } String correctSecretKeyValue = rsm.getCryptographicallyStrongTokenFromSession(context); diff --git a/impl/src/main/java/com/sun/faces/renderkit/StateHelper.java b/impl/src/main/java/com/sun/faces/renderkit/StateHelper.java index 6756c4028c..43380f0db1 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/StateHelper.java +++ b/impl/src/main/java/com/sun/faces/renderkit/StateHelper.java @@ -21,11 +21,10 @@ import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.CLIENT_WINDOW_PARAM; import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.RENDER_KIT_ID_PARAM; import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.VIEW_STATE_PARAM; +import static java.nio.charset.StandardCharsets.UTF_8; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.util.logging.Level; import java.util.logging.Logger; import com.sun.faces.RIConstants; @@ -113,12 +112,7 @@ public static void createAndStoreCryptographicallyStrongTokenInSession(HttpSessi ByteArrayGuardAESCTR guard = new ByteArrayGuardAESCTR(); String clearText = "" + System.currentTimeMillis(); String result = guard.encrypt(clearText); - try { - result = URLEncoder.encode(result, "UTF-8"); - } catch (UnsupportedEncodingException e) { - LOGGER.log(Level.SEVERE, "Unable to URL encode cryptographically strong token, storing clear text in session instead.", e); - result = clearText; - } + result = URLEncoder.encode(result, UTF_8); session.setAttribute(TOKEN_NAME, result); } @@ -173,7 +167,7 @@ protected static String getStateParamValue(FacesContext context) { if (pValue != null && pValue.length() == 0) { pValue = null; } - + return pValue; } diff --git a/impl/src/main/java/com/sun/faces/renderkit/html_basic/OutputLinkRenderer.java b/impl/src/main/java/com/sun/faces/renderkit/html_basic/OutputLinkRenderer.java index 2bb779516d..750b7668e4 100644 --- a/impl/src/main/java/com/sun/faces/renderkit/html_basic/OutputLinkRenderer.java +++ b/impl/src/main/java/com/sun/faces/renderkit/html_basic/OutputLinkRenderer.java @@ -19,6 +19,7 @@ package com.sun.faces.renderkit.html_basic; import static com.sun.faces.util.Util.componentIsDisabled; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.logging.Level.FINE; import java.io.IOException; @@ -35,7 +36,7 @@ /** * OutputLinkRenderer is a class ... - * + * * Lifetime And Scope * */ @@ -133,7 +134,7 @@ protected Object getValue(UIComponent component) { if (componentIsDisabled(component)) { return null; } - + return ((UIOutput) component).getValue(); } @@ -176,10 +177,10 @@ protected void renderAsActive(FacesContext context, UIComponent component) throw if (pn != null && pn.length() != 0) { String pv = paramList[i].value; sb.append(paramWritten ? '&' : '?'); - sb.append(URLEncoder.encode(pn, "UTF-8")); + sb.append(URLEncoder.encode(pn, UTF_8)); sb.append('='); if (pv != null && pv.length() != 0) { - sb.append(URLEncoder.encode(pv, "UTF-8")); + sb.append(URLEncoder.encode(pv, UTF_8)); } paramWritten = true; } diff --git a/impl/src/main/java/jakarta/faces/context/PartialResponseWriter.java b/impl/src/main/java/jakarta/faces/context/PartialResponseWriter.java index aa4b4cf893..49a6427519 100644 --- a/impl/src/main/java/jakarta/faces/context/PartialResponseWriter.java +++ b/impl/src/main/java/jakarta/faces/context/PartialResponseWriter.java @@ -17,8 +17,11 @@ package jakarta.faces.context; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Map; +import com.sun.faces.RIConstants; + import jakarta.faces.component.NamingContainer; import jakarta.faces.component.UIViewRoot; import jakarta.faces.render.ResponseStateManager; @@ -97,7 +100,7 @@ public void startDocument() throws IOException { ResponseWriter writer = getWrapped(); String encoding = writer.getCharacterEncoding(); if (encoding == null) { - encoding = "utf-8"; + encoding = RIConstants.CHAR_ENCODING; } writer.writePreamble("\n"); writer.startElement("partial-response", null); From 84f890583e94b9b6780417313fb134b7ada13854 Mon Sep 17 00:00:00 2001 From: Bauke Scholtz Date: Sat, 20 Jan 2024 15:40:45 -0400 Subject: [PATCH 2/4] Review/cleanup Facelets/Request/Response encoding implementation https://github.com/eclipse-ee4j/mojarra/issues/5383 --- .../view/FaceletViewHandlingStrategy.java | 71 +++-------------- .../application/view/MultiViewHandler.java | 22 +----- .../com/sun/faces/context/UrlBuilder.java | 5 +- .../main/java/com/sun/faces/util/Util.java | 77 ++++++++++++++++++- .../faces/application/ViewHandler.java | 13 +++- 5 files changed, 100 insertions(+), 88 deletions(-) diff --git a/impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java b/impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java index 4075ffb474..444082f30b 100644 --- a/impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java +++ b/impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java @@ -43,7 +43,6 @@ import static jakarta.faces.application.Resource.COMPONENT_RESOURCE_KEY; import static jakarta.faces.application.StateManager.IS_BUILDING_INITIAL_STATE; import static jakarta.faces.application.StateManager.STATE_SAVING_METHOD_SERVER; -import static jakarta.faces.application.ViewHandler.CHARACTER_ENCODING_KEY; import static jakarta.faces.application.ViewHandler.DEFAULT_FACELETS_SUFFIX; import static jakarta.faces.application.ViewVisitOption.RETURN_AS_MINIMAL_IMPLICIT_OUTCOME; import static jakarta.faces.component.UIComponent.BEANINFO_KEY; @@ -72,6 +71,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; @@ -904,26 +904,29 @@ protected ResponseWriter createResponseWriter(FacesContext context) throws IOExc // get our content type String contentType = (String) context.getAttributes().get("facelets.ContentType"); - // get the encoding + // get the last calculated encoding String encoding = (String) context.getAttributes().get(FACELETS_ENCODING_KEY); // Create a dummy ResponseWriter with a bogus writer, - // so we can figure out what content type the ReponseWriter + // so we can figure out what content type and encoding the ReponseWriter // is really going to ask for - ResponseWriter writer = renderKit.createResponseWriter(NullWriter.INSTANCE, contentType, encoding); + ResponseWriter initWriter = renderKit.createResponseWriter(NullWriter.INSTANCE, contentType, encoding); - contentType = getResponseContentType(context, writer.getContentType()); - encoding = getResponseEncoding(context, writer.getCharacterEncoding()); + contentType = getResponseContentType(context, initWriter.getContentType()); + encoding = Util.getResponseEncoding(context, Optional.ofNullable(initWriter.getCharacterEncoding())); // apply them to the response char[] buffer = new char[1028]; - HtmlUtils.writeTextForXML(writer, contentType, buffer); + HtmlUtils.writeTextForXML(initWriter, contentType, buffer); String str = String.valueOf(buffer).trim(); extContext.setResponseContentType(str); extContext.setResponseCharacterEncoding(encoding); + // Save encoding in UIViewRoot for faster consult when Util#getResponseEncoding() is invoked again elsewhere. + context.getViewRoot().getAttributes().put(FACELETS_ENCODING_KEY, encoding); + // Now, clone with the real writer - writer = writer.cloneWithWriter(extContext.getResponseOutputWriter()); + ResponseWriter writer = initWriter.cloneWithWriter(extContext.getResponseOutputWriter()); return writer; } @@ -974,58 +977,6 @@ protected void handleFaceletNotFound(FacesContext context, String viewId, String context.responseComplete(); } - /** - * @param context the {@link FacesContext} for the current request - * @param orig the original encoding - * @return the encoding to be used for this response - */ - protected String getResponseEncoding(FacesContext context, String orig) { - String encoding = orig; - - // 1. get it from request - encoding = context.getExternalContext().getRequestCharacterEncoding(); - - // 2. get it from the session - if (encoding == null) { - if (context.getExternalContext().getSession(false) != null) { - Map sessionMap = context.getExternalContext().getSessionMap(); - encoding = (String) sessionMap.get(CHARACTER_ENCODING_KEY); - if (LOGGER.isLoggable(FINEST)) { - LOGGER.log(FINEST, "Session specified alternate encoding {0}", encoding); - } - } - } - - // see if we need to override the encoding - Map ctxAttributes = context.getAttributes(); - - // 3. check the request attribute - if (ctxAttributes.containsKey(FACELETS_ENCODING_KEY)) { - encoding = (String) ctxAttributes.get(FACELETS_ENCODING_KEY); - if (LOGGER.isLoggable(FINEST)) { - LOGGER.log(FINEST, "Facelet specified alternate encoding {0}", encoding); - } - if (null != context.getExternalContext().getSession(false)) { - Map sessionMap = context.getExternalContext().getSessionMap(); - sessionMap.put(CHARACTER_ENCODING_KEY, encoding); - } - } - - // 4. default it - if (encoding == null) { - if (null != orig && 0 < orig.length()) { - encoding = orig; - } else { - encoding = "UTF-8"; - } - if (LOGGER.isLoggable(FINEST)) { - LOGGER.log(FINEST, "ResponseWriter created had a null CharacterEncoding, defaulting to {0}", orig); - } - } - - return encoding; - } - /** * @param context the {@link FacesContext} for the current request * @param orig the original contentType diff --git a/impl/src/main/java/com/sun/faces/application/view/MultiViewHandler.java b/impl/src/main/java/com/sun/faces/application/view/MultiViewHandler.java index bb131daa0f..89f34122ca 100644 --- a/impl/src/main/java/com/sun/faces/application/view/MultiViewHandler.java +++ b/impl/src/main/java/com/sun/faces/application/view/MultiViewHandler.java @@ -17,7 +17,6 @@ package com.sun.faces.application.view; -import static com.sun.faces.RIConstants.FACELETS_ENCODING_KEY; import static com.sun.faces.RIConstants.SAVESTATE_FIELD_MARKER; import static com.sun.faces.renderkit.RenderKitUtils.getResponseStateManager; import static com.sun.faces.renderkit.RenderKitUtils.PredefinedPostbackParameter.RENDER_KIT_ID_PARAM; @@ -35,10 +34,8 @@ import static jakarta.servlet.http.MappingMatch.EXACT; import static jakarta.servlet.http.MappingMatch.EXTENSION; import static jakarta.servlet.http.MappingMatch.PATH; -import static java.text.MessageFormat.format; import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; -import static java.util.logging.Level.FINE; import static java.util.logging.Level.SEVERE; import static java.util.logging.Level.WARNING; @@ -350,24 +347,7 @@ public boolean removeProtectedView(String urlPattern) { */ @Override public String getRedirectURL(FacesContext context, String viewId, Map> parameters, boolean includeViewParams) { - String encodingFromContext = (String) context.getAttributes().get(FACELETS_ENCODING_KEY); - if (encodingFromContext == null) { - encodingFromContext = (String) context.getViewRoot().getAttributes().get(FACELETS_ENCODING_KEY); - } - - String responseEncoding; - - if (encodingFromContext == null) { - try { - responseEncoding = context.getExternalContext().getResponseCharacterEncoding(); - } catch (Exception e) { - LOGGER.log(FINE, e, () -> - format("Unable to obtain response character encoding from ExternalContext {0}. Using UTF-8.", context.getExternalContext())); - responseEncoding = "UTF-8"; - } - } else { - responseEncoding = encodingFromContext; - } + String responseEncoding = Util.getResponseEncoding(context); if (parameters != null) { Map> decodedParameters = new HashMap<>(); diff --git a/impl/src/main/java/com/sun/faces/context/UrlBuilder.java b/impl/src/main/java/com/sun/faces/context/UrlBuilder.java index d51c3fb83d..e593f78d2e 100644 --- a/impl/src/main/java/com/sun/faces/context/UrlBuilder.java +++ b/impl/src/main/java/com/sun/faces/context/UrlBuilder.java @@ -16,6 +16,8 @@ package com.sun.faces.context; +import static com.sun.faces.RIConstants.CHAR_ENCODING; + import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; @@ -49,7 +51,6 @@ class UrlBuilder { public static final String PARAMETER_PAIR_SEPARATOR = "&"; public static final String PARAMETER_NAME_VALUE_SEPARATOR = "="; public static final String FRAGMENT_SEPARATOR = "#"; - public static final String DEFAULT_ENCODING = "UTF-8"; private static final List NULL_LIST = Arrays.asList((String) null); @@ -73,7 +74,7 @@ public UrlBuilder(String url, String encoding) { } public UrlBuilder(String url) { - this(url, DEFAULT_ENCODING); + this(url, CHAR_ENCODING); } // ---------------------------------------------------------- Public Methods diff --git a/impl/src/main/java/com/sun/faces/util/Util.java b/impl/src/main/java/com/sun/faces/util/Util.java index 57e2863a38..e5df94e0ec 100644 --- a/impl/src/main/java/com/sun/faces/util/Util.java +++ b/impl/src/main/java/com/sun/faces/util/Util.java @@ -19,6 +19,7 @@ package com.sun.faces.util; +import static com.sun.faces.RIConstants.FACELETS_ENCODING_KEY; import static com.sun.faces.RIConstants.FACES_SERVLET_MAPPINGS; import static com.sun.faces.RIConstants.FACES_SERVLET_REGISTRATION; import static com.sun.faces.RIConstants.NO_VALUE; @@ -27,9 +28,11 @@ import static com.sun.faces.util.MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID; import static com.sun.faces.util.MessageUtils.NULL_VIEW_ID_ERROR_MESSAGE_ID; import static com.sun.faces.util.MessageUtils.getExceptionMessageString; +import static jakarta.faces.application.ViewHandler.CHARACTER_ENCODING_KEY; import static java.lang.Character.isDigit; import static java.util.Collections.emptyList; import static java.util.logging.Level.FINE; +import static java.util.logging.Level.FINEST; import static java.util.logging.Level.SEVERE; import java.beans.FeatureDescriptor; @@ -110,7 +113,7 @@ /** * Util is a class ... - * + * * Lifetime And Scope * */ @@ -1618,4 +1621,76 @@ public static int extractFirstNumericSegment(String clientId, char separatorChar throw new NumberFormatException("there is no numeric segment"); } + /** + * @param context the {@link FacesContext} for the current request + * @return the encoding to be used for the response + */ + public static String getResponseEncoding(FacesContext context) { + return getResponseEncoding(context, Optional.empty()); + } + + /** + * @param context the {@link FacesContext} for the current request + * @param defaultEncoding the default encoding, if any + * @return the encoding to be used for the response + */ + public static String getResponseEncoding(FacesContext context, Optional defaultEncoding) { + + // 1. First get it from viewroot, if any. + if (context.getViewRoot() != null) { + String encoding = (String) context.getViewRoot().getAttributes().get(FACELETS_ENCODING_KEY); + + if (encoding != null) { + // If found, then immediately return it, this represents either the encoding explicitly set via or the one actually set on response. + return encoding; + } + } + + // 2. If none found then get it from context (this is usually set during compile/buildtime based on request character encoding). + String encoding = (String) context.getAttributes().get(FACELETS_ENCODING_KEY); + + if (encoding != null && LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "Using Facelet encoding {0}", encoding); + } + + if (encoding == null) { + // 3. If none found (this is unexpected! could only happen with a broken Facelets Compiler or ViewHandler) then get it from request. + encoding = context.getExternalContext().getRequestCharacterEncoding(); + + if (encoding != null && LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "Using Request encoding {0}", encoding); + } + } + + if (encoding == null && context.getExternalContext().getSession(false) != null) { + // 4. If still none found (also unexpected! could only happen with a broken HttpServletRequestWrapper) then get previously known request encoding from session. + encoding = (String) context.getExternalContext().getSessionMap().get(CHARACTER_ENCODING_KEY); + + if (encoding != null && LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "Using Session encoding {0}", encoding); + } + } + + if (encoding == null) { + // 5. If still none found then fall back to specified default. + encoding = defaultEncoding.get(); + + if (encoding != null && !encoding.isBlank()) { + if (LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "Using specified default encoding {0}", encoding); + } + } + else { + // 6. If specified default is null or blank then finally fall back to hardcoded default. + encoding = RIConstants.CHAR_ENCODING; + + if (LOGGER.isLoggable(FINEST)) { + LOGGER.log(FINEST, "No encoding found, defaulting to {0}", encoding); + } + } + } + + return encoding; + } + } diff --git a/impl/src/main/java/jakarta/faces/application/ViewHandler.java b/impl/src/main/java/jakarta/faces/application/ViewHandler.java index c63e37e8a3..95c67f65d2 100644 --- a/impl/src/main/java/jakarta/faces/application/ViewHandler.java +++ b/impl/src/main/java/jakarta/faces/application/ViewHandler.java @@ -228,12 +228,17 @@ public abstract class ViewHandler { * */ public void initView(FacesContext context) throws FacesException { - String encoding = context.getExternalContext().getRequestCharacterEncoding(); - if (encoding != null) { + String originalEncoding = context.getExternalContext().getRequestCharacterEncoding(); + String encoding = (originalEncoding != null) ? originalEncoding : calculateCharacterEncoding(context); + + if (encoding != null && context.getExternalContext().getSession(false) != null) { + context.getExternalContext().getSessionMap().put(CHARACTER_ENCODING_KEY, encoding); + } + + if (originalEncoding != null) { return; } - encoding = calculateCharacterEncoding(context); if (encoding != null) { try { context.getExternalContext().setRequestCharacterEncoding(encoding); @@ -491,7 +496,7 @@ public String deriveLogicalViewId(FacesContext context, String requestViewId) { *

* Return a Jakarta Faces action URL derived from the viewId argument that is suitable to be used by * the {@link NavigationHandler} to issue a redirect request to the URL using a NonFaces request. Compliant - * implementations must implement this method as specified in + * implementations must implement this method as specified in * section 7.6.2 "Default ViewHandler Implementation" of the Jakarta Faces Specification Document. * The default implementation simply calls * through to {@link #getActionURL}, passing the arguments context and viewId. From e0758c01e08eb6eeea2ad3ec460e48f04b9a5120 Mon Sep 17 00:00:00 2001 From: Bauke Scholtz Date: Sun, 21 Jan 2024 11:34:19 -0400 Subject: [PATCH 3/4] Further clarified comments after self-review. --- .../faces/application/view/FaceletViewHandlingStrategy.java | 2 +- impl/src/main/java/com/sun/faces/util/Util.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java b/impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java index 444082f30b..667e21b5e4 100644 --- a/impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java +++ b/impl/src/main/java/com/sun/faces/application/view/FaceletViewHandlingStrategy.java @@ -904,7 +904,7 @@ protected ResponseWriter createResponseWriter(FacesContext context) throws IOExc // get our content type String contentType = (String) context.getAttributes().get("facelets.ContentType"); - // get the last calculated encoding + // get the encoding String encoding = (String) context.getAttributes().get(FACELETS_ENCODING_KEY); // Create a dummy ResponseWriter with a bogus writer, diff --git a/impl/src/main/java/com/sun/faces/util/Util.java b/impl/src/main/java/com/sun/faces/util/Util.java index e5df94e0ec..ca35f1100c 100644 --- a/impl/src/main/java/com/sun/faces/util/Util.java +++ b/impl/src/main/java/com/sun/faces/util/Util.java @@ -1642,11 +1642,13 @@ public static String getResponseEncoding(FacesContext context, Optional if (encoding != null) { // If found, then immediately return it, this represents either the encoding explicitly set via or the one actually set on response. + // See also ViewHandler#apply() and FaceletViewHandlingStrategy#createResponseWriter(). return encoding; } } // 2. If none found then get it from context (this is usually set during compile/buildtime based on request character encoding). + // See also SAXCompiler#doCompile() and EncodingHandler#apply(). String encoding = (String) context.getAttributes().get(FACELETS_ENCODING_KEY); if (encoding != null && LOGGER.isLoggable(FINEST)) { @@ -1655,6 +1657,7 @@ public static String getResponseEncoding(FacesContext context, Optional if (encoding == null) { // 3. If none found (this is unexpected! could only happen with a broken Facelets Compiler or ViewHandler) then get it from request. + // See also ViewHandler#initView() and ViewHandler#calculateCharacterEncoding(). encoding = context.getExternalContext().getRequestCharacterEncoding(); if (encoding != null && LOGGER.isLoggable(FINEST)) { @@ -1664,6 +1667,7 @@ public static String getResponseEncoding(FacesContext context, Optional if (encoding == null && context.getExternalContext().getSession(false) != null) { // 4. If still none found (also unexpected! could only happen with a broken HttpServletRequestWrapper) then get previously known request encoding from session. + // See also ViewHandler#initView(). encoding = (String) context.getExternalContext().getSessionMap().get(CHARACTER_ENCODING_KEY); if (encoding != null && LOGGER.isLoggable(FINEST)) { From 263095b2d23e39de4709772af441a1bfde35855d Mon Sep 17 00:00:00 2001 From: Bauke Scholtz Date: Sun, 28 Jan 2024 08:24:00 -0400 Subject: [PATCH 4/4] Further cleanup of comments --- impl/src/main/java/com/sun/faces/util/Util.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/impl/src/main/java/com/sun/faces/util/Util.java b/impl/src/main/java/com/sun/faces/util/Util.java index ca35f1100c..5289d39f71 100644 --- a/impl/src/main/java/com/sun/faces/util/Util.java +++ b/impl/src/main/java/com/sun/faces/util/Util.java @@ -1656,7 +1656,7 @@ public static String getResponseEncoding(FacesContext context, Optional } if (encoding == null) { - // 3. If none found (this is unexpected! could only happen with a broken Facelets Compiler or ViewHandler) then get it from request. + // 3. If none found then get it from request (could happen when the view isn't built yet). // See also ViewHandler#initView() and ViewHandler#calculateCharacterEncoding(). encoding = context.getExternalContext().getRequestCharacterEncoding(); @@ -1666,7 +1666,7 @@ public static String getResponseEncoding(FacesContext context, Optional } if (encoding == null && context.getExternalContext().getSession(false) != null) { - // 4. If still none found (also unexpected! could only happen with a broken HttpServletRequestWrapper) then get previously known request encoding from session. + // 4. If still none found then get previously known request encoding from session. // See also ViewHandler#initView(). encoding = (String) context.getExternalContext().getSessionMap().get(CHARACTER_ENCODING_KEY);