Add support for media types (aka. MIME types): the types section is
authorreyk <reyk@openbsd.org>
Sun, 13 Jul 2014 14:17:37 +0000 (14:17 +0000)
committerreyk <reyk@openbsd.org>
Sun, 13 Jul 2014 14:17:37 +0000 (14:17 +0000)
compatible to nginx' mime.types file which can be included directly.
If not present, use a few built-in defaults for html, css, txt, jpeg,
gif, png, and js.

usr.sbin/httpd/config.c
usr.sbin/httpd/http.h
usr.sbin/httpd/httpd.c
usr.sbin/httpd/httpd.conf.5
usr.sbin/httpd/httpd.h
usr.sbin/httpd/parse.y
usr.sbin/httpd/server.c
usr.sbin/httpd/server_file.c
usr.sbin/httpd/server_http.c

index e59f6ed..230ec35 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: config.c,v 1.1 2014/07/12 23:34:54 reyk Exp $ */
+/*     $OpenBSD: config.c,v 1.2 2014/07/13 14:17:37 reyk Exp $ */
 
 /*
  * Copyright (c) 2011 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -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);
+}
index 76d98e3..46273b7 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: http.h,v 1.1 2014/07/12 23:34:54 reyk Exp $   */
+/*     $OpenBSD: http.h,v 1.2 2014/07/13 14:17:37 reyk Exp $   */
 
 /*
  * Copyright (c) 2012 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -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;
index c08db55..f8f0323 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: httpd.c,v 1.1 2014/07/12 23:34:54 reyk Exp $  */
+/*     $OpenBSD: httpd.c,v 1.2 2014/07/13 14:17:37 reyk Exp $  */
 
 /*
  * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
@@ -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);
index a2c0cc4..a6f90a6 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: httpd.conf.5,v 1.1 2014/07/12 23:34:54 reyk Exp $
+.\"    $OpenBSD: httpd.conf.5,v 1.2 2014/07/13 14:17:37 reyk Exp $
 .\"
 .\" Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
 .\"
@@ -14,7 +14,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: July 12 2014 $
+.Dd $Mdocdate: July 13 2014 $
 .Dt HTTPD.CONF 5
 .Os
 .Sh NAME
@@ -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
index d4fd4ca..fc1f9b8 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: httpd.h,v 1.1 2014/07/12 23:34:54 reyk Exp $  */
+/*     $OpenBSD: httpd.h,v 1.2 2014/07/13 14:17:37 reyk Exp $  */
 
 /*
  * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
 #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 */
index b7e5b25..ec2be91 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parse.y,v 1.1 2014/07/12 23:34:54 reyk Exp $  */
+/*     $OpenBSD: parse.y,v 1.2 2014/07/13 14:17:37 reyk Exp $  */
 
 /*
  * Copyright (c) 2007 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -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);
 }
 
index fe62b40..e963492 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: server.c,v 1.1 2014/07/12 23:34:54 reyk Exp $ */
+/*     $OpenBSD: server.c,v 1.2 2014/07/13 14:17:37 reyk Exp $ */
 
 /*
  * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -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;
index d9920df..9774173 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: server_file.c,v 1.1 2014/07/12 23:34:54 reyk Exp $    */
+/*     $OpenBSD: server_file.c,v 1.2 2014/07/13 14:17:37 reyk Exp $    */
 
 /*
  * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
 #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 ||
index 886dfa4..4cecaed 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: server_http.c,v 1.3 2014/07/13 09:46:19 beck Exp $    */
+/*     $OpenBSD: server_http.c,v 1.4 2014/07/13 14:17:37 reyk 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;
                }