/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * You can obtain a copy of the licence at RISC OS path @.^.LICENCE
 * or http://www.riscosdev.com/lanman98/LICENCE.CDDL
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the Licence file.
 *
 * CDDL HEADER END
 *
 * Portions Copyright 2026 RISC OS Developments
 * SPDX-License-Identifier: CDDL-1.0
 */
/* Native HTTP plus TLSManager-owned HTTPS I/O. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include "kernel.h"
#include "swis.h"
#include "LanMan98BaseLib/error.h"
#include "LanMan98BaseLib/tcp.h"
#include "LanMan98BaseLib/thread.h"
#include "LanMan98BaseLib/time.h"
#include "webio.h"

#ifndef WEB_HTTP_ONLY
#define TLSMANAGER_INFO 0xDFA40
#define TLSMANAGER_OPEN 0xDFA41
#define TLSMANAGER_STEP 0xDFA42
#define TLSMANAGER_READ 0xDFA43
#define TLSMANAGER_WRITE 0xDFA44
#define TLSMANAGER_CLOSE 0xDFA45
#define TLSMANAGER_ABI_VERSION 1
#define TLSMANAGER_STATUS_COMPLETE 0
#define TLSMANAGER_STATUS_EOF 4
#endif /* !WEB_HTTP_ONLY */
#define WEB_TIMEOUT 3500
/* Depth: 1 PROPFIND against cloud roots can need longer than a local SMB echo. */
/* An i.MX6 on a slow link can spend most of a minute in the handshake. */
#define WEB_TLS_TIMEOUT 18000
#define TLS_CONNECT_TIMEOUT 12000

#ifndef WEB_HTTP_ONLY
typedef struct {
    unsigned int size;
    unsigned int abi;
    const char *hostname;
    unsigned int hostname_length;
    unsigned int port;
    unsigned int connect_timeout_cs;
    unsigned int io_timeout_cs;
    unsigned int minimum_tls;
    unsigned int trust_policy;
    const char *proxy_hostname;
    unsigned int proxy_hostname_length;
    unsigned int proxy_port;
    unsigned int proxy_secure;
    unsigned int target_secure;
} tlsmanager_open_block;

typedef struct {
    int swi;
    int handle;
    void *buffer;
    int size;
    int result;
    int status;
    int timeout_cs;
} tls_operation;

static int tls_ready(void *argument)
{
    tls_operation *operation;
    _kernel_swi_regs in, out;
    _kernel_oserror *error;

    operation = argument;
    memset(&in, 0, sizeof(in));
    in.r[1] = operation->handle;
    if(operation->swi == TLSMANAGER_READ ||
       operation->swi == TLSMANAGER_WRITE)
    {
        in.r[2] = (int)operation->buffer;
        in.r[3] = operation->size;
    }
    error = _kernel_swi(operation->swi, &in, &out);
    if(error) ErrorErr(error);
    operation->result = out.r[0];
    operation->status = operation->swi == TLSMANAGER_STEP ?
        out.r[0] : out.r[1];
    if(operation->swi == TLSMANAGER_STEP)
        return operation->status == TLSMANAGER_STATUS_COMPLETE;
    if(operation->result > 0) return 1;
    return operation->swi == TLSMANAGER_READ &&
           operation->status == TLSMANAGER_STATUS_EOF;
}

static int tls_call(int swi, int handle, void *buffer, int size, int timeout_cs)
{
    tls_operation *operation;
    int result;

    /* ThreadWait can reuse the caller's saved stack, so its argument must be
       allocated outside that stack. */
    operation = Malloc(sizeof(*operation));
    memset(operation, 0, sizeof(*operation));
    operation->swi = swi;
    operation->handle = handle;
    operation->buffer = buffer;
    operation->size = size;
    operation->timeout_cs = timeout_cs;
    ThreadSetTimeout(timeout_cs);
    ExceptTry
    {
        if(!tls_ready(operation)) ThreadWait(tls_ready, operation);
    }
    ExceptCatch
    {
        _kernel_oserror *caught = ExceptCaught();

        WebIssue("TLS: %s", caught && caught->errmess[0] ? caught->errmess : "no error given");
        ThreadResetTimeout();
        Free(operation);
        ExceptRethrow();
    }
    ThreadResetTimeout();
    result = operation->result;
    Free(operation);
    return result;
}
#endif /* !WEB_HTTP_ONLY */

