Working CGI

This commit is contained in:
Neale Pickett 2010-07-20 20:35:24 -06:00
parent a7969c9edb
commit e7eca89844
9 changed files with 388 additions and 108 deletions

8
.gitignore vendored
View File

@ -1,3 +1,11 @@
*~ *~
*# *#
*.o *.o
round-*.html
run-tanks
forf.c
forf.h
forf.html
designer.cgi
next-round
summary.html

View File

@ -15,6 +15,7 @@ forf.html: forf.html.sh forf/forf.txt
forf.%: forf/forf.% forf.%: forf/forf.%
cp forf/$@ $@ cp forf/$@ $@
.PRECIOUS: forf/%
forf/%: forf/%:
git submodule update --init git submodule update --init

View File

@ -1,45 +1,35 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <ctype.h>
#define BASE_PATH "/tmp/" #define BASE_PATH "/tmp/tanks/"
struct string { struct {
char *s; char *name;
size_t size; size_t size;
size_t len; } entries[] = {
{"name", 20},
{"author", 40},
{"color", 10},
{"program", 8192},
{NULL, 0}
}; };
void
string_append(struct string *str, char c) size_t inlen;
{
if (str->len < str->size) {
str->s[str->len++] = c;
}
}
int int
string_cmp(struct string *a, char *b, size_t blen) read_char()
{ {
if (a->len > blen) { if (inlen) {
return 1; inlen -= 1;
} else if (a->len < blen) { return getchar();
return -1;
} else {
return memcmp(a->s, b, blen);
} }
} return EOF;
void
string_cpy(struct string *dst, struct string *src)
{
if (dst->size < src->len) {
dst->len = dst->size;
} else {
dst->len = src->len;
}
memcpy(dst->s, src->s, dst->len);
} }
char char
@ -58,10 +48,10 @@ tonum(int c)
} }
char char
read_hex(FILE *f) read_hex()
{ {
int a = fgetc(f); int a = read_char();
int b = fgetc(f); int b = read_char();
return tonum(a)*16 + tonum(b); return tonum(a)*16 + tonum(b);
} }
@ -69,90 +59,149 @@ read_hex(FILE *f)
/* Read a key or a value. Since & and = aren't supposed to appear /* Read a key or a value. Since & and = aren't supposed to appear
outside of boundaries, we can use the same function for both. outside of boundaries, we can use the same function for both.
*/ */
int size_t
read_item(FILE *f, struct string *str) read_item(char *str, size_t maxlen)
{ {
int c; int c;
size_t pos = 0;
str->len = 0;
while (1) { while (1) {
c = fgetc(f); c = read_char();
switch (c) { switch (c) {
case EOF: case EOF:
return 0;
break;
case '=': case '=':
case '&': case '&':
return 1; str[pos] = '\0';
break; return pos;
case '%': case '%':
string_append(str, read_hex(f)); c = read_hex();
break; break;
default: case '+':
string_append(str, c); c = ' ';
break; break;
} }
if (pos < maxlen - 1) {
str[pos] = c;
pos += 1;
}
}
}
size_t
copy_item(char *filename, size_t maxlen)
{
FILE *f;
char path[132];
int c;
size_t pos = 0;
snprintf(path, sizeof(path),
BASE_PATH "%05d.%s",
getpid(), filename);
f = fopen(path, "w");
if (! f) {
/* Just send it to the bit bucket */
maxlen = 0;
}
while (1) {
c = read_char();
switch (c) {
case EOF:
case '=':
case '&':
if (f) fclose(f);
return pos;
case '%':
c = read_hex();
break;
case '+':
c = ' ';
break;
}
if (pos < maxlen) {
fputc(c, f);
pos += 1;
}
} }
} }
int int
read_pair(FILE *f, struct string *key, struct string *val) croak(char *msg)
{ {
if (! read_item(f, key)) { int i;
return 0; char path[132];
}
return read_item(f, val);
}
/* This is ugly and I dislike it. */ for (i = 0; entries[i].name; i += 1) {
#define new_string(name, size) \ snprintf(path, sizeof(path),
char _##name[size]; \ BASE_PATH "%05d.%s",
struct string name = {_##name, size, 0 } getpid(), entries[i].name);
unlink(path);
}
printf("Content-type: text/html\n\n");
printf("<html><head>\n");
printf("<link rel=\"stylesheet\" href=\"dirtbags.css\" type=\"text/css\">\n");
printf("<title>Tank submission error</title>\n");
printf("</head><body><h1>Tank submission error</h1>\n");
if (msg) {
printf("<p>%s.</p>\n", msg);
} else {
printf("<p>Something went wrong.</p>.\n");
}
printf("<p>Sorry it didn't work out.</p>\n");
printf("<p>You could go back and try again, though.</p>\n");
printf("</body></html>\n");
return 0;
}
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
int sensor[10][4]; int sensor[10][4];
char key[20];
char token[40];
size_t len;
new_string(key, 20); memset(sensor, 0, sizeof(sensor));
new_string(val, 8192); token[0] = '\0';
new_string(token, 40);
new_string(name, 20);
new_string(author, 60);
new_string(color, 10);
new_string(program, 8192);
printf("Content-type: text/plain\n\n"); {
char *rm = getenv("REQUEST_METHOD");
while (! feof(stdin)) { if (! (rm && (0 == strcmp(rm, "POST")))) {
read_pair(stdin, &key, &val); printf("405 Method not allowed\n");
if (0 == string_cmp(&key, "token", 5)) { printf("Allow: POST\n");
string_cpy(&token, &key); printf("Content-type: text/html\n");
} else if (0 == string_cmp(&key, "name", 4)) { printf("\n");
string_cpy(&name, &key); printf("<h1>Method not allowed</h1>\n");
} else if (0 == string_cmp(&key, "author", 6)) { printf("<p>I only speak POST. Sorry.</p>\n");
string_cpy(&author, &key); return 0;
} else if (0 == string_cmp(&key, "color", 5)) { }
string_cpy(&color, &key);
} else if (0 == string_cmp(&key, "program", 7)) { inlen = atoi(getenv("CONTENT_LENGTH"));
string_cpy(&program, &key); }
} else if ((3 == key.len) && ('s' == key.s[0])) {
while (inlen) {
len = read_item(key, sizeof(key));
if (0 == strcmp(key, "token")) {
read_item(token, sizeof(token));
} else if ((3 == len) && ('s' == key[0])) {
/* sensor dealie, key = "s[0-9][rawt]" */ /* sensor dealie, key = "s[0-9][rawt]" */
int n = key.s[1] - '0'; char val[5];
int n = key[1] - '0';
int i; int i;
int p; int p;
read_item(val, sizeof(val));
if (! (n >= 0) && (n <= 9)) { if (! (n >= 0) && (n <= 9)) {
break; break;
} }
if (val.len > 3) { i = atoi(val);
break;
}
val.s[val.len] = '\0';
i = atoi(val.s);
switch (key.s[2]) { switch (key[2]) {
case 'r': case 'r':
p = 0; p = 0;
break; break;
@ -164,16 +213,87 @@ main(int argc, char *argv[])
break; break;
default: default:
p = 3; p = 3;
i = (val[0] != '\0');
break; break;
} }
sensor[n][p] = i; sensor[n][p] = i;
} else {
int i;
for (i = 0; entries[i].name; i += 1) {
if (0 == strcmp(key, entries[i].name)) {
len = copy_item(key, entries[i].size);
break;
} }
write(1, key.s, key.len);
write(1, "=", 1);
write(1, val.s, val.len);
write(1, "\n", 1);
} }
}
}
/* Sanitize token */
{
char *p = token;
while (*p) {
if (! isalnum(*p)) {
*p = '_';
}
p += 1;
}
if ('\0' == token[0]) {
token[0] = '_';
token[1] = '\0';
}
}
/* Move files into their directory */
{
char path[132];
char dest[132];
struct stat st;
int i;
snprintf(path, sizeof(path), BASE_PATH "%s/", token);
if (-1 == stat(path, &st)) return croak("Invalid token");
if (! S_ISDIR(st.st_mode)) return croak("Invalid token");
for (i = 0; entries[i].name; i += 1) {
snprintf(path, sizeof(path),
BASE_PATH "%05d.%s",
getpid(), entries[i].name);
snprintf(dest, sizeof(dest),
BASE_PATH "%s/%s",
token, entries[i].name);
rename(path, dest);
}
for (i = 0; i < 10; i += 1) {
FILE *f;
snprintf(dest, sizeof(dest),
BASE_PATH "%s/sensor%d",
token, i);
f = fopen(dest, "w");
if (! f) break;
fprintf(f, "%d %d %d %d\n",
sensor[i][0],
sensor[i][1],
sensor[i][2],
sensor[i][3]);
fclose(f);
}
}
printf("Content-type: text/html\n\n");
printf("<!DOCTYPE html>\n");
printf("<html><head>\n");
printf("<link rel=\"stylesheet\" href=\"dirtbags.css\" type=\"text/css\">\n");
printf("<title>Tank submitted</title>\n");
printf("</head><body><h1>Tank submitted</h1>\n");
printf("<p>You just uploaded a tank!</p>\n");
printf("<p>Let's hope it doesn't suck.</p>\n");
printf("</body></html>\n");
return 0; return 0;
} }

