uhttpd: add support for adding arbitrary headers via handler scripts
[project/uhttpd.git] / handler.c
1 /*
2  * uhttpd - Tiny single-threaded httpd
3  *
4  *   Copyright (C) 2015 Felix Fietkau <nbd@openwrt.org>
5  *
6  * Permission to use, copy, modify, and/or distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18
19 #include <libubox/blobmsg.h>
20 #include <libubox/blobmsg_json.h>
21 #include <libubox/json_script.h>
22
23 #include "uhttpd.h"
24
25 struct handler {
26         struct list_head list;
27
28         struct json_script_file *request;
29         struct json_script_file *fallback;
30 };
31
32 static LIST_HEAD(handlers);
33 static struct json_script_ctx handler_ctx;
34 static struct env_var *cur_vars;
35 static struct blob_buf b;
36 static int handler_ret;
37 static struct client *cur_client;
38 static char **cur_url;
39
40 static void
41 handle_redirect(struct json_script_ctx *ctx, struct blob_attr *data)
42 {
43         struct client *cl = cur_client;
44         static struct blobmsg_policy policy[3] = {
45                  { .type = BLOBMSG_TYPE_STRING },
46                  { .type = BLOBMSG_TYPE_INT32 },
47                  { .type = BLOBMSG_TYPE_STRING },
48         };
49         struct blob_attr *tb[3];
50         const char *status = "Found";
51         int code = 302;
52
53         blobmsg_parse_array(policy, ARRAY_SIZE(policy), tb, blobmsg_data(data), blobmsg_data_len(data));
54         if (!tb[0])
55                 return;
56
57         if (tb[1]) {
58                 code = blobmsg_get_u32(tb[1]);
59                 if (tb[2])
60                         status = blobmsg_get_string(tb[2]);
61         }
62
63         uh_http_header(cl, code, status);
64         if (!uh_use_chunked(cl))
65                 ustream_printf(cl->us, "Content-Length: 0\r\n");
66         ustream_printf(cl->us, "Location: %s\r\n\r\n",
67                        blobmsg_get_string(tb[0]));
68         uh_request_done(cl);
69         *cur_url = NULL;
70
71         handler_ret = 1;
72         json_script_abort(ctx);
73 }
74
75 static void
76 handle_set_uri(struct json_script_ctx *ctx, struct blob_attr *data)
77 {
78         struct client *cl = cur_client;
79         static struct blobmsg_policy policy = {
80                  .type = BLOBMSG_TYPE_STRING,
81         };
82         struct blob_attr *tb;
83         struct blob_attr *old_url = blob_data(cl->hdr.head);
84
85         blobmsg_parse_array(&policy, 1, &tb, blobmsg_data(data), blobmsg_data_len(data));
86         if (!tb)
87                 return;
88
89         blob_buf_init(&b, 0);
90         blob_put_raw(&b, blob_next(old_url), blob_len(cl->hdr.head) - blob_pad_len(old_url));
91
92         /* replace URL in client header cache */
93         blob_buf_init(&cl->hdr, 0);
94         blobmsg_add_string(&cl->hdr, "URL", blobmsg_get_string(tb));
95         blob_put_raw(&cl->hdr, blob_data(b.head), blob_len(b.head));
96         *cur_url = blobmsg_data(blob_data(cl->hdr.head));
97         cur_vars = NULL;
98
99         blob_buf_init(&b, 0);
100
101         handler_ret = 1;
102         json_script_abort(ctx);
103 }
104
105 static void
106 handle_add_header(struct json_script_ctx *ctx, struct blob_attr *data)
107 {
108         struct client *cl = cur_client;
109         static struct blobmsg_policy policy[2] = {
110                  { .type = BLOBMSG_TYPE_STRING },
111                  { .type = BLOBMSG_TYPE_STRING },
112         };
113         struct blob_attr *tb[2];
114
115         blobmsg_parse_array(policy, ARRAY_SIZE(tb), tb, blobmsg_data(data), blobmsg_data_len(data));
116         if (!tb[0] || !tb[1])
117                 return;
118
119         blobmsg_add_string(&cl->hdr_response, blobmsg_get_string(tb[0]),
120                            blobmsg_get_string(tb[1]));
121 }
122
123 static void
124 handle_command(struct json_script_ctx *ctx, const char *name,
125                struct blob_attr *data, struct blob_attr *vars)
126 {
127         static const struct {
128                 const char *name;
129                 void (*func)(struct json_script_ctx *ctx, struct blob_attr *data);
130         } cmds[] = {
131                 { "redirect", handle_redirect },
132                 { "rewrite", handle_set_uri },
133                 { "add-header", handle_add_header },
134         };
135         int i;
136
137         for (i = 0; i < ARRAY_SIZE(cmds); i++) {
138                 if (!strcmp(cmds[i].name, name)) {
139                         cmds[i].func(ctx, data);
140                         return;
141                 }
142         }
143 }
144
145 static const char *
146 handle_var(struct json_script_ctx *ctx, const char *name,
147            struct blob_attr *vars)
148 {
149         struct client *cl = cur_client;
150         struct env_var *cur;
151         static struct path_info empty_path;
152
153         if (!cur_vars) {
154                 struct path_info *p = uh_path_lookup(cl, *cur_url);
155
156                 if (!p)
157                         p = &empty_path;
158
159                 cur_vars = uh_get_process_vars(cl, p);
160         }
161
162         for (cur = cur_vars; cur->name; cur++) {
163                 if (!strcmp(cur->name, name))
164                         return cur->value;
165         }
166         return NULL;
167 }
168
169 static void
170 handler_init(void)
171 {
172         if (handler_ctx.handle_command)
173                 return;
174
175         json_script_init(&handler_ctx);
176         handler_ctx.handle_command = handle_command;
177         handler_ctx.handle_var = handle_var;
178 }
179
180 static bool set_handler(struct json_script_file **dest, struct blob_attr *data)
181 {
182         if (!data)
183                 return true;
184
185         *dest = json_script_file_from_blobmsg(NULL, blobmsg_data(data), blobmsg_data_len(data));
186         return *dest;
187 }
188
189 int uh_handler_add(const char *file)
190 {
191         enum {
192                 H_REQUEST,
193                 H_FALLBACK,
194                 __H_MAX,
195         };
196         struct blobmsg_policy policy[__H_MAX] = {
197                 [H_REQUEST] = { "request", BLOBMSG_TYPE_ARRAY },
198                 [H_FALLBACK] = { "fallback", BLOBMSG_TYPE_ARRAY },
199         };
200         struct blob_attr *tb[__H_MAX];
201         struct handler *h;
202
203         handler_init();
204         blob_buf_init(&b, 0);
205
206         if (!blobmsg_add_json_from_file(&b, file))
207                 return -1;
208
209         blobmsg_parse(policy, __H_MAX, tb, blob_data(b.head), blob_len(b.head));
210         if (!tb[H_REQUEST] && !tb[H_FALLBACK])
211                 return -1;
212
213         h = calloc(1, sizeof(*h));
214         if (!set_handler(&h->request, tb[H_REQUEST]) ||
215             !set_handler(&h->fallback, tb[H_FALLBACK])) {
216                 free(h->request);
217                 free(h->fallback);
218                 free(h);
219                 return -1;
220         }
221
222         list_add_tail(&h->list, &handlers);
223         return 0;
224 }
225
226 int uh_handler_run(struct client *cl, char **url, bool fallback)
227 {
228         struct json_script_file *f;
229         struct handler *h;
230
231         cur_client = cl;
232         cur_url = url;
233         cur_vars = NULL;
234
235         handler_ret = 0;
236
237         list_for_each_entry(h, &handlers, list) {
238                 f = fallback ? h->fallback : h->request;
239                 if (!f)
240                         continue;
241
242                 blob_buf_init(&b, 0);
243                 json_script_run_file(&handler_ctx, f, b.head);
244                 if (handler_ctx.abort)
245                         break;
246         }
247
248         return handler_ret;
249 }