From b0a1cfbe755d14e21af5e866fc7321cf6a3c8d0b Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sun, 13 Jul 2014 16:12:59 +0200 Subject: Implement media type support --- config.c | 60 ++++++++++++++++++++++++++++++++++++++++++++- http.h | 18 ++++++++++++++ httpd.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- httpd.conf.5 | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++- httpd.h | 29 ++++++++++++++++++++-- parse.y | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++------ server.c | 3 +++ server_file.c | 36 ++++++++++++++++++++------- server_http.c | 16 ++++++++---- 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 STRING %token 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 @@ -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) "\n" "\n" "\n" - "

%s

\n" + "

%s

\n" "
%s
\n" "
%s at %s port %d
\n" "\n" -- cgit v1.2.3-54-g00ecf