Files
hapiwiki/cgi/input.c
2025-09-09 15:11:17 +09:00

567 lines
12 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cgi/input.h"
#include "cgi/output.h"
static struct cgi_arg *cgi_arg_create(void) {
struct cgi_arg *this;
this = malloc(sizeof(struct cgi_arg));
if (this == NULL) {
cgi_die("can not allocate cgi_arg");
}
this->next = NULL;
this->name = NULL;
this->value = NULL;
return this;
}
static void cgi_arg_destroy(struct cgi_arg *this) {
if (this == NULL) {
return;
}
cgi_arg_destroy(this->next);
free(this);
}
static const char *cgi_arg_get_value(const struct cgi_arg *this,
const char *name) {
if (this == NULL) {
return NULL;
}
if (strcmp(this->name, name) == 0) {
return this->value;
}
return cgi_arg_get_value(this->next, name);
}
static struct cgi_file *cgi_file_create(void) {
struct cgi_file *this;
this = malloc(sizeof(struct cgi_file));
if (this == NULL) {
cgi_die("can not allocate cgi_file");
}
this->next = NULL;
this->name = NULL;
this->filename = NULL;
this->type = NULL;
this->data = NULL;
this->size = -1;
return this;
}
static void cgi_file_destroy(struct cgi_file *this) {
if (this == NULL) {
return;
}
cgi_file_destroy(this->next);
free(this);
}
static const struct cgi_file *cgi_file_find(const struct cgi_file *this,
const char *name) {
if (this == NULL) {
return NULL;
}
if (strcmp(this->name, name) == 0) {
return this;
}
return cgi_file_find(this->next, name);
}
struct cgi_input *cgi_input_create(void) {
struct cgi_input *this;
this = malloc(sizeof(struct cgi_input));
if (this == NULL) {
cgi_die("can not allocate cgi_input");
}
this->query_data = NULL;
this->post_data = NULL;
this->cookie_data = NULL;
this->query = cgi_arg_create();
this->post = cgi_arg_create();
this->file = cgi_file_create();
this->cookie = cgi_arg_create();
this->compound_cookie = cgi_arg_create();
return this;
}
void cgi_input_destroy(struct cgi_input *this) {
if (this == NULL) {
return;
}
free(this->query_data);
free(this->post_data);
free(this->cookie_data);
cgi_arg_destroy(this->query);
cgi_arg_destroy(this->post);
cgi_file_destroy(this->file);
cgi_arg_destroy(this->cookie);
cgi_arg_destroy(this->compound_cookie);
free(this);
}
static const char *url_decode(const char *src, char *dst) {
if (*src == '+') {
*dst = ' ';
src++;
} else if (*src == '%') {
char buf[3];
int c;
if (*(src + 1) == '\0' || *(src + 2) == '\0') {
cgi_die("url_decode: unexpected termination");
}
buf[0] = *(src + 1);
buf[1] = *(src + 2);
buf[2] = '\0';
if (sscanf(buf, "%02x", &c) != 1) { /* XXX do yourself for performance */
cgi_die("url_decode failed");
}
*dst = c;
src += 3;
} else {
*dst = *src;
src++;
}
return src;
}
static void split_query(const char *src, char *dst, struct cgi_arg *arg) {
char c;
while (*src != '\0') {
arg->next = cgi_arg_create();
arg = arg->next;
arg->name = dst;
/* parse key */
for (;;) {
if (*src == '=' || *src == '&' || *src == '\0') {
c = *src;
*dst = '\0';
dst++;
break;
} else {
src = url_decode(src, dst);
dst++;
}
}
/* parse value */
if (c != '=') {
arg->value = NULL;
if (c != '\0') {
src++;
}
} else {
src++;
arg->value = dst;
for (;;) {
if (*src == '&' || *src == '\0') {
if (*src != '\0') {
src++;
}
*dst = '\0';
dst++;
break;
} else {
src = url_decode(src, dst);
dst++;
}
}
}
}
}
static void split_cookie(const char *src, char *dst, struct cgi_arg *arg) {
char c;
while (*src != '\0') {
arg->next = cgi_arg_create();
arg = arg->next;
arg->name = dst;
/* parse key */
for (;;) {
if (*src == '=' || *src == ';' || *src == '\0') {
c = *src;
*dst = '\0';
dst++;
if (*src == ';' && *(src + 1) == ' ') {
src++;
}
break;
} else {
src = url_decode(src, dst);
dst++;
}
}
/* parse value */
if (c != '=') {
arg->value = NULL;
if (c != '\0') {
src++;
if (*src == ' ') {
src++;
}
}
} else {
src++;
arg->value = dst;
for (;;) {
if (*src == ';' || *src == '\0') {
if (*src != '\0') {
src++;
if (*src == ' ') {
src++;
}
}
*dst = '\0';
dst++;
break;
} else {
src = url_decode(src, dst);
dst++;
}
}
}
}
}
static char *find(const char *s, int slen, const char *t, int tlen) {
int i;
for (i = 0; i < slen - tlen; i++) {
int match;
int j;
match = 1;
for (j = 0; j < tlen; j++) {
if (*(s + i + j) != *(t + i + j)) {
match = 0;
break;
}
}
if (match) {
return (char *)(s + i);
}
}
return NULL;
}
static void split_multipart(char *s, int length, const char *boundary,
struct cgi_arg *arg, struct cgi_file *file) {
char *origin;
int boundary_length;
char *b;
origin = s;
boundary_length = strlen(boundary);
b = find(s, length - (s - origin), boundary, boundary_length);
if (b == NULL) {
cgi_die("multipart format error");
}
s = b + boundary_length;
for (;;) {
char *head, *begin, *end;
char *name, *e;
if (s - origin + 2 > length) {
cgi_die("multipart format error");
}
if (*s == '-' && *(s + 1) == '-') {
break;
}
if (*s != '\r' || *(s + 1) != '\n') {
cgi_die("multipart format error");
}
head = s + 2;
begin = find(head, length - (head - origin), "\r\n\r\n", 4);
if (begin == NULL) {
cgi_die("multipart format error");
}
end = find(begin, length - (begin - origin), boundary, boundary_length);
if (end == NULL) {
cgi_die("multipart format error");
}
if (*(end - 4) != '\r' || *(end - 3) != '\n' || *(end - 2) != '-' ||
*(end - 1) != '-') {
cgi_die("multipart format error");
}
end -= 4;
if (head - origin + strlen("Content-Disposition: form-data; name=\"") >
length) {
cgi_die("multipart format error");
}
if (strncmp(head, "Content-Disposition: form-data; name=\"",
strlen("Content-Disposition: form-data; name=\"")) != 0) {
cgi_die("multipart format error");
}
name = head + strlen("Content-Disposition: form-data; name=\"");
for (e = name; *e != '"' && e - origin < length; e++)
;
*e = '\0';
if (e - origin + 2 > length) {
cgi_die("multipart format error");
}
if (*(e + 1) == '\r' && *(e + 2) == '\n') {
arg->next = cgi_arg_create();
arg = arg->next;
arg->name = name;
*end = '\0';
if (e - origin + 5 > length) {
cgi_die("multipart format error");
}
arg->value = e + 5;
} else {
file->next = cgi_file_create();
file = file->next;
file->name = name;
e++;
if (e - origin + strlen("; filename=\"") > length) {
cgi_die("multipart format error");
}
if (strncmp(e, "; filename=\"", strlen("; filename=\"")) != 0) {
cgi_die("multipart format error");
}
e += strlen("; filename=\"");
file->filename = e;
for (; *e != '"' && e - origin < length; e++)
;
*e = '\0';
e++;
if (e - origin + strlen("\r\nContent-Type: ") > length) {
cgi_die("multipart format error");
}
if (strncmp(e, "\r\nContent-Type: ", strlen("\r\nContent-Type: ")) != 0) {
cgi_die("multipart format error");
}
e += strlen("\r\nContent-Type: ");
file->type = e;
for (; *e != '\r' && e - origin < length; e++)
;
*e = '\0';
e++;
if (e - origin + 3 > length) {
cgi_die("multipart format error");
}
if (strncmp(e, "\n\r\n", 3) != 0) {
cgi_die("multipart format error");
}
e += 3;
file->data = e;
file->size = end - e;
}
s = end + 4 + boundary_length;
}
}
static void read_query(struct cgi_input *this) {
char *query_string;
query_string = getenv("QUERY_STRING");
if (query_string == NULL) {
return;
}
this->query_data = malloc(strlen(query_string) + 1);
if (this->query_data == NULL) {
cgi_die("can not allocate query_data");
}
split_query(query_string, this->query_data, this->query);
}
static void read_post(struct cgi_input *input) {
const char *request_method;
const char *content_type;
int multipart;
const char *content_length;
int length;
request_method = getenv("REQUEST_METHOD");
if (request_method == NULL) {
cgi_die("REQUEST_METHOD is NULL");
}
if (strcmp("POST", request_method) != 0) {
return; /* may be GET or other, it's successful return */
}
content_type = getenv("CONTENT_TYPE");
if (content_type == NULL) {
cgi_die("CONTENT_TYPE is NULL");
}
if (strcmp("application/x-www-form-urlencoded", content_type) == 0) {
multipart = 0;
} else if (strncmp("multipart/form-data;", content_type,
strlen("multipart/form-data;")) == 0) {
multipart = 1;
} else {
cgi_die("unknown CONTENT_TYPE");
}
content_length = getenv("CONTENT_LENGTH");
if (content_length == NULL) {
cgi_die("CONTENT_LENGTH is NULL");
}
if (sscanf(content_length, "%d", &length) != 1) {
cgi_die("invalid CONTENT_LENGTH");
}
if (length < 0) {
cgi_die("invalid CONTENT_LENGTH");
}
input->post_data = malloc(length + 1);
if (input->post_data == NULL) {
cgi_die("can not allocate post_data");
}
if (fread(input->post_data, 1, length, stdin) != length) {
cgi_die("failed reading from stdin");
}
*(input->post_data + length) = '\0';
if (!multipart) {
split_query(input->post_data, input->post_data, input->post);
} else {
const char *boundary;
boundary = strstr(content_type, "boundary=");
if (boundary == NULL) {
cgi_die("can not find a boundary");
}
boundary += strlen("boundary=");
split_multipart(input->post_data, length, boundary, input->post,
input->file);
}
}
static void read_cookie(struct cgi_input *this) {
char *http_cookie;
http_cookie = getenv("HTTP_COOKIE");
if (http_cookie == NULL) {
return;
}
this->cookie_data = malloc(strlen(http_cookie) + 1);
if (this->cookie_data == NULL) {
cgi_die("can not allocate cookie_data");
}
split_cookie(http_cookie, this->cookie_data, this->cookie);
}
void cgi_input_read(struct cgi_input *this) {
read_query(this);
read_post(this);
read_cookie(this);
}
void cgi_input_read_compound(struct cgi_input *this) {
struct cgi_arg *arg;
read_query(this);
read_post(this);
read_cookie(this);
for (arg = this->cookie->next; arg != NULL; arg = arg->next) {
if (arg->value != NULL) {
struct cgi_arg *last;
for (last = this->compound_cookie; last->next != NULL; last = last->next)
;
split_query(arg->value, (char *)arg->value, last);
}
}
}
const char *cgi_input_get_value(struct cgi_input *this, const char *name) {
const char *value;
value = cgi_arg_get_value(this->query->next, name);
if (value == NULL) {
value = cgi_arg_get_value(this->post->next, name);
}
if (value == NULL) {
value = cgi_arg_get_value(this->cookie->next, name);
}
return value;
}
const char *cgi_input_get_value_compound(struct cgi_input *this,
const char *name) {
const char *value;
value = cgi_arg_get_value(this->query->next, name);
if (value == NULL) {
value = cgi_arg_get_value(this->post->next, name);
}
if (value == NULL) {
value = cgi_arg_get_value(this->compound_cookie->next, name);
}
return value;
}
const struct cgi_file *cgi_input_get_file(struct cgi_input *this,
const char *name) {
return cgi_file_find(this->file->next, name);
}