static char *get_var(const char *name)
{
    static char buf[256];
    int len;

    if(_swix(OS_ReadVarVal, _INR(0,4) | _OUT(2), name, buf, sizeof(buf) - 1, 0, 0, &len))
        return NULL;
    buf[len] = 0;
    return buf;
}

static int proxy_setting(web_url *proxy)
{
    char value[256];
    char *var;

    var = get_var("LanMan98$WebDAVProxy");
    if(var == NULL || *var == '\0')
        return 0;
    if(strlen(var) >= sizeof(value))
        Error("WebDAV proxy setting is too long");
    strcpy(value, var);
    if(!WebParseURL(value, proxy) || strcmp(proxy->base, "/"))
        Error("WebDAV proxy must be http://host:port or https://host:port");
    return 1;
}

#ifndef WEB_HTTP_ONLY
static int tlsmanager_supports_https_proxy(void)
{
    char info[192];
    unsigned int major;
    unsigned int minor;
    int used;

    memset(info, 0, sizeof(info));
    used = 0;
    if(_swix(TLSMANAGER_INFO, _INR(0,2) | _OUT(2),
             0, info, sizeof(info), &used) ||
       used <= 0 || used > (int)sizeof(info) ||
       sscanf(info, "ABI 1; TLSManager %u.%u", &major, &minor) != 2)
        return 0;
    return major > 0 || minor >= 6;
}
#endif

void WebIssue(const char *fmt, ...)
{
    char *var;
    FILE *f;
    va_list ap;

    var = get_var("LanMan98$TLSLog");
    if(var == NULL || *var == 0)
        return;
    f = fopen(var, "a");
    if(f == NULL)
        return;
    va_start(ap, fmt);
    vfprintf(f, fmt, ap);
    va_end(ap);
    fputc('\n', f);
    fclose(f);
}

void WebIOInit(web_io *io)
{
    memset(io, 0, sizeof(*io));
    io->tls = -1;
}

void WebIOClose(web_io *io)
{
    io->forward_proxy = 0;
    if(io->ahead)
    {
        Free(io->ahead);
        io->ahead = NULL;
        io->ahead_pos = 0;
        io->ahead_end = 0;
    }
#ifndef WEB_HTTP_ONLY
    if(io->tls != -1)
    {
        /* WebDAV uses Connection: close. Do not SSL_shutdown against a peer
           that has already FINed — that fights a half-dead session. */
        _swix(TLSMANAGER_CLOSE, _INR(0,1), 0, io->tls);
        io->tls = -1;
    }
#endif
    if(io->tcp)
    {
        TcpConnDestruct(io->tcp);
        io->tcp = NULL;
    }
}

