kore

An easy to use, scalable and secure web application framework for writing web APIs in C.
Commits | Files | Refs | README | LICENSE | git clone https://git.kore.io/kore.git

stream.c (6102B)



      1 /*
      2  * Copyright (c) 2014 Joris Vink <joris@coders.se>
      3  *
      4  * Permission to use, copy, modify, and distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include <sys/param.h>
     18 #include <sys/stat.h>
     19 #include <sys/mman.h>
     20 #include <sys/queue.h>
     21 
     22 #include <fcntl.h>
     23 #include <stdint.h>
     24 
     25 #include <kore/kore.h>
     26 #include <kore/http.h>
     27 
     28 #include "assets.h"
     29 
     30 struct video {
     31 	int			fd;
     32 	int			ref;
     33 	off_t			size;
     34 	char			*path;
     35 	u_int8_t		*data;
     36 	void			*base;
     37 
     38 	TAILQ_ENTRY(video)	list;
     39 };
     40 
     41 int		init(int);
     42 int		video_stream(struct http_request *);
     43 
     44 static void	video_unmap(struct video *);
     45 static int	video_stream_finish(struct netbuf *);
     46 static int	video_mmap(struct http_request *, struct video *);
     47 static int	video_open(struct http_request *, struct video **);
     48 
     49 TAILQ_HEAD(, video)		videos;
     50 
     51 int
     52 init(int state)
     53 {
     54 	if (state == KORE_MODULE_UNLOAD) {
     55 		kore_log(LOG_NOTICE, "not reloading module");
     56 		return (KORE_RESULT_ERROR);
     57 	}
     58 
     59 	TAILQ_INIT(&videos);
     60 	return (KORE_RESULT_OK);
     61 }
     62 
     63 int
     64 video_stream(struct http_request *req)
     65 {
     66 	struct video	*v;
     67 	const char	*header;
     68 	off_t		start, end;
     69 	int		n, err, status;
     70 	char		*bytes, *range[3], rb[128], *ext, ctype[32];
     71 
     72 	if (!video_open(req, &v))
     73 		return (KORE_RESULT_OK);
     74 
     75 	if ((ext = strrchr(req->path, '.')) == NULL) {
     76 		v->ref--;
     77 		http_response(req, 400, NULL, 0);
     78 		return (KORE_RESULT_OK);
     79 	}
     80 
     81 	if (!kore_snprintf(ctype, sizeof(ctype), NULL, "video/%s", ext + 1)) {
     82 		v->ref--;
     83 		http_response(req, 500, NULL, 0);
     84 		return (KORE_RESULT_OK);
     85 	}
     86 
     87 	kore_log(LOG_NOTICE, "%p: opened %s (%s) for streaming (%jd ref:%d)",
     88 	    (void *)req->owner, v->path, ctype, (intmax_t)v->size, v->ref);
     89 
     90 	if (http_request_header(req, "range", &header)) {
     91 		if ((bytes = strchr(header, '=')) == NULL) {
     92 			v->ref--;
     93 			http_response(req, 416, NULL, 0);
     94 			return (KORE_RESULT_OK);
     95 		}
     96 
     97 		bytes++;
     98 		n = kore_split_string(bytes, "-", range, 3);
     99 		if (n == 0) {
    100 			v->ref--;
    101 			http_response(req, 416, NULL, 0);
    102 			return (KORE_RESULT_OK);
    103 		}
    104 
    105 		if (n >= 1) {
    106 			start = kore_strtonum64(range[0], 1, &err);
    107 			if (err != KORE_RESULT_OK) {
    108 				v->ref--;
    109 				http_response(req, 416, NULL, 0);
    110 				return (KORE_RESULT_OK);
    111 			}
    112 		}
    113 
    114 		if (n > 1) {
    115 			end = kore_strtonum64(range[1], 1, &err);
    116 			if (err != KORE_RESULT_OK) {
    117 				v->ref--;
    118 				http_response(req, 416, NULL, 0);
    119 				return (KORE_RESULT_OK);
    120 			}
    121 		} else {
    122 			end = 0;
    123 		}
    124 
    125 		if (end == 0)
    126 			end = v->size;
    127 
    128 		if (start > end || start > v->size || end > v->size) {
    129 			v->ref--;
    130 			http_response(req, 416, NULL, 0);
    131 			return (KORE_RESULT_OK);
    132 		}
    133 
    134 		status = 206;
    135 		if (!kore_snprintf(rb, sizeof(rb), NULL,
    136 		    "bytes %ld-%ld/%ld", start, end - 1, v->size)) {
    137 			v->ref--;
    138 			http_response(req, 500, NULL, 0);
    139 			return (KORE_RESULT_OK);
    140 		}
    141 
    142 		kore_log(LOG_NOTICE, "%p: %s sending: %jd-%jd/%jd",
    143 		    (void *)req->owner, v->path, (intmax_t)start,
    144 		    (intmax_t)end - 1, (intmax_t)v->size);
    145 		http_response_header(req, "content-range", rb);
    146 	} else {
    147 		start = 0;
    148 		status = 200;
    149 		end = v->size;
    150 	}
    151 
    152 	http_response_header(req, "content-type", ctype);
    153 	http_response_header(req, "accept-ranges", "bytes");
    154 	http_response_stream(req, status, v->data + start,
    155 	    end - start, video_stream_finish, v);
    156 
    157 	return (KORE_RESULT_OK);
    158 }
    159 
    160 static int
    161 video_open(struct http_request *req, struct video **out)
    162 {
    163 	struct stat		st;
    164 	struct video		*v;
    165 	char			fpath[MAXPATHLEN];
    166 
    167 	if (!kore_snprintf(fpath, sizeof(fpath), NULL, "videos%s", req->path)) {
    168 		http_response(req, 500, NULL, 0);
    169 		return (KORE_RESULT_ERROR);
    170 	}
    171 
    172 	TAILQ_FOREACH(v, &videos, list) {
    173 		if (!strcmp(v->path, fpath)) {
    174 			if (video_mmap(req, v)) {
    175 				*out = v;
    176 				return (KORE_RESULT_OK);
    177 			}
    178 
    179 			close(v->fd);
    180 			TAILQ_REMOVE(&videos, v, list);
    181 			kore_free(v->path);
    182 			kore_free(v);
    183 
    184 			http_response(req, 500, NULL, 0);
    185 			return (KORE_RESULT_ERROR);
    186 		}
    187 	}
    188 
    189 	v = kore_malloc(sizeof(*v));
    190 	v->ref = 0;
    191 	v->base = NULL;
    192 	v->data = NULL;
    193 	v->path = kore_strdup(fpath);
    194 
    195 	if ((v->fd = open(fpath, O_RDONLY)) == -1) {
    196 		kore_free(v->path);
    197 		kore_free(v);
    198 
    199 		if (errno == ENOENT)
    200 			http_response(req, 404, NULL, 0);
    201 		else
    202 			http_response(req, 500, NULL, 0);
    203 
    204 		return (KORE_RESULT_ERROR);
    205 	}
    206 
    207 	if (fstat(v->fd, &st) == -1) {
    208 		close(v->fd);
    209 		kore_free(v->path);
    210 		kore_free(v);
    211 
    212 		http_response(req, 500, NULL, 0);
    213 		return (KORE_RESULT_ERROR);
    214 	}
    215 
    216 	v->size = st.st_size;
    217 	if (!video_mmap(req, v)) {
    218 		close(v->fd);
    219 		kore_free(v->path);
    220 		kore_free(v);
    221 
    222 		http_response(req, 500, NULL, 0);
    223 		return (KORE_RESULT_ERROR);
    224 	}
    225 
    226 	*out = v;
    227 	TAILQ_INSERT_TAIL(&videos, v, list);
    228 
    229 	return (KORE_RESULT_OK);
    230 }
    231 
    232 static int
    233 video_mmap(struct http_request *req, struct video *v)
    234 {
    235 	if (v->base != NULL && v->data != NULL) {
    236 		v->ref++;
    237 		return (KORE_RESULT_OK);
    238 	}
    239 
    240 	v->base = mmap(NULL, v->size, PROT_READ, MAP_SHARED, v->fd, 0);
    241 	if (v->base == MAP_FAILED)
    242 		return (KORE_RESULT_ERROR);
    243 
    244 	v->ref++;
    245 	v->data = v->base;
    246 
    247 	return (KORE_RESULT_OK);
    248 }
    249 
    250 static int
    251 video_stream_finish(struct netbuf *nb)
    252 {
    253 	struct video	*v = nb->extra;
    254 
    255 	v->ref--;
    256 	kore_log(LOG_NOTICE, "%p: video stream %s done (%zu/%zu ref:%d)",
    257 	    (void *)nb->owner, v->path, nb->s_off, nb->b_len, v->ref);
    258 
    259 	if (v->ref == 0)
    260 		video_unmap(v);
    261 
    262 	return (KORE_RESULT_OK);
    263 }
    264 
    265 static void
    266 video_unmap(struct video *v)
    267 {
    268 	if (munmap(v->base, v->size) == -1) {
    269 		kore_log(LOG_ERR, "munmap(%s): %s", v->path, errno_s);
    270 	} else {
    271 		v->base = NULL;
    272 		v->data = NULL;
    273 		kore_log(LOG_NOTICE,
    274 		    "unmapped %s for streaming, no refs left", v->path);
    275 	}
    276 }