add support for handling redirects via a script
[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 = {
45                  .type = BLOBMSG_TYPE_STRING,
46         };
47         struct blob_attr *tb;
48
49         blobmsg_parse_array(&policy, 1, &tb, blobmsg_data(data), blobmsg_data_len(data));
50         if (!tb)
51                 return;
52
53         uh_http_header(cl, 302, "Found");
54         ustream_printf(cl->us, "Content-Length: 0\r\n");
55         ustream_printf(cl->us, "Location: %s\r\n\r\n",
56                        blobmsg_get_string(tb));
57         uh_request_done(cl);
58         *cur_url = NULL;
59
60         handler_ret = 1;
61         json_script_abort(ctx);
62 }
63
64 static void
65 handle_set_uri(struct json_script_ctx *ctx, struct blob_attr *data)
66 {
67         struct client *cl = cur_client;
68         static struct blobmsg_policy policy = {
69                  .type = BLOBMSG_TYPE_STRING,
70         };
71         struct blob_attr *tb;
72         struct blob_attr *old_url = blob_data(cl->hdr.head);
73
74         blobmsg_parse_array(&policy, 1, &tb, blobmsg_data(data), blobmsg_data_len(data));
75         if (!tb)
76                 return;
77
78         blob_buf_init(&b, 0);
79         blob_put_raw(&b, blob_next(old_url), blob_len(cl->hdr.head) - blob_pad_len(old_url));
80
81         /* replace URL in client header cache */
82         blob_buf_init(&cl->hdr, 0);
83         blobmsg_add_string(&cl->hdr, "URL", blobmsg_get_string(tb));
84         blob_put_raw(&cl->hdr, blob_data(b.head), blob_len(b.head));
85         *cur_url = blobmsg_data(blob_data(cl->hdr.head));
86         cur_vars = NULL;
87
88         blob_buf_init(&b, 0);
89
90         handler_ret = 1;
91         json_script_abort(ctx);
92 }
93
94 static void
95 handle_command(struct json_script_ctx *ctx, const char *name,
96                struct blob_attr *data, struct blob_attr *vars)
97 {
98         static const struct {
99                 const char *name;
100                 void (*func)(struct json_script_ctx *ctx, struct blob_attr *data);
101         } cmds[] = {
102                 { "redirect", handle_redirect },
103                 { "set_uri", handle_set_uri }
104         };
105         int i;
106
107         for (i = 0; i < ARRAY_SIZE(cmds); i++) {
108                 if (!strcmp(cmds[i].name, name)) {
109                         cmds[i].func(ctx, data);
110                         return;
111                 }
112         }
113 }
114
115 static const char *
116 handle_var(struct json_script_ctx *ctx, const char *name,
117            struct blob_attr *vars)
118 {
119         struct client *cl = cur_client;
120         struct env_var *cur;
121         static struct path_info empty_path;
122
123         if (!cur_vars) {
124                 struct path_info *p = uh_path_lookup(cl, *cur_url);
125
126                 if (!p)
127                         p = &empty_path;
128
129                 cur_vars = uh_get_process_vars(cl, p);
130         }
131
132         for (cur = cur_vars; cur->name; cur++) {
133                 if (!strcmp(cur->name, name))
134                         return cur->value;
135         }
136         return NULL;
137 }
138
139 static void
140 handler_init(void)
141 {
142         if (handler_ctx.handle_command)
143                 return;
144
145         json_script_init(&handler_ctx);
146         handler_ctx.handle_command = handle_command;
147         handler_ctx.handle_var = handle_var;
148 }
149
150 static bool set_handler(struct json_script_file **dest, struct blob_attr *data)
151 {
152         if (!data)
153                 return true;
154
155         *dest = json_script_file_from_blobmsg(NULL, blobmsg_data(data), blobmsg_data_len(data));
156         return *dest;
157 }
158
159 int uh_handler_add(const char *file)
160 {
161         enum {
162                 H_REQUEST,
163                 H_FALLBACK,
164                 __H_MAX,
165         };
166         struct blobmsg_policy policy[__H_MAX] = {
167                 [H_REQUEST] = { "request", BLOBMSG_TYPE_ARRAY },
168                 [H_FALLBACK] = { "fallback", BLOBMSG_TYPE_ARRAY },
169         };
170         struct blob_attr *tb[__H_MAX];
171         struct handler *h;
172
173         handler_init();
174         blob_buf_init(&b, 0);
175
176         if (!blobmsg_add_json_from_file(&b, file))
177                 return -1;
178
179         blobmsg_parse(policy, __H_MAX, tb, blob_data(b.head), blob_len(b.head));
180         if (!tb[H_REQUEST] && !tb[H_FALLBACK])
181                 return -1;
182
183         h = calloc(1, sizeof(*h));
184         if (!set_handler(&h->request, tb[H_REQUEST]) ||
185             !set_handler(&h->fallback, tb[H_FALLBACK])) {
186                 free(h->request);
187                 free(h->fallback);
188                 free(h);
189                 return -1;
190         }
191
192         list_add_tail(&h->list, &handlers);
193         return 0;
194 }
195
196 int uh_handler_run(struct client *cl, char **url, bool fallback)
197 {
198         struct json_script_file *f;
199         struct handler *h;
200
201         cur_client = cl;
202         cur_url = url;
203         cur_vars = NULL;
204
205         handler_ret = 0;
206
207         list_for_each_entry(h, &handlers, list) {
208                 f = fallback ? h->fallback : h->request;
209                 if (!f)
210                         continue;
211
212                 blob_buf_init(&b, 0);
213                 json_script_run_file(&handler_ctx, f, b.head);
214                 if (handler_ctx.abort)
215                         break;
216         }
217
218         return handler_ret;
219 }