1#include "base.h" 2#include "log.h" 3#include "buffer.h" 4#include "response.h" 5#include "http_chunk.h" 6#include "stat_cache.h" 7 8#include "plugin.h" 9 10#include <ctype.h> 11#include <stdlib.h> 12#include <string.h> 13 14/* plugin config for all request/connections */ 15 16typedef struct { 17 array *extensions; 18} plugin_config; 19 20typedef struct { 21 PLUGIN_DATA; 22 23 buffer *query_str; 24 array *get_params; 25 26 plugin_config **config_storage; 27 28 plugin_config conf; 29} plugin_data; 30 31/* init the plugin data */ 32INIT_FUNC(mod_flv_streaming_init) { 33 plugin_data *p; 34 35 p = calloc(1, sizeof(*p)); 36 37 p->query_str = buffer_init(); 38 p->get_params = array_init(); 39 40 return p; 41} 42 43/* detroy the plugin data */ 44FREE_FUNC(mod_flv_streaming_free) { 45 plugin_data *p = p_d; 46 47 UNUSED(srv); 48 49 if (!p) return HANDLER_GO_ON; 50 51 if (p->config_storage) { 52 size_t i; 53 54 for (i = 0; i < srv->config_context->used; i++) { 55 plugin_config *s = p->config_storage[i]; 56 57 if (NULL == s) continue; 58 59 array_free(s->extensions); 60 61 free(s); 62 } 63 free(p->config_storage); 64 } 65 66 buffer_free(p->query_str); 67 array_free(p->get_params); 68 69 free(p); 70 71 return HANDLER_GO_ON; 72} 73 74/* handle plugin config and check values */ 75 76SETDEFAULTS_FUNC(mod_flv_streaming_set_defaults) { 77 plugin_data *p = p_d; 78 size_t i = 0; 79 80 config_values_t cv[] = { 81 { "flv-streaming.extensions", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ 82 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } 83 }; 84 85 if (!p) return HANDLER_ERROR; 86 87 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *)); 88 89 for (i = 0; i < srv->config_context->used; i++) { 90 data_config const* config = (data_config const*)srv->config_context->data[i]; 91 plugin_config *s; 92 93 s = calloc(1, sizeof(plugin_config)); 94 s->extensions = array_init(); 95 96 cv[0].destination = s->extensions; 97 98 p->config_storage[i] = s; 99 100 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { 101 return HANDLER_ERROR; 102 } 103 } 104 105 return HANDLER_GO_ON; 106} 107 108#define PATCH(x) \ 109 p->conf.x = s->x; 110static int mod_flv_streaming_patch_connection(server *srv, connection *con, plugin_data *p) { 111 size_t i, j; 112 plugin_config *s = p->config_storage[0]; 113 114 PATCH(extensions); 115 116 /* skip the first, the global context */ 117 for (i = 1; i < srv->config_context->used; i++) { 118 data_config *dc = (data_config *)srv->config_context->data[i]; 119 s = p->config_storage[i]; 120 121 /* condition didn't match */ 122 if (!config_check_cond(srv, con, dc)) continue; 123 124 /* merge config */ 125 for (j = 0; j < dc->value->used; j++) { 126 data_unset *du = dc->value->data[j]; 127 128 if (buffer_is_equal_string(du->key, CONST_STR_LEN("flv-streaming.extensions"))) { 129 PATCH(extensions); 130 } 131 } 132 } 133 134 return 0; 135} 136#undef PATCH 137 138static int split_get_params(array *get_params, buffer *qrystr) { 139 size_t is_key = 1; 140 size_t i, len; 141 char *key = NULL, *val = NULL; 142 143 key = qrystr->ptr; 144 145 /* we need the \0 */ 146 len = buffer_string_length(qrystr); 147 for (i = 0; i <= len; i++) { 148 switch(qrystr->ptr[i]) { 149 case '=': 150 if (is_key) { 151 val = qrystr->ptr + i + 1; 152 153 qrystr->ptr[i] = '\0'; 154 155 is_key = 0; 156 } 157 158 break; 159 case '&': 160 case '\0': /* fin symbol */ 161 if (!is_key) { 162 data_string *ds; 163 /* we need at least a = since the last & */ 164 165 /* terminate the value */ 166 qrystr->ptr[i] = '\0'; 167 168 if (NULL == (ds = (data_string *)array_get_unused_element(get_params, TYPE_STRING))) { 169 ds = data_string_init(); 170 } 171 buffer_copy_string_len(ds->key, key, strlen(key)); 172 buffer_copy_string_len(ds->value, val, strlen(val)); 173 174 array_insert_unique(get_params, (data_unset *)ds); 175 } 176 177 key = qrystr->ptr + i + 1; 178 val = NULL; 179 is_key = 1; 180 break; 181 } 182 } 183 184 return 0; 185} 186 187URIHANDLER_FUNC(mod_flv_streaming_path_handler) { 188 plugin_data *p = p_d; 189 int s_len; 190 size_t k; 191 192 UNUSED(srv); 193 194 if (con->mode != DIRECT) return HANDLER_GO_ON; 195 196 if (buffer_string_is_empty(con->physical.path)) return HANDLER_GO_ON; 197 198 mod_flv_streaming_patch_connection(srv, con, p); 199 200 s_len = buffer_string_length(con->physical.path); 201 202 for (k = 0; k < p->conf.extensions->used; k++) { 203 data_string *ds = (data_string *)p->conf.extensions->data[k]; 204 int ct_len = buffer_string_length(ds->value); 205 206 if (ct_len > s_len) continue; 207 if (buffer_is_empty(ds->value)) continue; 208 209 if (0 == strncmp(con->physical.path->ptr + s_len - ct_len, ds->value->ptr, ct_len)) { 210 data_string *get_param; 211 stat_cache_entry *sce = NULL; 212 int start; 213 char *err = NULL; 214 /* if there is a start=[0-9]+ in the header use it as start, 215 * otherwise send the full file */ 216 217 array_reset(p->get_params); 218 buffer_copy_buffer(p->query_str, con->uri.query); 219 split_get_params(p->get_params, p->query_str); 220 221 if (NULL == (get_param = (data_string *)array_get_element(p->get_params, "start"))) { 222 return HANDLER_GO_ON; 223 } 224 225 /* too short */ 226 if (buffer_string_is_empty(get_param->value)) return HANDLER_GO_ON; 227 228 /* check if it is a number */ 229 start = strtol(get_param->value->ptr, &err, 10); 230 if (*err != '\0') { 231 return HANDLER_GO_ON; 232 } 233 234 if (start <= 0) return HANDLER_GO_ON; 235 236 /* check if start is > filesize */ 237 if (HANDLER_GO_ON != stat_cache_get_entry(srv, con, con->physical.path, &sce)) { 238 return HANDLER_GO_ON; 239 } 240 241 if (start > sce->st.st_size) { 242 return HANDLER_GO_ON; 243 } 244 245 /* we are safe now, let's build a flv header */ 246 http_chunk_append_mem(srv, con, CONST_STR_LEN("FLV\x1\x1\0\0\0\x9\0\0\0\x9")); 247 http_chunk_append_file(srv, con, con->physical.path, start, sce->st.st_size - start); 248 http_chunk_close(srv, con); 249 250 response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("video/x-flv")); 251 252 con->file_finished = 1; 253 254 return HANDLER_FINISHED; 255 } 256 } 257 258 /* not found */ 259 return HANDLER_GO_ON; 260} 261 262/* this function is called at dlopen() time and inits the callbacks */ 263 264int mod_flv_streaming_plugin_init(plugin *p); 265int mod_flv_streaming_plugin_init(plugin *p) { 266 p->version = LIGHTTPD_VERSION_ID; 267 p->name = buffer_init_string("flv_streaming"); 268 269 p->init = mod_flv_streaming_init; 270 p->handle_physical = mod_flv_streaming_path_handler; 271 p->set_defaults = mod_flv_streaming_set_defaults; 272 p->cleanup = mod_flv_streaming_free; 273 274 p->data = NULL; 275 276 return 0; 277} 278