/*
 *  mod_musicindex.c
 *  mod_musicindex
 *
 *  $Id: mod_musicindex.c 1012 2012-08-08 08:58:25Z varenet $
 *
 *  Created by Regis BOUDIN on Sat Dec 28 2002.
 *  Copyright (c) 2002-2006 Regis BOUDIN
 *  Copyright (c) 2002-2005,2007-2008,2010-2012 Thibaut VARENE
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License version 2.1,
 *  as published by the Free Software Foundation.
 *
 */


/**
 * @mainpage
 * @section about About
 *	mod_musicindex is an Apache module initially aimed at being a C alternative 
 *	to the Perl module <a href="http://search.cpan.org/dist/Apache-MP3/">
 *	Apache::MP3</a>. It allows nice displaying of directories containing
 *	MP3, Ogg Vorbis, FLAC or MP4/AAC files, including sorting them on various
 *	fields, streaming/downloading them, constructing playlist, and searching.
 *	It also provides features such as RSS and Podcast feeds, multiple CSS
 *	support, and archive downloads.
 *
 * @section features Features
 *	- Allow/deny file downloading.
 *	- Allow/deny file streaming.
 *	- Allow/deny file searching (recursively or not).
 *	- Default sorting order in Apache configuration and dynamic sorting while browsing.
 *	- Random directory on the fly relocation.
 *	- Highly configurable data display.
 *	- Custom folder picture (using (.){cover,folder}.{png,jpg,gif} if found).
 *	- Cache subsystem (using flat files or MySQL db).
 *	- Can redirect to an icecast server running in "staticdir" mode.
 *	- Custom cross-directories playlists.
 *	- Multiple CSS support.
 *	- Per directory configuration (using .htaccess).
 *	- XHTML output.
 *	- Archive download.
 *	- RSS and Podcasts feeds generation.
 *	- Support for auth_basic authentication in URL generation
 *
 * @section dependencies Dependencies
 * 	- <a href="http://httpd.apache.org/download.cgi">Apache</a> (1.3 or 2).
 *	- <a href="http://downloads.xiph.org/releases/ogg/">libogg</a>.
 *	- <a href="http://downloads.xiph.org/releases/vorbis/">libvorbis</a>.
 *	- <a href="ftp://ftp.mars.org/pub/mpeg/">libmad0</a>.
 *	- <a href="ftp://ftp.mars.org/pub/mpeg/">libid3tag0</a>.
 *	- <a href="http://flac.sourceforge.net/">libflac</a>.
 *	- <a href="http://code.google.com/p/mp4v2/">libmp4v2</a>.
 *	- <a href="http://people.freebsd.org/~kientzle/libarchive/">libarchive</a>.
 *	- <a href="http://dev.mysql.com/downloads/#connector-c">libmysqlclient</a>.
 *
 * @subpage devnotes
 */

