/*
 * Implement the centralised parts of the server side of SFTP.
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

#include "putty.h"
#include "ssh.h"
#include "sftp.h"

struct sftp_packet *sftp_handle_request(
    SftpServer *srv, struct sftp_packet *req)
{
    struct sftp_packet *reply;
    unsigned id;
    uint32_t flags;
    ptrlen path, dstpath, handle, data;
    uint64_t offset;
    unsigned length;
    struct fxp_attrs attrs;
    DefaultSftpReplyBuilder dsrb;
    SftpReplyBuilder *rb;

    if (req->type == SSH_FXP_INIT) {
        /*
         * Special case which doesn't have a request id at the start.
         */
        reply = sftp_pkt_init(SSH_FXP_VERSION);
        /*
         * Since we support only the lowest protocol version, we don't
         * need to take the min of this and the client's version, or
         * even to bother reading the client version number out of the
         * input packet.
         */
        put_uint32(reply, SFTP_PROTO_VERSION);
        return reply;
    }

    /*
     * Centralise the request id handling. We'll overwrite the type
     * code of the output packet later.
     */
    id = get_uint32(req);
    reply = sftp_pkt_init(0);
    put_uint32(reply, id);

    dsrb.rb.vt = &DefaultSftpReplyBuilder_vt;
    dsrb.pkt = reply;
    rb = &dsrb.rb;

    switch (req->type) {
      case SSH_FXP_REALPATH:
        path = get_string(req);
        if (get_err(req))
            goto decode_error;
        sftpsrv_realpath(srv, rb, path);
        break;

      case SSH_FXP_OPEN:
        path = get_string(req);
        flags = get_uint32(req);
        get_fxp_attrs(req, &attrs);
        if (get_err(req))
            goto decode_error;
        if ((flags & (SSH_FXF_READ|SSH_FXF_WRITE)) == 0) {
            fxp_reply_error(rb, SSH_FX_BAD_MESSAGE,
                            "open without READ or WRITE flag");
        } else if ((flags & (SSH_FXF_CREAT|SSH_FXF_TRUNC)) == SSH_FXF_TRUNC) {
            fxp_reply_error(rb, SSH_FX_BAD_MESSAGE,
                            "open with TRUNC but not CREAT");
        } else if ((flags & (SSH_FXF_CREAT|SSH_FXF_EXCL)) == SSH_FXF_EXCL) {
            fxp_reply_error(rb, SSH_FX_BAD_MESSAGE,
                            "open with EXCL but not CREAT");
        } else {
            sftpsrv_open(srv, rb, path, flags, attrs);
        }
        break;

      case SSH_FXP_OPENDIR:
        path = get_string(req);
        if (get_err(req))
            goto decode_error;
        sftpsrv_opendir(srv, rb, path);
        break;

      case SSH_FXP_CLOSE:
        handle = get_string(req);
        if (get_err(req))
            goto decode_error;
        sftpsrv_close(srv, rb, handle);
        break;

      case SSH_FXP_MKDIR:
        path = get_string(req);
        get_fxp_attrs(req, &attrs);
        if (get_err(req))
            goto decode_error;
        sftpsrv_mkdir(srv, rb, path, attrs);
        break;

      case SSH_FXP_RMDIR:
        path = get_string(req);
        if (get_err(req))
            goto decode_error;
        sftpsrv_rmdir(srv, rb, path);
        break;

      case SSH_FXP_REMOVE:
        path = get_string(req);
        if (get_err(req))
            goto decode_error;
        sftpsrv_remove(srv, rb, path);
        break;

      case SSH_FXP_RENAME:
        path = get_string(req);
        dstpath = get_string(req);
        if (get_err(req))
            goto decode_error;
        sftpsrv_rename(srv, rb, path, dstpath);
        break;

      case SSH_FXP_STAT:
        path = get_string(req);
        if (get_err(req))
            goto decode_error;
        sftpsrv_stat(srv, rb, path, true);
        break;

      case SSH_FXP_LSTAT:
        path = get_string(req);
        if (get_err(req))
            goto decode_error;
        sftpsrv_stat(srv, rb, path, false);
        break;

      case SSH_FXP_FSTAT:
        handle = get_string(req);
        if (get_err(req))
            goto decode_error;
        sftpsrv_fstat(srv, rb, handle);
        break;

      case SSH_FXP_SETSTAT:
        path = get_string(req);
        get_fxp_attrs(req, &attrs);
        if (get_err(req))
            goto decode_error;
        sftpsrv_setstat(srv, rb, path, attrs);
        break;

      case SSH_FXP_FSETSTAT:
        handle = get_string(req);
        get_fxp_attrs(req, &attrs);
        if (get_err(req))
            goto decode_error;
        sftpsrv_fsetstat(srv, rb, handle, attrs);
        break;

      case SSH_FXP_READ:
        handle = get_string(req);
        offset = get_uint64(req);
        length = get_uint32(req);
        if (get_err(req))
            goto decode_error;
        sftpsrv_read(srv, rb, handle, offset, length);
        break;

      case SSH_FXP_READDIR:
        handle = get_string(req);
        if (get_err(req))
            goto decode_error;
        sftpsrv_readdir(srv, rb, handle, INT_MAX, false);
        break;

      case SSH_FXP_WRITE:
        handle = get_string(req);
        offset = get_uint64(req);
        data = get_string(req);
        if (get_err(req))
            goto decode_error;
        sftpsrv_write(srv, rb, handle, offset, data);
        break;

      default:
        if (get_err(req))
            goto decode_error;
        fxp_reply_error(rb, SSH_FX_OP_UNSUPPORTED,
                        "Unrecognised request type");
        break;

      decode_error:
        fxp_reply_error(rb, SSH_FX_BAD_MESSAGE, "Unable to decode request");
    }

    return reply;
}

