src/server/daemon/protocol.c

Sat, 30 Mar 2024 12:35:09 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 30 Mar 2024 12:35:09 +0100
changeset 513
9a49c245a49c
parent 433
39fe86ae4db0
permissions
-rw-r--r--

change net_write to attempt to write all bytes, improve error handling

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2013 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "protocol.h"

#include "session.h"
#include "httplistener.h"
#include "request.h"

#include "../util/pblock.h"
#include "../util/pool.h"
#include "../util/io.h"
#include "../util/util.h"
#include "../util/strbuf.h"

#define HTTP_SCHEME  "http"
#define HTTPS_SCHEME "https"

void protocol_status(Session *sn, Request *rq, int n, const char *m) {
    rq->status_num = n;

    const char *msg = m ? m : protocol_status_message(n);

    pb_param *pp = pblock_removekey(pb_key_status, rq->srvhdrs);
    if (pp != NULL) {
        param_free(pp);
    }

    pp = pblock_key_param_create(rq->srvhdrs, pb_key_status, msg, strlen(msg));
    pblock_kpinsert(pb_key_status, pp, rq->srvhdrs);
}


/*
 * http_status_message from Open Webserver (frame/http.cpp)
 * TODO: replace, use cxmutstr
 */
NSAPI_PUBLIC const char * protocol_status_message (int code)
{
    WS_ASSERT(code > 0);
    
    const char *r;

    switch (code)
    {
        case PROTOCOL_CONTINUE : // 100
            r = "Continue";
            break;
        case PROTOCOL_SWITCHING: //101
            r = "Switching Protocols";
            break;
        case PROTOCOL_OK: // 200
            r = "OK";
            break;
        case PROTOCOL_CREATED: // 201
            r = "Created";
            break;
        case PROTOCOL_ACCEPTED: // 202
            r = "Accepted";
            break;
        case PROTOCOL_NONAUTHORITATIVE: // 203
            r = "Non-Authoritative Information";
            break;
        case PROTOCOL_NO_CONTENT: //204
        /* There is another define to PROTOCOL_NO_RESPONSE for 204 in nsapi.h
           The spec maps this to No Content.
           Hence cahnging this to No Content
        */
            r = "No Content";
            break;
        case PROTOCOL_RESET_CONTENT: // 205
            r = "Reset Content";
            break;
        case PROTOCOL_PARTIAL_CONTENT: // 206
            r = "Partial Content";
            break;
        case PROTOCOL_MULTI_STATUS: // 207
            r = "Multi Status";
            break;
        case PROTOCOL_MULTIPLE_CHOICES: // 300
            r = "Multiple Choices";
            break;
        case PROTOCOL_MOVED_PERMANENTLY: // 301
            r = "Moved Permanently";
            break;
        case PROTOCOL_REDIRECT:          // 302
            r = "Moved Temporarily"; /* The spec actually says "Found" */
            break;
        case PROTOCOL_SEE_OTHER:         // 303
            r = "See Other";
            break;
        case PROTOCOL_NOT_MODIFIED:      // 304
            r = "Use local copy";    /* The spec actually says "Not Modified" */
            break;
        case PROTOCOL_USE_PROXY:         // 305
            r = "Use Proxy";
            break;
        case PROTOCOL_TEMPORARY_REDIRECT: // 307
            r = "Temporary Redirect";
            break;
        case PROTOCOL_BAD_REQUEST:        // 400
            r = "Bad request";
            break;
        case PROTOCOL_UNAUTHORIZED:       // 401
            r = "Unauthorized";
            break;
        case PROTOCOL_PAYMENT_REQUIRED:   // 402
            r = "Payment Required";
            break;
        case PROTOCOL_FORBIDDEN:          // 403
            r = "Forbidden";
            break;
        case PROTOCOL_NOT_FOUND:         // 404
            r = "Not found";
            break;
        case PROTOCOL_METHOD_NOT_ALLOWED: // 405                /* HTTP/1.1 */
            r = "Method Not Allowed";
            break;
        case PROTOCOL_NOT_ACCEPTABLE: // 406                /* HTTP/1.1 */
            r = "Not Acceptable";
            break;
        case PROTOCOL_PROXY_UNAUTHORIZED: // 407
            r = "Proxy Authentication Required";
            break;
        case PROTOCOL_REQUEST_TIMEOUT:    // 408            /* HTTP/1.1 */
            r = "Request Timeout";
            break;
        case PROTOCOL_CONFLICT:           // 409
            r = "Conflict";                         /* HTTP/1.1 */
            break;
        case PROTOCOL_GONE:           // 410
            r = "Gone";                         /* HTTP/1.1 */
            break;
        case PROTOCOL_LENGTH_REQUIRED:    // 411                /* HTTP/1.1 */
            r = "Length Required";
            break;
        case PROTOCOL_PRECONDITION_FAIL:  // 412                /* HTTP/1.1 */
            r = "Precondition Failed";
            break;
        case PROTOCOL_ENTITY_TOO_LARGE:   // 413                /* HTTP/1.1 */
            r = "Request Entity Too Large";
            break;
        case PROTOCOL_URI_TOO_LARGE:      // 414                /* HTTP/1.1 */
            r = "Request-URI Too Large";
            break;
        case PROTOCOL_UNSUPPORTED_MEDIA_TYPE: // 415
            r = "Unsupported Media Type";
            break;
        case PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE: // 416
            r = "Requested range not satisfiable";
            break;
        case PROTOCOL_EXPECTATION_FAILED:     // 417
            r = "Expectation Failed";
            break;
        case PROTOCOL_LOCKED:     // 423
            r = "Locked";
            break;
        case PROTOCOL_FAILED_DEPENDENCY:     // 424
            r = "Failed Dependency";
            break;
        case PROTOCOL_SERVER_ERROR:           // 500
            r = "Server Error";           /* The spec actually says "Internal Server Error" */
            break;
        case PROTOCOL_NOT_IMPLEMENTED:        // 501
            r = "Not Implemented";
            break;
        case PROTOCOL_BAD_GATEWAY:            // 502
            r = "Bad Gateway";
            break;
        case PROTOCOL_SERVICE_UNAVAILABLE:    // 503
            r = "Service Unavailable";
            break;
        case PROTOCOL_GATEWAY_TIMEOUT:        // 504            /* HTTP/1.1 */
            r = "Gateway Timeout";
            break;
        case PROTOCOL_VERSION_NOT_SUPPORTED:  // 505            /* HTTP/1.1 */
            r = "HTTP Version Not Supported";
            break;
        case PROTOCOL_INSUFFICIENT_STORAGE:  // 507
            r = "Insufficient Storage";
            break;
        default:
            switch (code / 100)
            {
                case 1:
                    r = "Information";
                    break;
                case 2:
                    r = "Success";
                    break;
                case 3:
                    r = "Redirect";
                    break;
                case 4:
                    r = "Client error";
                    break;
                case 5:
                    r = "Server error";
                    break;
                default:
                    r = "Unknown reason";
                    break;
            }
            break;
    }

    return r;
}


