// --- BEGIN COPYRIGHT BLOCK ---
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (C) 2007 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---
package com.netscape.cms.servlet.request;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Locale;
import java.util.StringTokenizer;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.dogtagpki.server.authorization.AuthzToken;
import org.dogtagpki.server.ca.ICertificateAuthority;
import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.asn1.ASN1Util;
import org.mozilla.jss.asn1.INTEGER;
import org.mozilla.jss.asn1.OBJECT_IDENTIFIER;
import org.mozilla.jss.asn1.OCTET_STRING;
import org.mozilla.jss.asn1.SEQUENCE;
import org.mozilla.jss.asn1.SET;
import org.mozilla.jss.crypto.DigestAlgorithm;
import org.mozilla.jss.crypto.SignatureAlgorithm;
import org.mozilla.jss.netscape.security.pkcs.PKCS7;
import org.mozilla.jss.netscape.security.util.Utils;
import org.mozilla.jss.netscape.security.x509.AlgorithmId;
import org.mozilla.jss.netscape.security.x509.X500Name;
import org.mozilla.jss.netscape.security.x509.X509CertImpl;
import org.mozilla.jss.pkix.cmc.CMCStatusInfoV2;
import org.mozilla.jss.pkix.cmc.PKIData;
import org.mozilla.jss.pkix.cmc.ResponseBody;
import org.mozilla.jss.pkix.cmc.TaggedAttribute;
import org.mozilla.jss.pkix.cms.EncapsulatedContentInfo;
import org.mozilla.jss.pkix.cms.IssuerAndSerialNumber;
import org.mozilla.jss.pkix.cms.SignedData;
import org.mozilla.jss.pkix.cms.SignerIdentifier;
import org.mozilla.jss.pkix.primitive.AlgorithmIdentifier;
import org.mozilla.jss.pkix.primitive.Name;

import com.netscape.certsrv.authentication.IAuthToken;
import com.netscape.certsrv.authority.ICertAuthority;
import com.netscape.certsrv.authorization.EAuthzAccessDenied;
import com.netscape.certsrv.base.EBaseException;
import com.netscape.certsrv.base.IArgBlock;
import com.netscape.certsrv.common.ICMSRequest;
import com.netscape.certsrv.ra.IRegistrationAuthority;
import com.netscape.certsrv.request.IRequest;
import com.netscape.certsrv.request.RequestId;
import com.netscape.certsrv.request.RequestStatus;
import com.netscape.cms.profile.common.EnrollProfile;
import com.netscape.cms.servlet.base.CMSServlet;
import com.netscape.cms.servlet.common.CMSRequest;
import com.netscape.cms.servlet.common.CMSTemplate;
import com.netscape.cms.servlet.common.CMSTemplateParams;
import com.netscape.cms.servlet.common.ECMSGWException;
import com.netscape.cmscore.apps.CMS;
import com.netscape.cmscore.base.ArgBlock;

/**
 * Check the status of a certificate request
 *
 * @version $Revision$, $Date$
 */
public class CheckRequest extends CMSServlet {

