mod_musicindex  1.4.1
mod_musicindex.c
Go to the documentation of this file.
1 /*
2  * mod_musicindex.c
3  * mod_musicindex
4  *
5  * $Id: mod_musicindex.c 1012 2012-08-08 08:58:25Z varenet $
6  *
7  * Created by Regis BOUDIN on Sat Dec 28 2002.
8  * Copyright (c) 2002-2006 Regis BOUDIN
9  * Copyright (c) 2002-2005,2007-2008,2010-2012 Thibaut VARENE
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 2.1,
13  * as published by the Free Software Foundation.
14  *
15  */
16 
17 
168 #include "mod_musicindex.h"
169 #include "playlist.h"
170 #include "config.h"
171 #include "html.h"
172 #include "http.h"
173 #include "sort.h"
174 
175 #include <http_core.h>
176 
177 #ifdef HAVE_UNISTD_H
178 #include <unistd.h>
179 #endif
180 #ifdef HAVE_SETLOCALE
181 #include <locale.h>
182 #endif
183 #ifdef HAVE_SYS_TIME_H
184 #include <sys/time.h>
185 #endif
186 
187 #ifdef BUILD_FOR_APACHE2
188 
189 static const char *handlers[] = {
190  "audio/mpeg",
191  "audio/mp4",
192  "audio/flac",
193  "audio/ogg",
194  "audio/x-flac",
195  "application/x-flac",
196  "application/ogg",
197  "audio/x-ogg", /* At some point, we should be able to remove this one */
198  NULL
199 };
200 #else
201 #include <http_main.h>
202 #endif
203 
204 #ifdef ENABLE_OUTPUT_ARCHIVE
205 extern void send_tarball(request_rec *, const mu_pack *const);
206 extern ssize_t tarball_size(request_rec *, const mu_pack *const);
207 #endif
208 
216 static int is_msie_user_agent(request_rec *r)
217 {
218  const char* user_agent = NULL;
219 
220  user_agent = apr_table_get(r->headers_in, "User-Agent");
221  return strstr(user_agent, "MSIE") && 1;
222 }
223 
227 static int handle_musicindex(request_rec *r)
228 {
229  /* Get the module configuration */
230  mu_config *const conf = (mu_config *)ap_get_module_config(r->per_dir_config, &musicindex_module);
231  mu_pack master_pack = {
232  .head = NULL,
233  .fhead = NULL,
234  .dirnb = 0,
235  .filenb = 0,
236  .fsize = 0,
237  }, custom_pack = {
238  .head = NULL,
239  .fhead = NULL,
240  .dirnb = 0,
241  .filenb = 0,
242  .fsize = 0,
243  };
244 
245  STRUCTTV tvbegin, tvprocess;
246 
247 #ifdef HAVE_GETTIMEOFDAY
248  struct timeval tvend;
249  gettimeofday(&tvbegin, NULL);
250 #endif
251 
252  r->allowed |= ((1 << M_GET) | (1 << M_POST));
253 
254  /* if the the module is not active, decline the request */
255  if (!(conf->options & MI_ACTIVE))
256  return DECLINED;
257 
258 #ifdef BUILD_FOR_APACHE2
259  if(!r->handler || strcmp(r->handler, DIR_MAGIC_TYPE))
260  return DECLINED;
261 #endif
262 
263  /* before doing anything (checking args, calling subfunctions), check we
264  can open the directory */
265  if (access(r->filename, R_OK|X_OK) != 0) {
266  mi_rerror("Can't open directory: %s", r->filename);
267  return HTTP_FORBIDDEN;
268  }
269 
270  /* This part mostly comes from mod_autoindex. If the requested dir does
271  not end with a '/', generate a new request with it */
272  if (r->uri[0] == '\0' || r->uri[strlen(r->uri) - 1] != '/') {
273  char *file;
274  if (r->args != NULL)
275  file = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri),
276  "/", "?", r->args, NULL);
277  else
278  file = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri),
279  "/", NULL);
280 
281  apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, file, r));
282  return HTTP_MOVED_PERMANENTLY;
283  }
284 
285  /* Deal with optional arguments or decline requests we don't handle */
286  switch (r->method_number) {
287  case M_GET:
288  treat_get_args(r);
289  break;
290  case M_POST:
291  treat_post_args(r);
292  break;
293  default:
294  return DECLINED;
295  }
296  /* Now, the conf structure contains all the flags correctly set and
297  conf->custom_list points to the arguments of the request, either given
298  by get or post method */
299 
300  /* XXX this is ugly */
301  if (conf->options & MI_RANDOMDIR) {
302  conf->options &= ~MI_RANDOMDIR;
303  send_randomdir(r);
304  return HTTP_MOVED_TEMPORARILY;
305  }
306 
307  /* Cache prologue can happen no later than here */
308  if (conf->cache && conf->cache->prologue)
309  conf->cache->prologue(r);
310 
311  /* build the string containing the cookie content. We need it for
312  webpages and requests to stream the cookie content */
313  if (((conf->options & (MI_STREAM|MI_TARBALL)) == 0) || ((conf->options & MI_STREAMRQ) == MI_STREAMCOOKIE) || ((conf->options & MI_DWNLDRQ) == MI_DWNLDCOOKIE))
315 
316  if (((conf->options & MI_STREAMRQ) == MI_STREAMLST) ||
317  ((conf->options & MI_STREAMRQ) == MI_COOKIESTREAM) ||
318  ((conf->options & MI_DWNLDRQ) == MI_DWNLDLST) ||
319  ((conf->options & MI_DWNLDRQ) == MI_COOKIEDWNLD)) {
320  /* We build a custom playlist, don't bother with current directory */
321  build_custom_list(r, &master_pack);
322  }
323  else {
324  /* Build current directory content (recursive if necessary) */
325  make_music_entry(r, r->pool, &master_pack, NULL, MI_RECURSIVE);
326  listsort(&master_pack, conf->order);
327  }
328 
329  /* Build custom link list, from conf->custom_list string */
330  if (((conf->options & MI_STREAM) == 0) && (conf->custom_list != NULL))
331  build_custom_list(r, &custom_pack);
332 
333  /* Cache epilogue can happen as early as here */
334  if (conf->cache && conf->cache->epilogue)
335  conf->cache->epilogue(r);
336 
337  /* Depending on the request type, set the HTTP headers. By default we
338  return a web page. */
339  if (conf->options & MI_STREAM) {
340  char content_disposition[64] = "";
341  if (is_msie_user_agent(r)) /* this helps IE dealing with the data */
342  strcat(content_disposition, "attachment; ");
343 
344  strcat(content_disposition, "filename=\"playlist.m3u\"");
345 
346  ap_set_content_type(r, "audio/x-mpegurl");
347  apr_table_set(r->headers_out, "Content-Disposition", content_disposition);
348  /* apr_table_set() duplicates keys and values, this is necessary here */
349  }
350 #ifdef ENABLE_OUTPUT_ARCHIVE
351  else if (conf->options & MI_TARBALL) {
352  const ssize_t tballsize = tarball_size(r, &master_pack);
353  ap_set_content_type(r, "application/x-tar");
354  apr_table_setn(r->headers_out, "Content-Disposition",
355  "filename = \"playlist.tar\"");
356  /* send the size of the archive so that browsers can put a nice
357  progress bar
358  XXX correct modifier for size_t is 'z' but seems (some versions of?)
359  apache doesn't know about it. */
360  apr_table_setn(r->headers_out, "Content-Length",
361  apr_psprintf(r->pool, "%lu", tballsize));
362  }
363 #endif
364  else if (conf->options & MI_RSS) {
365  ap_set_content_type(r, "text/xml; charset=\"utf-8\"");
366  }
367  else {
368  if (is_msie_user_agent(r)) /* IE derps on true XML */
369  ap_set_content_type(r, "text/html; charset=\"utf-8\"");
370  else
371  ap_set_content_type(r, "application/xhtml+xml; charset=\"utf-8\"");
372 
373  /* If we have a cookie string, send it */
374  if (conf->custom_list != NULL)
375  apr_table_setn(r->headers_out, "Set-Cookie", conf->custom_list);
376  }
377 
378  ap_send_http_header(r);
379 
380  if (r->header_only)
381  return OK;
382 
383 #ifdef HAVE_GETTIMEOFDAY
384  /* Timing request processing */
385  gettimeofday(&tvend, NULL);
386  timersub(&tvend, &tvbegin, &tvprocess);
387 #endif
388 
389  /* At this point we're sending data back */
390  if (conf->options & MI_STREAM)
391  send_playlist(r, &master_pack);
392  else if (conf->options & MI_RSS)
393  send_rss(r, &master_pack);
394 #ifdef ENABLE_OUTPUT_ARCHIVE
395  else if (conf->options & MI_TARBALL)
396  send_tarball(r, &master_pack);
397 #endif
398  else {
399  send_head(r);
400  if (!conf->search && conf->dir_per_line)
401  send_directories(r, &master_pack);
402  send_tracks(r, &master_pack);
403  send_customlist(r, &custom_pack);
404  send_foot(r, &tvbegin, &tvprocess);
405  }
406 
407  return OK;
408 }
409 
419 static int handle_musicfile(request_rec *r)
420 {
421  mu_config *const conf = (mu_config *)ap_get_module_config(r->per_dir_config, &musicindex_module);
422 
423 #ifdef BUILD_FOR_APACHE2
424  register unsigned short i;
425 #endif
426 
427  /* is this a request method we handle? */
428  if ((r->method_number != M_GET) || (!(conf->options & MI_ACTIVE)))
429  return DECLINED;
430 
431 #ifdef BUILD_FOR_APACHE2
432  /* is this a mime type we handle? */
433  if (!r->handler)
434  return DECLINED;
435 
436  for (i=0; handlers[i] && strcmp(r->handler, handlers[i]); i++);
437 
438  if (handlers[i] == NULL)
439  return DECLINED;
440 #endif
441 
442  /* request response rationale:
443  - Stream is allowed:
444  + we have a "?stream" argument in the request, the client asks for
445  a playlist containing a single file -> OK
446  + we have no argument and no icecast server is set -> set Content-Length
447  and DECLINED
448  - Download is allowed:
449  + we have no argument -> DECLINED
450  Otherwise, we consider it is a forbidden request. Keep in mind that
451  DECLINED just means the module won't handle the request, whereas
452  HTTP_FORBIDDEN means the request is not allowed, no matter what. */
453 
454  if (conf->options & MI_ALLOWSTREAM) {
455  mu_pack master_pack = {
456  .head = NULL,
457  .fhead = NULL,
458  .dirnb = 0,
459  .filenb = 0,
460  };
461 
462  if (r->args && !strcmp(r->args, "stream")) {
463  ap_set_content_type(r, "audio/x-mpegurl");
464  apr_table_setn(r->headers_out, "Content-Disposition",
465  "filename = \"playlist.m3u\"");
466 
467  ap_send_http_header(r);
468 
469  if (r->header_only)
470  return OK;
471  }
472 
473  if (conf->cache && conf->cache->prologue)
474  conf->cache->prologue(r);
475 
476  make_music_entry(r, r->pool, &master_pack, NULL, MI_ALLOWSTREAM);
477 
478  if (conf->cache && conf->cache->epilogue)
479  conf->cache->epilogue(r);
480 
481  master_pack.fhead = master_pack.head;
482 
483  /* the request is trying to fetch a file - STREAM allowed so we go the extra mile */
484  if (!r->args && !conf->iceserver) {
485  /* RFC 3803 */
486  apr_table_setn(r->headers_out, "Content-Duration",
487  apr_psprintf(r->pool, "%hu", master_pack.fhead->length));
488 
489  return DECLINED; /* let Apache deal with the actual file */
490  }
491 
492  if (r->args && !strcmp(r->args, "stream")) {
493  send_playlist(r, &master_pack);
494 
495  return OK;
496  }
497  }
498 
499  /* trying to fetch a file, STREAM disabled, let's see if DWNLD is allowed */
500  if ((!r->args) && (conf->options & MI_ALLOWDWNLD))
501  return DECLINED;
502 
503  /* all else failing, we deny the request */
504  return HTTP_FORBIDDEN;
505 }
506 
507 #ifndef BUILD_FOR_APACHE2 /* we build for apache 1.3 */
508 
515 static void musicindex_init(server_rec *s, apr_pool_t *p)
516 {
517 #ifdef HAVE_GETTEXT
518 #ifdef HAVE_SETLOCALE
519  setlocale(LC_ALL, "");
520 #endif
521  textdomain("mod_musicindex");
522  bind_textdomain_codeset("mod_musicindex","UTF-8"); /* forcons la raie */
523 #endif
524 
525  ap_add_version_component(MUSIC_HEADER_STRING);
526 }
527 
528 /* XXX Shit, that sucks hell, that's another place to edit when adding/removing a handler DAMN! */
529 static const handler_rec musicindex_handlers[] = {
530  {DIR_MAGIC_TYPE, handle_musicindex},
531  {"audio/mpeg", handle_musicfile},
532  {"audio/ogg", handle_musicfile},
533  {"audio/x-ogg", handle_musicfile}, /* Backward compatibility */
534  {"application/ogg", handle_musicfile},
535  {"audio/flac", handle_musicfile},
536  {"audio/x-flac", handle_musicfile},
537  {"application/x-flac", handle_musicfile},
538  {"audio/mp4", handle_musicfile}, /* XXX cette struct sert a quoi exactement? MP4 marchait meme sans ca */
539  {NULL}
540 };
541 
542 /* XXX init happens after configuration, it should be possible to hook cache init there */
543 module MODULE_VAR_EXPORT musicindex_module = {
544  STANDARD_MODULE_STUFF,
548  NULL,
549  NULL,
552  NULL,
553  NULL,
554  NULL,
555  NULL,
556  NULL,
557  NULL,
558  NULL,
559  NULL,
560  NULL,
561  NULL,
562  NULL
563 };
564 
565 #else /* we build for apache 2 */
566 
575 static int musicindex_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
576 {
577 #ifdef HAVE_GETTEXT
578 #ifdef HAVE_SETLOCALE
579  setlocale(LC_ALL, "");
580 #endif
581  textdomain("mod_musicindex");
582  bind_textdomain_codeset("mod_musicindex","UTF-8"); /* forcons la raie */
583 #endif
584 
585  ap_add_version_component(p, MUSIC_HEADER_STRING);
586 
587  return OK;
588 }
589 
590 static void register_hooks(apr_pool_t *p)
591 {
592  /* for directories, set the mod_autoindex to be called after us */
593  /* This should not be necessary with V2.0 */
594  static const char * const after[] = { "mod_autoindex.c", NULL };
595  ap_hook_handler(handle_musicindex, NULL, after, APR_HOOK_MIDDLE);
596  ap_hook_handler(handle_musicfile, NULL, NULL, APR_HOOK_MIDDLE);
597  ap_hook_post_config(musicindex_init, NULL, NULL, APR_HOOK_LAST);
598 }
599 
600 module AP_MODULE_DECLARE_DATA musicindex_module = {
601  STANDARD20_MODULE_STUFF,
602  create_musicindex_config, /* create per-directory config structure */
603  merge_musicindex_configs, /* merge per-directory config structures */
604  NULL, /* create per-server config structure */
605  NULL, /* merge per-server config structures */
606  musicindex_cmds, /* command apr_table_t */
607  register_hooks /* register hooks */
608 };
609 
610 #endif /* BUILD_FOR_APACHE2 */