void add_http_status_line(CxBuffer *out, pool_handle_t *pool, Request *rq) {
    cxBufferWrite("HTTP/1.1 ", 1, 9, out);

    char status_code_str[8];
    int sc_len = snprintf(status_code_str, 8, "%d ", rq->status_num);
    cxBufferWrite(status_code_str, 1, sc_len, out);
    
    char *scmsg = pblock_findkeyval(pb_key_status, rq->srvhdrs);
    if(scmsg == NULL) {
        scmsg = "OK";
    }
    cxBufferWrite(scmsg, 1, strlen(scmsg), out);

    cxBufferWrite("\r\n", 1, 2, out);
}

void add_http_response_header(CxBuffer *out, Request *rq) {
    pblock   *h = rq->srvhdrs;
    pb_entry *p;

    for(int i=0;i<h->hsize;i++) {
        p = h->ht[i];
        while(p != NULL) {           
            /* from http.cpp */
            const pb_key *key = PARAM_KEY(p->param);
            if (key == pb_key_status || key == pb_key_server || key == pb_key_date) {
                /* Skip internal Status:, Server:, and Date: information */
                p = p->next;
                continue;
            }
            /* end http.cpp */

            char *name  = p->param->name;
            char *value = p->param->value;

            // make first char of name uppercase
            size_t name_len = strlen(name);
            if(name[0] > 90) {
                /*
                 * make first char uppercase and write the remaining chars
                 * unmodified to the buffer
                 */
                cxBufferPut(out, name[0]-32);
                if(name_len > 1) {
                    cxBufferWrite(name+1, 1, name_len-1, out);
                }
            } else {
                // first char is already uppercase so just write the name
                cxBufferWrite(name, 1, name_len, out);
            }

            cxBufferWrite(": ", 1, 2, out);
            cxBufferWrite(value, 1, strlen(value), out);
            cxBufferWrite("\r\n", 1, 2, out);

            p = p->next;
        }
    }
}

struct HttpResponseWriter {
    Session *sn;
    Request *rq;
    CxBuffer buf;
};