/**
 * @page devnotes Developers Notes
 *	Developers want to keep in mind we are aiming at the cleanest code possible.
 *	No matter how wicked what the code does is, said code should do dirty
 *	things with grace and proper documentation ;)
 *
 *	The coding style used throughout the code tries to stick as much as
 *	possible to the Linux Kernel Coding Style.
 *
 *	Developers should also take special care to typing. Strict typing is the
 *	first protection against coding mistakes that might lead to dreadful
 *	crashes. The same applies to function modifier (in particular the
 *	@c static modifier, that should be used whenever appropriate).
 *
 *	Finally, we try to put Apache's specific arguments to functions at the
 *	beginning of their param list.
 *
 * @section reminders Coding style reminders
 * @subsection pointers Rules about pointers
 *	These are often forgotten, so let's recap how to enforce typing on pointers:
 *	- <code>const char *answer_ptr = "Forty-Two";</code>
 *		- <code>answer_ptr = "Fifty-One";</code>	is legal (answer_ptr is a variable)
 *		- <code>*answer_ptr = 'X';</code>	is illegal (*answer_ptr is a constant)
 *	- <code>char *const name_ptr = "Test";</code>
 *		- <code>name_ptr = "New";</code>	is illegal (name_ptr is constant)
 *		- <code>*name_ptr = 'B';</code>		is legal (*name_ptr is a char)
 *	- <code>const char *const title_ptr = "Title";</code>
 *		- <code>title_ptr = "New";</code>	is illegal (title_ptr is constant)
 *		- <code>*title_ptr = 'X';</code>	is illegal (*title_ptr is a constant)
 *	
 *	Enforcing these simple rules helps code readability (as well as improving
 *	build time checks): in fact, defining a pointer like <code>const char *p;</code>
 *	tells us that this pointer will only be used to navigate in a string, but
 *	won't be used to modify it.
 *
 * @subsection variables Rules about variables
 *	- As a rule of thumb, always try to declare all function variables at the
 *	beginning of the function, and avoid as much as possible to declare
 *	subvariables in blocks.
 *	- In any case, @b never declare new variables @b after code in a block.
 *	- Finally, special care should be borne to storage space: always declare
 *	as @e little as possible: we want the module to consume as few
 *	memory as possible. That also applies to functions return types.
 *
 * @subsection restrictmod The restrict contract
 *	I, [insert your name], a PROFESSIONAL or AMATEUR [circle one] programmer recognize
 *	that there are limits to what a compiler can do. I certify that, to the best
 *	of my knowledge, there are no magic elves or monkeys in the compiler which
 *	through the forces of fairy dust can always make code faster. I understand
 *	that there are some problems for which there is not enough information to
 *	solve. I hereby declare that given the opportunity to provide the compiler
 *	with sufficient information, perhaps through some key word, I will gladly use
 *	said keyword and not bitch and moan about how "the compiler should be doing
 *	this for me."
 *
 *	In this case, I promise that the pointer declared along with the restrict
 *	qualifier is not aliased. I certify that writes through this pointer will
 *	not effect the values read through any other pointer available in the same
 *	context which is also declared as restricted.
 *
 *	Your agreement to this contract is implied by use of the restrict keyword ;)
 *
 *	http://www.cellperformance.com/mike_acton/2006/05/demystifying_the_restrict_keyw.html
 *	http://developers.sun.com/solaris/articles/cc_restrict.html
 */
 
/**
 * @file
 * Core file.
 *
 * This file is the core of the module. It contains Apache's mandatory stuff.
 *
 * @author Regis Boudin
 * @version $Revision: 1012 $
 * @date 2002-2008
 * @date 2010-2012
 *
 * http://httpd.apache.org/docs-2.0/developer/API.html
 * http://httpd.apache.org/dev/apidoc/
 * http://www.apachetutor.org/dev/pools
 *
 * @warning Use it at your own risks!
 * @warning sub requests are @b EXTREMELY cpu expensive
 *
 * @todo Complete code documentation.
 * @todo (low pri whishlist) On the fly tag rewriting or metadata support.
 * @todo prepare the possibility to generate ices/mpd playlists.
 * @todo enforce strict type policy.
 * @todo get rid of str*cmp() whenever possible.
 * @todo review the randomdir implementation.
 *
 * @todo Check how much overhead a DAAP implementation would bring. The
 *	protocol works on top of HTTP (where we are) and could make use of the
 *	cache.
 *
 * @todo Audit the code: in many places we are using a lot of arguments passed
 * 	to functions that could easily be gotten otherwise by dereferencing a 
 * 	few pointers. Passing too many args increases stack usage.
 *
 * @todo Decide how long we want to support Apache 1.3. APR provides lots of
 *	useful features, and the API compatibility between 1.3 and 2.2 is not
 *	certain.
 *
 * @todo Check whether it'd make sense to abstract Apache's primitives so as
 *	to make the module portable to other webservers.
 */

#include "mod_musicindex.h"
#include "playlist.h"
#include "config.h"
#include "html.h"
#include "http.h"
#include "sort.h"

#include <http_core.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SETLOCALE
#include <locale.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#ifdef BUILD_FOR_APACHE2
/** mime types handled */
static const char *handlers[] = {
	"audio/mpeg",
	"audio/mp4",
	"audio/flac",
	"audio/ogg",
	"audio/x-flac",
	"application/x-flac",
	"application/ogg",
	"audio/x-ogg", /* At some point, we should be able to remove this one */
	NULL
};
#else
#include <http_main.h>
#endif

#ifdef ENABLE_OUTPUT_ARCHIVE
extern void send_tarball(request_rec *, const mu_pack *const);
extern ssize_t tarball_size(request_rec *, const mu_pack *const);
#endif

/**
 * Check whether we're serving MS IE.
 *
 * @param r Apache request_rec
 *
 * @return 1 if client is MSIE, 0 otherwise
 */
static int is_msie_user_agent(request_rec *r)
{
    const char* user_agent = NULL;

    user_agent = apr_table_get(r->headers_in, "User-Agent");
    return strstr(user_agent, "MSIE") && 1;
}

/**
 * This is where the magic happens.
 */
