ResponseFacade.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.catalina.connector;

  18. import java.io.IOException;
  19. import java.io.PrintWriter;
  20. import java.security.AccessController;
  21. import java.security.PrivilegedAction;
  22. import java.security.PrivilegedActionException;
  23. import java.security.PrivilegedExceptionAction;
  24. import java.util.Collection;
  25. import java.util.Locale;
  26. import java.util.Map;
  27. import java.util.function.Supplier;

  28. import jakarta.servlet.ServletOutputStream;
  29. import jakarta.servlet.http.Cookie;
  30. import jakarta.servlet.http.HttpServletResponse;

  31. import org.apache.catalina.Globals;
  32. import org.apache.catalina.security.SecurityUtil;
  33. import org.apache.tomcat.util.res.StringManager;

  34. /**
  35.  * Facade class that wraps a Coyote response object. All methods are delegated to the wrapped response.
  36.  *
  37.  * @author Remy Maucherat
  38.  */
  39. public class ResponseFacade implements HttpServletResponse {

  40.     // ----------------------------------------------------------- DoPrivileged

  41.     private final class SetContentTypePrivilegedAction implements PrivilegedAction<Void> {

  42.         private final String contentType;

  43.         SetContentTypePrivilegedAction(String contentType) {
  44.             this.contentType = contentType;
  45.         }

  46.         @Override
  47.         public Void run() {
  48.             response.setContentType(contentType);
  49.             return null;
  50.         }
  51.     }

  52.     private final class DateHeaderPrivilegedAction implements PrivilegedAction<Void> {

  53.         private final String name;
  54.         private final long value;
  55.         private final boolean add;

  56.         DateHeaderPrivilegedAction(String name, long value, boolean add) {
  57.             this.name = name;
  58.             this.value = value;
  59.             this.add = add;
  60.         }

  61.         @Override
  62.         public Void run() {
  63.             if (add) {
  64.                 response.addDateHeader(name, value);
  65.             } else {
  66.                 response.setDateHeader(name, value);
  67.             }
  68.             return null;
  69.         }
  70.     }

  71.     private static class FlushBufferPrivilegedAction implements PrivilegedExceptionAction<Void> {

  72.         private final Response response;

  73.         FlushBufferPrivilegedAction(Response response) {
  74.             this.response = response;
  75.         }

  76.         @Override
  77.         public Void run() throws IOException {
  78.             response.setAppCommitted(true);
  79.             response.flushBuffer();
  80.             return null;
  81.         }
  82.     }


  83.     // ----------------------------------------------------------- Constructors

  84.     /**
  85.      * Construct a wrapper for the specified response.
  86.      *
  87.      * @param response The response to be wrapped
  88.      */
  89.     public ResponseFacade(Response response) {
  90.         this.response = response;
  91.     }


  92.     // ----------------------------------------------- Class/Instance Variables

  93.     /**
  94.      * The string manager for this package.
  95.      */
  96.     protected static final StringManager sm = StringManager.getManager(ResponseFacade.class);


  97.     /**
  98.      * The wrapped response.
  99.      */
  100.     protected Response response = null;


  101.     // --------------------------------------------------------- Public Methods

  102.     /**
  103.      * Clear facade.
  104.      */
  105.     public void clear() {
  106.         response = null;
  107.     }


  108.     /**
  109.      * Prevent cloning the facade.
  110.      */
  111.     @Override
  112.     protected Object clone() throws CloneNotSupportedException {
  113.         throw new CloneNotSupportedException();
  114.     }


  115.     public void finish() {
  116.         checkFacade();
  117.         response.setSuspended(true);
  118.     }


  119.     public boolean isFinished() {
  120.         checkFacade();
  121.         return response.isSuspended();
  122.     }


  123.     public long getContentWritten() {
  124.         checkFacade();
  125.         return response.getContentWritten();
  126.     }


  127.     // ------------------------------------------------ ServletResponse Methods

  128.     @Override
  129.     public String getCharacterEncoding() {
  130.         checkFacade();
  131.         return response.getCharacterEncoding();
  132.     }


  133.     @Override
  134.     public ServletOutputStream getOutputStream() throws IOException {
  135.         if (isFinished()) {
  136.             response.setSuspended(true);
  137.         }
  138.         return response.getOutputStream();
  139.     }


  140.     @Override
  141.     public PrintWriter getWriter() throws IOException {
  142.         if (isFinished()) {
  143.             response.setSuspended(true);
  144.         }
  145.         return response.getWriter();
  146.     }


  147.     @Override
  148.     public void setContentLength(int len) {
  149.         if (isCommitted()) {
  150.             return;
  151.         }
  152.         response.setContentLength(len);
  153.     }


  154.     @Override
  155.     public void setContentLengthLong(long length) {
  156.         if (isCommitted()) {
  157.             return;
  158.         }
  159.         response.setContentLengthLong(length);
  160.     }


  161.     @Override
  162.     public void setContentType(String type) {
  163.         if (isCommitted()) {
  164.             return;
  165.         }

  166.         if (SecurityUtil.isPackageProtectionEnabled()) {
  167.             AccessController.doPrivileged(new SetContentTypePrivilegedAction(type));
  168.         } else {
  169.             response.setContentType(type);
  170.         }
  171.     }


  172.     @Override
  173.     public void setBufferSize(int size) {
  174.         checkCommitted("coyoteResponse.setBufferSize.ise");
  175.         response.setBufferSize(size);
  176.     }


  177.     @Override
  178.     public int getBufferSize() {
  179.         checkFacade();
  180.         return response.getBufferSize();
  181.     }


  182.     @Override
  183.     public void flushBuffer() throws IOException {
  184.         if (isFinished()) {
  185.             return;
  186.         }

  187.         if (SecurityUtil.isPackageProtectionEnabled()) {
  188.             try {
  189.                 AccessController.doPrivileged(new FlushBufferPrivilegedAction(response));
  190.             } catch (PrivilegedActionException e) {
  191.                 Exception ex = e.getException();
  192.                 if (ex instanceof IOException) {
  193.                     throw (IOException) ex;
  194.                 }
  195.             }
  196.         } else {
  197.             response.setAppCommitted(true);
  198.             response.flushBuffer();
  199.         }
  200.     }


  201.     @Override
  202.     public void resetBuffer() {
  203.         checkCommitted("coyoteResponse.resetBuffer.ise");
  204.         response.resetBuffer();
  205.     }


  206.     @Override
  207.     public boolean isCommitted() {
  208.         checkFacade();
  209.         return response.isAppCommitted();
  210.     }


  211.     @Override
  212.     public void reset() {
  213.         checkCommitted("coyoteResponse.reset.ise");
  214.         response.reset();
  215.     }


  216.     @Override
  217.     public void setLocale(Locale loc) {
  218.         if (isCommitted()) {
  219.             return;
  220.         }
  221.         response.setLocale(loc);
  222.     }


  223.     @Override
  224.     public Locale getLocale() {
  225.         checkFacade();
  226.         return response.getLocale();
  227.     }


  228.     @Override
  229.     public void addCookie(Cookie cookie) {
  230.         if (isCommitted()) {
  231.             return;
  232.         }
  233.         response.addCookie(cookie);
  234.     }


  235.     @Override
  236.     public boolean containsHeader(String name) {
  237.         checkFacade();
  238.         return response.containsHeader(name);
  239.     }


  240.     @Override
  241.     public String encodeURL(String url) {
  242.         checkFacade();
  243.         return response.encodeURL(url);
  244.     }


  245.     @Override
  246.     public String encodeRedirectURL(String url) {
  247.         checkFacade();
  248.         return response.encodeRedirectURL(url);
  249.     }


  250.     public void sendEarlyHints() {
  251.         response.sendEarlyHints();
  252.     }

  253.     /**
  254.      * {@inheritDoc}
  255.      * <p>
  256.      * Calling <code>sendError</code> with a status code of 103 differs from the usual behavior. Sending 103 will
  257.      * trigger the container to send a "103 Early Hints" informational response including all current headers. The
  258.      * application can continue to use the request and response after calling sendError with a 103 status code,
  259.      * including triggering a more typical response of any type.
  260.      * <p>
  261.      * Starting with Tomcat 12, applications should use {@link #sendEarlyHints}.
  262.      */
  263.     @Override
  264.     public void sendError(int sc, String msg) throws IOException {
  265.         checkCommitted("coyoteResponse.sendError.ise");
  266.         if (Response.SC_EARLY_HINTS == sc) {
  267.             sendEarlyHints();
  268.         } else {
  269.             response.setAppCommitted(true);
  270.             response.sendError(sc, msg);
  271.         }
  272.     }


  273.     /**
  274.      * {@inheritDoc}
  275.      * <p>
  276.      * Calling <code>sendError</code> with a status code of 103 differs from the usual behavior. Sending 103 will
  277.      * trigger the container to send a "103 Early Hints" informational response including all current headers. The
  278.      * application can continue to use the request and response after calling sendError with a 103 status code,
  279.      * including triggering a more typical response of any type.
  280.      * <p>
  281.      * Starting with Tomcat 12, applications should use {@link #sendEarlyHints}.
  282.      */
  283.     @Override
  284.     public void sendError(int sc) throws IOException {
  285.         checkCommitted("coyoteResponse.sendError.ise");
  286.         if (Response.SC_EARLY_HINTS == sc) {
  287.             sendEarlyHints();
  288.         } else {
  289.             response.setAppCommitted(true);
  290.             response.sendError(sc);
  291.         }
  292.     }


  293.     @Override
  294.     public void sendRedirect(String location) throws IOException {
  295.         checkCommitted("coyoteResponse.sendRedirect.ise");
  296.         response.setAppCommitted(true);
  297.         response.sendRedirect(location);
  298.     }


  299.     @Override
  300.     public void setDateHeader(String name, long date) {
  301.         if (isCommitted()) {
  302.             return;
  303.         }

  304.         if (Globals.IS_SECURITY_ENABLED) {
  305.             AccessController.doPrivileged(new DateHeaderPrivilegedAction(name, date, false));
  306.         } else {
  307.             response.setDateHeader(name, date);
  308.         }
  309.     }


  310.     @Override
  311.     public void addDateHeader(String name, long date) {
  312.         if (isCommitted()) {
  313.             return;
  314.         }

  315.         if (Globals.IS_SECURITY_ENABLED) {
  316.             AccessController.doPrivileged(new DateHeaderPrivilegedAction(name, date, true));
  317.         } else {
  318.             response.addDateHeader(name, date);
  319.         }
  320.     }


  321.     @Override
  322.     public void setHeader(String name, String value) {
  323.         if (isCommitted()) {
  324.             return;
  325.         }
  326.         response.setHeader(name, value);
  327.     }


  328.     @Override
  329.     public void addHeader(String name, String value) {
  330.         if (isCommitted()) {
  331.             return;
  332.         }
  333.         response.addHeader(name, value);
  334.     }


  335.     @Override
  336.     public void setIntHeader(String name, int value) {
  337.         if (isCommitted()) {
  338.             return;
  339.         }
  340.         response.setIntHeader(name, value);
  341.     }


  342.     @Override
  343.     public void addIntHeader(String name, int value) {
  344.         if (isCommitted()) {
  345.             return;
  346.         }
  347.         response.addIntHeader(name, value);
  348.     }


  349.     @Override
  350.     public void setStatus(int sc) {
  351.         if (isCommitted()) {
  352.             return;
  353.         }
  354.         response.setStatus(sc);
  355.     }


  356.     @Override
  357.     public String getContentType() {
  358.         checkFacade();
  359.         return response.getContentType();
  360.     }


  361.     @Override
  362.     public void setCharacterEncoding(String encoding) {
  363.         checkFacade();
  364.         response.setCharacterEncoding(encoding);
  365.     }

  366.     @Override
  367.     public int getStatus() {
  368.         checkFacade();
  369.         return response.getStatus();
  370.     }

  371.     @Override
  372.     public String getHeader(String name) {
  373.         checkFacade();
  374.         return response.getHeader(name);
  375.     }

  376.     @Override
  377.     public Collection<String> getHeaderNames() {
  378.         checkFacade();
  379.         return response.getHeaderNames();
  380.     }

  381.     @Override
  382.     public Collection<String> getHeaders(String name) {
  383.         checkFacade();
  384.         return response.getHeaders(name);
  385.     }


  386.     @Override
  387.     public void setTrailerFields(Supplier<Map<String,String>> supplier) {
  388.         checkFacade();
  389.         response.setTrailerFields(supplier);
  390.     }


  391.     @Override
  392.     public Supplier<Map<String,String>> getTrailerFields() {
  393.         checkFacade();
  394.         return response.getTrailerFields();
  395.     }


  396.     private void checkFacade() {
  397.         if (response == null) {
  398.             throw new IllegalStateException(sm.getString("responseFacade.nullResponse"));
  399.         }
  400.     }


  401.     private void checkCommitted(String messageKey) {
  402.         if (isCommitted()) {
  403.             throw new IllegalStateException(sm.getString(messageKey));
  404.         }
  405.     }
  406. }