    public static org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CheckRequest.class);

    private static final long serialVersionUID = 2791195859767119636L;
    // constants
    public static String FULL_RESPONSE = "cmcFullEnrollmentResponse";
    private final static String REQ_ID = "requestId";
    private final static String STATUS = "status";
    private final static String CREATE_ON = "createdOn";
    private final static String UPDATE_ON = "updatedOn";

    private final static String TPL_FILE = "requestStatus.template";

    // variables
    private String mFormPath = null;
    private String mAuthorityId = null;

    @Override
    public CMSRequest newCMSRequest() {
        return new CMSRequest();
    }

    /**
     * Constructs request query servlet.
     */
    public CheckRequest()
            throws EBaseException {
        super();
    }

    /**
     * initialize the servlet. This servlet uses the template file
     * "requestStatus.template" to process the response.
     *
     * @param sc servlet configuration, read from the web.xml file
     */
    @Override
    public void init(ServletConfig sc) throws ServletException {

        super.init(sc);

        mAuthorityId = mAuthority.getId();
        mFormPath = "/" + mAuthorityId + "/" + TPL_FILE;

        mTemplates.remove(ICMSRequest.SUCCESS);
    }

    /**
     * Process the HTTP request.
     * <ul>
     * <li>http.param requestId ID of the request to check
     * <li>http.param format if 'id', then check the request based on the request ID parameter. If set to CMC, then use
     * the 'queryPending' parameter.
     * <li>http.param queryPending query formatted as a CMC request
     * </ul>
     *
     * @param cmsReq the object holding the request and response information
     */
    @Override
    public void process(CMSRequest cmsReq) throws EBaseException {
        logger.debug("checkRequest: in process!");
        SET transIds = null, sNonces = null;
        boolean isCMCReq = false;
        INTEGER bodyPartId = null;

        HttpServletRequest req = cmsReq.getHttpReq();
        HttpServletResponse resp = cmsReq.getHttpResp();

        IAuthToken authToken = authenticate(cmsReq);

        AuthzToken authzToken = null;

        try {
            authzToken = authorize(mAclMethod, authToken,
                        mAuthzResourceName, "read");
        } catch (EAuthzAccessDenied e) {
            logger.warn(CMS.getLogMessage("ADMIN_SRVLT_AUTH_FAILURE", e.toString()), e);
        } catch (Exception e) {
            logger.warn(CMS.getLogMessage("ADMIN_SRVLT_AUTH_FAILURE", e.toString()), e);
        }

        if (authzToken == null) {
            cmsReq.setStatus(ICMSRequest.UNAUTHORIZED);
            return;
        }

        CMSTemplate form = null;
        Locale[] locale = new Locale[1];

        if (mOutputTemplatePath != null)
            mFormPath = mOutputTemplatePath;

        try {
            form = getTemplate(mFormPath, req, locale);
        } catch (IOException e) {
            logger.error(CMS.getLogMessage("CMSGW_ERR_GET_TEMPLATE", mFormPath, e.toString()), e);
            throw new ECMSGWException(CMS.getUserMessage("CMS_GW_DISPLAY_TEMPLATE_ERROR"), e);
        }

        ArgBlock header = new ArgBlock();
        ArgBlock fixed = new ArgBlock();
        CMSTemplateParams argSet = new CMSTemplateParams(header, fixed);

        // Note error is covered in the same template as success.
        EBaseException error = null;

        String requestId = req.getParameter("requestId");
        String format = req.getParameter("format");

        logger.debug("checkRequest: requestId " + requestId);

        // They may check the status using CMC queryPending
        String queryPending = req.getParameter("queryPending");

        if (format != null && format.equals("cmc") && queryPending != null && !queryPending.equals("")) {
            try {
                isCMCReq = true;
                byte[] cmcBlob = Utils.base64decode(queryPending);
                ByteArrayInputStream cmcBlobIn =
                        new ByteArrayInputStream(cmcBlob);

                org.mozilla.jss.pkix.cms.ContentInfo cii = (org.mozilla.jss.pkix.cms.ContentInfo)
                        org.mozilla.jss.pkix.cms.ContentInfo.getTemplate().decode(cmcBlobIn);
                SignedData cmcFullReq = (SignedData)
                        cii.getInterpretedContent();

                EncapsulatedContentInfo ci = cmcFullReq.getContentInfo();

                OBJECT_IDENTIFIER id = ci.getContentType();

                if (!id.equals(OBJECT_IDENTIFIER.id_cct_PKIData) || !ci.hasContent()) {
                    throw new ECMSGWException(CMS.getUserMessage("CMS_GW_NO_PKIDATA"));
                }
                OCTET_STRING content = ci.getContent();
                ByteArrayInputStream s = new ByteArrayInputStream(content.toByteArray());
                PKIData pkiData = (PKIData) (new PKIData.Template()).decode(s);

                SEQUENCE controlSequence = pkiData.getControlSequence();
                int numControls = controlSequence.size();

                for (int i = 0; i < numControls; i++) {
                    // decode message.
                    TaggedAttribute taggedAttr = (TaggedAttribute) controlSequence.elementAt(i);
                    OBJECT_IDENTIFIER type = taggedAttr.getType();

                    if (type.equals(OBJECT_IDENTIFIER.id_cmc_QueryPending)) {
                        bodyPartId = taggedAttr.getBodyPartID();
                        SET requestIds = taggedAttr.getValues();
                        int numReq = requestIds.size();

                        // We only process one for now.
                        if (numReq > 0) {
                            OCTET_STRING reqId = (OCTET_STRING)
                                    ASN1Util.decode(OCTET_STRING.getTemplate(),
                                            ASN1Util.encode(requestIds.elementAt(0)));

                            requestId = new String(reqId.toByteArray());
                        }
                    } else if (type.equals(OBJECT_IDENTIFIER.id_cmc_transactionId)) {
                        transIds = taggedAttr.getValues();
                    } else if (type.equals(OBJECT_IDENTIFIER.id_cmc_recipientNonce)) {
                        // recipient nonce
                    } else if (type.equals(OBJECT_IDENTIFIER.id_cmc_senderNonce)) {
                        sNonces = taggedAttr.getValues();
                    }
                }
            } catch (Exception e) {
                error = new EBaseException(e.toString());
            }
        }

        IArgBlock httpParams = cmsReq.getHttpParams();
        boolean importCert = httpParams.getValueAsBoolean("importCert",
                false);
        // xxx need to check why this is not available at startup
        X509Certificate mCACerts[] = null;

        try {
            mCACerts = ((ICertAuthority) mAuthority).getCACertChain().getChain();
        } catch (Exception e) {
            throw new ECMSGWException(
                    CMS.getUserMessage("CMS_GW_CA_CHAIN_NOT_AVAILABLE"));
        }

        if (requestId == null || requestId.trim().equals("")) {
            logger.error(CMS.getLogMessage("CMSGW_NO_REQUEST_ID_PROVIDED"));
            throw new ECMSGWException(CMS.getUserMessage("CMS_GW_NO_REQUEST_ID_PROVIDED"));
        }
        try {
            new BigInteger(requestId);
        } catch (NumberFormatException e) {
            logger.error(CMS.getLogMessage("BASE_INVALID_NUMBER_FORMAT_1",  requestId), e);
            throw new EBaseException(
                    CMS.getUserMessage(getLocale(req), "CMS_BASE_INVALID_NUMBER_FORMAT_1",CMSTemplate.escapeJavaScriptStringHTML( requestId)));
        }

        IRequest r = requestRepository.readRequest(new RequestId(requestId));

        if (r == null) {
            logger.error(CMS.getLogMessage("CMSGW_REQUEST_ID_NOT_FOUND_1", requestId));
            throw new ECMSGWException(CMS.getUserMessage("CMS_GW_REQUEST_ID_NOT_FOUND", requestId));
        }

        if (authToken != null) {
            // if RA, requestOwner must match the group
            String group = authToken.getInString("group");
            if ((group != null) && (group != "")) {
                if (group.equals("Registration Manager Agents")) {
                    boolean groupMatched = false;
                    String requestOwner = r.getExtDataInString("requestOwner");
                    if (requestOwner != null) {
                        if (requestOwner.equals(group))
                            groupMatched = true;
                    }
                    if (groupMatched == false) {
                        logger.error(CMS.getLogMessage("BASE_INVALID_NUMBER_FORMAT_1", requestId.toString()));
                        throw new EBaseException(CMS.getUserMessage(getLocale(req), "CMS_BASE_INVALID_NUMBER_FORMAT_1", requestId));
                    }
                }
            }
        }

        RequestStatus status = r.getRequestStatus();
        String note = r.getExtDataInString("requestNotes");

        header.addStringValue("authority", mAuthorityId);
        header.addStringValue(REQ_ID, r.getRequestId().toString());
        header.addStringValue(STATUS, status.toString());
        header.addLongValue(CREATE_ON, r.getCreationTime().getTime() / 1000);
        header.addLongValue(UPDATE_ON, r.getModificationTime().getTime() / 1000);
        if (note != null && note.length() > 0)
            header.addStringValue("requestNotes",CMSTemplate.escapeJavaScriptStringHTML(note));

        String type = r.getRequestType();
        Integer result = r.getExtDataInInteger(IRequest.RESULT);

        /*        if (type.equals(IRequest.ENROLLMENT_REQUEST) && (r.get("profile") != null) && status.equals(RequestStatus.COMPLETE)) {
                    X509CertImpl cert = (X509CertImpl) r.get(IEnrollProfile.REQUEST_ISSUED_CERT);
                    ArgBlock rarg = new ArgBlock();

                    rarg.addBigIntegerValue("serialNumber",
                        cert.getSerialNumber(), 16);
                    argSet.addRepeatRecord(rarg);
                }
        */
        String profileId = r.getExtDataInString(IRequest.PROFILE_ID);
        if (profileId != null) {
            result = IRequest.RES_SUCCESS;
        }
        if ((type != null) && (type.equals(IRequest.ENROLLMENT_REQUEST) ||
                type.equals(IRequest.RENEWAL_REQUEST)) && (status != null) &&
                status.equals(RequestStatus.COMPLETE) && (result != null) &&
                result.equals(IRequest.RES_SUCCESS)) {
            Object o = r.getExtDataInCertArray(IRequest.ISSUED_CERTS);

            if (profileId != null) {
                X509CertImpl impl[] = new X509CertImpl[1];
                impl[0] = r.getExtDataInCert(EnrollProfile.REQUEST_ISSUED_CERT);
                o = impl;
            }
            if (o != null && (o instanceof X509CertImpl[])) {
                X509CertImpl[] certs = (X509CertImpl[]) o;

                if (certs != null && certs.length > 0) {
                    for (int i = 0; i < certs.length; i++) {
                        if (certs[i] != null) {
                            ArgBlock rarg = new ArgBlock();

                            rarg.addBigIntegerValue("serialNumber",
                                    certs[i].getSerialNumber(), 16);
                            // add pkcs7 cert for importing
                            if (importCert || isCMCReq) {
                                //byte[] ba = certs[i].getEncoded();
                                X509CertImpl[] certsInChain = new X509CertImpl[1];
                                ;
                                if (mCACerts != null) {
                                    for (int ii = 0; ii < mCACerts.length; ii++) {
                                        if (certs[i].equals(mCACerts[ii])) {
                                            certsInChain = new
                                                    X509CertImpl[mCACerts.length];
                                            break;
                                        }
                                        certsInChain = new X509CertImpl[mCACerts.length + 1];
                                    }
                                }

                                // Set the EE cert
                                certsInChain[0] = certs[i];

                                // Set the Ca certificate chain
                                if (mCACerts != null) {
                                    for (int ii = 0; ii < mCACerts.length; ii++) {
                                        if (!certs[i].equals(mCACerts[ii]))
                                            certsInChain[ii + 1] = (X509CertImpl) mCACerts[ii];
                                    }
                                }
                                // Wrap the chain into a degenerate P7 object
                                String p7Str;

                                try {
                                    PKCS7 p7 = new PKCS7(new AlgorithmId[0],
                                            new org.mozilla.jss.netscape.security.pkcs.ContentInfo(new byte[0]),
                                            certsInChain,
                                            new org.mozilla.jss.netscape.security.pkcs.SignerInfo[0]);

                                    byte[] p7Bytes = p7.getBytes();
                                    p7Str = Utils.base64encode(p7Bytes, true);

                                    StringTokenizer tokenizer = null;

                                    if (File.separator.equals("\\")) {
                                        char[] nl = new char[2];

                                        nl[0] = 10;
                                        nl[1] = 13;
                                        String nlstr = new String(nl);

                                        tokenizer = new StringTokenizer(p7Str, nlstr);
                                    } else
                                        tokenizer = new StringTokenizer(p7Str, "\n");
                                    StringBuffer res = new StringBuffer();

                                    while (tokenizer.hasMoreTokens()) {
                                        String elem = tokenizer.nextToken();

                                        res.append(elem);
                                    }

                                    header.addStringValue("pkcs7ChainBase64", res.toString());

                                    // compose full response
                                    if (isCMCReq) {
                                        SEQUENCE controlSeq = new SEQUENCE();
                                        int bpid = 1;
                                        SEQUENCE bpids = new SEQUENCE();

                                        if (bodyPartId != null)
                                            bpids.addElement(bodyPartId);
                                        CMCStatusInfoV2 cmcStatusInfo = new
                                                CMCStatusInfoV2(CMCStatusInfoV2.SUCCESS, bpids);
                                        TaggedAttribute ta = new TaggedAttribute(new
                                                INTEGER(bpid++),
                                                OBJECT_IDENTIFIER.id_cmc_statusInfoV2,
                                                cmcStatusInfo);

                                        controlSeq.addElement(ta);

                                        // copy transactionID, senderNonce,
                                        // create recipientNonce
                                        if (transIds != null) {
                                            ta = new TaggedAttribute(new
                                                        INTEGER(bpid++),
                                                        OBJECT_IDENTIFIER.id_cmc_transactionId,
                                                        transIds);
                                            controlSeq.addElement(ta);
                                        }

                                        if (sNonces != null) {
                                            ta = new TaggedAttribute(new
                                                        INTEGER(bpid++),
                                                        OBJECT_IDENTIFIER.id_cmc_recipientNonce,
                                                        sNonces);
                                            controlSeq.addElement(ta);
                                        }

                                        String salt = CMSServlet.generateSalt();
                                        byte[] dig;

                                        try {
                                            MessageDigest SHA1Digest = MessageDigest.getInstance("SHA1");

                                            dig = SHA1Digest.digest(salt.getBytes());
                                        } catch (NoSuchAlgorithmException ex) {
                                            dig = salt.getBytes();
                                        }
                                        String b64E = Utils.base64encode(dig, true);
                                        String[] newNonce = { b64E };

                                        ta = new TaggedAttribute(new
                                                    INTEGER(bpid++),
                                                    OBJECT_IDENTIFIER.id_cmc_senderNonce,
                                                    new OCTET_STRING(newNonce[0].getBytes()));
                                        controlSeq.addElement(ta);

                                        ResponseBody rb = new ResponseBody(controlSeq, new
                                                SEQUENCE(), new
                                                SEQUENCE());
                                        EncapsulatedContentInfo ci = new
                                                EncapsulatedContentInfo(OBJECT_IDENTIFIER.id_cct_PKIResponse,
                                                        rb);

                                        org.mozilla.jss.crypto.X509Certificate x509cert = null;

                                        if (mAuthority instanceof ICertificateAuthority) {
                                            x509cert = ((ICertificateAuthority) mAuthority).getCaX509Cert();
                                        } else if (mAuthority instanceof IRegistrationAuthority) {
                                            x509cert = ((IRegistrationAuthority) mAuthority).getRACert();
                                        }
                                        if (x509cert == null)
                                            throw new ECMSGWException(CMS.getUserMessage("CMS_GW_CMC_ERROR",
                                                    "No signing cert found."));

                                        X509CertImpl cert = new X509CertImpl(x509cert.getEncoded());
                                        ByteArrayInputStream issuer1 = new
                                                ByteArrayInputStream(((X500Name) cert.getIssuerDN()).getEncoded());
                                        Name issuer = (Name) Name.getTemplate().decode(issuer1);
                                        IssuerAndSerialNumber ias =
                                                new
                                                IssuerAndSerialNumber(issuer, new INTEGER(cert.getSerialNumber()
                                                        .toString()));
                                        SignerIdentifier si = new
                                                SignerIdentifier(SignerIdentifier.ISSUER_AND_SERIALNUMBER, ias, null);

                                        // SHA1 is the default digest Alg for now.
                                        DigestAlgorithm digestAlg = null;
                                        SignatureAlgorithm signAlg = null;
                                        org.mozilla.jss.crypto.PrivateKey privKey =
                                                CryptoManager.getInstance().findPrivKeyByCert(x509cert);
                                        org.mozilla.jss.crypto.PrivateKey.Type keyType = privKey.getType();

                                        if (keyType.equals(org.mozilla.jss.crypto.PrivateKey.RSA))
                                            signAlg = SignatureAlgorithm.RSASignatureWithSHA1Digest;
                                        else if (keyType.equals(org.mozilla.jss.crypto.PrivateKey.DSA))
                                            signAlg = SignatureAlgorithm.DSASignatureWithSHA1Digest;
                                        MessageDigest SHADigest = null;
                                        byte[] digest = null;

                                        try {
                                            SHADigest = MessageDigest.getInstance("SHA1");
                                            digestAlg = DigestAlgorithm.SHA1;
                                            ByteArrayOutputStream ostream = new ByteArrayOutputStream();

                                            rb.encode(ostream);
                                            digest = SHADigest.digest(ostream.toByteArray());
                                        } catch (NoSuchAlgorithmException ex) {
                                            //logger.warn("digest fail", ex);
                                        }

                                        org.mozilla.jss.pkix.cms.SignerInfo signInfo = new
                                                org.mozilla.jss.pkix.cms.SignerInfo(si, null, null,
                                                        OBJECT_IDENTIFIER.id_cct_PKIResponse,
                                                        digest, signAlg,
                                                        privKey);
                                        SET signInfos = new SET();

                                        signInfos.addElement(signInfo);

                                        SET digestAlgs = new SET();

                                        if (digestAlg != null) {
                                            AlgorithmIdentifier ai = new
                                                    AlgorithmIdentifier(digestAlg.toOID(),
                                                            null);

                                            digestAlgs.addElement(ai);
                                        }

                                        SET jsscerts = new SET();

                                        for (int j = 0; j < certsInChain.length; j++) {
                                            ByteArrayInputStream is = new
                                                    ByteArrayInputStream(certsInChain[j].getEncoded());
                                            org.mozilla.jss.pkix.cert.Certificate certJss =
                                                    (org.mozilla.jss.pkix.cert.Certificate)
                                                    org.mozilla.jss.pkix.cert.Certificate.getTemplate().decode(is);

                                            jsscerts.addElement(certJss);
                                        }

                                        SignedData fResponse = new
                                                SignedData(digestAlgs, ci,
                                                        jsscerts, null, signInfos);
                                        org.mozilla.jss.pkix.cms.ContentInfo fullResponse =
                                                new
                                                org.mozilla.jss.pkix.cms.ContentInfo(
                                                        org.mozilla.jss.pkix.cms.ContentInfo.SIGNED_DATA, fResponse);
                                        ByteArrayOutputStream ostream = new
                                                ByteArrayOutputStream();

                                        fullResponse.encode(ostream);
                                        byte[] fr = ostream.toByteArray();

                                        header.addStringValue(FULL_RESPONSE, Utils.base64encode(fr, true));
                                    }
                                } catch (Exception e) {
                                    logger.error(CMS.getLogMessage("CMSGW_ERROR_FORMING_PKCS7_1", e.toString()), e);
                                    throw new ECMSGWException(CMS.getUserMessage("CMS_GW_FORMING_PKCS7_ERROR"), e);
                                }
                            }
                            argSet.addRepeatRecord(rarg);
                        }
                    }
                }
            }
        }

        try {
            ServletOutputStream out = resp.getOutputStream();

            if (error == null) {
                String xmlOutput = req.getParameter("xml");
                if (xmlOutput != null && xmlOutput.equals("true")) {
                    outputXML(resp, argSet);
                } else {
                    resp.setContentType("text/html");
                    form.renderOutput(out, argSet);
                    cmsReq.setStatus(ICMSRequest.SUCCESS);
                }
            } else {
                cmsReq.setStatus(ICMSRequest.ERROR);
                cmsReq.setError(error);
            }
        } catch (IOException e) {
            logger.error(CMS.getLogMessage("CMSGW_ERR_STREAM_TEMPLATE", e.toString()), e);
            throw new ECMSGWException(CMS.getUserMessage("CMS_GW_DISPLAY_TEMPLATE_ERROR"), e);
        }
    }
}