static int handle_musicindex(request_rec *r)
{
	/* Get the module configuration */
	mu_config *const conf = (mu_config *)ap_get_module_config(r->per_dir_config, &musicindex_module);
	mu_pack master_pack = {
		.head = NULL,
		.fhead = NULL,
		.dirnb = 0,
		.filenb = 0,
		.fsize = 0,
	},	custom_pack = {
		.head = NULL,
		.fhead = NULL,
		.dirnb = 0,
		.filenb = 0,
		.fsize = 0,
	};

	STRUCTTV tvbegin, tvprocess;
	
#ifdef HAVE_GETTIMEOFDAY
	struct timeval tvend;
	gettimeofday(&tvbegin, NULL);
#endif

	r->allowed |= ((1 << M_GET) | (1 << M_POST));

	/* if the the module is not active, decline the request */
	if (!(conf->options & MI_ACTIVE))
		return DECLINED;

#ifdef BUILD_FOR_APACHE2
	if(!r->handler || strcmp(r->handler, DIR_MAGIC_TYPE))
		return DECLINED;
#endif

	/* before doing anything (checking args, calling subfunctions), check we
	   can open the directory */
	if (access(r->filename, R_OK|X_OK) != 0) {
		mi_rerror("Can't open directory: %s", r->filename);
		return HTTP_FORBIDDEN;
	}

	/* This part mostly comes from mod_autoindex. If the requested dir does
	   not end with a '/', generate a new request with it */
	if (r->uri[0] == '\0' || r->uri[strlen(r->uri) - 1] != '/') {
		char *file;
		if (r->args != NULL)
			file = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri),
						"/", "?", r->args, NULL);
		else
			file = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri),
						"/", NULL);

		apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, file, r));
		return HTTP_MOVED_PERMANENTLY;
	}

	/* Deal with optional arguments or decline requests we don't handle */
	switch (r->method_number) {
		case M_GET:
			treat_get_args(r);
			break;
		case M_POST:
			treat_post_args(r);
			break;
		default:
			return DECLINED;
	}
	/* Now, the conf structure contains all the flags correctly set and
	   conf->custom_list points to the arguments of the request, either given
	   by get or post method */

	/* XXX this is ugly */
	if (conf->options & MI_RANDOMDIR) {
		conf->options &= ~MI_RANDOMDIR;
		send_randomdir(r);
		return HTTP_MOVED_TEMPORARILY;
	}

	/* Cache prologue can happen no later than here */
	if (conf->cache && conf->cache->prologue)
		conf->cache->prologue(r);

	/* build the string containing the cookie content. We need it for
	   webpages and requests to stream the cookie content */
	if (((conf->options & (MI_STREAM|MI_TARBALL)) == 0) || ((conf->options & MI_STREAMRQ) == MI_STREAMCOOKIE) || ((conf->options & MI_DWNLDRQ) == MI_DWNLDCOOKIE))
		cookie_and_stream_work(r);

	if (((conf->options & MI_STREAMRQ) == MI_STREAMLST) ||
		((conf->options & MI_STREAMRQ) == MI_COOKIESTREAM) ||
		((conf->options & MI_DWNLDRQ) == MI_DWNLDLST) ||
		((conf->options & MI_DWNLDRQ) == MI_COOKIEDWNLD)) {
		/* We build a custom playlist, don't bother with current directory */
		build_custom_list(r, &master_pack);
	}
	else {
		/* Build current directory content (recursive if necessary) */
		make_music_entry(r, r->pool, &master_pack, NULL, MI_RECURSIVE);
		listsort(&master_pack, conf->order);
	}

	/* Build custom link list, from conf->custom_list string */
	if (((conf->options & MI_STREAM) == 0) && (conf->custom_list != NULL))
		build_custom_list(r, &custom_pack);
	
	/* Cache epilogue can happen as early as here */
	if (conf->cache && conf->cache->epilogue)
		conf->cache->epilogue(r);

	/* Depending on the request type, set the HTTP headers. By default we
	   return a web page. */
	if (conf->options & MI_STREAM) {
		char content_disposition[64] = "";
			if (is_msie_user_agent(r))	/* this helps IE dealing with the data */
				strcat(content_disposition, "attachment; ");

		strcat(content_disposition, "filename=\"playlist.m3u\"");

		ap_set_content_type(r, "audio/x-mpegurl");
		apr_table_set(r->headers_out, "Content-Disposition", content_disposition);
		/* apr_table_set() duplicates keys and values, this is necessary here */
	}
