ksgi
/
sample.c
352 строки · 8.3 Кб
1/* $Id$ */
2/*
3* Copyright (c) 2014, 2015, 2017 Kristaps Dzonsons <kristaps@bsd.lv>
4*
5* Permission to use, copy, modify, and distribute this software for any
6* purpose with or without fee is hereby granted, provided that the above
7* copyright notice and this permission notice appear in all copies.
8*
9* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16*/
17#include <sys/types.h> /* size_t, ssize_t */18#include <stdarg.h> /* va_list */19#include <stddef.h> /* NULL */20#include <stdint.h> /* int64_t */21#include <stdlib.h>22#include <string.h> /* memset */23
24#include "kcgi.h"25#include "kcgihtml.h"26
27/*
28* Simple CGI application.
29* Compile it with `make samples` (or using gmake) and install it into
30* your web server's /cgi-bin.
31* The "template.xml" file should be in the /cgi-bin directory as well
32* and readable by the server process.
33* (Obviously this is just for a sample.)
34*
35* Assuming localhost/cgi-bin, the script is localhost/cgi-bin/sample.
36* The pages recognised are:
37*
38* - /cgi-bin/sample/index.html
39* - /cgi-bin/sample/template.html
40* - /cgi-bin/sample/senddata.html
41*
42* See the sendindex et al. functions for what these do.
43*/
44
45/* Recognised page requests. See pages[]. */
46enum page {47PAGE_INDEX,48PAGE_TEMPLATE,49PAGE_SENDDATA,50PAGE__MAX
51};52
53/*
54* All of the keys (input field names) we accept.
55* The key names are in the "keys" array.
56* See sendindex() for how these are used.
57*/
58enum key {59KEY_INTEGER,60KEY_FILE,61KEY_PAGECOUNT,62KEY_PAGESIZE,63KEY__MAX
64};65
66/*
67* The elements in our template file.
68* The element key names are in the "templs" array.
69* See sendtemplate() for how this is used.
70*/
71enum templ {72TEMPL_TITLE,73TEMPL_NAME,74TEMPL_REMOTE_ADDR,75TEMPL__MAX
76};77
78/*
79* We need a structure because we can't get the "r" from the request.
80* This is used by our template callback.
81*/
82struct tstrct {83struct khtmlreq req;84struct kreq *r;85};86
87/*
88* We'll use this to route pages by creating an array indexed by our
89* page.
90* Then when the page is parsed, we'll route directly into it.
91*/
92typedef void (*disp)(struct kreq *);93
94static void senddata(struct kreq *);95static void sendindex(struct kreq *);96static void sendtemplate(struct kreq *);97
98static const disp disps[PAGE__MAX] = {99sendindex, /* PAGE_INDEX */100sendtemplate, /* PAGE_TEMPLATE */101senddata, /* PAGE_SENDDATA */102};103
104static const struct kvalid keys[KEY__MAX] = {105{ kvalid_int, "integer" }, /* KEY_INTEGER */106{ NULL, "file" }, /* KEY_FILE */107{ kvalid_uint, "count" }, /* KEY_PAGECOUNT */108{ kvalid_uint, "size" }, /* KEY_PAGESIZE */109};110
111/*
112* Template key names (as in @@TITLE@@ in the file).
113*/
114static const char *const templs[TEMPL__MAX] = {115"title", /* TEMPL_TITLE */116"name", /* TEMPL_NAME */117"remote_addr", /* TEMPL_REMOTE_ADDR */118};119
120/*
121* Page names (as in the URL component) mapped from the first name part
122* of requests, e.g., /sample.cgi/index.html -> index -> PAGE_INDEX.
123*/
124static const char *const pages[PAGE__MAX] = {125"index", /* PAGE_INDEX */126"template", /* PAGE_TEMPLATE */127"senddata" /* PAGE_SENDDATA */128};129
130/*
131* Open an HTTP response with a status code and a particular
132* content-type, then open the HTTP content body.
133* You can call khttp_head(3) before this: CGI doesn't dictate any
134* particular header order.
135*/
136static void137resp_open(struct kreq *req, enum khttp http)138{
139enum kmime mime;140
141/*142* If we've been sent an unknown suffix like '.foo', we won't
143* know what it is.
144* Default to an octet-stream response.
145*/
146if (KMIME__MAX == (mime = req->mime))147mime = KMIME_APP_OCTET_STREAM;148
149khttp_head(req, kresps[KRESP_STATUS],150"%s", khttps[http]);151khttp_head(req, kresps[KRESP_CONTENT_TYPE],152"%s", kmimetypes[mime]);153khttp_body(req);154}
155
156/*
157* Callback for filling in a particular template part.
158* Let's just be simple for simplicity's sake.
159*/
160static int161template(size_t key, void *arg)162{
163struct tstrct *p = arg;164
165switch (key) {166case (TEMPL_TITLE):167khtml_puts(&p->req, "title");168break;169case (TEMPL_NAME):170khtml_puts(&p->req, "name");171break;172case (TEMPL_REMOTE_ADDR):173khtml_puts(&p->req, p->r->remote);174break;175default:176return(0);177}178
179return(1);180}
181
182/*
183* Demonstrates how to use templates.
184* Returns HTTP 200 and the template content.
185*/
186static void187sendtemplate(struct kreq *req)188{
189struct ktemplate t;190struct tstrct p;191
192memset(&t, 0, sizeof(struct ktemplate));193memset(&p, 0, sizeof(struct tstrct));194
195p.r = req;196t.key = templs;197t.keysz = TEMPL__MAX;198t.arg = &p;199t.cb = template;200
201resp_open(req, KHTTP_200);202khtml_open(&p.req, req, 0);203khttp_template(req, &t, "template.xml");204khtml_close(&p.req);205}
206
207/*
208* Send a random amount of data.
209* Requires KEY_PAGECOUNT (optional), KEY_PAGESIZE (optional).
210* Page count is the number of times we flush a page (with the given
211* size) to the wire.
212* Returns HTTP 200 and the random data.
213*/
214static void215senddata(struct kreq *req)216{
217int64_t i, j, nm, sz;218char *buf;219
220nm = 1024 * 1024;221if (NULL != req->fieldmap[KEY_PAGECOUNT])222nm = req->fieldmap[KEY_PAGECOUNT]->parsed.i;223if (0 == nm)224nm = 1;225
226sz = 1;227if (NULL != req->fieldmap[KEY_PAGESIZE])228sz = req->fieldmap[KEY_PAGESIZE]->parsed.i;229if (0 == sz || (uint64_t)sz > SIZE_MAX)230sz = 1;231
232buf = kmalloc(sz);233
234resp_open(req, KHTTP_200);235for (i = 0; i < nm; i++) {236for (j = 0; j < sz; j++)237#ifndef __linux__238buf[j] = 65 + arc4random_uniform(24);239#else240buf[j] = 65 + (random() % 24);241#endif242khttp_write(req, buf, sz);243}244
245free(buf);246}
247
248/*
249* Demonstrates how to use GET and POST forms and building with the HTML
250* builder functions.
251* Returns HTTP 200 and HTML content.
252*/
253static void254sendindex(struct kreq *req)255{
256char *page;257struct khtmlreq r;258const char *cp;259
260cp = NULL == req->fieldmap[KEY_INTEGER] ?261"" : req->fieldmap[KEY_INTEGER]->val;262kasprintf(&page, "%s/%s", req->pname, pages[PAGE_INDEX]);263
264resp_open(req, KHTTP_200);265khtml_open(&r, req, 0);266khtml_elem(&r, KELEM_DOCTYPE);267khtml_elem(&r, KELEM_HTML);268khtml_elem(&r, KELEM_HEAD);269khtml_elem(&r, KELEM_TITLE);270khtml_puts(&r, "Welcome!");271khtml_closeelem(&r, 2);272khtml_elem(&r, KELEM_BODY);273khtml_puts(&r, "Welcome!");274khtml_attr(&r, KELEM_FORM,275KATTR_METHOD, "post",276KATTR_ENCTYPE, "multipart/form-data",277KATTR_ACTION, page,278KATTR__MAX);279khtml_elem(&r, KELEM_FIELDSET);280khtml_elem(&r, KELEM_LEGEND);281khtml_puts(&r, "Post (multipart)");282khtml_closeelem(&r, 1);283khtml_elem(&r, KELEM_P);284khtml_attr(&r, KELEM_INPUT,285KATTR_TYPE, "number",286KATTR_NAME, keys[KEY_INTEGER].name,287KATTR_VALUE, cp, KATTR__MAX);288khtml_closeelem(&r, 1);289khtml_elem(&r, KELEM_P);290khtml_attr(&r, KELEM_INPUT,291KATTR_TYPE, "file",292KATTR_MULTIPLE, "",293KATTR_NAME, keys[KEY_FILE].name,294KATTR__MAX);295if (NULL != req->fieldmap[KEY_FILE]) {296if (NULL != req->fieldmap[KEY_FILE]->file) {297khtml_puts(&r, "file: ");298khtml_puts(&r, req->fieldmap[KEY_FILE]->file);299khtml_puts(&r, " ");300}301if (NULL != req->fieldmap[KEY_FILE]->ctype) {302khtml_puts(&r, "ctype: ");303khtml_puts(&r, req->fieldmap[KEY_FILE]->ctype);304}305}306khtml_closeelem(&r, 1);307khtml_elem(&r, KELEM_P);308khtml_attr(&r, KELEM_INPUT,309KATTR_TYPE, "submit",310KATTR__MAX);311khtml_closeelem(&r, 0);312khtml_close(&r);313free(page);314}
315
316int
317main(void)318{
319struct kreq r;320enum kcgi_err er;321
322/* Set up our main HTTP context. */323
324er = khttp_parse(&r, keys, KEY__MAX,325pages, PAGE__MAX, PAGE_INDEX);326
327if (KCGI_OK != er)328return(EXIT_FAILURE);329
330/*331* Accept only GET, POST, and OPTIONS.
332* Restrict to text/html and a valid page.
333* If all of our parameters are valid, use a dispatch array to
334* send us to the page handlers.
335*/
336
337if (KMETHOD_OPTIONS == r.method) {338khttp_head(&r, kresps[KRESP_ALLOW],339"OPTIONS GET POST");340resp_open(&r, KHTTP_200);341} else if (KMETHOD_GET != r.method &&342KMETHOD_POST != r.method) {343resp_open(&r, KHTTP_405);344} else if (PAGE__MAX == r.page ||345KMIME_TEXT_HTML != r.mime) {346resp_open(&r, KHTTP_404);347} else348(*disps[r.page])(&r);349
350khttp_free(&r);351return(EXIT_SUCCESS);352}
353
354