/*
 * Zed Attack Proxy (ZAP) and its related class files.
 *
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 *
 * Copyright 2013 The ZAP Development Team
 *
 * Licensed 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.parosproxy.paros.core.scanner;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.network.HttpHeader;
import org.parosproxy.paros.network.HttpMessage;

/**
 * Abstract class for HTTP RPC request handling
 *
 * @author andy
 */
public abstract class VariantAbstractRPCQuery implements Variant {

    private final Logger logger = LogManager.getLogger(this.getClass());

    private final List<RPCParameter> listParam = new ArrayList<>();
    private final List<NameValuePair> params = new ArrayList<>();
    private String requestContent;

    private final int variantType;

    public VariantAbstractRPCQuery() {
        this(NameValuePair.TYPE_POST_DATA);
    }

    public VariantAbstractRPCQuery(int vt) {
        variantType = vt;
    }

    @Override
    public void setMessage(HttpMessage msg) {
        // First check if it's a gwt rpc form data request
        // Otherwise give back an empty param list
        String contentType = msg.getRequestHeader().getHeader(HttpHeader.CONTENT_TYPE);
        if (contentType != null && isValidContentType(contentType)) {
            try {
                setRequestContent(msg.getRequestBody().toString());
            } catch (Exception e) {
                logger.warn("Failed to parse the request body: " + e.getMessage(), e);
            }
        }
    }

    /**
     * @param name the name of the parameter
     * @param beginOffset the begin offset of the parameter value inside the RPC content body
     * @param endOffset the ending offset of the parameter value inside the RPC content body
     * @param toQuote the parameter need to be quoted when used
     * @param escaped the parameter value should be escaped so it has to be unescaped before
     */
    public void addParameter(
            String name, int beginOffset, int endOffset, boolean toQuote, boolean escaped) {
        RPCParameter param = new RPCParameter();
        String value = requestContent.substring(beginOffset, endOffset);
        param.setName(name);
        param.setValue((escaped) ? getUnescapedValue(value) : value);
        param.setBeginOffset(beginOffset);
        param.setEndOffset(endOffset);
        param.setToQuote(toQuote);
        listParam.add(param);
    }

    /**
     * @param name the name of the parameter
     * @param beginOffset the begin offset of the parameter value inside the RPC content body
     * @param endOffset the ending offset of the parameter value inside the RPC content body
     * @param toQuote the parameter need to be quoted when used
     * @param value the value that need to be set
     */
    public void addParameter(
            String name, int beginOffset, int endOffset, boolean toQuote, String value) {
        RPCParameter param = new RPCParameter();
        param.setName(name);
        param.setValue(value);
        param.setBeginOffset(beginOffset);
        param.setEndOffset(endOffset);
        param.setToQuote(toQuote);
        listParam.add(param);
    }

    /**
     * @param beginOffset
     * @param endOffset
     * @return
     */
    public String getToken(int beginOffset, int endOffset) {
        return requestContent.substring(beginOffset, endOffset);
    }

    /** @return */
    @Override
    public List<NameValuePair> getParamList() {
        return params;
    }

    /**
     * @param msg
     * @param originalPair
     * @param name
     * @param value
     * @return
     */
    @Override
    public String setParameter(
            HttpMessage msg, NameValuePair originalPair, String name, String value) {
        return this.setParameter(msg, originalPair, name, value, false);
    }

    /**
     * @param msg
     * @param originalPair
     * @param name
     * @param value
     * @return
     */
    @Override
    public String setEscapedParameter(
            HttpMessage msg, NameValuePair originalPair, String name, String value) {
        return this.setParameter(msg, originalPair, name, value, true);
    }

    /**
     * @param msg
     * @param originalPair
     * @param name
     * @param value
     * @param escaped
     * @return
     */
    private String setParameter(
            HttpMessage msg,
            NameValuePair originalPair,
            String name,
            String value,
            boolean escaped) {
        RPCParameter param = listParam.get(originalPair.getPosition());
        StringBuilder sb = new StringBuilder();
        sb.append(requestContent.substring(0, param.getBeginOffset()));
        sb.append(escaped ? value : getEscapedValue(value, param.isToQuote()));
        sb.append(requestContent.substring(param.getEndOffset()));

        String query = sb.toString();
        msg.getRequestBody().setBody(query);
        return query;
    }

    /** @param requestContent */
    protected void setRequestContent(String requestContent) {
        this.requestContent = requestContent;
        parseContent(requestContent);

        // Put each parameter in order by beginOffset
        // so that we can inject payloads
        // following the request order
        // --------------------------------------
        Collections.sort(listParam);

        for (int i = 0; i < listParam.size(); i++) {
            RPCParameter param = listParam.get(i);
            params.add(new NameValuePair(variantType, param.getName(), param.getValue(), i));
        }
    }

    /** @return */
    public String getReadableParametrizedQuery() {
        StringBuilder result = new StringBuilder();
        int begin = 0;
        int end;

        for (RPCParameter param : listParam) {
            end = param.getBeginOffset();
            result.append(requestContent.substring(begin, end));
            result.append("__INJECTABLE_PARAM__");
            begin = param.getEndOffset();
        }

        result.append(requestContent.substring(begin));

        return result.toString();
    }

    /**
     * @param contentType
     * @return
     */
    public abstract boolean isValidContentType(String contentType);

    /** @param content */
    public abstract void parseContent(String content);

    /**
     * @param value
     * @param toQuote
     * @return
     */
    public abstract String getEscapedValue(String value, boolean toQuote);

    /**
     * @param value
     * @return
     */
    public abstract String getUnescapedValue(String value);

    /** Inner support class */
    protected class RPCParameter implements Comparable<RPCParameter> {
        private String name;
        private String value;
        private int beginOffset;
        private int endOffset;
        private boolean toQuote;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public int getBeginOffset() {
            return beginOffset;
        }

        public void setBeginOffset(int beginOffset) {
            this.beginOffset = beginOffset;
        }

        public int getEndOffset() {
            return endOffset;
        }

        public void setEndOffset(int endOffset) {
            this.endOffset = endOffset;
        }

        public boolean isToQuote() {
            return toQuote;
        }

        public void setToQuote(boolean toQuote) {
            this.toQuote = toQuote;
        }

        @Override
        public int compareTo(RPCParameter t) {
            return this.beginOffset - t.beginOffset;
        }
    }
}

@@ -6214,32 +6214,221 @@
        %7D%0A    %7D%0A%0A
+    /** @deprecated use %7Blink #getReadableParameterizedQuery()%7D */%0A    @Deprecated%0A    public String getReadableParametrizedQuery() %7B%0A        return getReadableParameterizedQuery();%0A    %7D%0A%0A
     /** @return 
@@ -6458,32 +6458,33 @@
 tReadableParamet
+e
 rizedQuery() %7B%0A 

[API] parameterized