static void default_reply_ok(SftpReplyBuilder *reply)
{
    DefaultSftpReplyBuilder *d =
        container_of(reply, DefaultSftpReplyBuilder, rb);
    d->pkt->type = SSH_FXP_STATUS;
    put_uint32(d->pkt, SSH_FX_OK);
    put_stringz(d->pkt, "");
}

static void default_reply_error(
    SftpReplyBuilder *reply, unsigned code, const char *msg)
{
    DefaultSftpReplyBuilder *d =
        container_of(reply, DefaultSftpReplyBuilder, rb);
    d->pkt->type = SSH_FXP_STATUS;
    put_uint32(d->pkt, code);
    put_stringz(d->pkt, msg);
}

static void default_reply_name_count(SftpReplyBuilder *reply, unsigned count)
{
    DefaultSftpReplyBuilder *d =
        container_of(reply, DefaultSftpReplyBuilder, rb);
    d->pkt->type = SSH_FXP_NAME;
    put_uint32(d->pkt, count);
}

static void default_reply_full_name(SftpReplyBuilder *reply, ptrlen name,
                                    ptrlen longname, struct fxp_attrs attrs)
{
    DefaultSftpReplyBuilder *d =
        container_of(reply, DefaultSftpReplyBuilder, rb);
    d->pkt->type = SSH_FXP_NAME;
    put_stringpl(d->pkt, name);
    put_stringpl(d->pkt, longname);
    put_fxp_attrs(d->pkt, attrs);
}

static void default_reply_simple_name(SftpReplyBuilder *reply, ptrlen name)
{
    fxp_reply_name_count(reply, 1);
    fxp_reply_full_name(reply, name, PTRLEN_LITERAL(""), no_attrs);
}

static void default_reply_handle(SftpReplyBuilder *reply, ptrlen handle)
{
    DefaultSftpReplyBuilder *d =
        container_of(reply, DefaultSftpReplyBuilder, rb);
    d->pkt->type = SSH_FXP_HANDLE;
    put_stringpl(d->pkt, handle);
}

static void default_reply_data(SftpReplyBuilder *reply, ptrlen data)
{
    DefaultSftpReplyBuilder *d =
        container_of(reply, DefaultSftpReplyBuilder, rb);
    d->pkt->type = SSH_FXP_DATA;
    put_stringpl(d->pkt, data);
}

static void default_reply_attrs(
    SftpReplyBuilder *reply, struct fxp_attrs attrs)
{
    DefaultSftpReplyBuilder *d =
        container_of(reply, DefaultSftpReplyBuilder, rb);
    d->pkt->type = SSH_FXP_ATTRS;
    put_fxp_attrs(d->pkt, attrs);
}

const SftpReplyBuilderVtable DefaultSftpReplyBuilder_vt = {
    .reply_ok = default_reply_ok,
    .reply_error = default_reply_error,
    .reply_simple_name = default_reply_simple_name,
    .reply_name_count = default_reply_name_count,
    .reply_full_name = default_reply_full_name,
    .reply_handle = default_reply_handle,
    .reply_data = default_reply_data,
    .reply_attrs = default_reply_attrs,
};
