"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AllowAcceptHeaderWriter = void 0;
const MethodNotAllowedHttpError_1 = require("../../../util/errors/MethodNotAllowedHttpError");
const NotFoundHttpError_1 = require("../../../util/errors/NotFoundHttpError");
const UnsupportedMediaTypeHttpError_1 = require("../../../util/errors/UnsupportedMediaTypeHttpError");
const HeaderUtil_1 = require("../../../util/HeaderUtil");
const PathUtil_1 = require("../../../util/PathUtil");
const Vocabularies_1 = require("../../../util/Vocabularies");
const MetadataWriter_1 = require("./MetadataWriter");
// Only PUT and PATCH can be used to create a new resource
const NEW_RESOURCE_ALLOWED_METHODS = new Set(['PUT', 'PATCH']);
/**
 * Generates Allow, Accept-Patch, Accept-Post, and Accept-Put headers.
 * The resulting values depend on the choses input methods and types.
 * The input metadata also gets used to remove methods from that list
 * if they are not valid in the given situation.
 */
class AllowAcceptHeaderWriter extends MetadataWriter_1.MetadataWriter {
    supportedMethods;
    acceptTypes;
    constructor(supportedMethods, acceptTypes) {
        super();
        this.supportedMethods = supportedMethods;
        this.acceptTypes = { patch: [], post: [], put: [], ...acceptTypes };
    }
    async handle(input) {
        const { response, metadata } = input;
        // Filter out methods which are not allowed
        const allowedMethods = this.filterAllowedMethods(metadata);
        // Generate the Allow headers (if required)
        const generateAllow = this.generateAllow(allowedMethods, response, metadata);
        // Generate Accept-[Method] headers (if required)
        this.generateAccept(allowedMethods, generateAllow, response, metadata);
    }
    /**
     * Starts from the stored set of methods and removes all those that are not allowed based on the metadata.
     */
    filterAllowedMethods(metadata) {
        const disallowedMethods = new Set(metadata.getAll(Vocabularies_1.SOLID_ERROR.terms.disallowedMethod)
            .map((term) => term.value));
        const allowedMethods = new Set(this.supportedMethods.filter((method) => !disallowedMethods.has(method)));
        // POST is only allowed on containers.
        // Metadata only has the resource URI in case it has resource metadata.
        if (!this.isPostAllowed(metadata)) {
            allowedMethods.delete('POST');
        }
        if (!this.isPutAllowed(metadata)) {
            allowedMethods.delete('PUT');
        }
        if (!this.isDeleteAllowed(metadata)) {
            allowedMethods.delete('DELETE');
        }
        // If we are sure the resource does not exist: only keep methods that can create a new resource.
        if (metadata.has(Vocabularies_1.SOLID_ERROR.terms.errorResponse, NotFoundHttpError_1.NotFoundHttpError.uri)) {
            for (const method of allowedMethods) {
                if (!NEW_RESOURCE_ALLOWED_METHODS.has(method)) {
                    allowedMethods.delete(method);
                }
            }
        }
        return allowedMethods;
    }
    /**
     * POST is only allowed on containers.
     * The metadata URI is only valid in case there is resource metadata,
     * otherwise it is just a blank node.
     */
    isPostAllowed(metadata) {
        return !metadata.has(Vocabularies_1.RDF.terms.type, Vocabularies_1.LDP.terms.Resource) || (0, PathUtil_1.isContainerPath)(metadata.identifier.value);
    }
    /**
     * PUT is not allowed on existing containers.
     */
    isPutAllowed(metadata) {
        return !metadata.has(Vocabularies_1.RDF.terms.type, Vocabularies_1.LDP.terms.Resource) || !(0, PathUtil_1.isContainerPath)(metadata.identifier.value);
    }
    /**
     * DELETE is allowed if the target exists,
     * is not a container,
     * or is an empty container that isn't a storage.
     *
     * Note that the identifier value check only works if the metadata is not about an error.
     */
    isDeleteAllowed(metadata) {
        if (!(0, PathUtil_1.isContainerPath)(metadata.identifier.value)) {
            return true;
        }
        const isStorage = metadata.has(Vocabularies_1.RDF.terms.type, Vocabularies_1.PIM.terms.Storage);
        const isEmpty = metadata.has(Vocabularies_1.LDP.terms.contains);
        return !isStorage && !isEmpty;
    }
    /**
     * Generates the Allow header if required.
     * It only needs to get added for successful GET/HEAD requests, 404s, or 405s.
     * The spec only requires it for GET/HEAD requests and 405s.
     * In the case of other error messages we can't deduce what the request method was,
     * so we do not add the header as we don't have enough information.
     */
    generateAllow(methods, response, metadata) {
        const methodDisallowed = metadata.has(Vocabularies_1.SOLID_ERROR.terms.errorResponse, MethodNotAllowedHttpError_1.MethodNotAllowedHttpError.uri);
        // 405s indicate the target resource exists.
        // This is a heuristic, but one that should always be correct in our case.
        const resourceExists = methodDisallowed || metadata.has(Vocabularies_1.RDF.terms.type, Vocabularies_1.LDP.terms.Resource);
        const generateAllow = resourceExists || metadata.has(Vocabularies_1.SOLID_ERROR.terms.errorResponse, NotFoundHttpError_1.NotFoundHttpError.uri);
        if (generateAllow) {
            (0, HeaderUtil_1.addHeader)(response, 'Allow', [...methods].join(', '));
        }
        return generateAllow;
    }
    /**
     * Generates the Accept-[Method] headers if required.
     * Will be added if the Allow header was added, or in case of a 415 error.
     * Specific Accept-[Method] headers will only be added if the method is in the `methods` set.
     */
    generateAccept(methods, generateAllow, response, metadata) {
        const typeWasUnsupported = metadata.has(Vocabularies_1.SOLID_ERROR.terms.errorResponse, UnsupportedMediaTypeHttpError_1.UnsupportedMediaTypeHttpError.uri);
        const generateAccept = generateAllow || typeWasUnsupported;
        if (generateAccept) {
            if (methods.has('PATCH')) {
                (0, HeaderUtil_1.addHeader)(response, 'Accept-Patch', this.acceptTypes.patch.join(', '));
            }
            if (methods.has('POST')) {
                (0, HeaderUtil_1.addHeader)(response, 'Accept-Post', this.acceptTypes.post.join(', '));
            }
            if (methods.has('PUT')) {
                (0, HeaderUtil_1.addHeader)(response, 'Accept-Put', this.acceptTypes.put.join(', '));
            }
        }
    }
}
exports.AllowAcceptHeaderWriter = AllowAcceptHeaderWriter;
//# sourceMappingURL=AllowAcceptHeaderWriter.js.map