#ifdef ENABLE_OUTPUT_ARCHIVE
	else if (conf->options & MI_TARBALL) {
		const ssize_t tballsize = tarball_size(r, &master_pack);
		ap_set_content_type(r, "application/x-tar");
		apr_table_setn(r->headers_out, "Content-Disposition",
			"filename = \"playlist.tar\"");
		/* send the size of the archive so that browsers can put a nice
		   progress bar
		   XXX correct modifier for size_t is 'z' but seems (some versions of?)
		   apache doesn't know about it. */
		apr_table_setn(r->headers_out, "Content-Length",
			apr_psprintf(r->pool, "%lu", tballsize));
	}
#endif
	else if (conf->options & MI_RSS) {
		ap_set_content_type(r, "text/xml; charset=\"utf-8\"");
	}
	else {
		if (is_msie_user_agent(r))	/* IE derps on true XML */
			ap_set_content_type(r, "text/html; charset=\"utf-8\"");
		else
			ap_set_content_type(r, "application/xhtml+xml; charset=\"utf-8\"");

		/* If we have a cookie string, send it */
		if (conf->custom_list != NULL)
			apr_table_setn(r->headers_out, "Set-Cookie", conf->custom_list);
	}

	ap_send_http_header(r);

	if (r->header_only)
		return OK;

#ifdef HAVE_GETTIMEOFDAY
	/* Timing request processing */
	gettimeofday(&tvend, NULL);
	timersub(&tvend, &tvbegin, &tvprocess);
#endif

	/* At this point we're sending data back */
	if (conf->options & MI_STREAM)
		send_playlist(r, &master_pack);
	else if (conf->options & MI_RSS)
		send_rss(r, &master_pack);
#ifdef ENABLE_OUTPUT_ARCHIVE
	else if (conf->options & MI_TARBALL)
		send_tarball(r, &master_pack);
#endif
	else {
		send_head(r);
		if (!conf->search && conf->dir_per_line)
			send_directories(r, &master_pack);
		send_tracks(r, &master_pack);
		send_customlist(r, &custom_pack);
		send_foot(r, &tvbegin, &tvprocess);
	}

	return OK;
}

/**
 * Handler for requests on music files.
 *
 * At the moment it can only forbid acces to files if neither streaming nor download is allowed.
 *
 * @param r Apache request_rec to get and send data
 *
 * @return Standard HTTP code (see httpd.h for more infos)
 */
static int handle_musicfile(request_rec *r)
{
	mu_config *const conf = (mu_config *)ap_get_module_config(r->per_dir_config, &musicindex_module);

#ifdef BUILD_FOR_APACHE2
	register unsigned short i;
#endif

	/* is this a request method we handle? */
	if ((r->method_number != M_GET) || (!(conf->options & MI_ACTIVE)))
		return DECLINED;

#ifdef BUILD_FOR_APACHE2
	/* is this a mime type we handle? */
	if (!r->handler)
		return DECLINED;
	
	for (i=0; handlers[i] && strcmp(r->handler, handlers[i]); i++);

	if (handlers[i] == NULL)
		return DECLINED;
#endif

	/* request response rationale:
	   - Stream is allowed:
		+ we have a "?stream" argument in the request, the client asks for
		  a playlist containing a single file -> OK
		+ we have no argument and no icecast server is set -> set Content-Length
		  and DECLINED
	   - Download is allowed:
	   	+ we have no argument -> DECLINED
	   Otherwise, we consider it is a forbidden request. Keep in mind that
	   DECLINED just means the module won't handle the request, whereas
	   HTTP_FORBIDDEN means the request is not allowed, no matter what. */

	if (conf->options & MI_ALLOWSTREAM) {
		mu_pack master_pack = {
			.head = NULL,
			.fhead = NULL,
			.dirnb = 0,
			.filenb = 0,
		};

		if (r->args && !strcmp(r->args, "stream")) {
			ap_set_content_type(r, "audio/x-mpegurl");
			apr_table_setn(r->headers_out, "Content-Disposition",
				"filename = \"playlist.m3u\"");

			ap_send_http_header(r);

			if (r->header_only)
				return OK;
		}			

		if (conf->cache && conf->cache->prologue)
			conf->cache->prologue(r);

		make_music_entry(r, r->pool, &master_pack, NULL, MI_ALLOWSTREAM);

		if (conf->cache && conf->cache->epilogue)
			conf->cache->epilogue(r);

		master_pack.fhead = master_pack.head;
		
		/* the request is trying to fetch a file - STREAM allowed so we go the extra mile */
		if (!r->args && !conf->iceserver) {
			/* RFC 3803 */
			apr_table_setn(r->headers_out, "Content-Duration", 
				apr_psprintf(r->pool, "%hu", master_pack.fhead->length));

			return DECLINED; /* let Apache deal with the actual file */
		}
		
		if (r->args && !strcmp(r->args, "stream")) {
			send_playlist(r, &master_pack);

			return OK;
		}
	}

	/* trying to fetch a file, STREAM disabled, let's see if DWNLD is allowed */
	if ((!r->args) && (conf->options & MI_ALLOWDWNLD))
		return DECLINED;

	/* all else failing, we deny the request */
	return HTTP_FORBIDDEN;
}