void WebIOOpen(web_io *io, const web_url *url)
{
    tcp_addr_t address;
    web_url proxy;
    const char *connect_host;
    int connect_port;
    int have_proxy;
#ifndef WEB_HTTP_ONLY
    tlsmanager_open_block block;
    _kernel_oserror *error;
#endif

    address = NULL;
    if(io->tcp || io->tls != -1)
        return;
    have_proxy = proxy_setting(&proxy);
    io->forward_proxy = have_proxy && !proxy.secure && !url->secure;
    if(url->secure || (have_proxy && proxy.secure))
    {
#ifdef WEB_HTTP_ONLY
        Error("This build supports WebDAV over HTTP only");
#else
        error = _swix(OS_Module, _INR(0,1), 18, "TLSManager");
        if(error)
        {
            WebIssue("TLS: HTTPS WebDAV requires !TLSManager startup");
            Error("Open WebDAV Login to start TLSManager before mounting HTTPS");
        }
        if(have_proxy && proxy.secure && !tlsmanager_supports_https_proxy())
        {
            WebIssue("TLS: HTTPS proxy requires TLSManager 0.06 or later");
            Error("HTTPS proxy needs TLSManager 0.06; reopen WebDAV Login");
        }
        memset(&block, 0, sizeof(block));
        block.size = sizeof(block);
        block.abi = TLSMANAGER_ABI_VERSION;
        block.hostname = url->host;
        block.hostname_length = strlen(url->host);
        block.port = url->port;
        block.connect_timeout_cs = TLS_CONNECT_TIMEOUT;
        block.io_timeout_cs = WEB_TLS_TIMEOUT;
        block.target_secure = url->secure;
        if(have_proxy)
        {
            block.proxy_hostname = proxy.host;
            block.proxy_hostname_length = strlen(proxy.host);
            block.proxy_port = proxy.port;
            block.proxy_secure = proxy.secure;
            WebIssue("%s proxy CONNECT via %s:%d to %s:%d",
                     proxy.secure ? "HTTPS" : "HTTP", proxy.host,
                     proxy.port, url->host, url->port);
        }
        error = _swix(TLSMANAGER_OPEN, _INR(0,2) | _OUT(0), 0, &block,
                      sizeof(block), &io->tls);
        if(error)
        {
            WebIssue("TLS open for %s:%d: %s", url->host, url->port, error->errmess);
            ErrorErr(error);
        }
        ExceptTry
        {
            WebIssue("TLS starting handshake for %s:%d (timeout %d cs)", url->host, url->port, WEB_TLS_TIMEOUT);
            tls_call(TLSMANAGER_STEP, io->tls, NULL, 0, WEB_TLS_TIMEOUT);
            WebIssue("TLS handshake successful for %s:%d", url->host, url->port);
        }
        ExceptCatch
        {
            _kernel_oserror *caught = ExceptCaught();

            WebIssue("TLS handshake failed for %s:%d: %s", url->host, url->port,
                     caught && caught->errmess[0] ? caught->errmess : "no error given");
            WebIOClose(io);
            ExceptRethrow();
        }
        return;
#endif
    }
    if(!url->secure && (!have_proxy || !proxy.secure))
    {
        connect_host = have_proxy ? proxy.host : url->host;
        connect_port = have_proxy ? proxy.port : url->port;
        ThreadSetTimeout(WEB_TIMEOUT);
        ExceptTry
        {
            address = TcpResolve((char *)connect_host);
            if(!address)
            {
                WebIssue("connect: WebDAV endpoint name could not be resolved");
                Error("WebDAV endpoint name could not be resolved");
            }
            io->tcp = TcpCall(0, connect_port, address);
        }
        ExceptCatch
        {
            _kernel_oserror *caught = ExceptCaught();

            WebIssue("connect: %s", caught && caught->errmess[0] ? caught->errmess : "no error given");
            ThreadResetTimeout();
            if(address) TcpAddrDestruct(address);
            WebIOClose(io);
            ExceptRethrow();
        }
        ThreadResetTimeout();
        TcpAddrDestruct(address);
        return;
    }
}

int WebIORead(web_io *io, void *buffer, int size)
{
    int got;

    if(io->ahead && io->ahead_pos < io->ahead_end)
    {
        got = io->ahead_end - io->ahead_pos;
        if(got > size) got = size;
        memcpy(buffer, io->ahead + io->ahead_pos, got);
        io->ahead_pos += got;
        if(io->ahead_pos >= io->ahead_end)
        {
            Free(io->ahead);
            io->ahead = NULL;
            io->ahead_pos = 0;
            io->ahead_end = 0;
        }
        return got;
    }
#ifndef WEB_HTTP_ONLY
    if(io->tls != -1)
        return tls_call(TLSMANAGER_READ, io->tls, buffer, size, WEB_TLS_TIMEOUT);
#endif
    ThreadSetTimeout(WEB_TIMEOUT);
    ExceptTry { got = TcpReadSome(buffer, size, io->tcp); }
    ExceptCatch { ThreadResetTimeout(); ExceptRethrow(); }
    ThreadResetTimeout();
    return got;
}

void WebIOWrite(web_io *io, const void *buffer, int size)
{
    const char *position;
#ifndef WEB_HTTP_ONLY
    int used;
#endif

    position = buffer;
#ifndef WEB_HTTP_ONLY
    if(io->tls != -1)
    {
        while(size)
        {
            used = tls_call(TLSMANAGER_WRITE, io->tls,
                            (void *)position, size, WEB_TLS_TIMEOUT);
            if(used <= 0 || used > size)
            {
                WebIssue("TLS: WebDAV TLS connection stopped sending");
                Error("WebDAV TLS connection stopped sending");
            }
            position += used;
            size -= used;
        }
        return;
    }
#endif
    ThreadSetTimeout(WEB_TIMEOUT);
    ExceptTry { TcpWrite((void *)buffer, size, io->tcp); }
    ExceptCatch
    {
        _kernel_oserror *caught = ExceptCaught();

        WebIssue("connect: %s", caught && caught->errmess[0] ? caught->errmess : "no error given");
        ThreadResetTimeout();
        ExceptRethrow();
    }
    ThreadResetTimeout();
}
