summaryrefslogtreecommitdiff
path: root/xs_httpd.h
blob: 57759c4667d0b539b46c990052ece7feb6095e33 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/* copyright (c) 2022 - 2025 grunfink et al. / MIT license */

#ifndef _XS_HTTPD_H

#define _XS_HTTPD_H

xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size);
void xs_httpd_response(FILE *f, int status, const char *status_text,
                        const xs_dict *headers, const xs_val *body, int b_size);


#ifdef XS_IMPLEMENTATION

xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size)
/* processes an httpd connection */
{
    xs *q_vars = NULL;
    xs *p_vars = NULL;
    xs *l1;
    const char *v;
    char *saveptr;

    xs_socket_timeout(fileno(f), 2.0, 0.0);

    /* read the first line and split it */
    l1 = xs_strip_i(xs_readline(f));
    char *raw_path;
    const char *mtd;
    const char *proto;

    if (!(mtd = strtok_r(l1, " ", &saveptr)) ||
        !(raw_path = strtok_r(NULL, " ", &saveptr)) ||
        !(proto = strtok_r(NULL, " ", &saveptr)) ||
        strtok_r(NULL, " ", &saveptr))
        return NULL;

    if (!xs_is_string(mtd) || !xs_is_string(raw_path) || !xs_is_string(proto))
        return NULL;

    xs_dict *req = xs_dict_new();

    req = xs_dict_append(req, "method", mtd);
    req = xs_dict_append(req, "raw_path", raw_path);
    req = xs_dict_append(req, "proto",  proto);

    {
        char *q = strchr(raw_path, '?');

        /* get the variables */
        if (q) {
                *q++ = '\0';
                q_vars = xs_url_vars(q);
        }
        /* store the path */
        req = xs_dict_append(req, "path", raw_path);
    }

    /* read the headers */
    for (;;) {
        xs *l;

        l = xs_strip_i(xs_readline(f));

        /* done with the header? */
        if (strcmp(l, "") == 0)
            break;

        /* split header and content */
        char *cnt = strchr(l, ':');
        if (!cnt)
            continue;

        *cnt++ = '\0';
        cnt += strspn(cnt, " \r\n\t\v\f");
        l = xs_rstrip_chars_i(l, " \r\n\t\v\f");

        if (!xs_is_string(cnt))
            continue;

        req = xs_dict_append(req, xs_tolower_i(l), cnt);
    }

    xs_socket_timeout(fileno(f), 5.0, 0.0);

    if ((v = xs_dict_get(req, "content-length")) != NULL) {
        /* if it has a payload, load it */
        *p_size  = atoi(v);
        *payload = xs_read(f, p_size);
    }

    v = xs_dict_get(req, "content-type");

    if (*payload && v && strcmp(v, "application/x-www-form-urlencoded") == 0) {
        p_vars  = xs_url_vars(*payload);
    }
    else
    if (*payload && v && xs_startswith(v, "multipart/form-data")) {
        p_vars = xs_multipart_form_data(*payload, *p_size, v);
    }
    else
        p_vars = xs_dict_new();

    req = xs_dict_append(req, "q_vars",  q_vars);
    req = xs_dict_append(req, "p_vars",  p_vars);

    if (errno)
        req = xs_free(req);

    return req;
}


void xs_httpd_response(FILE *f, int status, const char *status_text,
                        const xs_dict *headers, const xs_val *body, int b_size)
/* sends an httpd response */
{
    fprintf(f, "HTTP/1.1 %d %s\r\n", status, status_text ? status_text : "");

    const xs_str *k;
    const xs_val *v;

    xs_dict_foreach(headers, k, v) {
        fprintf(f, "%s: %s\r\n", k, v);
    }

    if (b_size != 0)
        fprintf(f, "content-length: %d\r\n", b_size);

    fprintf(f, "\r\n");

    if (body != NULL && b_size != 0)
        fwrite(body, b_size, 1, f);
}


#endif /* XS_IMPLEMENTATION */

#endif /* XS_HTTPD_H */