View File

@ -27,10 +27,20 @@
<body> <body>
<h1>Tank Designer</h1> <h1>Tank Designer</h1>
<div id="preview"><canvas id="design"></canvas><p id="debug"></p></div> <div id="preview"><canvas id="design"></canvas><p id="debug"></p></div>
<form>
<p>
Before you can get going with a tank, you need a token. If you
need a token, just ask one of the dirtbags.
</p>
<form action="designer.cgi" method="post">
<fieldset id="metadata"> <fieldset id="metadata">
<legend>Information</legend> <legend>Information</legend>
<table> <table>
<tr>
<td>Token:</td>
<td><input name="token"></td>
</tr>
<tr> <tr>
<td>Tank name:</td> <td>Tank name:</td>
<td><input name="name"></td> <td><input name="name"></td>

View File

@ -18,7 +18,7 @@ function update() {
var color = document.getElementsByName('color')[0].value; var color = document.getElementsByName('color')[0].value;
var sensors = new Array(); var sensors = new Array();
for (i = 0; i < 4; i += 1) { for (i = 0; i < 10; i += 1) {
var range = document.getElementsByName('s'+i+'r')[0].value; var range = document.getElementsByName('s'+i+'r')[0].value;
var angle = document.getElementsByName('s'+i+'a')[0].value; var angle = document.getElementsByName('s'+i+'a')[0].value;
var width = document.getElementsByName('s'+i+'w')[0].value; var width = document.getElementsByName('s'+i+'w')[0].value;

View File

@ -17,8 +17,6 @@ body {
h1:first-child { h1:first-child {
text-transform: lowercase; text-transform: lowercase;
font-size: 1.6em; font-size: 1.6em;
/* background-color: #222; */
/* opacity: 0.9; */
padding: 3px; padding: 3px;
color: #2a2; color: #2a2;
margin: 0 0 1em 70px; margin: 0 0 1em 70px;
@ -117,15 +115,6 @@ pre {
} }
th {
vertical-align: top;
text-align: center;
}
td {
vertical-align: top;
text-align: right;
}
p { p {
line-height: 1.4em; line-height: 1.4em;
margin-bottom: 20px; margin-bottom: 20px;

View File

@ -1,5 +1,13 @@
#! /bin/sh #! /bin/sh
if [ "$#" -gt 0 ]; then
tanks="$@"
else
echo "Usage: $0 tank1 tank2 [...]"
exit 1
fi
if [ -f next-round ]; then if [ -f next-round ]; then
next=$(cat next-round) next=$(cat next-round)
else else
@ -18,13 +26,13 @@ cat <<EOF >$fn
<head> <head>
<title>Tanks Round $next</title> <title>Tanks Round $next</title>
<script type="application/javascript" src="tanks.js"></script> <script type="application/javascript" src="tanks.js"></script>
<link rel="stylesheet" href="tanks.css" type="text/css"> <link rel="stylesheet" href="dirtbags.css" type="text/css">
<script type="application/javascript"> <script type="application/javascript">
function go() { function go() {
start("battlefield", start("battlefield",
// Start JSON data // Start JSON data
EOF EOF
./run-tanks players/* >>$fn 3>$rfn ./run-tanks $tanks >>$fn 3>$rfn
cat <<EOF >>$fn cat <<EOF >>$fn
// end JSON data // end JSON data
); );
@ -43,7 +51,7 @@ cat <<EOF >>$fn
</html> </html>
EOF EOF
./summary.awk > summary.html ./summary.awk $tanks > summary.html
echo "done." echo "done."

View File

@ -12,7 +12,7 @@ BEGIN {
print "<html>"; print "<html>";
print " <head>"; print " <head>";
print " <title>Dirtbags Tanks</title>"; print " <title>Dirtbags Tanks</title>";
print " <link rel=\"stylesheet\" href=\"tanks.css\" type=\"text/css\">"; print " <link rel=\"stylesheet\" href=\"dirtbags.css\" type=\"text/css\">";
print " </head>"; print " </head>";
print " <body>"; print " <body>";
print " <h1>Dirtbags Tanks</h1>"; print " <h1>Dirtbags Tanks</h1>";
@ -27,7 +27,9 @@ BEGIN {
print " <h2>Rankings</h2>"; print " <h2>Rankings</h2>";
print " <ol>"; print " <ol>";
while ("ls -d players/*" | getline id) { for (i = 1; i < ARGC; i += 1) {
id = ARGV[i];
if (1 == getline < (id "/name")) { if (1 == getline < (id "/name")) {
names[id] = esc($0); names[id] = esc($0);
} else { } else {

142
token.svg Normal file
View File

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="315"
height="180"
id="svg2"
sodipodi:version="0.32"
inkscape:version="0.46"
version="1.0"
sodipodi:docname="token.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs4">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
gridtolerance="10000"
guidetolerance="10"
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="269.04724"
inkscape:cy="12"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="in"
inkscape:window-width="1020"
inkscape:window-height="687"
inkscape:window-x="0"
inkscape:window-y="17" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g3161"
transform="matrix(1.0829475,0,0,1.0829475,-3.8658087,40.040188)">
<rect
y="29.306273"
x="39.023125"
height="58.804661"
width="46.712982"
id="rect2383"
style="fill:#4f4f4f;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.96922117px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<rect
y="11.121428"
x="85.736092"
height="89.043518"
width="28.585094"
id="rect2385"
style="fill:#777777;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.96357071px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<rect
style="fill:#777777;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.96357071px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="rect2387"
width="28.585094"
height="89.043518"
x="10.438034"
y="11.121428" />
<rect
inkscape:transform-center-y="-20.587678"
inkscape:transform-center-x="-11.779977"
transform="matrix(0.8668119,0.4986353,-0.4986353,0.8668119,0,0)"
y="-37.782883"
x="75.485535"
height="60.45842"
width="12.091684"
id="rect3159"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</g>
<text
xml:space="preserve"
style="font-size:36.12366104px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans;-inkscape-font-specification:Sans Bold"
x="52.018929"
y="36.05489"
id="text3167"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3169"
x="52.018929"
y="36.05489">Play Tanks</tspan></text>
<text
xml:space="preserve"
style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans;-inkscape-font-specification:Sans"
x="128"
y="69"
id="text3171"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3173"
x="128"
y="69">essid: Tanks</tspan><tspan
sodipodi:role="line"
x="128"
y="99"
id="tspan3175">http://10.0.0.1/</tspan><tspan
sodipodi:role="line"
x="128"
y="129"
id="tspan3179">Token:</tspan></text>
<text
xml:space="preserve"
style="font-size:12px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Sans;-inkscape-font-specification:Sans"
x="8"
y="168"
id="text3183"
sodipodi:linespacing="100%"><tspan
sodipodi:role="line"
id="tspan3185"
x="8"
y="168">a dirtbags production</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB