"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
var xml_js_builder_1 = require("xml-js-builder");
var IfParser_1 = require("../../helper/v2/IfParser");
var HTTPCodes_1 = require("../HTTPCodes");
var Path_1 = require("../../manager/v2/Path");
var Errors_1 = require("../../Errors");
var http = require("http");
var url = require("url");
var promise_1 = require("../../helper/v2/promise");
var RequestContextHeaders = /** @class */ (function () {
    function RequestContextHeaders(headers) {
        this.headers = headers;
        this.isSource = this.find('source', 'F').toUpperCase() === 'T' || this.find('translate', 'T').toUpperCase() === 'F';
        this.host = this.find('Host', 'localhost');
        var depth = this.find('Depth');
        try {
            if (depth.toLowerCase() === 'infinity')
                this.depth = -1;
            else
                this.depth = Math.max(-1, parseInt(depth, 10));
        }
        catch (_) {
            this.depth = undefined;
        }
        try {
            this.contentLength = Math.max(0, parseInt(this.find('Content-length', '0'), 10));
        }
        catch (_) {
            this.contentLength = 0;
        }
    }
    RequestContextHeaders.prototype.find = function (name, defaultValue) {
        if (defaultValue === void 0) { defaultValue = null; }
        name = name.replace(/(-| )/g, '').toLowerCase();
        for (var k in this.headers)
            if (k.replace(/(-| )/g, '').toLowerCase() === name) {
                var value = this.headers[k].toString().trim();
                if (value.length !== 0)
                    return value;
            }
        return defaultValue;
    };
    RequestContextHeaders.prototype.findBestAccept = function (defaultType) {
        if (defaultType === void 0) { defaultType = 'xml'; }
        var accepts = this.find('Accept', 'text/xml').split(',');
        var regex = {
            'xml': /[^a-z0-9A-Z]xml$/,
            'json': /[^a-z0-9A-Z]json$/
        };
        for (var _i = 0, accepts_1 = accepts; _i < accepts_1.length; _i++) {
            var value = accepts_1[_i];
            for (var name_1 in regex)
                if (regex[name_1].test(value))
                    return name_1;
        }
        return defaultType;
    };
    return RequestContextHeaders;
}());
exports.RequestContextHeaders = RequestContextHeaders;
var DefaultRequestContextExternalOptions = /** @class */ (function () {
    function DefaultRequestContextExternalOptions() {
        this.headers = {
            host: 'localhost'
        };
        this.url = '/';
        this.user = {
            isAdministrator: true,
            isDefaultUser: false,
            password: null,
            uid: '-1',
            username: '_default_super_admin_'
        };
    }
    return DefaultRequestContextExternalOptions;
}());
exports.DefaultRequestContextExternalOptions = DefaultRequestContextExternalOptions;
var RequestContext = /** @class */ (function () {
    function RequestContext(server, uri, headers, rootPath) {
        this.overridePrivileges = false;
        this.rootPath = rootPath;
        this.headers = new RequestContextHeaders(headers);
        this.server = server;
        uri = url.parse(uri).pathname;
        uri = uri ? uri : '';
        this.requested = {
            uri: uri,
            path: new Path_1.Path(uri)
        };
        this.requested.path.decode();
        if (this.rootPath) {
            this.rootPath = new Path_1.Path(this.rootPath).toString(false);
            if (this.rootPath === '/')
                this.rootPath = undefined;
        }
    }
    RequestContext.prototype.getResourceAsync = function (path) {
        var _this = this;
        return promise_1.promisifyCall(function (cb) { return _this.getResource(path, cb); });
    };
    RequestContext.prototype.getResource = function (_path, _callback) {
        var path = Path_1.Path.isPath(_path) ? new Path_1.Path(_path) : this.requested.path;
        var callback = _callback ? _callback : _path;
        this.server.getResource(this, path, callback);
    };
    RequestContext.prototype.getResourceSync = function (path) {
        path = path ? path : this.requested.path;
        return this.server.getResourceSync(this, path);
    };
    RequestContext.prototype.fullUri = function (uri) {
        if (uri === void 0) { uri = null; }
        if (!uri)
            uri = this.requested.uri;
        if (this.server.options.respondWithPaths)
            return this.rootPath ? this.rootPath + uri : uri;
        else
            return (this.prefixUri() + uri).replace(/([^:])\/\//g, '$1/');
    };
    RequestContext.prototype.prefixUri = function () {
        return 'http://' + this.headers.host.replace('/', '') + (this.rootPath ? this.rootPath : '');
    };
    return RequestContext;
}());
exports.RequestContext = RequestContext;
var ExternalRequestContext = /** @class */ (function (_super) {
    __extends(ExternalRequestContext, _super);
    function ExternalRequestContext() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    ExternalRequestContext.create = function (server, _options, _callback) {
        var defaultValues = new DefaultRequestContextExternalOptions();
        var options = _options && _options.constructor !== Function ? _options : defaultValues;
        var callback = _callback ? _callback : _options && _options.constructor === Function ? _options : function () { };
        if (defaultValues !== options) {
            for (var name_2 in defaultValues)
                if (options[name_2] === undefined)
                    options[name_2] = defaultValues[name_2];
        }
        var ctx = new ExternalRequestContext(server, options.url, options.headers);
        if (options.user) {
            ctx.user = options.user;
            process.nextTick(function () { return callback(null, ctx); });
        }
        return ctx;
    };
    return ExternalRequestContext;
}(RequestContext));
exports.ExternalRequestContext = ExternalRequestContext;
var HTTPRequestContext = /** @class */ (function (_super) {
    __extends(HTTPRequestContext, _super);
    function HTTPRequestContext(server, request, response, exit, rootPath) {
        var _this = _super.call(this, server, request.url, request.headers, rootPath) || this;
        _this.responseBody = undefined;
        _this.response = response;
        _this.request = request;
        _this.exit = exit;
        if (_this.response) {
            _this.response.on('error', function (e) {
                console.error(e);
            });
        }
        return _this;
    }
    HTTPRequestContext.create = function (server, request, response, _rootPath, _callback) {
        var rootPath = _callback ? _rootPath : undefined;
        var callback = _callback ? _callback : _rootPath;
        var ctx = new HTTPRequestContext(server, request, response, null, rootPath);
        response.setHeader('DAV', '1,2');
        response.setHeader('Access-Control-Allow-Origin', '*');
        response.setHeader('Access-Control-Allow-Credentials', 'true');
        response.setHeader('Access-Control-Expose-Headers', 'DAV, content-length, Allow');
        response.setHeader('MS-Author-Via', 'DAV');
        response.setHeader('Server', server.options.serverName + '/' + server.options.version);
        if (server.options.headers) {
            for (var headerName in server.options.headers)
                response.setHeader(headerName, server.options.headers[headerName]);
        }
        var setAllowHeader = function (type) {
            var allowedMethods = [];
            for (var name_3 in server.methods) {
                var method = server.methods[name_3];
                if (!method.isValidFor || method.isValidFor(ctx, type))
                    allowedMethods.push(name_3.toUpperCase());
            }
            response.setHeader('Allow', allowedMethods.join(','));
            callback(null, ctx);
        };
        ctx.askForAuthentication(false, function (e) {
            if (e) {
                callback(e, ctx);
                return;
            }
            server.httpAuthentication.getUser(ctx, function (e, user) {
                ctx.user = user;
                if (e && e !== Errors_1.Errors.UserNotFound) {
                    if (server.options.requireAuthentification || e !== Errors_1.Errors.MissingAuthorisationHeader)
                        return callback(e, ctx);
                }
                if (server.options.requireAuthentification && (!user || user.isDefaultUser || e === Errors_1.Errors.UserNotFound))
                    return callback(Errors_1.Errors.MissingAuthorisationHeader, ctx);
                server.getFileSystem(ctx.requested.path, function (fs, _, subPath) {
                    fs.type(ctx.requested.path.isRoot() ? server.createExternalContext() : ctx, subPath, function (e, type) {
                        if (e)
                            type = undefined;
                        setAllowHeader(type);
                    });
                });
            });
        });
    };
    HTTPRequestContext.encodeURL = function (url) {
        return encodeURI(url);
    };
    HTTPRequestContext.prototype.noBodyExpected = function (callback) {
        if (this.server.options.strictMode && this.headers.contentLength !== 0) {
            this.setCode(HTTPCodes_1.HTTPCodes.UnsupportedMediaType);
            this.exit();
        }
        else
            callback();
    };
    HTTPRequestContext.prototype.checkIfHeader = function (_fs, _path, _callback) {
        var _this = this;
        var fs = _callback ? _fs : null;
        var path = _callback ? _path : null;
        var resource = _callback ? null : _fs;
        var callback = _callback ? _callback : _path;
        var ifHeader = this.headers.find('If');
        if (!ifHeader) {
            callback();
            return;
        }
        if (!resource) {
            resource = fs.resource(this, path);
        }
        IfParser_1.parseIfHeader(ifHeader)(this, resource, function (e, passed) {
            if (e) {
                _this.setCode(HTTPCodes_1.HTTPCodes.InternalServerError);
                _this.exit();
            }
            else if (!passed) {
                _this.setCode(HTTPCodes_1.HTTPCodes.PreconditionFailed);
                _this.exit();
            }
            else
                callback();
        });
    };
    HTTPRequestContext.prototype.askForAuthentication = function (checkForUser, callback) {
        if (checkForUser && this.user !== null && !this.user.isDefaultUser) {
            callback(Errors_1.Errors.AlreadyAuthenticated);
            return;
        }
        var auth = this.server.httpAuthentication.askForAuthentication(this);
        for (var name_4 in auth)
            this.response.setHeader(name_4, auth[name_4]);
        callback(null);
    };
    HTTPRequestContext.prototype.writeBody = function (xmlObject) {
        var content = xml_js_builder_1.XML.toXML(xmlObject);
        switch (this.headers.findBestAccept()) {
            default:
            case 'xml':
                this.response.setHeader('Content-Type', 'application/xml;charset=utf-8');
                this.response.setHeader('Content-Length', Buffer.from(content).length.toString());
                this.response.write(content, 'UTF-8');
                break;
            case 'json':
                content = xml_js_builder_1.XML.toJSON(content);
                this.response.setHeader('Content-Type', 'application/json;charset=utf-8');
                this.response.setHeader('Content-Length', Buffer.from(content).length.toString());
                this.response.write(content, 'UTF-8');
                break;
        }
        this.responseBody = content;
    };
    HTTPRequestContext.prototype.setCode = function (code, message) {
        if (!message)
            message = http.STATUS_CODES[code];
        if (!message) {
            this.response.statusCode = code;
        }
        else {
            this.response.statusCode = code;
            this.response.statusMessage = message;
        }
    };
    HTTPRequestContext.defaultStatusCode = function (error) {
        var code = null;
        for (var _i = 0, _a = this.defaultErrorStatusCodes; _i < _a.length; _i++) {
            var errorCode = _a[_i];
            if (errorCode.error === error) {
                code = errorCode.code;
                break;
            }
        }
        return code;
    };
    HTTPRequestContext.prototype.setCodeFromError = function (error) {
        var code = HTTPRequestContext.defaultStatusCode(error);
        if (code)
            this.setCode(code);
        return !!code;
    };
    HTTPRequestContext.defaultErrorStatusCodes = [
        { error: Errors_1.Errors.ResourceNotFound, code: HTTPCodes_1.HTTPCodes.NotFound },
        { error: Errors_1.Errors.Locked, code: HTTPCodes_1.HTTPCodes.Locked },
        { error: Errors_1.Errors.BadAuthentication, code: HTTPCodes_1.HTTPCodes.Unauthorized },
        { error: Errors_1.Errors.NotEnoughPrivilege, code: HTTPCodes_1.HTTPCodes.Unauthorized },
        { error: Errors_1.Errors.ResourceAlreadyExists, code: HTTPCodes_1.HTTPCodes.Conflict },
        { error: Errors_1.Errors.IntermediateResourceMissing, code: HTTPCodes_1.HTTPCodes.Conflict },
        { error: Errors_1.Errors.WrongParentTypeForCreation, code: HTTPCodes_1.HTTPCodes.Conflict },
        { error: Errors_1.Errors.InsufficientStorage, code: HTTPCodes_1.HTTPCodes.InsufficientStorage },
        { error: Errors_1.Errors.Forbidden, code: HTTPCodes_1.HTTPCodes.Forbidden }
    ];
    return HTTPRequestContext;
}(RequestContext));
exports.HTTPRequestContext = HTTPRequestContext;