HttpResponseWriter *http_create_response(Session *sn, Request *rq) {
    HttpResponseWriter *writer = pool_malloc(sn->pool, sizeof(HttpResponseWriter));
    if(!writer) {
        return NULL;
    }
    if(cxBufferInit(&writer->buf, NULL, 512, pool_allocator(sn->pool), CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS)) {
        pool_free(sn->pool, writer);
        return NULL;
    }
    writer->sn = sn;
    writer->rq = rq;
    
    if(rq->status_num == -1) {
        protocol_status(sn, rq, 200, "OK");
    }
    
    // add the http status line to the output buffer
    add_http_status_line(&writer->buf, sn->pool, rq);
    
    // add date header
    struct tm mtms;
    struct tm *mtm = system_gmtime(&rq->req_start, &mtms);
    char date[HTTP_DATE_LEN + 1];
    strftime(date, HTTP_DATE_LEN, HTTP_DATE_FMT, mtm);
    cxBufferWrite("Date: ", 1, 6, &writer->buf);
    cxBufferWrite(date, 1, strlen(date), &writer->buf);
    cxBufferWrite("\r\n", 1, 2, &writer->buf);
    
    // add server header
    cxBufferWrite("Server: webserver\r\n", 1, 19, &writer->buf);
    
    // check content length ans transfer encoding
    char *ctlen = pblock_findkeyval(pb_key_content_length, rq->srvhdrs);
    char *enc = pblock_findkeyval(pb_key_transfer_encoding, rq->srvhdrs);
    if(ctlen && enc) {
        pblock_removekey(pb_key_transfer_encoding, rq->srvhdrs);
    }
    if(!ctlen) {
        // set transfer-encoding header
        if(!enc) {
            pblock_kvinsert(
                    pb_key_transfer_encoding,
                    "chunked",
                    7,
                    rq->srvhdrs);
        } else if(strcmp(enc, "chunked")) {
            pblock_removekey(pb_key_transfer_encoding, rq->srvhdrs);
            pblock_kvinsert(
                    pb_key_transfer_encoding,
                    "chunked",
                    7,
                    rq->srvhdrs);
        }
        
        // set stream property
        httpstream_enable_chunked_write(sn->csd);
        rq->rq_attr.chunked = 1;
    }
    
    // add header from rq->srvhdrs
    add_http_response_header(&writer->buf, rq);
    
    // add connection header
    if(rq->rq_attr.keep_alive) {
        cxBufferWrite("Connection: keep-alive\r\n", 1, 24, &writer->buf);
        pblock_kvinsert(pb_key_connection, "keep-alive", 10, rq->srvhdrs);
    } else {
        cxBufferWrite("Connection: close\r\n", 1, 19, &writer->buf);
        pblock_kvinsert(pb_key_connection, "close", 5, rq->srvhdrs);
    }

    // response header end
    cxBufferWrite("\r\n", 1, 2,& writer->buf);
    
    // reset pos (required for http_start_response_async)
    writer->buf.pos = 0;
    
    return writer;
}

int http_send_response(HttpResponseWriter *writer) {
    Connection *conn = ((NSAPISession*)writer->sn)->connection;
    CxBuffer *buf = &writer->buf;
    
    // flush buffer to the socket
    int ret = 0;
    while(buf->pos < buf->size) {
        int w = conn->write(conn, buf->space + buf->pos, buf->size - buf->pos);
        if(w <= 0) {
            if(conn->ssl) {
                if(conn->ssl_error == SSL_ERROR_WANT_WRITE) {
                    return 1;
                }
            } else {
                if(errno == EWOULDBLOCK) {
                    return 1;
                }
            }
            ret = -1;
            break;
        }
        buf->pos += w;
    }
     
    if(ret == 0) {
        writer->rq->senthdrs = 1;
    }
    
    cxBufferDestroy(buf);
    pool_free(writer->sn->pool, writer);
    
    return ret;
}


int http_start_response(Session *sn, Request *rq) {
    HttpResponseWriter *writer = http_create_response(sn, rq);
    if(!writer) {
        return 1;
    }
    
    return http_send_response(writer);
}

int http_send_continue(Session *sn) {
    NSAPISession *s = (NSAPISession*)sn;
    cxstring msg = CX_STR("HTTP/1.1 100 Continue\r\n\r\n");
    int w = s->connection->write(s->connection, msg.ptr, msg.length);
    if(w != msg.length) {
        return 1;
    }
    return 0;
}

int request_header(char *name, char **value, Session *sn, Request *rq) {
    const pb_key *key = pblock_key(name);
    pb_param *pp = pblock_findkey(key, rq->headers);
    if(pp != NULL) {
        *value = pp->value;
        return REQ_PROCEED;
    } else {
        //return REQ_ABORTED;
        *value = NULL;
        return REQ_NOACTION;
    }
}

char *http_uri2url(const char *prefix, const char *suffix) {
    // TODO: implement
    return NULL;
}

char *http_uri2url_dynamic(const char *prefix, const char *suffix,
                                    Session *sn, Request *rq)
{
    // TODO: implement
    return NULL;
}

void http_get_scheme_host_port(
        Session *sn,
        Request *rq,
        char **scheme,
        char **host,
        uint16_t *port)
{
    Connection *con = ((NSAPISession*)sn)->connection;
    
    if(con->ssl) {
        *scheme = HTTPS_SCHEME;
    } else {
        *scheme = HTTP_SCHEME;
    }
    
    NSAPIRequest *request = (NSAPIRequest*)rq;
    
    if(request->host) {
        *host = request->host;
    } else {
        *host = "localhost";
    }
    
    *port = request->port;
    
}

mercurial