aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorReyk Floeter <reyk@esdenera.com>2014-07-13 16:12:59 +0200
committerReyk Floeter <reyk@esdenera.com>2014-07-13 16:12:59 +0200
commitb0a1cfbe755d14e21af5e866fc7321cf6a3c8d0b (patch)
tree948585fe33752889c039c67cc1eccd73ec5fa5c8
parent660b4faa8d41d0817c59d00f8d606daab3586535 (diff)
downloadhttpd-b0a1cfbe755d14e21af5e866fc7321cf6a3c8d0b.tar.gz
httpd-b0a1cfbe755d14e21af5e866fc7321cf6a3c8d0b.zip
Implement media type support
-rw-r--r--config.c60
-rw-r--r--http.h18
-rw-r--r--httpd.c79
-rw-r--r--httpd.conf.571
-rw-r--r--httpd.h29
-rw-r--r--parse.y79
-rw-r--r--server.c3
-rw-r--r--server_file.c36
-rw-r--r--server_http.c16
9 files changed, 364 insertions, 27 deletions
diff --git a/config.c b/config.c
index e59f6ed..01a8e5e 100644
--- a/config.c
+++ b/config.c
@@ -57,7 +57,7 @@ config_init(struct httpd *env)
env->sc_prefork_server = SERVER_NUMPROC;
ps->ps_what[PROC_PARENT] = CONFIG_ALL;
- ps->ps_what[PROC_SERVER] = CONFIG_SERVERS;
+ ps->ps_what[PROC_SERVER] = CONFIG_SERVERS|CONFIG_MEDIA;
}
/* Other configuration */
@@ -70,6 +70,13 @@ config_init(struct httpd *env)
TAILQ_INIT(env->sc_servers);
}
+ if (what & CONFIG_MEDIA) {
+ if ((env->sc_mediatypes =
+ calloc(1, sizeof(*env->sc_mediatypes))) == NULL)
+ return (-1);
+ RB_INIT(env->sc_mediatypes);
+ }
+
return (0);
}
@@ -88,6 +95,9 @@ config_purge(struct httpd *env, u_int reset)
free(srv);
}
}
+
+ if (what & CONFIG_MEDIA && env->sc_mediatypes != NULL)
+ media_purge(env->sc_mediatypes);
}
int
@@ -226,3 +236,51 @@ config_getserver(struct httpd *env, struct imsg *imsg)
return (0);
}
+
+int
+config_setmedia(struct httpd *env, struct media_type *media)
+{
+ struct privsep *ps = env->sc_ps;
+ int id;
+ u_int what;
+
+ for (id = 0; id < PROC_MAX; id++) {
+ what = ps->ps_what[id];
+
+ if ((what & CONFIG_MEDIA) == 0 || id == privsep_process)
+ continue;
+
+ DPRINTF("%s: sending media \"%s\" to %s", __func__,
+ media->media_name, ps->ps_title[id]);
+
+ proc_compose_imsg(ps, id, -1, IMSG_CFG_MEDIA, -1,
+ media, sizeof(*media));
+ }
+
+ return (0);
+}
+
+int
+config_getmedia(struct httpd *env, struct imsg *imsg)
+{
+#ifdef DEBUG
+ struct privsep *ps = env->sc_ps;
+#endif
+ struct media_type media;
+ u_int8_t *p = imsg->data;
+
+ IMSG_SIZE_CHECK(imsg, &media);
+ memcpy(&media, p, sizeof(media));
+
+ if (media_add(env->sc_mediatypes, &media) == NULL) {
+ log_debug("%s: failed to add media \"%s\"",
+ __func__, media.media_name);
+ return (-1);
+ }
+
+ DPRINTF("%s: %s %d received media \"%s\"", __func__,
+ ps->ps_title[privsep_process], ps->ps_instance,
+ media.media_name);
+
+ return (0);
+}
diff --git a/http.h b/http.h
index 76d98e3..d9cf13e 100644
--- a/http.h
+++ b/http.h
@@ -119,6 +119,24 @@ struct http_error {
{ 0, NULL } \
}
+struct http_mediatype {
+ char *media_name;
+ char *media_type;
+ char *media_subtype;
+};
+/* Some default media types */
+#define MEDIA_TYPES { \
+ { "css", "text", "css" }, \
+ { "html", "text", "html" }, \
+ { "txt", "text", "plain" }, \
+ { "gif", "image", "gif" }, \
+ { "jpeg", "image", "jpeg" }, \
+ { "jpg", "image", "jpeg" }, \
+ { "png", "image", "png" }, \
+ { "js", "application", "javascript" }, \
+ { NULL } \
+}
+
/* Used during runtime */
struct http_descriptor {
struct kv http_pathquery;
diff --git a/httpd.c b/httpd.c
index c08db55..7db7288 100644
--- a/httpd.c
+++ b/httpd.c
@@ -269,10 +269,16 @@ parent_configure(struct httpd *env)
struct ctl_flags cf;
int ret = -1;
struct server *srv;
+ struct media_type *media;
+
+ RB_FOREACH(media, mediatypes, env->sc_mediatypes) {
+ if (config_setmedia(env, media) == -1)
+ fatal("send media");
+ }
TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
if (config_setserver(env, srv) == -1)
- fatal("create server");
+ fatal("send server");
}
/* The servers need to reload their config. */
@@ -290,7 +296,7 @@ parent_configure(struct httpd *env)
ret = 0;
- config_purge(env, CONFIG_ALL & ~CONFIG_SERVERS);
+ config_purge(env, CONFIG_ALL);
return (ret);
}
@@ -735,3 +741,72 @@ kv_cmp(struct kv *a, struct kv *b)
}
RB_GENERATE(kvtree, kv, kv_node, kv_cmp);
+
+struct media_type *
+media_add(struct mediatypes *types, struct media_type *media)
+{
+ struct media_type *entry;
+
+ if ((entry = RB_FIND(mediatypes, types, media)) != NULL) {
+ log_debug("%s: duplicated entry for \"%s\"", __func__,
+ media->media_name);
+ return (NULL);
+ }
+
+ if ((entry = malloc(sizeof(*media))) == NULL)
+ return (NULL);
+
+ memcpy(entry, media, sizeof(*entry));
+ RB_INSERT(mediatypes, types, entry);
+
+ return (entry);
+}
+
+void
+media_delete(struct mediatypes *types, struct media_type *media)
+{
+ RB_REMOVE(mediatypes, types, media);
+ if (media->media_encoding != NULL)
+ free(media->media_encoding);
+ free(media);
+}
+
+void
+media_purge(struct mediatypes *types)
+{
+ struct media_type *media;
+
+ while ((media = RB_MIN(mediatypes, types)) != NULL)
+ media_delete(types, media);
+}
+
+struct media_type *
+media_find(struct mediatypes *types, char *file)
+{
+ struct media_type *match, media;
+ char *p;
+
+ if ((p = strrchr(file, '.')) == NULL) {
+ p = file;
+ } else if (*p++ == '\0') {
+ return (NULL);
+ }
+ if (strlcpy(media.media_name, p,
+ sizeof(media.media_name)) >=
+ sizeof(media.media_name)) {
+ return (NULL);
+ }
+
+ /* Find media type by extension name */
+ match = RB_FIND(mediatypes, types, &media);
+
+ return (match);
+}
+
+int
+media_cmp(struct media_type *a, struct media_type *b)
+{
+ return (strcasecmp(a->media_name, b->media_name));
+}
+
+RB_GENERATE(mediatypes, media_type, media_entry, media_cmp);
diff --git a/httpd.conf.5 b/httpd.conf.5
index a2c0cc4..2e99949 100644
--- a/httpd.conf.5
+++ b/httpd.conf.5
@@ -26,7 +26,7 @@ is the configuration file for the HTTP daemon,
.Xr httpd 8 .
.Sh SECTIONS
.Nm
-is divided into three main sections:
+is divided into four main sections:
.Bl -tag -width xxxx
.It Sy Macros
User-defined variables may be defined and used later, simplifying the
@@ -36,6 +36,8 @@ Global settings for
.Xr httpd 8 .
.It Sy Servers
Listening HTTP web servers.
+.It Sy Types
+Media types and extensions.
.El
.Pp
Within the sections,
@@ -114,6 +116,73 @@ The following general table options are available:
.It Ic listen on Ar address Ic port Ar number
Set the listen address and port.
.El
+.Sh TYPES
+Configure the supported media types.
+.Nm httpd
+will set the
+.Ar Content-Type
+of the response header based on the file extension that is listed in the
+.Ic types
+section.
+If not specified,
+.Nm httpd
+will use built-in media types for
+.Ar text/css ,
+.Ar text/html ,
+.Ar text/plain ,
+.Ar image/gif ,
+.Ar image/png ,
+.Ar image/jpeg ,
+and
+.Ar application/javascript .
+.Pp
+The
+.Ic types
+section must include one or more lines of the following syntax:
+.Bl -tag -width Ds
+.It Ar type/subtype Ar name Oo Ar name ... Oc Ic ;
+Set the media
+.Ar type
+and
+.Ar subtype
+to the specified extension
+.Ar name .
+One or more names can be specified per line.
+.El
+.Sh EAMPLES
+The following example will start two pre-forked servers that are
+listening on the primary IP address of the network interface that is a
+member of the
+.Ar egress
+group.
+It additionally defines some media types overriding the defaults.
+.Bd -literal -offset indent
+prefork 2
+
+server "default" {
+ listen on egress port 80
+}
+
+types {
+ text/css css;
+ text/html html html;
+ text/txt txt;
+ image/gif gif;
+ image/jpeg jpg jpeg;
+ image/png png;
+ application/javascript js;
+ application/xml xml;
+}
+.Ed
+.Pp
+The syntax of the types section is compatible to the format that is used by
+.Xr nginx 8 ,
+so you can optionally include its
+.Pa mime.types
+file directly:
+.Bd -literal -offset indent
+include "/etc/nginx/mime.types"
+.Ed
.Sh SEE ALSO
.Xr httpd 8 .
.Sh AUTHORS
diff --git a/httpd.h b/httpd.h
index d4fd4ca..f4ba745 100644
--- a/httpd.h
+++ b/httpd.h
@@ -42,8 +42,12 @@
#define SERVER_BACKLOG 10
#define SERVER_OUTOF_FD_RETRIES 5
+#define MEDIATYPE_NAMEMAX 128 /* file name extension */
+#define MEDIATYPE_TYPEMAX 64 /* length of type/subtype */
+
#define CONFIG_RELOAD 0x00
-#define CONFIG_SERVERS 0x01
+#define CONFIG_MEDIA 0x01
+#define CONFIG_SERVERS 0x02
#define CONFIG_ALL 0xff
#define TCPFLAG_NODELAY 0x01
@@ -182,6 +186,7 @@ enum imsg_type {
IMSG_CTL_END,
IMSG_CTL_START,
IMSG_CFG_SERVER,
+ IMSG_CFG_MEDIA,
IMSG_CFG_DONE
};
@@ -304,6 +309,15 @@ struct server {
};
TAILQ_HEAD(serverlist, server);
+struct media_type {
+ char media_name[MEDIATYPE_NAMEMAX];
+ char media_type[MEDIATYPE_TYPEMAX];
+ char media_subtype[MEDIATYPE_TYPEMAX];
+ char *media_encoding;
+ RB_ENTRY(media_type) media_entry;
+};
+RB_HEAD(mediatypes, media_type);
+
struct httpd {
u_int8_t sc_opts;
u_int32_t sc_flags;
@@ -313,6 +327,7 @@ struct httpd {
u_int16_t sc_id;
struct serverlist *sc_servers;
+ struct mediatypes *sc_mediatypes;
struct privsep *sc_ps;
int sc_reload;
@@ -383,7 +398,7 @@ void server_reset_http(struct client *);
void server_close_http(struct client *);
/* server_file.c */
-int server_response(struct client *);
+int server_response(struct httpd *, struct client *);
/* httpd.c */
void event_again(struct event *, int, short,
@@ -409,7 +424,15 @@ struct kv *kv_inherit(struct kv *, struct kv *);
int kv_log(struct evbuffer *, struct kv *);
struct kv *kv_find(struct kvtree *, struct kv *);
int kv_cmp(struct kv *, struct kv *);
+struct media_type
+ *media_add(struct mediatypes *, struct media_type *);
+void media_delete(struct mediatypes *, struct media_type *);
+void media_purge(struct mediatypes *);
+struct media_type *
+ media_find(struct mediatypes *, char *);
+int media_cmp(struct media_type *, struct media_type *);
RB_PROTOTYPE(kvtree, kv, kv_node, kv_cmp);
+RB_PROTOTYPE(mediatypes, media_type, media_entry, media_cmp);
/* log.c */
void log_init(int);
@@ -459,5 +482,7 @@ int config_getreset(struct httpd *, struct imsg *);
int config_getcfg(struct httpd *, struct imsg *);
int config_setserver(struct httpd *, struct server *);
int config_getserver(struct httpd *, struct imsg *);
+int config_setmedia(struct httpd *, struct media_type *);
+int config_getmedia(struct httpd *, struct imsg *);
#endif /* _HTTPD_H */
diff --git a/parse.y b/parse.y
index b7e5b25..43e00c9 100644
--- a/parse.y
+++ b/parse.y
@@ -96,6 +96,7 @@ uint32_t last_server_id = 0;
static struct server *srv = NULL;
struct serverlist servers;
+struct media_type media;
struct address *host_v4(const char *);
struct address *host_v6(const char *);
@@ -125,7 +126,7 @@ typedef struct {
%}
-%token ALL PORT LISTEN PREFORK SERVER ERROR INCLUDE LOG VERBOSE ON
+%token ALL PORT LISTEN PREFORK SERVER ERROR INCLUDE LOG VERBOSE ON TYPES
%token UPDATES INCLUDE
%token <v.string> STRING
%token <v.number> NUMBER
@@ -140,6 +141,7 @@ grammar : /* empty */
| grammar varset '\n'
| grammar main '\n'
| grammar server '\n'
+ | grammar types '\n'
| grammar error '\n' { file->errors++; }
;
@@ -264,6 +266,51 @@ serveroptsl : LISTEN ON STRING port {
}
;
+types : TYPES '{' optnl mediaopts_l '}'
+ ;
+
+mediaopts_l : mediaopts_l mediaoptsl nl
+ | mediaoptsl optnl
+ ;
+
+mediaoptsl : STRING '/' STRING {
+ if (strlcpy(media.media_type, $1,
+ sizeof(media.media_type)) >=
+ sizeof(media.media_type) ||
+ strlcpy(media.media_subtype, $3,
+ sizeof(media.media_subtype)) >=
+ sizeof(media.media_subtype)) {
+ yyerror("media type too long");
+ free($1);
+ free($3);
+ YYERROR;
+ }
+ free($1);
+ free($3);
+ } medianames_l ';'
+ ;
+
+medianames_l : medianames_l medianamesl
+ | medianamesl
+ ;
+
+medianamesl : STRING {
+ if (strlcpy(media.media_name, $1,
+ sizeof(media.media_name)) >=
+ sizeof(media.media_name)) {
+ yyerror("media name too long");
+ free($1);
+ YYERROR;
+ }
+ free($1);
+
+ if (media_add(conf->sc_mediatypes, &media) == NULL) {
+ yyerror("failed to add media type");
+ YYERROR;
+ }
+ }
+ ;
+
port : PORT STRING {
char *a, *b;
int p[2];
@@ -304,11 +351,6 @@ loglevel : UPDATES { $$ = HTTPD_OPT_LOGUPDATE; }
| ALL { $$ = HTTPD_OPT_LOGALL; }
;
-comma : ','
- | nl
- | /* empty */
- ;
-
optnl : '\n' optnl
|
;
@@ -358,6 +400,7 @@ lookup(char *s)
{ "port", PORT },
{ "prefork", PREFORK },
{ "server", SERVER },
+ { "types", TYPES },
{ "updates", UPDATES }
};
const struct keywords *p;
@@ -585,7 +628,7 @@ nodigits:
(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
x != '{' && x != '}' && x != '<' && x != '>' && \
x != '!' && x != '=' && x != '#' && \
- x != ',' && x != '/'))
+ x != ',' && x != ';' && x != '/'))
if (isalnum(c) || c == ':' || c == '_') {
do {
@@ -722,6 +765,9 @@ int
load_config(const char *filename, struct httpd *x_conf)
{
struct sym *sym, *next;
+ struct http_mediatype mediatypes[] = MEDIA_TYPES;
+ struct media_type m;
+ int i;
conf = x_conf;
conf->sc_flags = 0;
@@ -764,6 +810,25 @@ load_config(const char *filename, struct httpd *x_conf)
errors++;
}
+ if (RB_EMPTY(conf->sc_mediatypes)) {
+ /* Add default media types */
+ for (i = 0; mediatypes[i].media_name != NULL; i++) {
+ (void)strlcpy(m.media_name, mediatypes[i].media_name,
+ sizeof(m.media_name));
+ (void)strlcpy(m.media_type, mediatypes[i].media_type,
+ sizeof(m.media_type));
+ (void)strlcpy(m.media_subtype,
+ mediatypes[i].media_subtype,
+ sizeof(m.media_subtype));
+
+ if (media_add(conf->sc_mediatypes, &m) == NULL) {
+ log_warnx("failed to add default media \"%s\"",
+ m.media_name);
+ errors++;
+ }
+ }
+ }
+
return (errors ? -1 : 0);
}
diff --git a/server.c b/server.c
index fe62b40..4106d92 100644
--- a/server.c
+++ b/server.c
@@ -566,6 +566,9 @@ int
server_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
{
switch (imsg->hdr.type) {
+ case IMSG_CFG_MEDIA:
+ config_getmedia(env, imsg);
+ break;
case IMSG_CFG_SERVER:
config_getserver(env, imsg);
break;
diff --git a/server_file.c b/server_file.c
index d9920df..de3be92 100644
--- a/server_file.c
+++ b/server_file.c
@@ -47,11 +47,12 @@
#include "http.h"
int
-server_response(struct client *clt)
+server_response(struct httpd *env, struct client *clt)
{
struct http_descriptor *desc = clt->clt_desc;
struct server *srv = clt->clt_server;
- struct kv *kv;
+ struct kv *ct, *cl;
+ struct media_type *media;
const char *errstr = NULL;
int fd = -1;
char path[MAXPATHLEN];
@@ -90,14 +91,31 @@ server_response(struct client *clt)
if ((fd = open(path, O_RDONLY)) == -1 || fstat(fd, &st) == -1)
goto fail;
- /* XXX verify results XXX */
kv_purge(&desc->http_headers);
- kv_add(&desc->http_headers, "Server", HTTPD_SERVERNAME);
- kv_add(&desc->http_headers, "Connection", "close");
- if ((kv = kv_add(&desc->http_headers, "Content-Length", NULL)) != NULL)
- kv_set(kv, "%ld", st.st_size);
- kv_setkey(&desc->http_pathquery, "200");
- kv_set(&desc->http_pathquery, "%s", server_httperror_byid(200));
+
+ /* Add error codes */
+ if (kv_setkey(&desc->http_pathquery, "200") == -1 ||
+ kv_set(&desc->http_pathquery, "%s",
+ server_httperror_byid(200)) == -1)
+ goto fail;
+
+ /* Add headers */
+ if (kv_add(&desc->http_headers, "Server", HTTPD_SERVERNAME) == NULL ||
+ kv_add(&desc->http_headers, "Connection", "close") == NULL ||
+ (ct = kv_add(&desc->http_headers, "Content-Type", NULL)) == NULL ||
+ (cl = kv_add(&desc->http_headers, "Content-Length", NULL)) == NULL)
+ goto fail;
+
+ /* Set content type */
+ media = media_find(env->sc_mediatypes, path);
+ if (kv_set(ct, "%s/%s",
+ media == NULL ? "application" : media->media_type,
+ media == NULL ? "octet-stream" : media->media_subtype) == -1)
+ goto fail;
+
+ /* Set content length */
+ if (kv_set(cl, "%ld", st.st_size) == -1)
+ goto fail;
if (server_writeresponse_http(clt) == -1 ||
server_bufferevent_print(clt, "\r\n") == -1 ||
diff --git a/server_http.c b/server_http.c
index 9a956c9..674e92b 100644
--- a/server_http.c
+++ b/server_http.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: server_http.c,v 1.2 2014/07/12 23:55:35 reyk Exp $ */
+/* $OpenBSD: server_http.c,v 1.3 2014/07/13 09:46:19 beck Exp $ */
/*
* Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -319,7 +319,7 @@ server_read_http(struct bufferevent *bev, void *arg)
done:
if (clt->clt_toread <= 0) {
- if (server_response(clt) == -1)
+ if (server_response(env, clt) == -1)
return;
}
@@ -550,8 +550,14 @@ server_abort_http(struct client *clt, u_int code, const char *msg)
/* A CSS stylesheet allows minimal customization by the user */
style = "body { background-color: white; color: black; font-family: "
- "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }";
-
+ "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }"
+ "blink { animation:blink 1s; animation-iteration-count: infinite;"
+ "-webkit-animation:blink 1s;"
+ "-webkit-animation-iteration-count: infinite;}"
+ "@keyframes blink { 0%{opacity:0.0;} 50%{opacity:0.0;}"
+ "50.01%{opacity:1.0;} 100%{opacity:1.0;} }"
+ "@-webkit-keyframes blink { 0%{opacity:0.0;} 50%{opacity:0.0;}"
+ "50.01%{opacity:1.0;} 100%{opacity:1.0;} }";
/* Generate simple HTTP+HTML error document */
if (asprintf(&httpmsg,
"HTTP/1.0 %03d %s\r\n"
@@ -568,7 +574,7 @@ server_abort_http(struct client *clt, u_int code, const char *msg)
"<style type=\"text/css\"><!--\n%s\n--></style>\n"
"</head>\n"
"<body>\n"
- "<h1>%s</h1>\n"
+ "<h1><blink>%s</blink></h1>\n"
"<div id='m'>%s</div>\n"
"<hr><address>%s at %s port %d</address>\n"
"</body>\n"