# CacheUtils.java

    /*
     *  Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
     *
     *  WSO2 Inc. licenses this file to you under the Apache License,
     *  Version 2.0 (the "License"); you may not use this file except
     *  in compliance with the License.
     *  You may obtain a copy of the License at
     *
     *  http://www.apache.org/licenses/LICENSE-2.0
     *
     *  Unless required by applicable law or agreed to in writing,
     *  software distributed under the License is distributed on an
     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     *  KIND, either express or implied.  See the License for the
     *  specific language governing permissions and limitations
     *  under the License.
     *
     */
    package org.apache.synapse.transport.netty.util;

    import org.wso2.transport.http.netty.message.HttpCarbonMessage;

    import java.time.ZonedDateTime;
    import java.time.format.DateTimeFormatter;
    import java.time.format.DateTimeParseException;
    import java.time.temporal.TemporalAccessor;

    /**
     * A utility class for HTTP caching related tasks.
     */
    public class CacheUtils {

        public static final String WEAK_VALIDATOR_TAG = "W/";
        public static final String ETAG_HEADER = "ETag";
        public static final String IF_NONE_MATCH_HEADER = "If-None-Match";
        public static final String IF_MODIFIED_SINCE_HEADER = "If-Modified-Since";
        public static final String LAST_MODIFIED_HEADER = "Last-Modified";

        /**
         * Method for revalidating a cached response. This method follows the RFC7232 and RFC7234 specifications.
         *
         * @param outboundResponse The response to be sent to downstream
         * @param inboundRequest   The request received from downstream
         * @return Returns true if the cached response is still valid
         */
        public static boolean isValidCachedResponse(HttpCarbonMessage outboundResponse, HttpCarbonMessage inboundRequest) {

            String outgoingETag = outboundResponse.getHeader(ETAG_HEADER);
            String incomingETags = inboundRequest.getHeader(IF_NONE_MATCH_HEADER);

            if (incomingETags != null) {
                if (outgoingETag == null) {
                    // If inbound request has the If-None-Match header, but the outgoing request does not have an ETag
                    // header, it is considered that the cached response is invalid.
                    return false;
                }

                return !isNonMatchingETag(incomingETags, outgoingETag);
            }

            // If there isn't an If-None-Match header, then check if there is a If-Modified-Since header.
            String ifModifiedSince = inboundRequest.getHeader(IF_MODIFIED_SINCE_HEADER);
            if (ifModifiedSince == null) {
                // If both If-None-Match and If-Modified-Since headers aren't there, then it is not looking for cache
                // revalidation.
                return false;
            }

            String lastModified = outboundResponse.getHeader(LAST_MODIFIED_HEADER);
            if (lastModified == null) {
                return false;
            }

            try {
                TemporalAccessor ifModifiedSinceTime = ZonedDateTime.parse(ifModifiedSince,
                        DateTimeFormatter.RFC_1123_DATE_TIME);
                TemporalAccessor lastModifiedTime = ZonedDateTime.parse(lastModified, DateTimeFormatter.RFC_1123_DATE_TIME);
                return ifModifiedSinceTime.equals(lastModifiedTime);
            } catch (DateTimeParseException e) {
                // If the Date header cannot be parsed, it is ignored.
                return false;
            }
        }

        private static boolean isNonMatchingETag(String etags, String outgoingETag) {

            String[] etagArray = etags.split(",");

            if (etagArray.length == 1 && "*".equals(etagArray[0])) {
                return false;
            }

            for (String etag : etagArray) {
                if (weakEquals(etag.trim(), outgoingETag)) {
                    return false;
                }
            }

            return true;
        }

        private static boolean weakEquals(String requestETag, String responseETag) {
            // Taking the sub string to ignore "W/"
            String requestTagPortion = isWeakEntityTag(requestETag) ?
                    requestETag.substring(WEAK_VALIDATOR_TAG.length()) : requestETag;
            String responseTagPortion = isWeakEntityTag(responseETag) ?
                    responseETag.substring(WEAK_VALIDATOR_TAG.length()) : responseETag;

            return requestTagPortion.equals(responseTagPortion);
        }

        private static boolean isWeakEntityTag(String etag) {

            return etag.startsWith(WEAK_VALIDATOR_TAG);
        }

        private CacheUtils() {

        }

    }