#ifndef BUILD_FOR_APACHE2 /* we build for apache 1.3 */

/**
 * Adds the mod signature into Apache's headers.
 *
 * @param s server_rec
 * @param p pool
 */
static void musicindex_init(server_rec *s, apr_pool_t *p)
{
#ifdef HAVE_GETTEXT
#ifdef HAVE_SETLOCALE
	setlocale(LC_ALL, "");
#endif
	textdomain("mod_musicindex");
	bind_textdomain_codeset("mod_musicindex","UTF-8");	/* forcons la raie */
#endif

	ap_add_version_component(MUSIC_HEADER_STRING);
}

/* XXX Shit, that sucks hell, that's another place to edit when adding/removing a handler DAMN! */
static const handler_rec musicindex_handlers[] = {
	{DIR_MAGIC_TYPE, handle_musicindex},
	{"audio/mpeg", handle_musicfile},
	{"audio/ogg", handle_musicfile},
	{"audio/x-ogg", handle_musicfile}, /* Backward compatibility */
	{"application/ogg", handle_musicfile},
	{"audio/flac", handle_musicfile},
	{"audio/x-flac", handle_musicfile},
	{"application/x-flac", handle_musicfile},
	{"audio/mp4", handle_musicfile},	/* XXX cette struct sert a quoi exactement? MP4 marchait meme sans ca */
	{NULL}
};

/* XXX init happens after configuration, it should be possible to hook cache init there */
module MODULE_VAR_EXPORT musicindex_module = {
	STANDARD_MODULE_STUFF,
	musicindex_init,		/**< initializer */
	create_musicindex_config,	/**< dir config creater */
	merge_musicindex_configs,	/**< dir merger */
	NULL,				/**< server config */
	NULL,				/**< merge server config */
	musicindex_cmds,		/**< command table */
	musicindex_handlers,		/**< handlers */
	NULL,				/**< filename translation */
	NULL,				/**< check_user_id */
	NULL,				/**< check auth */
	NULL,				/**< check access */
	NULL,				/**< type_checker */
	NULL,				/**< fixups */
	NULL,				/**< logger */
	NULL,				/**< header parser */
	NULL,				/**< child_init */
	NULL,				/**< child_exit */
	NULL				/**< post read-request */
};

#else /* we build for apache 2 */

/**
 * Adds the mod signature into Apache's headers.
 *
 * @param p pool
 * @param plog pool
 * @param ptemp pool
 * @param s server_rec
 */
static int musicindex_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
{
#ifdef HAVE_GETTEXT
#ifdef HAVE_SETLOCALE
	setlocale(LC_ALL, "");
#endif
	textdomain("mod_musicindex");
	bind_textdomain_codeset("mod_musicindex","UTF-8");	/* forcons la raie */
#endif

	ap_add_version_component(p, MUSIC_HEADER_STRING);

	return OK;
}

static void register_hooks(apr_pool_t *p)
{
	/* for directories, set the mod_autoindex to be called after us */
	/* This should not be necessary with V2.0 */
	static const char * const after[] = { "mod_autoindex.c", NULL };
	ap_hook_handler(handle_musicindex, NULL, after, APR_HOOK_MIDDLE);
	ap_hook_handler(handle_musicfile, NULL, NULL, APR_HOOK_MIDDLE);
	ap_hook_post_config(musicindex_init, NULL, NULL, APR_HOOK_LAST);
}

module AP_MODULE_DECLARE_DATA musicindex_module = {
    STANDARD20_MODULE_STUFF,
    create_musicindex_config,	/* create per-directory config structure */
    merge_musicindex_configs,	/* merge per-directory config structures */
    NULL,			/* create per-server config structure */
    NULL,			/* merge per-server config structures */
    musicindex_cmds,		/* command apr_table_t */
    register_hooks		/* register hooks */
};

#endif /* BUILD_FOR_APACHE2 */
