From 9ef8acfa28799ab3052ef4e77d3f95b2b25fe476 Mon Sep 17 00:00:00 2001 From: Julien BLACHE Date: Sun, 3 May 2009 11:13:23 +0200 Subject: [PATCH] Remove old webserver --- src/Makefile.am | 6 +- src/conf.h | 4 - src/conffile.c | 7 - src/configfile.c | 1111 ----------------------- src/configfile.h | 41 - src/daapd.h | 2 - src/db-generic.h | 2 - src/main.c | 1 - src/webserver.c | 2219 ---------------------------------------------- src/webserver.h | 142 --- src/wsprivate.h | 29 - src/xml-rpc.c | 657 -------------- src/xml-rpc.h | 19 - 13 files changed, 3 insertions(+), 4237 deletions(-) delete mode 100644 src/configfile.c delete mode 100644 src/configfile.h delete mode 100644 src/webserver.c delete mode 100644 src/webserver.h delete mode 100644 src/wsprivate.h delete mode 100644 src/xml-rpc.c delete mode 100644 src/xml-rpc.h diff --git a/src/Makefile.am b/src/Makefile.am index 288b21fc..2163723e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,8 +12,8 @@ endif mt_daapd_CPPFLAGS = -D_GNU_SOURCE @AVAHI_CFLAGS@ @SQLITE3_CFLAGS@ @FFMPEG_CFLAGS@ @CONFUSE_CFLAGS@ @TAGLIB_CFLAGS@ @MINIXML_CFLAGS@ mt_daapd_LDADD = @AVAHI_LIBS@ @SQLITE3_LIBS@ @FFMPEG_LIBS@ @CONFUSE_LIBS@ @FLAC_LIBS@ @TAGLIB_LIBS@ @LIBEVENT_LIBS@ @LIBAVL_LIBS@ @MINIXML_LIBS@ mt_daapd_LDFLAGS = -export-dynamic -mt_daapd_SOURCES = main.c daapd.h webserver.c \ - webserver.h configfile.c configfile.h err.c err.h \ +mt_daapd_SOURCES = main.c daapd.h \ + err.c err.h \ conffile.c conffile.h \ filescanner.c filescanner.h \ filescanner_ffmpeg.c filescanner_urlfile.c filescanner_m3u.c \ @@ -27,7 +27,7 @@ mt_daapd_SOURCES = main.c daapd.h webserver.c \ misc.c misc.h \ db-generic.c db-generic.h \ scan-wma.c \ - smart-parser.c smart-parser.h xml-rpc.c xml-rpc.h \ + smart-parser.c smart-parser.h \ util.c util.h \ db-sql-updates.c \ io.h io.c io-errors.h io-plugin.h \ diff --git a/src/conf.h b/src/conf.h index e09dbbc4..db513537 100644 --- a/src/conf.h +++ b/src/conf.h @@ -54,10 +54,6 @@ extern void conf_dispose_array(char **argv); extern char *conf_get_filename(void); -/* FIXME: get enum functions and move to xml-rpc */ -#include "webserver.h" -extern int conf_xml_dump(WS_CONNINFO *pwsc); - /* FIXME: well used config reading stuff... */ extern char *conf_get_servername(void); diff --git a/src/conffile.c b/src/conffile.c index cd303393..4b5d53e1 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -574,10 +574,3 @@ conf_get_filename(void) { return "NOT SUPPORTED"; } - -int -conf_xml_dump(WS_CONNINFO *pwsc) -{ - /* Not supported */ - return CONF_E_NOTSUPP; -} diff --git a/src/configfile.c b/src/configfile.c deleted file mode 100644 index c7a29657..00000000 --- a/src/configfile.c +++ /dev/null @@ -1,1111 +0,0 @@ -/* - * Functions for reading and writing the config file - * - * Copyright (C) 2003 Ron Pedde (ron@pedde.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - - -/** - * \file configfile.c - * - * Configfile and web interface handling. - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include -#include -#include -#include -#include -#include -#ifdef HAVE_STDINT_H -#include -#endif -#include -#include -#include -#include -#include - -#include -#ifdef HAVE_SYS_WAIT_H -# include -#endif - -#include "daapd.h" -#include "conf.h" -#include "configfile.h" -#include "db-generic.h" -#include "err.h" -#include "xml-rpc.h" - - -/* - * Forwards - */ -static void config_emit_literal(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_include(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_threadstatus(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_ispage(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_session_count(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_service_status(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_user(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_readonly(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_version(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_system(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_flags(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_conffile(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_host(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_config(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_emit_servername(WS_CONNINFO *pwsc, void *value, char *arg); -static void config_subst_stream(WS_CONNINFO *pwsc, IOHANDLE hfile); -static void config_content_type(WS_CONNINFO *pwsc, char *path); - -/* - * Defines - */ -#define CONFIG_TYPE_INT 0 /**< Config element is an integer */ -#define CONFIG_TYPE_STRING 1 /**< Config element is a string */ -#define CONFIG_TYPE_SPECIAL 4 /**< Config element is a web parameter */ - -typedef struct tag_contenttypes { - char *extension; - char *contenttype; - int decache; -} CONTENTTYPES; - -CONTENTTYPES config_default_types[] = { - { ".css", "text/css; charset=utf-8", 0 }, - { ".txt", "text/plain; charset=utf-8", 0 }, - { ".xml", "text/xml; charset=utf-8", 1 }, - { NULL, NULL } -}; - -/** Every config file directive and web interface directive is a CONFIGELEMENT */ -typedef struct tag_configelement { - int config_element; /**< config file directive or web interface directive */ - int required; /**< If config file, is it required? */ - int changed; /**< Has this been changed since the last load? */ - int type; /**< Int, string, or special? */ - char *name; /**< config file directive name */ - void *var; /**< if config file, where is the corresponding var? */ - void (*emit)(WS_CONNINFO *, void *, char *); /* how to display it on a web page */ -} CONFIGELEMENT; - -/** List of all valid config entries and web interface directives */ -CONFIGELEMENT config_elements[] = { - /* - { 1,1,0,CONFIG_TYPE_STRING,"runas",(void*)&config.runas,config_emit_string }, - { 1,1,0,CONFIG_TYPE_STRING,"web_root",(void*)&config.web_root,config_emit_string }, - { 1,1,0,CONFIG_TYPE_INT,"port",(void*)&config.port,config_emit_int }, - { 1,1,0,CONFIG_TYPE_STRING,"admin_pw",(void*)&config.adminpassword,config_emit_string }, - { 1,1,0,CONFIG_TYPE_STRING,"mp3_dir",(void*)&config.mp3dir,config_emit_string }, - { 1,0,0,CONFIG_TYPE_STRING,"db_dir",(void*)&config.dbdir,config_emit_string }, - { 1,0,0,CONFIG_TYPE_STRING,"db_type",(void*)&config.dbtype,config_emit_string }, - { 1,0,0,CONFIG_TYPE_STRING,"db_parms",(void*)&config.dbparms,config_emit_string }, - { 1,0,0,CONFIG_TYPE_INT,"debuglevel",(void*)NULL,config_emit_debuglevel }, - { 1,1,0,CONFIG_TYPE_STRING,"servername",(void*)&config.servername,config_emit_string }, - { 1,0,0,CONFIG_TYPE_INT,"rescan_interval",(void*)&config.rescan_interval,config_emit_int }, - { 1,0,0,CONFIG_TYPE_INT,"always_scan",(void*)&config.always_scan,config_emit_int }, - { 1,0,0,CONFIG_TYPE_INT,"latin1_tags",(void*)&config.latin1_tags,config_emit_int }, - { 1,0,0,CONFIG_TYPE_INT,"process_m3u",(void*)&config.process_m3u,config_emit_int }, - { 1,0,0,CONFIG_TYPE_INT,"scan_type",(void*)&config.scan_type,config_emit_int }, - { 1,0,0,CONFIG_TYPE_INT,"compress",(void*)&config.compress,config_emit_int }, - { 1,0,0,CONFIG_TYPE_STRING,"playlist",(void*)&config.playlist,config_emit_string }, - { 1,0,0,CONFIG_TYPE_STRING,"extensions",(void*)&config.extensions,config_emit_string }, - { 1,0,0,CONFIG_TYPE_STRING,"interface",(void*)&config.iface,config_emit_string }, - { 1,0,0,CONFIG_TYPE_STRING,"ssc_codectypes",(void*)&config.ssc_codectypes,config_emit_string }, - { 1,0,0,CONFIG_TYPE_STRING,"ssc_prog",(void*)&config.ssc_prog,config_emit_string }, - { 1,0,0,CONFIG_TYPE_STRING,"password",(void*)&config.readpassword, config_emit_string }, - { 1,0,0,CONFIG_TYPE_STRING,"compdirs",(void*)&config.compdirs, config_emit_string }, - { 1,0,0,CONFIG_TYPE_STRING,"logfile",(void*)&config.logfile, config_emit_string }, - { 1,0,0,CONFIG_TYPE_STRING,"art_filename",(void*)&config.artfilename,config_emit_string }, - */ - { 0,0,0,CONFIG_TYPE_SPECIAL,"servername",(void*)NULL,config_emit_servername }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"config",(void*)NULL,config_emit_config }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"conffile",(void*)NULL,config_emit_conffile }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"host",(void*)NULL,config_emit_host }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"release",(void*)VERSION,config_emit_literal }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"package",(void*)PACKAGE,config_emit_literal }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"include",(void*)NULL,config_emit_include }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"threadstat",(void*)NULL,config_emit_threadstatus }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"ispage",(void*)NULL,config_emit_ispage }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"session-count",(void*)NULL,config_emit_session_count }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"service-status",(void*)NULL,config_emit_service_status }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"user",(void*)NULL,config_emit_user }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"readonly",(void*)NULL,config_emit_readonly }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"version",(void*)NULL,config_emit_version }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"system",(void*)NULL,config_emit_system }, - { 0,0,0,CONFIG_TYPE_SPECIAL,"flags",(void*)NULL,config_emit_flags }, - { -1,1,0,CONFIG_TYPE_STRING,NULL,NULL,NULL } -}; - -int config_session=0; /**< session counter */ - -#define MAX_LINE 1024 - - -int config_password_required(WS_CONNINFO *pwsc, char *role) { - char *pw; - - DPRINTF(E_DBG,L_MISC,"Checking if pw required for %s as %s\n", - ws_uri(pwsc), role); - - if(strcasecmp(role,"admin") == 0) { - pw = conf_alloc_string("general","admin_pw",NULL); - if((!pw) || ((pw) && (!strlen(pw)))) { - /* don't need a password from localhost - when the password isn't set */ - - if((ws_hostname(pwsc)) - && (strncmp(ws_hostname(pwsc), "127.", 4) == 0)) { - if(pw) free(pw); - return FALSE; - } - } - if(pw) free(pw); - DPRINTF(E_DBG,L_MISC,"Yep\n"); - - return TRUE; - } - - if(!strcasecmp(role,"user")) { - pw = conf_alloc_string("general","password",NULL); - if(pw && strlen(pw)) { - DPRINTF(E_DBG,L_MISC,"Yep: %s\n",pw); - free(pw); - return TRUE; - } - if(pw) free(pw); - DPRINTF(E_DBG,L_MISC,"Nope\n"); - return FALSE; - } - - DPRINTF(E_LOG,L_MISC,"Bad role type for auth: %s\n",role); - return TRUE; /* other class */ -} - - -/** - * used in auth handlers to see if the username and password - * are sufficient to grant a specific role. The roles "user" and - * "admin" are built-in roles, and are the roles used to control access - * to the music and the admin pages respectively. - */ -int config_matches_role(WS_CONNINFO *pwsc, char *username, - char *password, char *role) { - char *required_pw; - int result; - - /* sanity check */ - if((strcasecmp(role,"admin") != 0) && - (strcasecmp(role,"user") !=0)) { - DPRINTF(E_LOG,L_MISC,"Unknown role: %s\n",role); - return FALSE; - } - - if(password == NULL) - return config_password_required(pwsc,role) ? FALSE : TRUE; - - /* if we have admin auth, we have everything */ - required_pw = conf_alloc_string("general","admin_pw",NULL); - if((required_pw) && (strcmp(required_pw, password)==0)) { - free(required_pw); - return TRUE; - } - - if(required_pw) - free(required_pw); - - if(strcasecmp(role,"admin") == 0) { - DPRINTF(E_LOG,L_MISC,"Auth: failed attempt to gain admin privs by " - "%s from %s\n",username, ws_hostname(pwsc)); - return FALSE; - } - - /* we're checking for user privs */ - required_pw = conf_alloc_string("general","password",NULL); - - if(!required_pw) /* none set */ - return TRUE; - - result = FALSE; - if(strcmp(required_pw,password) == 0) - result = TRUE; - - free(required_pw); - if(!result) { - DPRINTF(E_LOG,L_MISC,"Auth: failed attempt to gain user privs by " - "%s from %s\n",ws_hostname(pwsc), username); - } - return result; -} - -/** - * set the content type based on the file type - */ -void config_content_type(WS_CONNINFO *pwsc, char *path) { - char *extension; - CONTENTTYPES *pct=config_default_types; - - extension=strrchr(path,'.'); - if(extension) { - while((pct->extension) && (strcasecmp(pct->extension,extension))) - pct++; - - if(pct->extension) { - ws_addresponseheader(pwsc,"Content-Type",pct->contenttype); - } - - if(pct->decache) { - ws_addresponseheader(pwsc,"Cache-Control","no-cache"); - ws_addresponseheader(pwsc,"Expires","-1"); - } - } -} - -/** - * walk through a stream doing substitution on the - * meta commands. This is what fills in the fields - * on the web page, or throws out the thread status - * table on the status page. - * - * \param pwsc the web connection for the original page - * \param fd_src the fd of the file to do substitutions on - */ -void config_subst_stream(WS_CONNINFO *pwsc, IOHANDLE hfile) { - int in_arg; - char *argptr; - char argbuffer[256]; - char next; - CONFIGELEMENT *pce; - char *first, *last; - - char outbuffer[1024]; - int out_index; - uint32_t bytes_read; - - /* now throw out the file, with replacements */ - in_arg=0; - argptr=argbuffer; - out_index=0; - - while(1) { - /* FIXME: use a reasonable sized buffer, or implement buffered io */ - bytes_read = 1; - if((!io_read(hfile,(unsigned char *)&next,&bytes_read)) || !bytes_read) - break; - - if(in_arg) { - if((next == '@') && (strlen(argbuffer) > 0)) { - in_arg=0; - - DPRINTF(E_DBG,L_CONF,"Got directive %s\n",argbuffer); - - /* see if there are args */ - first=last=argbuffer; - strsep(&last," "); - - pce=config_elements; - while(pce->config_element != -1) { - if(strcasecmp(first,pce->name) == 0) { - pce->emit(pwsc, pce->var,last); - break; - } - pce++; - } - - if(pce->config_element == -1) { /* bad subst */ - ws_writefd(pwsc,"@%s@",argbuffer); - } - } else if(next == '@') { - ws_writefd(pwsc,"@"); - in_arg=0; - } else { - if((argptr - argbuffer) < (sizeof(argbuffer)-1)) - *argptr++ = next; - } - } else { - if(next == '@') { - argptr=argbuffer; - memset(argbuffer,0,sizeof(argbuffer)); - in_arg=1; - - /* flush whatever is in the buffer */ - if(out_index) { - ws_writebinary(pwsc,outbuffer,out_index); - out_index=0; - } - } else { - outbuffer[out_index] = next; - out_index++; - - if(out_index == sizeof(outbuffer)) { - ws_writebinary(pwsc,outbuffer,out_index); - out_index=0; - } - } - } - } - if(out_index) { - ws_writebinary(pwsc,outbuffer,out_index); - out_index=0; - } -} - -/** - * This is the webserver callback for all non-daap web requests. - * This includes the admin page handling. - * - * \param pwsc web connection of request - */ -void config_handler(WS_CONNINFO *pwsc) { - char path[PATH_MAX]; - char resolved_path[PATH_MAX]; - char web_root[PATH_MAX]; - IOHANDLE hfile; - struct stat sb; - char *pw; - int size; - char *urltemp; - int ret; - - if(!ws_hostname(pwsc)) { - ws_returnerror(pwsc,500,"Couldn't determine remote hostname"); - ws_should_close(pwsc,1); - return; - } - - /* FIXME: should feed this file directly, so as to bypass perms */ - if(strncmp(ws_hostname(pwsc), "127.", 4) != 0) { - pw=conf_alloc_string("general","admin_pw",NULL); - if((!pw) || (strlen(pw) == 0)) { - if(pw) free(pw); - /* if we aren't getting the no_access.html page, we should */ - if(strcmp(ws_uri(pwsc),"/no_access.html") != 0) { - ws_addresponseheader(pwsc,"location","/no_access.html"); - ws_returnerror(pwsc,302,"Moved Temporarily"); - ws_should_close(pwsc,1); - return; - } - } - } - - size = sizeof(web_root); - if(conf_get_string("general","web_root",NULL,web_root, - &size) == CONF_E_NOTFOUND) { - DPRINTF(E_FATAL,L_WS,"No web root!\n"); - } - - DPRINTF(E_DBG,L_CONF|L_WS,"Entering config_handler\n"); - - config_set_status(pwsc,0,"Serving admin pages"); - - ws_should_close(pwsc,1); - ws_addresponseheader(pwsc,"Connection","close"); - - if(strcasecmp(ws_uri(pwsc),"/xml-rpc")==0) { - // perhaps this should get a separate handler - config_set_status(pwsc,0,"Serving xml-rpc method"); - xml_handle(pwsc); - DPRINTF(E_DBG,L_CONF|L_XML,"Thread %d: xml-rpc served\n",ws_threadno(pwsc)); - config_set_status(pwsc,0,NULL); - return; - } - - if(('/' == web_root[strlen(web_root) - 1]) || - ('\\' == web_root[strlen(web_root) - 1])) { - web_root[strlen(web_root) - 1] = '\0'; - } - - snprintf(path,PATH_MAX,"%s/%s",web_root,ws_uri(pwsc)); - DPRINTF(E_DBG,L_CONF|L_WS,"Realpathing %s\n",path); - if(!realpath(path,resolved_path)) { - pwsc->error=errno; - DPRINTF(E_WARN,L_CONF|L_WS,"Cannot resolve %s\n",path); - ws_returnerror(pwsc,404,"Not found"); - config_set_status(pwsc,0,NULL); - return; - } - - /* this is quite broken, but will work */ - stat(resolved_path,&sb); - if(S_ISDIR(sb.st_mode)) { - ws_addresponseheader(pwsc,"Location","index.html"); - ws_returnerror(pwsc,302,"Moved"); - config_set_status(pwsc,0,NULL); - ws_should_close(pwsc,1); - return; - } - - DPRINTF(E_DBG,L_CONF|L_WS,"Thread %d: Preparing to serve %s\n", - ws_threadno(pwsc), resolved_path); - - if(strncmp(resolved_path,web_root,strlen(web_root))) { - pwsc->error=EINVAL; - DPRINTF(E_WARN,L_CONF|L_WS,"Thread %d: Requested file %s out of root\n", - ws_threadno(pwsc),resolved_path); - ws_returnerror(pwsc,403,"Forbidden"); - config_set_status(pwsc,0,NULL); - return; - } - - hfile = io_new(); - if(!hfile) { - DPRINTF(E_FATAL,L_CONF | L_WS,"Cannot create file handle\n"); - exit(-1); - } - - DPRINTF(E_DBG,L_CONF|L_WS,"Opening %s\n",resolved_path); - urltemp = io_urlencode(resolved_path); - if (!urltemp) - { - ws_set_err(pwsc,E_WS_NATIVE); - DPRINTF(E_WARN,L_CONF|L_WS,"Thread %d: Error opening %s: out of memory\n", - ws_threadno(pwsc),resolved_path); - ws_returnerror(pwsc,404,"Not found"); - config_set_status(pwsc,0,NULL); - io_dispose(hfile); - return; - } - - ret = io_open(hfile, "file://%s", urltemp); - free(urltemp); - if(!ret) { - ws_set_err(pwsc,E_WS_NATIVE); - DPRINTF(E_WARN,L_CONF|L_WS,"Thread %d: Error opening %s: %s\n", - ws_threadno(pwsc),resolved_path,io_errstr(hfile)); - ws_returnerror(pwsc,404,"Not found"); - config_set_status(pwsc,0,NULL); - io_dispose(hfile); - return; - } - - /* FIXME: use xml-rpc for all these */ - if(strcasecmp(ws_uri(pwsc),"/config-update.html")==0) { - /* don't update (and turn everything to (null)) the - configuration file if what the user's really trying to do is - stop the server */ - pw=ws_getvar(pwsc,"action"); - if(pw) { - /* ignore stopmdns and startmdns */ - if (strcasecmp(pw,"stopdaap")==0) { - config.stop=1; - } else if (strcasecmp(pw,"rescan")==0) { - config.reload=1; - } - } - } - - config_content_type(pwsc, resolved_path); - - ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); - ws_emitheaders(pwsc); - - if((strcasecmp(&resolved_path[strlen(resolved_path) - 5],".html") == 0) || - (strcasecmp(&resolved_path[strlen(resolved_path) - 4],".xml") == 0)) { - config_subst_stream(pwsc, hfile); - } else { - ws_copyfile(pwsc,hfile,NULL); - } - - io_close(hfile); - io_dispose(hfile); - - DPRINTF(E_DBG,L_CONF|L_WS,"Thread %d: Served successfully\n",ws_threadno(pwsc)); - config_set_status(pwsc,0,NULL); - return; -} - -/** - * The auth handler for the admin pages - * - * \param user username passed in the auth request - * \param password password passed in the auth request - */ -int config_auth(WS_CONNINFO *pwsc, char *user, char *password) { - return config_matches_role(pwsc,user,password,"admin"); -} - -/** - * write the current servername - */ -void config_emit_servername(WS_CONNINFO *pwsc, void *value, char *arg) { - char *servername = conf_get_servername(); - - ws_writefd(pwsc,"%s",servername); - free(servername); -} - -/** - * write a config value - */ -void config_emit_config(WS_CONNINFO *pwsc, void *value, char *arg) { - char *new_arg = strdup(arg); - char *section = new_arg; - char *key; - char *result_value; - - if(!new_arg) - return; - - key = strchr(new_arg,'/'); - if(!key) - return; - - *key = '\0'; - key++; - - result_value = conf_alloc_string(section, key, NULL); - if(!result_value) - ws_writefd(pwsc,"NULL"); - else { - ws_writefd(pwsc,"%s",result_value); - free(result_value); - } -} - -/** - * emit the host value passed by the client web server. This - * is really used to autoconfig the java client - * - * \param pwsc web connection - * \param value the variable that was requested - * \param arg any args passwd with the meta command - */ -void config_emit_host(WS_CONNINFO *pwsc, void *value, char *arg) { - char *host; - char *port; - char hostname[256]; - - if(ws_getrequestheader(pwsc,"host")) { - host = strdup(ws_getrequestheader(pwsc,"host")); - if((port = strrchr(host,':'))) { - *port = '\0'; - } - ws_writefd(pwsc,"%s",host); - free(host); - } else { - DPRINTF(E_LOG,L_CONF,"Didn't get a host header!\n"); - gethostname(hostname,sizeof(hostname)); - ws_writefd(pwsc,hostname); - } - - return; -} - - -/** - * emit the path to the current config file - * - * \param pwsc web connection - * \param value the variable that was requested - * \param arg any args passwd with the meta command - */ -void config_emit_conffile(WS_CONNINFO *pwsc, void *value, char *arg) { - char fullpath[PATH_MAX]; - - realpath(conf_get_filename(),fullpath); - ws_writefd(pwsc,"%s",fullpath); - return; -} - -/** - * Emit a string to the admin page. This is an actual string, - * not a pointer to a string pointer, like emit_string. - * - * \param pwsc web connection - * \param value the variable that was requested - * \param arg any args passwd with the meta command - */ -void config_emit_literal(WS_CONNINFO *pwsc, void *value, char *arg) { - ws_writefd(pwsc,"%s",(char*)value); -} - -/** - * Dumps the status and control block to the web page. This is the - * page that offers the user the ability to rescan, or stop the - * daap server. - * - * \param pwsc web connection - * \param value the variable that was requested. This is - * required by the config struct, but unused. - * \param arg any args passwd with the meta command. Also unused - */ -void config_emit_service_status(WS_CONNINFO *pwsc, void *value, char *arg) { - char buf[256]; - int r_days, r_hours, r_mins, r_secs; - int scanning; - int song_count; - - ws_writefd(pwsc,""); - ws_writefd(pwsc,"\n"); - - ws_writefd(pwsc,"",config.stop ? "Stopping":"Running"); - if(config.stop) { - ws_writefd(pwsc,"\n"); - } else { - ws_writefd(pwsc,""); - } - - scanning = config.reload; - ws_writefd(pwsc,"",scanning ? "Running":"Idle"); - if(scanning) { - ws_writefd(pwsc,""); - } else { - ws_writefd(pwsc,""); - } - - ws_writefd(pwsc,"
ServiceStatusControl
DAAP Server%sWait...
Stop DAAP Server
Background scanner%sWait...
Start Scan
\n"); - - ws_writefd(pwsc,"
\n"); - - ws_writefd(pwsc,"\n"); - ws_writefd(pwsc,"\n"); - ws_writefd(pwsc," \n"); - - r_secs=(int)(time(NULL)-config.stats.start_time); - - r_days=r_secs/(3600 * 24); - r_secs -= ((3600 * 24) * r_days); - - r_hours=r_secs/3600; - r_secs -= (3600 * r_hours); - - r_mins=r_secs/60; - r_secs -= 60 * r_mins; - - memset(buf,0x0,sizeof(buf)); - if(r_days) - sprintf((char*)&buf[strlen(buf)],"%d day%s, ", r_days, - r_days == 1 ? "" : "s"); - - if(r_days || r_hours) - sprintf((char*)&buf[strlen(buf)],"%d hour%s, ", r_hours, - r_hours == 1 ? "" : "s"); - - if(r_days || r_hours || r_mins) - sprintf((char*)&buf[strlen(buf)],"%d minute%s, ", r_mins, - r_mins == 1 ? "" : "s"); - - sprintf((char*)&buf[strlen(buf)],"%d second%s ", r_secs, - r_secs == 1 ? "" : "s"); - - ws_writefd(pwsc," \n",buf); - ws_writefd(pwsc,"\n"); - - ws_writefd(pwsc,"\n"); - ws_writefd(pwsc," \n"); - db_get_song_count(NULL,&song_count); - ws_writefd(pwsc," \n",song_count); - ws_writefd(pwsc,"\n"); - - ws_writefd(pwsc,"\n"); - ws_writefd(pwsc," \n"); - ws_writefd(pwsc," \n",config.stats.songs_served); - ws_writefd(pwsc,"\n"); - - if(!scanning) { - ws_writefd(pwsc,"\n"); - ws_writefd(pwsc," \n"); - ws_writefd(pwsc," \n",db_revision()); - ws_writefd(pwsc,"\n"); - } - - /* - ws_writefd(pwsc,"\n"); - ws_writefd(pwsc," \n"); - ws_writefd(pwsc," \n",config.stats.songs_served); - ws_writefd(pwsc,"\n"); - */ - - ws_writefd(pwsc,"
Uptime%s
Songs%d
Songs Served%d
DB Version%d
Bytes Served%d
\n"); -} - - -/** - * Get the count of connected users. This is actually not totally accurate, - * as there may be a "connected" host that is in between requesting songs. - * It's marginally close though. It is really the number of connections - * with non-zero session numbers. - * - * \returns connected user count - */ -int config_get_session_count(void) { - WSTHREADENUM wste; - WS_CONNINFO *pwsc; - int count=0; - - pwsc = ws_thread_enum_first(config.server,&wste); - while(pwsc) { - count++; - pwsc = ws_thread_enum_next(config.server,&wste); - } - - return count; -} - - -/** - * dump the html code for the SESSION-COUNT command - * - * \param pwsc web connection - * \param value the variable that was requested. This is - * required by the config struct, but unused. - * \param arg any args passwd with the meta command. Also unused - */ -void config_emit_session_count(WS_CONNINFO *pwsc, void *value, char *arg) { - ws_writefd(pwsc,"%d",config_get_session_count()); -} - -/** - * dump the html code for the THREADSTAT command - * - * \param pwsc web connection - * \param value the variable that was requested. This is - * required by the config struct, but unused. - * \param arg any args passwd with the meta command. Also unused - */ -void config_emit_threadstatus(WS_CONNINFO *pwsc, void *value, char *arg) { - WS_CONNINFO *pci; - SCAN_STATUS *pss; - WSTHREADENUM wste; - - ws_writefd(pwsc,""); - ws_writefd(pwsc,""); - ws_writefd(pwsc,"\n"); - - - pci = ws_thread_enum_first(config.server,&wste); - while(pci) { - pss = ws_get_local_storage(pci); - if(pss) { - ws_writefd(pwsc,"\n", - pss->thread,pss->session,pss->host,pss->what ? pss->what : "Idle"); - } - pci=ws_thread_enum_next(config.server,&wste); - } - - ws_writefd(pwsc,"
ThreadSessionHostAction
%d%d%s%s
\n"); -} - - -/** - * Kind of a hackish function to emit false or true if the served page is the - * same name as the argument. This is basically used to not make the current - * tab a link when it is selected. Kind of a hack. - * - * \param pwsc web connection - * \param value the variable that was requested. Unused. - * \param arg any args passwd with the meta command. In this case - * it is the page to test to see if it matches - */ -void config_emit_ispage(WS_CONNINFO *pwsc, void *value, char *arg) { - char *first; - char *last; - - char *page, *true, *false; - - DPRINTF(E_DBG,L_CONF|L_WS,"Splitting arg %s\n",arg); - - first=last=arg; - strsep(&last,":"); - - if(last) { - page=strdup(first); - if(!page) - return; - first=last; - strsep(&last,":"); - if(last) { - true=strdup(first); - false=strdup(last); - if((!true)||(!false)) - return; - } else { - true=strdup(first); - if(!true) - return; - false=NULL; - } - } else { - return; - } - - - DPRINTF(E_DBG,L_CONF|L_WS,"page: %s, uri: %s\n",page,ws_uri(pwsc)); - - if((strlen(page) > strlen(ws_uri(pwsc))) || - (strcasecmp(page,(char*)&ws_uri(pwsc)[strlen(ws_uri(pwsc)) - strlen(page)]) != 0)) { - if(false) { - ws_writefd(pwsc,"%s",false); - } - } else { - if(true) { - ws_writefd(pwsc,"%s",true); - } - } - - - if(page) - free(page); - - if(true) - free(true); - - if(false) - free(false); -} - -/** - * Emit the html for the USER command - * - * \param pwsc web connection - * \param value the variable that was requested. Unused. - * \param arg any args passwd with the meta command. Unused. - */ -void config_emit_user(WS_CONNINFO *pwsc, void *value, char *arg) { - if(ws_getvar(pwsc, "HTTP_USER")) { - ws_writefd(pwsc,"%s",ws_getvar(pwsc, "HTTP_USER")); - } - return; -} -/** - * implement the READONLY command. This is really a hack so - * that the html config form isn't editable if the config file - * isn't writable by the server. - * - * \param pwsc web connection - * \param value the variable that was requested. Unused. - * \param arg any args passwd with the meta command. Unused - */ -void config_emit_readonly(WS_CONNINFO *pwsc, void *value, char *arg) { - if(!conf_iswritable()) { - ws_writefd(pwsc,"readonly=\"readonly\""); - } -} - -/** - * implement the INCLUDE command. This is essentially a server - * side include. - * - * \param pwsc web connection - * \param value the variable that was requested. Unused. - * \param arg any args passwd with the meta command. Unused. - */ -void config_emit_include(WS_CONNINFO *pwsc, void *value, char *arg) { - char resolved_path[PATH_MAX]; - char path[PATH_MAX]; - char web_root[PATH_MAX]; - IOHANDLE hfile; - struct stat sb; - int size; - char *urltemp; - int ret; - - size = sizeof(web_root); - if(conf_get_string("general","web_root",NULL,web_root, - &size) == CONF_E_NOTFOUND) { - DPRINTF(E_FATAL,L_WS,"No web root!\n"); - } - - DPRINTF(E_DBG,L_CONF|L_WS,"Preparing to include %s\n",arg); - - if(('/' == web_root[strlen(web_root) - 1]) || - ('\\' == web_root[strlen(web_root) - 1])) { - web_root[strlen(web_root) - 1] = '\0'; - } - - snprintf(path,PATH_MAX,"%s/%s",web_root,arg); - if(!realpath(path,resolved_path)) { - pwsc->error=errno; - DPRINTF(E_WARN,L_CONF|L_WS,"Cannot resolve %s\n",path); - ws_writefd(pwsc,"
error: cannot find %s
",arg); - return; - } - - /* this should really return a 302:Found */ - stat(resolved_path,&sb); - if(sb.st_mode & S_IFDIR) { - ws_writefd(pwsc,"
error: cannot include dir %s
",arg); - return; - } - - - DPRINTF(E_DBG,L_CONF|L_WS,"Thread %d: Preparing to serve %s\n", - ws_threadno(pwsc), resolved_path); - - if(strncmp(resolved_path,web_root,strlen(web_root))) { - pwsc->error=EINVAL; - DPRINTF(E_LOG,L_CONF|L_WS,"Thread %d: Requested file %s out of root\n", - ws_threadno(pwsc),resolved_path); - ws_writefd(pwsc,"
error: %s out of web root
",arg); - return; - } - - hfile = io_new(); - if(!hfile) { - DPRINTF(E_LOG,L_CONF | L_WS,"Thread %d: Cannot create file handle\n", - ws_threadno(pwsc)); - return; - } - - urltemp = io_urlencode(resolved_path); - if (!urltemp) - { - ws_set_err(pwsc,E_WS_NATIVE); - DPRINTF(E_LOG,L_CONF|L_WS,"Thread %d: Error opening %s: out of memory\n", - ws_threadno(pwsc),resolved_path); - ws_writefd(pwsc,"
error: cannot open %s: out of memory
",arg); - io_dispose(hfile); - return; - } - - ret = io_open(hfile, "file://%s", urltemp); - free(urltemp); - if (!ret) { - ws_set_err(pwsc,E_WS_NATIVE); - DPRINTF(E_LOG,L_CONF|L_WS,"Thread %d: Error opening %s: %s\n", - ws_threadno(pwsc),resolved_path,io_errstr(pwsc)); - ws_writefd(pwsc,"
error: cannot open %s: %s
",arg, - io_errstr(pwsc)); - io_dispose(hfile); - return; - } - - config_subst_stream(pwsc, hfile); - - io_close(hfile); - io_dispose(hfile); - DPRINTF(E_DBG,L_CONF|L_WS,"Thread %d: included successfully\n",ws_threadno(pwsc)); - return; -} - -/** - * free a SCAN_STATUS block - * - * @param vp pointer to SCAN_STATUS block - */ -void config_freescan(void *vp) { - SCAN_STATUS *pss = (SCAN_STATUS*)vp; - - if(pss) { - if(pss->what) - free(pss->what); - if(pss->host) - free(pss->host); - free(pss); - } -} - -/** - * Update the status info for a particular thread. The thread - * status is the string displayed in the THREADSTAT block of the - * admin page. That string is set with the function, which take - * a printf-style format specifier. - * - * \param pwsc the web connection of the thread to update - * \param session the session id of that thread - * \param fmt printf style form specifier - */ -void config_set_status(WS_CONNINFO *pwsc, int session, char *fmt, ...) { - char buffer[1024]; - va_list ap; - SCAN_STATUS *pfirst; - char *newmsg = NULL; - - DPRINTF(E_DBG,L_CONF,"Entering config_set_status\n"); - - if(fmt) { - va_start(ap, fmt); - vsnprintf(buffer, 1024, fmt, ap); - va_end(ap); - - newmsg = strdup(buffer); - } - - ws_lock_local_storage(pwsc); - if(!(pfirst = ws_get_local_storage(pwsc))) { - /* new info */ - pfirst=(SCAN_STATUS*)malloc(sizeof(SCAN_STATUS)); - if(pfirst) { - pfirst->what = NULL; - pfirst->thread = ws_threadno(pwsc); - pfirst->host = strdup(ws_hostname(pwsc)); - ws_set_local_storage(pwsc,pfirst,config_freescan); - } else { - DPRINTF(E_FATAL,L_CONF,"Malloc Error\n"); - } - } - - if(pfirst) { - /* just update */ - if(pfirst->what) { - free(pfirst->what); - } - pfirst->what=newmsg; - pfirst->session=session; - } else { - if(newmsg) - free(newmsg); - } - - ws_unlock_local_storage(pwsc); - DPRINTF(E_DBG,L_CONF,"Exiting config_set_status\n"); -} - -/** - * implement the VERSION command - * - * \param pwsc web connection - * \param value the variable that was requested. Unused. - * \param arg any args passwd with the meta command. Unused. - */ -void config_emit_version(WS_CONNINFO *pwsc, void *value, char *arg) { - ws_writefd(pwsc,"%s",VERSION); -} - -/** - * implement the SYSTEM command. - * - * \param pwsc web connection - * \param value the variable that was requested. Unused. - * \param arg any args passwd with the meta command. Unused. - */ -void config_emit_system(WS_CONNINFO *pwsc, void *value, char *arg) { - ws_writefd(pwsc,"%s",HOST); -} - -/** - * Implement the FLAGS command. - * - * \param pwsc web connection - * \param value the variable that was requested. Unused. - * \param arg any args passwd with the meta command. Unused. - */ -void config_emit_flags(WS_CONNINFO *pwsc, void *value, char *arg) { -#ifdef FLAC - ws_writefd(pwsc,"%s ","--enable-flac"); -#endif - -#ifdef MUSEPACK - ws_writefd(pwsc,"%s ","--enable-musepack"); -#endif -} diff --git a/src/configfile.h b/src/configfile.h deleted file mode 100644 index 5c883134..00000000 --- a/src/configfile.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Functions for reading and writing the config file - * - * Copyright (C) 2003 Ron Pedde (ron@pedde.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef _CONFIGFILE_H_ -#define _CONFIGFILE_H_ - -#include "daapd.h" -#include "webserver.h" - -extern int config_auth(WS_CONNINFO *pwsc, char *user, char *password); -extern void config_handler(WS_CONNINFO *pwsc); -extern void config_set_status(WS_CONNINFO *pwsc, int session, char *fmt, ...); -extern int config_get_session_count(void); -extern int config_matches_role(WS_CONNINFO *pwsc, char *username, char *password, char *role); - -/** thread local storage */ -typedef struct tag_scan_status { - int session; - int thread; - char *what; - char *host; -} SCAN_STATUS; - -#endif /* _CONFIGFILE_H_ */ diff --git a/src/daapd.h b/src/daapd.h index 18448bfb..f3d9a6dd 100644 --- a/src/daapd.h +++ b/src/daapd.h @@ -56,7 +56,6 @@ #endif #include -#include "webserver.h" /** Simple struct for holding stat info. */ @@ -77,7 +76,6 @@ typedef struct tag_config { int full_reload; /**< Whether the reload should be a full one */ STATS stats; /**< Stats structure (see above) */ - WSHANDLE server; /**< webserver handle */ } CONFIG; extern CONFIG config; diff --git a/src/db-generic.h b/src/db-generic.h index a9f46b7d..d36d7968 100644 --- a/src/db-generic.h +++ b/src/db-generic.h @@ -23,7 +23,6 @@ #include "ff-dbstruct.h" /** for MP3FILE */ #include "smart-parser.h" /** for PARSETREE */ -#include "webserver.h" /** for WS_CONNINFO */ typedef enum { queryTypeItems, @@ -63,7 +62,6 @@ typedef struct tag_dbqueryinfo { char *uri_sections[10]; PARSETREE pt; void *output_info; - WS_CONNINFO *pwsc; } DBQUERYINFO; extern int db_open(char **pe, char *type, char *parameters); diff --git a/src/main.c b/src/main.c index 4d1687a3..ed07be9a 100644 --- a/src/main.c +++ b/src/main.c @@ -84,7 +84,6 @@ #include "conffile.h" #include "conf.h" -#include "configfile.h" #include "err.h" #include "misc.h" #include "filescanner.h" diff --git a/src/webserver.c b/src/webserver.c deleted file mode 100644 index e8a5d6f1..00000000 --- a/src/webserver.c +++ /dev/null @@ -1,2219 +0,0 @@ -/* - * Webserver library - * - * Copyright (C) 2003 Ron Pedde (ron@pedde.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_UNISTD_H -# include -#endif - -#include -#include -#include -#include -#include -#include - -#include "daapd.h" -#include "webserver.h" -#include "io.h" - -/* - * Defines - */ - -#ifndef PACKAGE -# define PACKAGE "Firefly Web Server" -# define VERSION "1.0" -#endif - -#define MAX_HOSTNAME 256 -#define MAX_LINEBUFFER 2048 -#define BLKSIZE PIPE_BUF - -#ifdef DEBUG -# ifndef ASSERT -# define ASSERT(f) \ - if(f) \ - {} \ - else \ - ws_dprintf(0,"Assert error in %s, line %d\n",__FILE__,__LINE__) -# endif /* ndef ASSERT */ -# define WS_ENTER() ws_dprintf(10,"Entering %s",__func__) -# define WS_EXIT() ws_printf(10,"Exiting %s",__func__) -#else /* ndef DEBUG */ -# ifndef ASSERT -# define ASSERT(f) -# endif -# define WS_ENTER() -# define WS_EXIT() -#endif - -/* - * Local (private) typedefs - */ - -typedef struct tag_ws_handler { - char *stem; - void (*req_handler)(WS_CONNINFO*); - int(*auth_handler)(WS_CONNINFO*, char *, char *); - int flags; - int addheaders; - struct tag_ws_handler *next; -} WS_HANDLER; - -typedef struct tag_ws_connlist { - WS_CONNINFO *pwsc; - struct tag_ws_connlist *next; -} WS_CONNLIST; - -typedef struct tag_ws_private { - WSCONFIG wsconfig; - WS_HANDLER handlers; - WS_CONNLIST connlist; - - IOHANDLE hserver; - int stop; - int running; - int threadno; - int dispatch_threads; - pthread_t server_tid; - pthread_cond_t exit_cond; - pthread_mutex_t exit_mutex; -} WS_PRIVATE; - - -/* - * Forwards - */ -void *ws_mainthread(void*); -void *ws_dispatcher(void*); -int ws_lock_unsafe(void); -int ws_unlock_unsafe(void); -void ws_defaulthandler(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc); -int ws_addarg(ARGLIST *root, char *key, char *fmt, ...); -void ws_freearglist(ARGLIST *root); -char *ws_urldecode(char *string, int space_as_plus); -int ws_getheaders(WS_CONNINFO *pwsc); -int ws_getpostvars(WS_CONNINFO *pwsc); -int ws_getgetvars(WS_CONNINFO *pwsc, char *string); -char *ws_getarg(ARGLIST *root, char *key); -int ws_testarg(ARGLIST *root, char *key, char *value); -int ws_findhandler(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc, - void(**preq)(WS_CONNINFO*), - int(**pauth)(WS_CONNINFO*, char *, char *), - int *addheaders); -int ws_registerhandler(WSHANDLE ws, char *stem, - void(*handler)(WS_CONNINFO*), - int(*auth)(WS_CONNINFO*, char *, char *), - int flags, - int addheaders); -int ws_decodepassword(char *header, char **username, char **password); -int ws_testrequestheader(WS_CONNINFO *pwsc, char *header, char *value); -char *ws_getrequestheader(WS_CONNINFO *pwsc, char *header); -static void ws_add_dispatch_thread(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc); -static void ws_remove_dispatch_thread(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc); -static int ws_encoding_hack(WS_CONNINFO *pwsc); - -static void ws_default_errhandler(int level, char *msg); -static void(*ws_err_handler)(int, char*) = ws_default_errhandler; - -/* - * Globals - */ -pthread_mutex_t ws_unsafe=PTHREAD_MUTEX_INITIALIZER; - -char *ws_dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; -char *ws_moy[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", - "Aug", "Sep", "Oct", "Nov", "Dec" }; - -char *ws_errors[] = { - "Success", /**< E_WS_SUCCESS */ - "Native Error", /**< E_WS_NATIVE */ - "Out of memory", /**< E_WS_MEMORY */ - "pthreads error", /**< E_WS_PTHREADS */ - "ports exhausted", /**< E_WS_EXHAUSTED */ - "Can't listen on port", /**< E_WS_LISTEN */ - "Invalid content length", /**< E_WS_CONTENTLEN */ - "Read error", /**< E_WS_READ */ - "Couldn't parse GET vars", /**< E_WS_GETVARS */ - "Timeout error", /**< E_WS_TIMEOUT */ - "Invalid reqeust type", /**< E_WS_REQTYPE */ - "Bad path - out of root" /**< E_WS_BADPATH */ -}; - -/** - * ws_dprintf - * - * Do debugging printfs. This will use the registered errhandler - */ -void ws_dprintf(int level, char *fmt, ...) { /* FIXME: dynamic buf len */ - char errbuf[4096]; - va_list ap; - - va_start(ap,fmt); - vsnprintf(errbuf,sizeof(errbuf),fmt,ap); - va_end(ap); - - ws_err_handler(level,errbuf); -} - -/** - * set the error handler to something other than default - * - * @param err_handler new error handler to use - */ -void ws_set_errhandler(void(*err_handler)(int, char*)) { - ws_err_handler = err_handler; -} - - -/** - * by default, just dump to stdout - */ -void ws_default_errhandler(int level, char *msg) { - fprintf(stderr,"%d: %s", level, msg); - if(!level) { /* fatal! */ - exit(0); - } -} - -/* - * ws_lock_unsafe - * - * Lock non-thread-safe functions - * - * returns TRUE on success - */ -int ws_lock_unsafe(void) { - int err; - int retval=TRUE; - - WS_ENTER(); - - if((err=pthread_mutex_lock(&ws_unsafe))) { - ws_dprintf(L_WS_FATAL,"Cannot lock mutex: %s\n",strerror(err)); - retval=FALSE; - } - - WS_EXIT(); - return retval; -} - -/* - * ws_unlock_unsafe - * - * Lock non-thread-safe functions - * - * returns TRUE on success - */ -int ws_unlock_unsafe(void) { - int err; - int retval=TRUE; - - WS_ENTER(); - - if((err=pthread_mutex_unlock(&ws_unsafe))) { - ws_dprintf(L_WS_FATAL,"Cannot unlock mutex: %s\n",strerror(err)); - retval=FALSE; - } - - WS_EXIT(); - return retval; -} - -/** - * lock the connection list - */ -void ws_lock_connlist(WS_PRIVATE *pwsp) { - if(pthread_mutex_lock(&pwsp->exit_mutex)) - ws_dprintf(L_WS_FATAL,"Cannot lock condition mutex\n"); -} - -/** - * Unlock the connection list - */ -void ws_unlock_connlist(WS_PRIVATE *pwsp) { - if(pthread_mutex_unlock(&pwsp->exit_mutex)) - ws_dprintf(L_WS_FATAL,"Cannot unlock condition mutex\n"); -} - -/** - * Create a new webserver object, with a specific config. - * - * @param config config to use for newly created web server - * @returns WSHANDLE on success, NULL on failure - */ -WSHANDLE ws_init(WSCONFIG *config) { - int err; - WS_PRIVATE *pwsp; - - WS_ENTER(); - if((pwsp=(WS_PRIVATE*)malloc(sizeof(WS_PRIVATE))) == NULL) { - ws_dprintf(L_WS_SPAM,"Malloc error: %s\n",strerror(errno)); - return NULL; - } - - memcpy(&pwsp->wsconfig,config,sizeof(WS_PRIVATE)); - pwsp->connlist.next=NULL; - pwsp->running=0; - pwsp->threadno=0; - pwsp->stop=0; - pwsp->dispatch_threads=0; - pwsp->handlers.next=NULL; - - if((err=pthread_cond_init(&pwsp->exit_cond, NULL))) { - ws_dprintf(L_WS_LOG,"Error in pthread_cond_init: %s\n",strerror(err)); - return NULL; - } - - if((err=pthread_mutex_init(&pwsp->exit_mutex,NULL))) { - ws_dprintf(L_WS_LOG,"Error in pthread_mutex_init: %s\n",strerror(err)); - return NULL; - } - - WS_EXIT(); - return (WSHANDLE)pwsp; -} - - -/* - * ws_start - * - * Start the main webserver thread. Should really - * bind and listen to the port before spawning the thread, - * since it will be hard to detect and return the error unless - * we listen first - * - * RETURNS - * Success: E_WS_SUCCESS - * Failure: Appropriate E_WS_ error code - * - */ -int ws_start(WSHANDLE ws) { - int err; - int ephemeral = 0; - WS_PRIVATE *pwsp = (WS_PRIVATE*)ws; - - WS_ENTER(); - - /* this is kind of stupid, but is easier to fix once here - * rather than mucking with differences in sockets on windows/unix - */ - - if(pwsp->wsconfig.port == 0) { - pwsp->wsconfig.port = 1024; /* start with default port */ - ephemeral = 1; - } - - while(1) { - ws_dprintf(L_WS_INF,"Listening on port %d\n",pwsp->wsconfig.port); - pwsp->hserver = io_new(); - if(!pwsp->hserver) - ws_dprintf(L_WS_FATAL,"Cannot create new IO object"); - - if(!io_open(pwsp->hserver,"listen://%d",pwsp->wsconfig.port)) { - if((!ephemeral) || (io_errcode(pwsp->hserver) != IO_E_SOCKET_INUSE)) { - ws_dprintf(L_WS_LOG,"Listen port: %s\n",io_errstr(pwsp->hserver)); - WS_EXIT(); - return E_WS_LISTEN; - } - } else { - break; - } - - pwsp->wsconfig.port++; - if(!pwsp->wsconfig.port) { - ws_dprintf(L_WS_LOG,"Exhausted ports\n"); - io_dispose(pwsp->hserver); - pwsp->hserver = NULL; - WS_EXIT(); - return E_WS_EXHAUSTED; - } - } - - ws_dprintf(L_WS_INF,"Starting server thread\n"); - if((err=pthread_create(&pwsp->server_tid,NULL,ws_mainthread,(void*)pwsp))) { - ws_dprintf(L_WS_LOG,"Could not spawn thread: %s\n",strerror(err)); - io_close(pwsp->hserver); - io_dispose(pwsp->hserver); - WS_EXIT(); - return E_WS_PTHREADS; - } - - /* we're really running */ - pwsp->running=1; - - WS_EXIT(); - return E_WS_SUCCESS; -} - - -/* - * ws_remove_dispatch_thread - * - * remove a dispatch thread from the thread list - */ -void ws_remove_dispatch_thread(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc) { - WS_CONNLIST *pHead, *pTail; - - WS_ENTER(); - - ws_lock_connlist(pwsp); - - pTail=&(pwsp->connlist); - pHead=pwsp->connlist.next; - - while((pHead) && (pHead->pwsc != pwsc)) { - pTail=pHead; - pHead=pHead->next; - } - - if(pHead) { - pwsp->dispatch_threads--; - ws_dprintf(L_WS_DBG,"With thread %d exiting, %d are still running\n", - pwsc->threadno,pwsp->dispatch_threads); - - pTail->next = pHead->next; - - free(pHead); - - /* signal condition in case something is waiting */ - pthread_cond_signal(&pwsp->exit_cond); - } - - ws_unlock_connlist(pwsp); - WS_EXIT(); -} - - -/* - * ws_add_dispatch_thread - * - * Add a thread to the dispatch thread list - */ -void ws_add_dispatch_thread(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc) { - WS_CONNLIST *pNew; - - WS_ENTER(); - - pNew=(WS_CONNLIST*)malloc(sizeof(WS_CONNLIST)); - pNew->next=NULL; - pNew->pwsc=pwsc; - - if(!pNew) { - ws_dprintf(L_WS_FATAL,"Malloc: %s\n",strerror(errno)); - exit(1); /* shouldn't strictly be necessary unless paren doesn't - * honor L_WS_FATAL */ - } - - ws_lock_connlist(pwsp); - - /* list is locked... */ - pwsp->dispatch_threads++; - pNew->next = pwsp->connlist.next; - pwsp->connlist.next = pNew; - - ws_unlock_connlist(pwsp); - WS_EXIT(); -} - -/** - * Stop the web server and all the child threads. If there are any - * active sessions, the web server ungraciously closes the underlying - * socket, in an effort to make the child stop. - * - * @param ws handle of webserver to stop - * @returns TRUE on success - */ -int ws_stop(WSHANDLE ws) { - WS_PRIVATE *pwsp = (WS_PRIVATE*)ws; - WS_HANDLER *current; - WS_CONNLIST *pcl; - void *result; - - WS_ENTER(); - - /* free the ws_handlers */ - while(pwsp->handlers.next) { - current=pwsp->handlers.next; - pwsp->handlers.next=current->next; - free(¤t->stem); - free(current); - } - - pwsp->stop=1; - pwsp->running=0; - - ws_dprintf(L_WS_DBG,"ws_stop: closing the server fd\n"); - io_close(pwsp->hserver); - io_dispose(pwsp->hserver); - - /* wait for the server thread to terminate. SHould be quick! */ - pthread_join(pwsp->server_tid,&result); - - /* Give the threads an extra push */ - ws_lock_connlist(pwsp); - - pcl=pwsp->connlist.next; - - /* Closing the client sockets out from under the dispatch threads - * should cause the dispatch threads to exit out with an error. - */ - while(pcl) { - if(pcl->pwsc->hclient) { - io_close(pcl->pwsc->hclient); - io_dispose(pcl->pwsc->hclient); - pcl->pwsc->hclient = NULL; - } - pcl=pcl->next; - } - - /* wait for the threads to be done */ - while(pwsp->dispatch_threads) { - ws_dprintf(L_WS_DBG,"ws_stop: I still see %d threads\n",pwsp->dispatch_threads); - pthread_cond_wait(&pwsp->exit_cond, &pwsp->exit_mutex); - } - - ws_unlock_connlist(pwsp); - - free(pwsp); - - WS_EXIT(); - return TRUE; -} - -/** - * Main thread for webserver - this accepts connections - * and spawns a handler thread for each incoming connection. - * - * For a persistant connection, these threads will be - * long-lived, otherwise, they will terminate as soon as - * the request has been honored. - * - * These client threads will, of course, be detached - * - * @param arg this is actually a pointer to the web server private session - */ -void *ws_mainthread(void *arg) { - int err; - IOHANDLE hnew; - WS_PRIVATE *pwsp = (WS_PRIVATE*)arg; - WS_CONNINFO *pwsc; - pthread_t tid; - /* FIXME: endpoint from io_socket */ - char hostname[MAX_HOSTNAME+1]; - struct in_addr hostaddr; - - WS_ENTER(); - - while(1) { - pwsc=(WS_CONNINFO*)malloc(sizeof(WS_CONNINFO)); - if(!pwsc) { - /* can't very well service any more threads! */ - ws_dprintf(L_WS_FATAL,"Error: %s\n",strerror(errno)); - pwsp->running=0; - WS_EXIT(); - exit(1); /* should be unnecessary */ - return NULL; - } - - memset(pwsc,0,sizeof(WS_CONNINFO)); - - hnew = io_new(); - if(!hnew) - ws_dprintf(L_WS_FATAL,"Malloc error in io_new()"); - - if(!io_listen_accept(pwsp->hserver,hnew,&hostaddr)) { - ws_dprintf(L_WS_LOG,"Dispatcher: accept failed: %s\n", - io_errstr(pwsp->hserver)); - io_close(pwsp->hserver); - io_dispose(pwsp->hserver); - pwsp->running=0; - free(pwsc); - - ws_dprintf(L_WS_FATAL,"Dispatcher: Aborting\n"); - WS_EXIT(); - return NULL; - } - - /* FIXME: Get remote endpoint */ - strncpy(hostname,inet_ntoa(hostaddr),MAX_HOSTNAME); - - pwsc->hostname=strdup(hostname); - pwsc->hclient = hnew; - pwsc->pwsp = pwsp; - - /* Spawn off a dispatcher to decide what to do with - * the request - */ - - /* don't really care if it locks or not */ - ws_lock_unsafe(); - pwsc->threadno=pwsp->threadno; - pwsp->threadno++; - /* we'll hold the unsafe until the dispatch thread is registered */ - - /* now, throw off a dispatch thread */ - if((err=pthread_create(&tid,NULL,ws_dispatcher,(void*)pwsc))) { - ws_set_err(pwsc,E_WS_PTHREADS); - ws_dprintf(L_WS_FATAL,"Could not spawn thread: %s\n",strerror(err)); - ws_close(pwsc); - } else { - ws_add_dispatch_thread(pwsp,pwsc); - pthread_detach(tid); - } - ws_unlock_unsafe(); - } - - WS_EXIT(); -} - - -/** - * Close the connection. This might be called when things - * are already in bad shape, so we'll ignore errors and let - * them be detected back in either the dispatch - * thread or the main server thread - * - * Mainly, we just want to make sure that any - * allocated memory has been freed - * - * @param pwsc connection to close - * @returns TRUE on success - */ -void ws_close(WS_CONNINFO *pwsc) { - WS_PRIVATE *pwsp = (WS_PRIVATE *)(pwsc->pwsp); - - WS_ENTER(); - - ws_dprintf(L_WS_DBG,"Thread %d: Terminating\n",pwsc->threadno); - ws_dprintf(L_WS_DBG,"Thread %d: Freeing request headers\n",pwsc->threadno); - ws_freearglist(&pwsc->request_headers); - ws_dprintf(L_WS_DBG,"Thread %d: Freeing response headers\n",pwsc->threadno); - ws_freearglist(&pwsc->response_headers); - ws_dprintf(L_WS_DBG,"Thread %d: Freeing request vars\n",pwsc->threadno); - ws_freearglist(&pwsc->request_vars); - if(pwsc->uri) { - free(pwsc->uri); - pwsc->uri=NULL; - } - - if((pwsc->close)||(pwsc->error)) { - ws_dprintf(L_WS_DBG,"Thread %d: Closing fd\n",pwsc->threadno); - io_close(pwsc->hclient); - io_dispose(pwsc->hclient); - /* this thread is done */ - - ws_remove_dispatch_thread(pwsp, pwsc); - - /* Get rid of the local storage */ - if(pwsc->local_storage) { - if(pwsc->storage_callback) { - pwsc->storage_callback(pwsc->local_storage); - pwsc->local_storage=NULL; - pwsc->storage_callback=NULL; - } - } - - free(pwsc->hostname); - memset(pwsc,0x00,sizeof(WS_CONNINFO)); - free(pwsc); - WS_EXIT(); - pthread_exit(NULL); - } - WS_EXIT(); -} - -/* - * ws_freearglist - * - * Walks through an arg list freeing as it goes - */ -void ws_freearglist(ARGLIST *root) { - ARGLIST *current; - - WS_ENTER(); - - while(root->next) { - free(root->next->key); - free(root->next->value); - current=root->next; - root->next=current->next; - free(current); - } - - WS_EXIT(); -} - - -void ws_emitheaders(WS_CONNINFO *pwsc) { - ARGLIST *pcurrent=pwsc->response_headers.next; - - WS_ENTER(); - while(pcurrent) { - ws_dprintf(L_WS_DBG,"Emitting reponse header %s: %s\n",pcurrent->key, - pcurrent->value); - ws_writefd(pwsc,"%s: %s\r\n",pcurrent->key,pcurrent->value); - pcurrent=pcurrent->next; - } - - ws_writefd(pwsc,"\r\n"); - WS_EXIT(); -} - - -/** - * Receive and parse headers. These will trump - * get headers. This should really only be called if - * the method if post. This will fill in the request_headers - * linked list. - * - * @param pwsc session to parse post vars for - * @returns TRUE on success - */ -int ws_getpostvars(WS_CONNINFO *pwsc) { - char *content_length; - unsigned char *buffer; - uint32_t length; - uint32_t ms; - - WS_ENTER(); - - content_length = ws_getarg(&pwsc->request_headers,"Content-Length"); - if(!content_length) { - ws_set_err(pwsc,E_WS_CONTENTLEN); - WS_EXIT(); - return FALSE; - } - - length=strtol(content_length, NULL, 10); - if(EINVAL == errno || UINT_MAX - 1 <= length){ - ws_dprintf(L_WS_WARN, "Thread %d: Suspicious Content-Length value, ignoring request\n", pwsc->threadno); - return FALSE; - } - - ws_dprintf(L_WS_DBG,"Thread %d: Post var length: %d\n", - pwsc->threadno,length); - - buffer=(unsigned char*)malloc(length+1); - - if(!buffer) { - ws_set_err(pwsc,E_WS_MEMORY); - ws_dprintf(L_WS_INF,"Thread %d: Could not malloc %d bytes\n", - pwsc->threadno, length); - WS_EXIT(); - return FALSE; - } - - // make the read time out 30 minutes like we said in the - // /server-info response - ms = 1800 * 1000; - if(!io_read_timeout(pwsc->hclient, buffer, &length, &ms)) { - if(0 == ms) { - ws_dprintf(L_WS_INF,"Thread %d: Timeout reading post vars\n", - pwsc->threadno); - ws_set_err(pwsc,E_WS_TIMEOUT); - WS_EXIT(); - free(buffer); - return FALSE; - } - /* FIXME: Need to pass real errors back from the webserver */ - ws_dprintf(L_WS_LOG,"Thread %d: Read error: %s", - pwsc->threadno,io_errstr(pwsc->hclient)); - ws_set_err(pwsc,E_WS_READ); - free(buffer); - return FALSE; - } - - ws_dprintf(L_WS_DBG,"Thread %d: Read post vars: %s\n",pwsc->threadno,buffer); - - if(!ws_getgetvars(pwsc,(char*)buffer)) { - /* assume error was set already */ - free(buffer); - ws_dprintf(L_WS_LOG,"Could not parse get vars\n"); - return FALSE; - } - - free(buffer); - - WS_EXIT(); - return TRUE; -} - - -/** - * Receive and parse headers. This is called from - * ws_dispatcher - * - * @param pwsc session to get headers for - * @returns TRUE on success - */ -int ws_getheaders(WS_CONNINFO *pwsc) { - char *first, *last; - int done; - char buffer[MAX_LINEBUFFER]; - uint32_t len; - - WS_ENTER(); - - /* Break down the headers into some kind of header list */ - done=0; - while(!done) { - len = sizeof(buffer); - if(!io_readline(pwsc->hclient,(unsigned char *)buffer,&len)) { - ws_set_err(pwsc,E_WS_READ); - pwsc->error=errno; - ws_dprintf(L_WS_INF,"Thread %d: read error: %s\n",pwsc->threadno, - io_errstr(pwsc->hclient)); - WS_EXIT(); - return FALSE; - } - - ws_dprintf(L_WS_DBG,"Thread %d: Read: %s",pwsc->threadno,buffer); - - first=buffer; - if(buffer[0] == '\r') - first=&buffer[1]; - - /* trim the trailing \n */ - if(first[strlen(first)-1] == '\n') - first[strlen(first)-1] = '\0'; - - if(strlen(first) == 0) { - ws_dprintf(L_WS_DBG,"Thread %d: Headers parsed!\n",pwsc->threadno); - done=1; - } else { - /* we have a header! */ - last=first; - strsep(&last,":"); - - if(!last) { - ws_dprintf(L_WS_WARN,"Thread %d: Invalid header: %s\n", - pwsc->threadno,first); - } else { - while(*last==' ') - last++; - - while(last[strlen(last)-1] == '\r') - last[strlen(last)-1] = '\0'; - - ws_dprintf(L_WS_DBG,"Thread %d: Adding header *%s=%s*\n", - pwsc->threadno,first,last); - - if(!ws_addarg(&pwsc->request_headers,first,"%s",last)) { - ws_dprintf(L_WS_FATAL,"Thread %d: Out of memory\n", - pwsc->threadno); - ws_set_err(pwsc,E_WS_MEMORY); - WS_EXIT(); - return FALSE; - } - } - } - } - - WS_EXIT(); - return TRUE; -} - - -/** - * Some client choose to encode a space as +, others dont. This - * causes problems when the literal text to be passed is really a - * '+' and not a ' '. This attempts to fix broken browsers by - * setting the appropriate flag for whether or not the client encodes - * a space as plus. - * - * This is indeed a hack, as the url encoding spec makes it clear - * that spaces should be encoded as a +. Firefix and IE do this, - * but the SoundBridge and iTunes don't. (But Safari does, go figure) - * - * This could be used to set other browser-specific quirks, like the - * Olive/HiFidelio issue with not honoring "Connection: close", as is - * *REQUIRED* by RFC for HTTP 1.1 browsers, and ignored by HiFidelio. - * - * HiFidelio programmers, take note -- if you fixed your HTTP - * implementation to be RFC compliant, I could provide transcoding - * for your clients. (And they want it, judging by my forums) - * - * @param pwsc session to check for encoding style - * @returns TRUE if the remote user agent encodes space as a plus. - */ -int ws_encoding_hack(WS_CONNINFO *pwsc) { - char *user_agent; - int space_as_plus=1; - - WS_ENTER(); - user_agent=ws_getrequestheader(pwsc, "user-agent"); - if(user_agent) { - if(strncasecmp(user_agent,"Roku",4) == 0) - space_as_plus=0; - if(strncasecmp(user_agent,"iTunes",6) == 0) - space_as_plus=0; - } - WS_EXIT(); - return space_as_plus; -} - - -/** - * parse a GET string of variables, filling in the request_vars - * linked list. - * - * @params pwsc the session to parse vars for - * @params string the argument part of the URI as requested by the client - * @returns - */ -int ws_getgetvars(WS_CONNINFO *pwsc, char *string) { - char *first, *last, *middle; - char *key, *value; - int done; - - int space_as_plus; - - WS_ENTER(); - - space_as_plus=ws_encoding_hack(pwsc); - - done=0; - - first=string; - - while((!done) && (first)) { - last=middle=first; - strsep(&last,"&"); - strsep(&middle,"="); - - if(!middle) { - ws_dprintf(L_WS_WARN,"Thread %d: Bad arg: %s\n", - pwsc->threadno,first); - } else { - key=ws_urldecode(first,space_as_plus); - value=ws_urldecode(middle,space_as_plus); - - ws_dprintf(L_WS_DBG,"Thread %d: Adding arg %s = %s\n", - pwsc->threadno,key,value); - ws_addarg(&pwsc->request_vars,key,"%s",value); - - free(key); - free(value); - } - - if(!last) { - ws_dprintf(L_WS_DBG,"Thread %d: Done parsing GET/POST args!\n", - pwsc->threadno); - done=1; - } else { - first=last; - } - } - - WS_EXIT(); - return TRUE; -} - - -/** - * Main dispatch thread. This gets the request, reads the - * headers, decodes the GET'd or POST'd variables, - * then decides what function should service the request - * - * @param arg a pointer to the WS_CONNINFO struct we are servicing - */ -void *ws_dispatcher(void *arg) { - WS_CONNINFO *pwsc=(WS_CONNINFO*)arg; - WS_PRIVATE *pwsp=pwsc->pwsp; - char buffer[MAX_LINEBUFFER]; - char *first,*last; - int connection_done=0; - int can_dispatch; - char *auth, *username, *password; - int hdrs,handler; - time_t now; - struct tm now_tm; - void (*req_handler)(WS_CONNINFO*); - int(*auth_handler)(WS_CONNINFO*, char *, char *); - uint32_t ms, len; - - WS_ENTER(); - - /* quick fence to ensure that we have been registered on the thread list */ - ws_lock_unsafe(); - ws_unlock_unsafe(); - - while(!connection_done) { - // Now, get the request from the other end - // and decide where to dispatch it - ms = 1800 * 1000; - len = sizeof(buffer); - if(!io_readline_timeout(pwsc->hclient,(unsigned char *)buffer,&len,&ms) || (!len)) { - ws_set_err(pwsc,E_WS_TIMEOUT); - pwsc->error=errno; - pwsc->close=1; - ws_dprintf(L_WS_WARN,"Thread %d: could not read: %s\n", - pwsc->threadno,io_errstr(pwsc->hclient)); - ws_close(pwsc); - WS_EXIT(); - return NULL; - } - - ws_dprintf(L_WS_DBG,"Thread %d: \n",pwsc->threadno); - ws_dprintf(L_WS_DBG - 1, "Request: %s", buffer); - - first=last=buffer; - strsep(&last," "); - if(!last) { - pwsc->close=1; - ws_returnerror(pwsc,400,"Bad request"); - ws_close(pwsc); - ws_dprintf(L_WS_SPAM,"Error: bad request. Exiting ws_dispatcher\n"); - WS_EXIT(); - return NULL; - } - - if(!strcasecmp(first,"get")) { - pwsc->request_type = RT_GET; - } else if(!strcasecmp(first,"post")) { - pwsc->request_type = RT_POST; - } else { - /* TODO: pass these to the underlying client, as they - * might be interested in them (UPnP, maybe) */ - ws_set_err(pwsc,E_WS_REQTYPE); - ws_returnerror(pwsc,501,"Not implemented"); - ws_close(pwsc); - ws_dprintf(L_WS_SPAM,"Error: not get or post. Exiting ws_dispatcher\n"); - WS_EXIT(); - return NULL; - } - - first=last; - strsep(&last," "); - pwsc->uri=strdup(first); - - /* Get headers */ - if((!ws_getheaders(pwsc)) || (!last)) { /* didn't provide a HTTP/1.x */ - /* error already set */ - ws_dprintf(L_WS_LOG,"Thread %d: Couldn't parse headers - aborting\n",pwsc->threadno); - ws_should_close(pwsc,TRUE); - ws_close(pwsc); - WS_EXIT(); - return NULL; - } - - - /* Now that we have the headers, we can - * decide whether or not this is a persistant - * connection */ - if(strncasecmp(last,"HTTP/1.0",8)==0) { /* defaults to non-persistant */ - pwsc->close=!ws_testarg(&pwsc->request_headers,"connection","keep-alive"); - } else { /* default to persistant for HTTP/1.1 and above */ - pwsc->close=ws_testarg(&pwsc->request_headers,"connection","close"); - } - - ws_dprintf(L_WS_DBG,"Thread %d: Connection type %s: Connection: %s\n", - pwsc->threadno, last, pwsc->close ? "non-persist" : "persist"); - - if(!pwsc->uri) { - ws_set_err(pwsc,E_WS_MEMORY); - ws_dprintf(L_WS_LOG,"Thread %d: Error allocation URI\n", - pwsc->threadno); - ws_returnerror(pwsc,500,"Internal server error"); - ws_close(pwsc); - WS_EXIT(); - return NULL; - } - - /* trim the URI */ - first=pwsc->uri; - strsep(&first,"?"); - - if(first) { /* got some GET args */ - ws_dprintf(L_WS_DBG,"Thread %d: parsing GET args\n",pwsc->threadno); - ws_getgetvars(pwsc,first); - } - - /* fix the URI by un urldecoding it */ - - ws_dprintf(L_WS_DBG,"Thread %d: Original URI: %s\n", - pwsc->threadno,pwsc->uri); - - first=ws_urldecode(pwsc->uri,ws_encoding_hack(pwsc)); - free(pwsc->uri); - pwsc->uri=first; - - /* Strip out the proxy stuff - iTunes 4.5 */ - first=strstr(pwsc->uri,"://"); - if(first) { - first += 3; - first=strchr(first,'/'); - if(first) { - first=strdup(first); - free(pwsc->uri); - pwsc->uri=first; - } - } - - - ws_dprintf(L_WS_DBG,"Thread %d: Translated URI: %s\n",pwsc->threadno, - pwsc->uri); - - /* now, parse POST args */ - if(pwsc->request_type == RT_POST) - ws_getpostvars(pwsc); - - hdrs=1; - - handler=ws_findhandler(pwsp,pwsc,&req_handler,&auth_handler,&hdrs); - - time(&now); - ws_dprintf(L_WS_DBG,"Thread %d: Time is %d seconds after epoch\n", - pwsc->threadno,now); - gmtime_r(&now,&now_tm); - ws_dprintf(L_WS_DBG,"Thread %d: Setting time header\n",pwsc->threadno); - ws_addarg(&pwsc->response_headers,"Date", - "%s, %d %s %d %02d:%02d:%02d GMT", - ws_dow[now_tm.tm_wday],now_tm.tm_mday, - ws_moy[now_tm.tm_mon],now_tm.tm_year + 1900, - now_tm.tm_hour,now_tm.tm_min,now_tm.tm_sec); - - if(hdrs) { - ws_addarg(&pwsc->response_headers,"Connection", - pwsc->close ? "close" : "keep-alive"); - - ws_addarg(&pwsc->response_headers,"Server", - PACKAGE "/" VERSION); - - ws_addarg(&pwsc->response_headers,"Content-Type","text/html"); - ws_addarg(&pwsc->response_headers,"Content-Language","en_us"); - } - - /* Find the appropriate handler and dispatch it */ - if(handler == -1) { - ws_dprintf(L_WS_DBG,"Thread %d: Using default handler.\n", - pwsc->threadno); - ws_defaulthandler(pwsp,pwsc); - } else { - ws_dprintf(L_WS_DBG,"Thread %d: Using non-default handler\n", - pwsc->threadno); - - can_dispatch=0; - /* If an auth handler is registered, but it accepts a - * username and password of NULL, then don't bother - * authing. - */ - if((auth_handler) && (auth_handler(pwsc,NULL,NULL)==0)) { - /* do the auth thing */ - auth=ws_getarg(&pwsc->request_headers,"Authorization"); - if((auth) && (ws_decodepassword(auth,&username, &password))) { - if(auth_handler(pwsc,username,password)) - can_dispatch=1; - ws_addarg(&pwsc->request_vars,"HTTP_USER","%s",username); - ws_addarg(&pwsc->request_vars,"HTTP_PASSWD","%s",password); - free(username); /* this frees password too */ - } - - if(!can_dispatch) { /* auth failed, or need auth */ - //ws_addarg(&pwsc->response_headers,"Connection","close"); - ws_addarg(&pwsc->response_headers,"WWW-Authenticate", - "Basic realm=\"webserver\""); - ws_returnerror(pwsc,401,"Unauthorized"); - ws_set_err(pwsc,E_WS_SUCCESS); /* don't close! */ - } - } else { - can_dispatch=1; - } - - if(can_dispatch) { - if(req_handler) - req_handler(pwsc); - else - ws_defaulthandler(pwsp,pwsc); - } - } - - if((pwsc->close) || (pwsc->error) || (pwsp->stop)) { - ws_should_close(pwsc,TRUE); - connection_done=1; - } - ws_close(pwsc); - } - - WS_EXIT(); - return NULL; -} - - -/** - * Write a printf-style output to a connection. - * - * FIXME: This has a max buffer length of 1024, and shold - * be fixed to use a variable-length buffer. Note to self: - * use io_printf to write a platform-indepentent aprintf, or - * make an io_vprintf function... - * - * Note also that the return value is particularly useless. - * - * @param pwsc session to print output to - * @param fmt format of output string - * @returns number of bytes in the formatted string, not bytes written - */ -int ws_writefd(WS_CONNINFO *pwsc, char *fmt, ...) { - char buffer[1024]; - va_list ap; - uint32_t len; - - WS_ENTER(); - - va_start(ap, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, ap); - va_end(ap); - - len = (uint32_t)strlen(buffer); - if(!io_write(pwsc->hclient,(unsigned char *)buffer,&len)) { - ws_dprintf(L_WS_DBG,"Error writing to client socket: %s\n", - io_errstr(pwsc->hclient)); - } - - WS_EXIT(); - return (int)len; -} - -/** -* write a binary block of data to the connection -* -* @params pwsc connection to write data to -* @params data data to write -* @params len length of data to write -* @returns bytes actually written -*/ -int ws_writebinary(WS_CONNINFO *pwsc, char *data, int len) { - uint32_t bytes_written; - - WS_ENTER(); - bytes_written = (uint32_t) len; - if(!io_write(pwsc->hclient, (unsigned char *)data, &bytes_written)) { - ws_dprintf(L_WS_LOG,"Error writing to client socket: %s\n", - io_errstr(pwsc->hclient)); - } - - WS_EXIT(); - return (int)bytes_written; -} - -/** - * return a particular error code to the requesting - * agent - * - * This will always succeed. If it cannot, it will - * just close the connection with prejudice. - * - * @param pwsc connection to write error to - * @param int error to return (500, 302, etc) - * @param description (internal server error, content moved, etc) - * @return TRUE on success - */ -int ws_returnerror(WS_CONNINFO *pwsc,int error, char *description) { - char *useragent; - int err_code; - char *err_str; - int keep_alive = 0; - - WS_ENTER(); - - ws_dprintf(L_WS_WARN,"Thread %d: Entering ws_returnerror (%d: %s)\n", - pwsc->threadno,error,description); - ws_writefd(pwsc,"HTTP/1.1 %d %s\r\n",error,description); - - /* we'll force a close here unless the user agent is - iTunes, which seems to get pissy about it */ - - useragent = ws_getarg(&pwsc->request_headers,"User-Agent"); - if((useragent) && - (((strncmp(useragent,"iTunes",6) == 0) && (error == 401)) || - ((strncmp(useragent,"Java",4) == 0)))) { - keep_alive = 1; - } - - if(error == 302) { - keep_alive = 1; - } - - if(keep_alive) { - ws_addarg(&pwsc->response_headers,"Connection","keep-alive"); - ws_addarg(&pwsc->response_headers,"Content-Length","2"); - ws_emitheaders(pwsc); - ws_writefd(pwsc,"\r\n"); - WS_EXIT(); - return TRUE; - } - - ws_should_close(pwsc,TRUE); - ws_addarg(&pwsc->response_headers,"Connection","close"); - - ws_emitheaders(pwsc); - - ws_writefd(pwsc,"\r\n"); - ws_writefd(pwsc,"%d %s\r\n",error,description); - ws_writefd(pwsc,"\r\n

%s

\r\n",description); - ws_writefd(pwsc,"Error %d\r\n
\r\n",error); - ws_writefd(pwsc,"" PACKAGE ": %s\r\n
",VERSION); - - ws_get_err(pwsc,&err_code, &err_str); - if(E_WS_SUCCESS != err_code) - ws_writefd(pwsc,"Error: %s\r\n",strerror(errno)); - - ws_writefd(pwsc,"
\r\n\r\n"); - - WS_EXIT(); - return TRUE; -} - -/** - * default URI handler. This simply finds the file - * and serves it up - * - * @param pwsp webserver private struct - * @param pwsc connection to serve over - */ -void ws_defaulthandler(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc) { - char path[PATH_MAX]; - char resolved_path[PATH_MAX]; - IOHANDLE hfile; - uint64_t len; - char *urltemp; - int ret; - - WS_ENTER(); - - snprintf(path,PATH_MAX,"%s/%s",pwsp->wsconfig.web_root,pwsc->uri); - if(!realpath(path,resolved_path)) { - ws_set_err(pwsc,E_WS_NATIVE); - ws_dprintf(L_WS_WARN,"Exiting ws_defaulthandler: Cannot resolve %s\n",path); - ws_returnerror(pwsc,404,"Not found"); - ws_close(pwsc); - WS_EXIT(); - return; - } - - ws_dprintf(L_WS_DBG,"Thread %d: Preparing to serve %s\n", - pwsc->threadno, resolved_path); - - if(strncmp(resolved_path,pwsp->wsconfig.web_root, - strlen(pwsp->wsconfig.web_root))) { - ws_set_err(pwsc,E_WS_BADPATH); - ws_dprintf(L_WS_WARN,"Exiting ws_defaulthandler: Thread %d: " - "Requested file %s out of root\n", - pwsc->threadno,resolved_path); - ws_returnerror(pwsc,403,"Forbidden"); - ws_close(pwsc); - WS_EXIT(); - return; - } - - hfile = io_new(); - if(!hfile) { - ws_set_err(pwsc,E_WS_NATIVE); - ws_dprintf(L_WS_LOG,"Error creating file handle\n"); - ws_returnerror(pwsc,500,"Internal Server Error"); - ws_close(pwsc); - WS_EXIT(); - return; - } - - urltemp = io_urlencode(resolved_path); - if (!urltemp) - { - ws_set_err(pwsc,E_WS_NATIVE); /* FIXME: ws_set_errstr */ - ws_dprintf(L_WS_LOG,"Error opening %s: out of memory",resolved_path); - ws_dprintf(L_WS_WARN,"Exiting ws_defaulthandler: Thread %d: " - "Error opening %s: out of memory\n", - pwsc->threadno,resolved_path); - ws_returnerror(pwsc,404,"Not found"); - ws_close(pwsc); - - io_dispose(hfile); - WS_EXIT(); - return; - } - - ret = io_open(hfile, "file://%s", urltemp); /* default is O_RDONLY */ - free(urltemp); - if(!ret) { - ws_set_err(pwsc,E_WS_NATIVE); /* FIXME: ws_set_errstr */ - ws_dprintf(L_WS_LOG,"Error opening %s: %s",resolved_path, - io_errstr(hfile)); - ws_dprintf(L_WS_WARN,"Exiting ws_defaulthandler: Thread %d: " - "Error opening %s: %s\n", - pwsc->threadno,resolved_path,strerror(errno)); - ws_returnerror(pwsc,404,"Not found"); - ws_close(pwsc); - - io_dispose(hfile); - WS_EXIT(); - return; - } - - /* set the Content-Length response header */ - io_size(hfile,&len); - - ws_dprintf(L_WS_DBG,"Length of file is %lld\n",len); - ws_addarg(&pwsc->response_headers,"Content-Length","%lld",len); - - ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); - ws_emitheaders(pwsc); - - /* now throw out the file */ - ws_copyfile(pwsc,hfile,NULL); - - io_close(hfile); - io_dispose(hfile); - - WS_EXIT(); - return; -} - - -/** - * Check to see if a request header is a particular value. This is - * a thin wrapper of the interal funcion ws_testarg - * - * Example: - * - * if(ws_testrequestheader(pwsc,"Connection","keep-alive")) {} - * - * @param pwsc session to test headers of - * @param header request header to test - * @param value value to compare against - * @returns TRUE if request header is set to checked value - */ -int ws_testrequestheader(WS_CONNINFO *pwsc, char *header, char *value) { - return ws_testarg(&pwsc->request_headers,header,value); -} - -/** - * Check an arg for a particular value - * - * Example: - * - * pwsc->close=ws_queryarg(&pwsc->request_headers,"Connection","close"); - * - */ -int ws_testarg(ARGLIST *root, char *key, char *value) { - char *retval; - int result; - - WS_ENTER(); - ws_dprintf(L_WS_DBG,"Checking to see if %s matches %s\n",key,value); - - retval=ws_getarg(root,key); - if(!retval) { - ws_dprintf(L_WS_DBG,"Nope!\n"); - WS_EXIT(); - return FALSE; - } - - result=!strcasecmp(value,retval); - ws_dprintf(L_WS_DBG,"And it %s\n",result ? "DOES!" : "does NOT"); - WS_EXIT(); - return result; -} - -/** - * Find an argument in an argument list - * - * returns a pointer to the value if successful, - * NULL otherwise. - * - * This should be passed the pointer to the - * stub in the pwsc. - * - * Ex: ws_getarg(pwsc->request_headers,"Connection"); - * - * @param root root of linked ARGLIST list - * @param key key to find - * @returns pointer to value - */ -char *ws_getarg(ARGLIST *root, char *key) { - ARGLIST *pcurrent=root->next; - - WS_ENTER(); - while((pcurrent)&&(strcasecmp(pcurrent->key,key))) - pcurrent=pcurrent->next; - - if(pcurrent) { - WS_EXIT(); - return pcurrent->value; - } - - WS_EXIT(); - return NULL; -} - - -/** - * Add an argument to an arg list - * - * This will strdup the passed key and value. - * The arglist will then have to be freed. This should - * be done in ws_close, and handler functions will - * have to remember to ws_close them. - * - * FIXME: This uses a fixed-size buffer. Again, need to make - * an aprintf function like io_printf. - * - * @param root root of the ARGLIST list ot add key/value to - * @param key key to add - * @param fmt printf-style format to add - * @returns TRUE on success - */ -int ws_addarg(ARGLIST *root, char *key, char *fmt, ...) { - char *newkey; - char *newvalue; - ARGLIST *pnew; - ARGLIST *current; - va_list ap; - char value[MAX_LINEBUFFER]; - - WS_ENTER(); - - va_start(ap,fmt); - vsnprintf(value,sizeof(value),fmt,ap); - va_end(ap); - - newkey=strdup(key); - newvalue=strdup(value); - pnew=(ARGLIST*)malloc(sizeof(ARGLIST)); - - if((!pnew)||(!newkey)||(!newvalue)) { - WS_EXIT(); - return FALSE; - } - - pnew->key=newkey; - pnew->value=newvalue; - - /* first, see if the key exists... if it does, simply - * replace it rather than adding a duplicate key - */ - - current=root->next; - while(current) { - if(!strcasecmp(current->key,key)) { - /* got a match! */ - ws_dprintf(L_WS_DBG,"Updating %s from %s to %s\n", - key,current->value,value); - free(current->value); - current->value = newvalue; - free(newkey); - free(pnew); - WS_EXIT(); - return TRUE; - } - current=current->next; - } - - - pnew->next=root->next; - ws_dprintf(L_WS_DBG,"Added *%s=%s*\n",newkey,newvalue); - root->next=pnew; - - WS_EXIT(); - return TRUE; -} - -/** - * decode a urlencoded string - * - * the returned char will be malloced -- it must be - * freed by the caller - * - * @param string string to decode - * @param space_as_plus whether the client encodes ' ' as '+' - * @returns NULL on error, else the decoded URI - */ -char *ws_urldecode(char *string, int space_as_plus) { - char *pnew; - char *src,*dst; - int val=0; - - WS_ENTER(); - - pnew=(char*)malloc(strlen(string)+1); - if(!pnew) { - WS_EXIT(); - return NULL; - } - - src=string; - dst=pnew; - - while(*src) { - switch(*src) { - case '+': - if(space_as_plus) { - *dst++=' '; - } else { - *dst++=*src; - } - src++; - break; - case '%': - /* this is hideous */ - src++; - if(*src) { - if((*src <= '9') && (*src >='0')) - val=(*src - '0'); - else if((tolower(*src) <= 'f')&&(tolower(*src) >= 'a')) - val=10+(tolower(*src) - 'a'); - src++; - } - if(*src) { - val *= 16; - if((*src <= '9') && (*src >='0')) - val+=(*src - '0'); - else if((tolower(*src) <= 'f')&&(tolower(*src) >= 'a')) - val+=(10+(tolower(*src) - 'a')); - src++; - } - *dst++=val; - break; - default: - *dst++=*src++; - break; - } - } - - *dst='\0'; - WS_EXIT(); - return pnew; -} - - -/** - * Register a page and auth handler. - * - * @param ws pointer to private webserver struct - * @param stem the prefix of the handled web namespace ("/upnp", etc) - * @param handler page handler - * @param auth auth handler - * @param flags currently unused - * @param addheaders whether default headers should be added (date, etc) - * @returns TRUE on success, FALSE otherwise - */ -int ws_registerhandler(WSHANDLE ws, char *stem, - void(*handler)(WS_CONNINFO*), - int(*auth)(WS_CONNINFO *, char *, char *), - int flags, - int addheaders) { - WS_HANDLER *phandler; - WS_PRIVATE *pwsp = (WS_PRIVATE *)ws; - - WS_ENTER(); - phandler=(WS_HANDLER *)malloc(sizeof(WS_HANDLER)); - if(!phandler) - ws_dprintf(L_WS_FATAL,"Malloc error in ws_registerhandler\n"); - - phandler->stem=strdup(stem); - phandler->req_handler=handler; - phandler->auth_handler=auth; - phandler->addheaders=addheaders; - - ws_lock_unsafe(); - phandler->next=pwsp->handlers.next; - pwsp->handlers.next=phandler; - ws_unlock_unsafe(); - - WS_EXIT(); - return TRUE; -} - -/** - * Given a URI, determine the appropriate handler. - * - * If a handler is found, it returns TRUE, else FALSE - * - * @param pwsp pointer to internal webserver struct - * @param pwsc session (from which URI is gained) - * @param preq filled iwth request handler - * @param pauth filled with auth handler - * @param addheaders whether or not to add headers - * @returns TRUE on success, FALSE otherwise - */ -int ws_findhandler(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc, - void(**preq)(WS_CONNINFO*), - int(**pauth)(WS_CONNINFO *, char *, char *), - int *addheaders) { - WS_HANDLER *phandler; - - WS_ENTER(); - ws_lock_unsafe(); - - phandler = pwsp->handlers.next; - *preq=NULL; - - ws_dprintf(L_WS_DBG,"Thread %d: Preparing to find handler\n", - pwsc->threadno); - - while(phandler) { - ws_dprintf(L_WS_DBG,"Checking %s against handler for %s\n", - pwsc->uri, phandler->stem); - if(!strncasecmp(phandler->stem,pwsc->uri,strlen(phandler->stem))) { - /* that's a match */ - ws_dprintf(L_WS_DBG,"Thread %d: URI Match!\n",pwsc->threadno); - *preq=phandler->req_handler; - *pauth=phandler->auth_handler; - *addheaders=phandler->addheaders; - ws_unlock_unsafe(); - WS_EXIT(); - return 0; - } - phandler=phandler->next; - } - - ws_unlock_unsafe(); - ws_dprintf(L_WS_DBG,"Didn't find one!\n"); - WS_EXIT(); - return -1; -} - -/** - * Given a base64 encoded Authentication request header, - * decode it into the username and password. The memory for - * the decoding is allocated, but the username and password - * share the same allocated chunk of memory. The caller is - * responsible for freeing username when the username and - * password are no longer needed. - * - * @param header the base64 encoded source - * @param username username to be filled in - * @param password to be filled in - * @returns TRUE on success - */ -int ws_decodepassword(char *header, char **username, char **password) { - static char ws_xlat[256]; - static int ws_xlat_init=0; - int index; - int len; - int rack=0; - int pads=0; - unsigned char *decodebuffer; - unsigned char *pin, *pout; - char *type,*base64; - int lookup; - - *username=NULL; - *password=NULL; - - WS_ENTER(); - - ws_lock_unsafe(); - - if(!ws_xlat_init) { - ws_xlat_init=1; - - memset((char*)&ws_xlat,0xFF,sizeof(ws_xlat)); - for(index=0; index < 26; index++) { - ws_xlat['A' + index] = index; - ws_xlat['a' + index] = index + 26; - } - - for(index=0; index < 10; index++) { - ws_xlat['0' + index] = index + 52; - } - - ws_xlat['+'] = 62; - ws_xlat['/'] = 63; - } - - ws_unlock_unsafe(); - - /* xlat table is initialized */ - - // Trim leading spaces - while((*header) && (*header == ' ')) - header++; - - // Should be in the form "Basic " - type=header; - base64 = strchr(header,' '); - if(!base64) { - // invalid auth header - ws_dprintf(L_WS_DBG,"Bad authentication header: %s\n",header); - WS_EXIT(); - return FALSE; - } - - *base64 = '\0'; - base64++; - - decodebuffer=(unsigned char *)malloc(strlen(base64)); - if(!decodebuffer) { - WS_EXIT(); - return FALSE; - } - - ws_dprintf(L_WS_DBG,"Preparing to decode %s\n",base64); - - memset(decodebuffer,0,strlen(base64)); - len=0; - pout=decodebuffer; - pin=(unsigned char *)base64; - - /* this is more than a little sloppy */ - while(pin[rack]) { - if(pin[rack] != '=') { - lookup=ws_xlat[pin[rack]]; - if(lookup == 0xFF) { - ws_dprintf(L_WS_WARN,"Got garbage Authenticate header\n"); - free(decodebuffer); - *username = NULL; - *password = NULL; - return FALSE; - } - - /* valid character */ - switch(rack) { - case 0: - pout[0]=(lookup << 2); - break; - case 1: - pout[0] |= (lookup >> 4); - pout[1] = (lookup << 4); - break; - case 2: - pout[1] |= (lookup >> 2); - pout[2] = (lookup << 6); - break; - case 3: - pout[2] |= lookup; - break; - } - rack++; - } else { - /* padding char */ - pads++; - rack++; - } - - if(rack == 4) { - pin += 4; - pout += 3; - - len += (3-pads); - rack=0; - } - } - - /* we now have the decoded string */ - ws_dprintf(L_WS_DBG,"Decoded %s\n",decodebuffer); - - *username = (char*)decodebuffer; - *password = *username; - - strsep(password,":"); - - ws_dprintf(L_WS_DBG,"Decoded user=%s, pw=%s\n",*username,*password); - WS_EXIT(); - return TRUE; -} - -/** - * Simple wrapper around the CONNINFO response headers - * - * FIXME: See comments on other printf style functions - * - * @param pwsc session to add a response header to - * @param header header to add - * @param fmt format of value to add - * @return TRUE on success - */ -int ws_addresponseheader(WS_CONNINFO *pwsc, char *header, char *fmt, ...) { - va_list ap; - char value[MAX_LINEBUFFER]; - - WS_ENTER(); - - va_start(ap,fmt); - vsnprintf(value,sizeof(value),fmt,ap); - va_end(ap); - - WS_EXIT(); /* FIXME: Put this after the ws_addarg */ - return ws_addarg(&pwsc->response_headers,header,value); -} - -/** - * Fetch a get/post variable (case insensitive) - * - * @param pwsc session to get variable for - * @param var get/post variable to retrieve - * @returns pointer to value of variable, or NULL - */ -char *ws_getvar(WS_CONNINFO *pwsc, char *var) { - return ws_getarg(&(pwsc->request_vars),var); -} - -/** - * Fetch a http request header (case insenstive) - * - * @param pwsc session to get http request header for - * @param header header value to retrieve - * @returns pointer to value of header variable, or NULL - */ -char *ws_getrequestheader(WS_CONNINFO *pwsc, char *header) { - return ws_getarg(&pwsc->request_headers,header); -} - -/** - * get the local storage pointer. - * - * @param pwsc connection to get local storage for - */ -void *ws_get_local_storage(WS_CONNINFO *pwsc) { - return pwsc->local_storage; -} - - -/** - * lock the local storage pointer. This is sort of wrong, as - * the all operations manipulating local storage are locked, - * not just the one you are working on. - * - * @param pwsc connection to lock storage pointer for - */ -void ws_lock_local_storage(WS_CONNINFO *pwsc) { - WS_PRIVATE *pwsp; - - pwsp = (WS_PRIVATE *)pwsc->pwsp; - ws_lock_connlist(pwsp); -} - - -/** - * unlock the local storage pointer. Again, this suffers (?) - * from the same lack of fine-grained semaphores described above - * - * @param pwsc connection to unlock storage for - */ -void ws_unlock_local_storage(WS_CONNINFO *pwsc) { - WS_PRIVATE *pwsp; - - pwsp = (WS_PRIVATE *)pwsc->pwsp; - ws_unlock_connlist(pwsp); -} - -/** - * set the local storage pointer (and callback). If there is no - * callback, the storage pointer will be free'd when the session is - * terminated. If the callback is set, then the application can - * choose a better session deallocation routine. - * - * @param pwsc session to set ptr and callback for - * @param ptr new local storage pointer - * @param callback callback function to call (passes the ptr) - */ -void ws_set_local_storage(WS_CONNINFO *pwsc, void *ptr, void (*callback)(void *)) { - if(!pwsc) - return; - - if(pwsc->local_storage) { - ws_dprintf(L_WS_FATAL,"ls already allocated"); - } - - pwsc->storage_callback = callback; - pwsc->local_storage = ptr; -} - -/** - * Walk through the connection list and enumerate all the items. - * vpp should be a pointer to a WSTHREADENUM type. This is opaque, - * and used by the enumerator functions. The same WSTHREADENUM must - * be passed to the ws_thread_enum_ functions. Also, note that as soon - * as the enum is started, the connlist is locked, so the enumeration - * must be completed, or new connections will be blocked. - * - * FIXME: should this be a session connection? - * - * @param wsh server handle - * @param vpp opaque WSTHREADENUM structure - * @returns first WS_CONNINFO, or NULL, if no connections - */ -WS_CONNINFO *ws_thread_enum_first(WSHANDLE wsh, WSTHREADENUM *vpp) { - WS_PRIVATE *pwsp = (WS_PRIVATE *)wsh; - WS_CONNINFO *pwsc = NULL; - WS_CONNLIST *pconlist; - - ws_lock_connlist(pwsp); - - pconlist = pwsp->connlist.next; - *vpp = (WSTHREADENUM)pconlist; - if(pconlist) { - pwsc = pconlist->pwsc; - } else { - ws_unlock_connlist(pwsp); - } - - return pwsc; -} - -/** - * continue enumerating the connection list. This should only - * be called if ws_thread_enum_first returns a non-null value. - * again, this must be called repeatedly until a NULL result is - * returned, or the connection list will remain locked - * - * @param wsh web server handle - * @param vpp opaque WSTHREADENUM structure - * @returns next connection handle, or NULL - */ -WS_CONNINFO *ws_thread_enum_next(WSHANDLE wsh, WSTHREADENUM *vpp) { - WS_PRIVATE *pwsp = (WS_PRIVATE *)wsh; - WS_CONNINFO *pwsc = NULL; - WS_CONNLIST *pconlist; - - pconlist = (WS_CONNLIST*)*vpp; - if((!pconlist) || (!pconlist->next)) { - ws_unlock_connlist(pwsp); - } else { - pconlist=pconlist->next; - *vpp = (WSTHREADENUM)pconlist; - pwsc = pconlist->pwsc; - } - - return pwsc; -} - -/** - * Enumerate a key list (could be either request headers or get/post vars). - * To start the enumeration, pass NULL to last. - * - * @param pwsc session to enumerate vars for - * @param key filled in with next key - * @param value filled in with next value - * @param last NULL to start the enumeration, or return value from last call - * @returns value o be passed into last for next round. - */ -void *ws_enum_var(WS_CONNINFO *pwsc, char **key, char **value, void *last) { - ARGLIST *plist = (ARGLIST *)last; - - if(!plist) { - plist = pwsc->request_vars.next; - } else { - plist = plist->next; - } - - if(plist) { - if(key) *key = plist->key; - if(value) *value = plist->value; - } - - return plist; -} - -/** - * copy a file (given a fd) to the output socket - * - * FIXME: Move this to something like io_readwrite - * - * @param pwsc connection to stream output to - * @param fromfd fd to read and strem from - * @returns number of bytes copied, or -1 FIXME: pass copy bytes, return T/F - */ -int ws_copyfile(WS_CONNINFO *pwsc, IOHANDLE hfile, uint64_t *bytes_copied) { - int retval = FALSE; - unsigned char buf[BLKSIZE]; - - uint64_t total_bytes = 0; - uint32_t bytes_read = 0; - uint32_t bytes_written = 0; - - ASSERT(pwsc); - if(!pwsc) - return -1; /* error handling! */ - - bytes_read = BLKSIZE; - while(io_read(hfile,buf,&bytes_read) && bytes_read) { - bytes_written = bytes_read; - if(!io_write(pwsc->hclient,buf,&bytes_written)) { - ws_dprintf(L_WS_LOG,"Write error: %s\n",io_errstr(pwsc->hclient)); - - if (bytes_copied) - *bytes_copied = total_bytes; - - return FALSE; - } - - if(bytes_written != bytes_read) { - ws_dprintf(L_WS_LOG,"Internal error in ws_copyfile\n"); - break; - } - - total_bytes += bytes_read; - } - - if(!bytes_read) { - retval = TRUE; - } else { - ws_dprintf(L_WS_LOG,"Read error %s\n",io_errstr(hfile)); - } - - if(bytes_copied) - *bytes_copied = total_bytes; - - return retval; -} - -/** - * return the URI requested - * - * @param pwsc connection handle - * @returns the URI requested - */ -char *ws_uri(WS_CONNINFO *pwsc) { - return pwsc->uri; -} - -/** - * mark this thread as one to be closed - * - * @param pwsc connection handle to close - * @param should_close whether the connection should be closed at finish - */ -void ws_should_close(WS_CONNINFO *pwsc, int should_close) { - ASSERT(pwsc); - - pwsc->close = should_close; -} - -/** - * retrive the thread number, internal id - * - * @param pwsc connection to get thread number for - * @returns thread number (int) - */ -extern int ws_threadno(WS_CONNINFO *pwsc) { - ASSERT(pwsc); - - if(pwsc) - return pwsc->threadno; - return 0; -} - -/** - * return the hostname of the connected endpoint - * - * @param pwsc connection to get endpoint for - */ -extern char *ws_hostname(WS_CONNINFO *pwsc) { - ASSERT(pwsc); - ASSERT(pwsc->hostname); - - if((pwsc) && (pwsc->hostname)) { - return pwsc->hostname; - } - - return NULL; -} - -/** - * set the local error for the selected socket session. This looks - * up and populates the error message at the time of failure, presuming - * that the application will subsequently just print the error message - * anyway. - * - * @param pwsc session to set error for - * @param error code to set - * @return TRUE on success - */ -int ws_set_err(WS_CONNINFO *pwsc, int ws_error) { - - ASSERT(pwsc); - - if(!pwsc) - return FALSE; - - if(pwsc->err_msg) - free(pwsc->err_msg); - - pwsc->err_code = ws_error; - if(E_WS_SUCCESS == ws_error) - return TRUE; - - ws_should_close(pwsc,TRUE); /* close the session on error */ - - if(E_WS_NATIVE == ws_error) { - pwsc->err_native = errno; - pwsc->err_msg = strdup(strerror(pwsc->err_native)); - } - - return TRUE; -} - -/** - * get the local error and error code for a selected socket session. - * Note that the error message is a pointer to an internal string. - * The error isn't valid of other socket operations take place. The - * error should be dup'ed or used immediately. - * - * Sample: - * - * char *errstr; - * int errcode; - * - * if(!ws_writebinary(pwsc,"foo",3)) { - * ws_get_err(pwsc,&errcode, &errstr); - * fprintf(stderr,"Error: %s",errstr); - * } - * @param pwsc session to get error of - * @param errcode to be filled with error number - * @param err_msg to be filled with non-freeable pointer to error msg - */ -int ws_get_err(WS_CONNINFO *pwsc, int *errcode, char **err_msg) { - ASSERT(pwsc); - - if(!pwsc) - return FALSE; - - if(errcode) { - *errcode = pwsc->err_code; - } - - if(err_msg) { - if(E_WS_NATIVE != pwsc->err_code) { - *err_msg = ws_errors[pwsc->err_code]; - } else { - *err_msg = pwsc->err_msg; - } - } - - return TRUE; -} - diff --git a/src/webserver.h b/src/webserver.h deleted file mode 100644 index d9e9c70f..00000000 --- a/src/webserver.h +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Webserver library - * - * Copyright (C) 2003 Ron Pedde (ron@pedde.com) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef _WEBSERVER_H_ -#define _WEBSERVER_H_ - -#include "io.h" - -/* - * Defines - */ - -#define RT_GET 0 -#define RT_POST 1 - -#define E_WS_SUCCESS 0 -#define E_WS_NATIVE 1 /**< A native platform error */ -#define E_WS_MEMORY 2 /**< Malloc error */ -#define E_WS_PTHREADS 3 /**< Pthreads error */ -#define E_WS_EXHAUSTED 4 /**< ports exhausted!? */ -#define E_WS_LISTEN 5 /**< can't listen */ -#define E_WS_CONTENTLEN 6 /**< Invalid content length in POST */ -#define E_WS_READ 7 /**< read error on socket */ -#define E_WS_GETVARS 8 /**< could not parse get vars */ -#define E_WS_TIMEOUT 9 /**< timeout listening on socket */ -#define E_WS_REQTYPE 10 /**< invalid request type (POST, GET, etc) */ -#define E_WS_BADPATH 11 /**< requested path out of root */ - -#define L_WS_SPAM 10 /**< Logorrhea! */ -#define L_WS_DBG 9 /**< Way too verbose */ -#define L_WS_INF 5 /**< Good info, not too much spam */ -#define L_WS_WARN 2 /**< Goes in text log, but not syslog */ -#define L_WS_LOG 1 /**< Something that should go in syslog */ -#define L_WS_FATAL 0 /**< Log and force an exit */ - -/* - * Typedefs - */ -typedef void* WSHANDLE; -typedef void* WSTHREADENUM; - -typedef struct tag_wsconfig { - char *web_root; - char *id; - unsigned short port; -} WSCONFIG; - -typedef struct tag_arglist { - char *key; - char *value; - struct tag_arglist *next; -} ARGLIST; - -typedef struct tag_ws_conninfo { - int err_code; - int err_native; - char *err_msg; - WSHANDLE pwsp; - int threadno; - int error; - IOHANDLE hclient; - int request_type; - char *uri; - char *hostname; - int close; - int secure; - void *secure_storage; - void *local_storage; - void (*storage_callback)(void*); - ARGLIST request_headers; - ARGLIST response_headers; - ARGLIST request_vars; -} WS_CONNINFO; - -/* - * Externs - */ - -#define WS_REQ_HANDLER void (*)(WS_CONNINFO *) -#define WS_AUTH_HANDLER int (*)(WS_CONNINFO*, char *, char *) - -/** Server functions */ -extern WSHANDLE ws_init(WSCONFIG *config); -extern int ws_start(WSHANDLE ws); -extern int ws_stop(WSHANDLE ws); -extern int ws_registerhandler(WSHANDLE ws, char *stem, - void(*handler)(WS_CONNINFO*), - int(*auth)(WS_CONNINFO*, char *, char *), - int flags, - int addheaders); -extern int ws_server_errcode(WSHANDLE ws); - - - -/** Local storage functions */ -extern void ws_lock_local_storage(WS_CONNINFO *pwsc); -extern void ws_unlock_local_storage(WS_CONNINFO *pwsc); -extern void *ws_get_local_storage(WS_CONNINFO *pwsc); -extern void ws_set_local_storage(WS_CONNINFO *pwsc, void *ptr, void (*callback)(void *)); - -extern WS_CONNINFO *ws_thread_enum_first(WSHANDLE, WSTHREADENUM *); -extern WS_CONNINFO *ws_thread_enum_next(WSHANDLE, WSTHREADENUM *); - -/* for handlers */ -extern void ws_close(WS_CONNINFO *pwsc); -extern int ws_returnerror(WS_CONNINFO *pwsc, int error, char *description); -extern int ws_addresponseheader(WS_CONNINFO *pwsc, char *header, char *fmt, ...); -extern int ws_writefd(WS_CONNINFO *pwsc, char *fmt, ...); -extern int ws_writebinary(WS_CONNINFO *pwsc, char *data, int len); -extern char *ws_getvar(WS_CONNINFO *pwsc, char *var); -extern char *ws_getrequestheader(WS_CONNINFO *pwsc, char *header); -extern int ws_testrequestheader(WS_CONNINFO *pwsc, char *header, char *value); -extern void ws_emitheaders(WS_CONNINFO *pwsc); -extern char *ws_uri(WS_CONNINFO *pwsc); -extern void *ws_enum_var(WS_CONNINFO *pwsc, char **key, char **value, void *last); -extern int ws_copyfile(WS_CONNINFO *pwsc, IOHANDLE hfile, uint64_t *bytes_copied); -extern void ws_should_close(WS_CONNINFO *pwsc, int should_close); -extern int ws_threadno(WS_CONNINFO *pwsc); -extern char *ws_hostname(WS_CONNINFO *pwsc); - -extern void ws_set_errhandler(void(*err_handler)(int, char*)); -extern int ws_set_err(WS_CONNINFO *pwsc, int ws_error); -extern int ws_get_err(WS_CONNINFO *pwsc, int *errcode, char **err_msg); - -#endif /* _WEBSERVER_H_ */ diff --git a/src/wsprivate.h b/src/wsprivate.h deleted file mode 100644 index 8405eddc..00000000 --- a/src/wsprivate.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Abstract os interface for non-unix platforms - * - * Copyright (C) 2006 Ron Pedde (rpedde@users.sourceforge.net) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - - -#ifndef _WSPRIVATE_H_ -#define _WSPRIVATE_H_ - -extern int ws_socket_write(WS_CONNINFO *pwsc, unsigned char *buffer, int len); -extern int ws_socket_read(WS_CONNINFO *pwsc, unsigned char *buffer, int len); -extern void ws_socket_shutdown(WS_CONNINFO *pwsc); - -#endif /* _WSPRIVATE_H_ */ diff --git a/src/xml-rpc.c b/src/xml-rpc.c deleted file mode 100644 index 8ac89301..00000000 --- a/src/xml-rpc.c +++ /dev/null @@ -1,657 +0,0 @@ -/* - * This really isn't xmlrpc. It's xmlrpc-ish. Emphasis on -ish. - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#ifdef HAVE_DIRENT_H -# include -#endif -#include -#include -#ifdef HAVE_STDINT_H -#include -#endif -#include -#include -#include -#include -#include -#include -#ifdef HAVE_UNISTD_H -# include -#endif - -#include -#include - -#include "daapd.h" - -#include "configfile.h" -#include "conf.h" -#include "db-generic.h" -#include "err.h" -#include "webserver.h" -#include "xml-rpc.h" - -/* typedefs */ -typedef struct tag_xmlstack { - char *tag; - struct tag_xmlstack *next; -} XMLSTACK; - -struct tag_xmlstruct { - WS_CONNINFO *pwsc; - int stack_level; - XMLSTACK stack; -}; - -/* Forwards */ -void xml_get_stats(WS_CONNINFO *pwsc); -void xml_set_config(WS_CONNINFO *pwsc); -void xml_browse_path(WS_CONNINFO *pwsc); -void xml_rescan(WS_CONNINFO *pwsc); -void xml_return_error(WS_CONNINFO *pwsc, int err, char *errstr); -char *xml_entity_encode(char *original); - -void xml_return_error(WS_CONNINFO *pwsc, int err, char *errstr) { - XMLSTRUCT *pxml; - - pxml=xml_init(pwsc,TRUE); - xml_push(pxml,"results"); - - xml_output(pxml,"status","%d",err); - xml_output(pxml,"statusstring","%s",errstr); - - xml_pop(pxml); /* results */ - xml_deinit(pxml); - return; -} - - -/** - * create an xml response structure, a helper struct for - * building xml responses. - * - * @param pwsc the pwsc we are emitting to - * @param emit_header whether or not to throw out html headers and xml header - * @returns XMLSTRUCT on success, or NULL if failure - */ -XMLSTRUCT *xml_init(WS_CONNINFO *pwsc, int emit_header) { - XMLSTRUCT *pxml; - - pxml=(XMLSTRUCT*)malloc(sizeof(XMLSTRUCT)); - if(!pxml) { - DPRINTF(E_FATAL,L_XML,"Malloc error\n"); - } - - memset(pxml,0,sizeof(XMLSTRUCT)); - - pxml->pwsc = pwsc; - - /* the world would be a wonderful place without ie */ - ws_addresponseheader(pwsc,"Cache-Control","no-cache"); - ws_addresponseheader(pwsc,"Expires","-1"); - - if(emit_header) { - ws_addresponseheader(pwsc,"Content-Type","text/xml; charset=utf-8"); - ws_writefd(pwsc,"HTTP/1.0 200 OK\r\n"); - ws_emitheaders(pwsc); - - ws_writefd(pwsc,""); - } - - return pxml; -} - -/** - * bulk set config - */ -void xml_update_config(WS_CONNINFO *pwsc) { - char *arg, *value, *duparg; - char *ptmp; - char *badparms=NULL; - void *handle; - int has_error=0; - int badparms_len=0; - int err; - - handle = NULL; - - - while((handle=ws_enum_var(pwsc,&arg,&value,handle)) != NULL) { - /* arg will be section:value */ - duparg = strdup(arg); - ptmp = strchr(duparg,':'); - if(ptmp) { - *ptmp++ = '\0'; - /* this is stupidly inefficient */ - - DPRINTF(E_DBG,L_XML,"Setting %s/%s to %s\n",duparg,ptmp,value); - err = conf_set_string(duparg,ptmp,value,TRUE); - if(err != CONF_E_SUCCESS) { - DPRINTF(E_DBG,L_XML,"Error setting %s/%s\n",duparg,ptmp); - has_error = TRUE; - if(!badparms) { - badparms_len = (int)strlen(arg) + 1; - badparms = (char*)malloc(badparms_len); - if(!badparms) { - DPRINTF(E_FATAL,L_XML,"xml_update_config: malloc\n"); - } - strcpy(badparms,arg); - } else { - badparms_len += (int)strlen(arg) + 1; - badparms = (char*)realloc(badparms,badparms_len); - if(!badparms) { - DPRINTF(E_FATAL,L_XML,"xml_update_config: malloc\n"); - } - strcat(badparms,","); - strcat(badparms,arg); - } - } - } - free(duparg); - } - - if(has_error) { - DPRINTF(E_INF,L_XML,"Bad parms; %s\n",badparms); - xml_return_error(pwsc,500,badparms); - free(badparms); - return; - } - - handle = NULL; - - /* now set! */ - while((handle=ws_enum_var(pwsc,&arg,&value,handle)) != NULL) { - /* arg will be section:value */ - duparg = strdup(arg); - ptmp = strchr(duparg,':'); - if(ptmp) { - *ptmp++ = '\0'; - /* this is stupidly inefficient */ - - DPRINTF(E_DBG,L_XML,"Setting %s/%s to %s\n",duparg,ptmp,value); - err = conf_set_string(duparg,ptmp,value,FALSE); - if(err != CONF_E_SUCCESS) { - /* shouldn't happen */ - DPRINTF(E_DBG,L_XML,"Error setting %s/%s\n",duparg,ptmp); - xml_return_error(pwsc,500,arg); - return; - } - } - free(duparg); - } - - xml_return_error(pwsc,200,"Success"); -} - -/** - * rescan the database - */ -void xml_rescan(WS_CONNINFO *pwsc) { - if(ws_getvar(pwsc,"full")) { - config.full_reload=1; - } - - config.reload=1; - - xml_return_error(pwsc,200,"Success"); -} - -/** - * post settings back to the config file - * - * @param pwsc connection do dump results back to - */ -void xml_set_config(WS_CONNINFO *pwsc) { - char *section; - char *key; - char *value; - int verify_only; - int err; - - section = ws_getvar(pwsc,"section"); - key = ws_getvar(pwsc,"key"); - value = ws_getvar(pwsc,"value"); - - verify_only=0; - if(ws_getvar(pwsc,"verify_only")) { - verify_only = 1; - } - - if((!section) || (!key) || (!value)) { - xml_return_error(pwsc,500,"Missing section, key, or value"); - return; - } - - if((err=conf_set_string(section,key,value,verify_only)!=CONF_E_SUCCESS)) { - /* should return text error from conf_ */ - switch(err) { - case CONF_E_BADELEMENT: - xml_return_error(pwsc,500,"Unknown section/key pair"); - break; - case CONF_E_PATHEXPECTED: - xml_return_error(pwsc,500,"Expecting valid path"); - break; - case CONF_E_INTEXPECTED: - xml_return_error(pwsc,500,"Expecting integer value"); - break; - case CONF_E_NOTWRITABLE: - xml_return_error(pwsc,500,"Config file not writable"); - break; - default: - xml_return_error(pwsc,500,"conf_set_string: error"); - } - return; - } - - xml_return_error(pwsc,200,"Success"); - return; -} - -/** - * push a new term on the stack - * - * @param pxml xml struct obtained from xml_init - * @param term next xlm section to start - */ -void xml_push(XMLSTRUCT *pxml, char *term) { - XMLSTACK *pstack; - - pstack = (XMLSTACK *)malloc(sizeof(XMLSTACK)); - pstack->next=pxml->stack.next; - pstack->tag=strdup(term); - pxml->stack.next=pstack; - - pxml->stack_level++; - - ws_writefd(pxml->pwsc,"<%s>",term); -} - -/** - * end an xml section - * - * @param pxml xml struct we are working with - */ -void xml_pop(XMLSTRUCT *pxml) { - XMLSTACK *pstack; - - pstack=pxml->stack.next; - if(!pstack) { - DPRINTF(E_LOG,L_XML,"xml_pop: tried to pop an empty stack\n"); - return; - } - - pxml->stack.next = pstack->next; - - ws_writefd(pxml->pwsc,"",pstack->tag); - free(pstack->tag); - free(pstack); - - pxml->stack_level--; -} - -/* FIXME: Fixed at 256? And can't I get an expandable sprintf/cat? */ - -/** - * output a string - */ -void xml_output(XMLSTRUCT *pxml, char *section, char *fmt, ...) { - va_list ap; - char buf[256]; - char *output; - - va_start(ap, fmt); - vsnprintf(buf, sizeof(buf), fmt, ap); - va_end(ap); - - output = xml_entity_encode(buf); - if(section) { - xml_push(pxml,section); - } - ws_writefd(pxml->pwsc,"%s",output); - free(output); - if(section) { - xml_pop(pxml); - } -} - -/** - * clean up an xml struct - * - * @param pxml xml struct to clean up - */ -void xml_deinit(XMLSTRUCT *pxml) { - XMLSTACK *pstack; - - if(pxml->stack.next) { - DPRINTF(E_LOG,L_XML,"xml_deinit: entries still on stack (%s)\n", - pxml->stack.next->tag); - } - - while((pstack=pxml->stack.next)) { - pxml->stack.next=pstack->next; - free(pstack->tag); - free(pstack); - } - free(pxml); -} - -/** - * main entrypoint for the xmlrpc functions. - * - * @arg pwsc Pointer to the web request structure - */ -void xml_handle(WS_CONNINFO *pwsc) { - char *method; - - if((method=ws_getvar(pwsc,"method")) == NULL) { - ws_returnerror(pwsc,500,"no method specified"); - return; - } - - if(strcasecmp(method,"stats") == 0) { - xml_get_stats(pwsc); - return; - } - - if(strcasecmp(method,"config") == 0) { - conf_xml_dump(pwsc); - return; - } - - if(strcasecmp(method,"setconfig") == 0) { - xml_set_config(pwsc); - return; - } - - if(strcasecmp(method,"updateconfig") == 0) { - xml_update_config(pwsc); - return; - } - -#if(0) /* Turn this off until it's done in the web admin */ - if(strcasecmp(method,"browse_path") == 0) { - xml_browse_path(pwsc); - return; - } -#endif - - if(strcasecmp(method,"rescan") == 0) { - xml_rescan(pwsc); - return; - } - - if(strcasecmp(method,"shutdown") == 0) { - config.stop=1; - xml_return_error(pwsc,200,"Success"); - return; - } - - xml_return_error(pwsc,500,"Invalid method"); - return; -} - -/** - * Given a base directory, walk through the directory and enumerate - * all folders underneath it. - * - * @param pwsc connection over which to dump the result - */ -void xml_browse_path(WS_CONNINFO *pwsc) { - XMLSTRUCT *pxml; - DIR *pd; - char *base_path; - char de[sizeof(struct dirent) + MAXNAMLEN + 1]; - struct dirent *pde; - int readable, writable; - char full_path[PATH_MAX+1]; - char resolved_path[PATH_MAX]; - int err; - int ignore_dotfiles=1; - int ignore_files=1; - struct stat sb; - - base_path = ws_getvar(pwsc, "path"); - if(!base_path) - base_path = PATHSEP_STR; - - if(ws_getvar(pwsc,"show_dotfiles")) - ignore_dotfiles = 0; - - if(ws_getvar(pwsc,"show_files")) - ignore_files = 0; - - pd = opendir(base_path); - if(!pd) { - xml_return_error(pwsc,500,"Bad path"); - return; - } - - pxml=xml_init(pwsc,1); - xml_push(pxml,"results"); - - /* get rid of trailing slash */ - while(1) { - pde = (struct dirent *)&de; - err = readdir_r(pd,(struct dirent *)de, &pde); - - if(err == -1) { - DPRINTF(E_LOG,L_SCAN,"Error in readdir_r: %s\n", - strerror(errno)); - break; - } - - if(!pde) - break; - - if((!strcmp(pde->d_name,".")) || (!strcmp(pde->d_name,".."))) - continue; - - - snprintf(full_path,PATH_MAX,"%s%c%s",base_path,PATHSEP,pde->d_name); - realpath(full_path,resolved_path); - - if(stat(resolved_path, &sb)) { - DPRINTF(E_INF,L_XML,"Error statting %s: %s\n", - resolved_path,strerror(errno)); - continue; - } - - if((sb.st_mode & S_IFREG) && (ignore_files)) - continue; - - /* skip symlinks and devices and whatnot */ - if((!(sb.st_mode & S_IFDIR)) && - (!(sb.st_mode & S_IFREG))) - continue; - - if((ignore_dotfiles) && (pde->d_name) && (pde->d_name[0] == '.')) - continue; - - readable = !access(resolved_path,R_OK); - writable = !access(resolved_path,W_OK); - - if(sb.st_mode & S_IFDIR) { - xml_push(pxml,"directory"); - } else if((sb.st_mode & S_IFLNK) == S_IFLNK) { - xml_push(pxml,"symlink"); - } else { - xml_push(pxml,"file"); - } - xml_output(pxml,"name",pde->d_name); - xml_output(pxml,"full_path",resolved_path); - xml_output(pxml,"readable","%d",readable); - xml_output(pxml,"writable","%d",writable); - - xml_pop(pxml); /* directory */ - - } - - - xml_pop(pxml); - xml_deinit(pxml); - - closedir(pd); -} - -/** - * return xml file of all playlists - */ -void xml_get_stats(WS_CONNINFO *pwsc) { - int r_secs, r_days, r_hours, r_mins; - char buf[80]; - WS_CONNINFO *pci; - SCAN_STATUS *pss; - WSTHREADENUM wste; - int count; - XMLSTRUCT *pxml; - - pxml=xml_init(pwsc,1); - xml_push(pxml,"status"); - - xml_push(pxml,"service_status"); - - xml_push(pxml,"service"); - xml_output(pxml,"name","DAAP Server"); - xml_output(pxml,"status",config.stop ? "Stopping" : "Running"); - xml_pop(pxml); /* service */ - - xml_push(pxml,"service"); - xml_output(pxml,"name","File Scanner"); - xml_output(pxml,"status",config.reload ? "Running" : "Idle"); - xml_pop(pxml); /* service */ - - xml_pop(pxml); /* service_status */ - - xml_push(pxml,"thread_status"); - - pci = ws_thread_enum_first(config.server,&wste); - while(pci) { - pss = ws_get_local_storage(pci); - if(pss) { - xml_push(pxml,"thread"); - xml_output(pxml,"id","%d",pss->thread); - xml_output(pxml,"sourceip","%s",pss->host); - xml_output(pxml,"action","%s",pss->what); - xml_pop(pxml); /* thread */ - } - pci=ws_thread_enum_next(config.server,&wste); - } - - xml_pop(pxml); /* thread_status */ - - xml_push(pxml,"statistics"); - - r_secs=(int)(time(NULL)-config.stats.start_time); - - r_days=r_secs/(3600 * 24); - r_secs -= ((3600 * 24) * r_days); - - r_hours=r_secs/3600; - r_secs -= (3600 * r_hours); - - r_mins=r_secs/60; - r_secs -= 60 * r_mins; - - memset(buf,0x0,sizeof(buf)); - if(r_days) - sprintf((char*)&buf[strlen(buf)],"%d day%s, ", r_days, - r_days == 1 ? "" : "s"); - - if(r_days || r_hours) - sprintf((char*)&buf[strlen(buf)],"%d hour%s, ", r_hours, - r_hours == 1 ? "" : "s"); - - if(r_days || r_hours || r_mins) - sprintf((char*)&buf[strlen(buf)],"%d minute%s, ", r_mins, - r_mins == 1 ? "" : "s"); - - sprintf((char*)&buf[strlen(buf)],"%d second%s ", r_secs, - r_secs == 1 ? "" : "s"); - - xml_push(pxml,"stat"); - xml_output(pxml,"name","Uptime"); - xml_output(pxml,"value","%s",buf); - xml_pop(pxml); /* stat */ - - xml_push(pxml,"stat"); - xml_output(pxml,"name","Songs"); - db_get_song_count(NULL,&count); - xml_output(pxml,"value","%d",count); - xml_pop(pxml); /* stat */ - - xml_push(pxml,"stat"); - xml_output(pxml,"name","Songs Served"); - xml_output(pxml,"value","%d",config.stats.songs_served); - xml_pop(pxml); /* stat */ - - xml_pop(pxml); /* statistics */ - - - xml_push(pxml,"misc"); - xml_output(pxml,"writable_config","%d",conf_iswritable()); - xml_output(pxml,"config_path","%s",conf_get_filename()); - xml_output(pxml,"version","%s",VERSION); - xml_pop(pxml); /* misc */ - - xml_pop(pxml); /* status */ - - xml_deinit(pxml); - return; -} - -/** - * xml entity encoding, stupid style - */ -char *xml_entity_encode(char *original) { - char *new; - char *s, *d; - int destsize; - - destsize = 6*(int)strlen(original)+1; - new=(char *)malloc(destsize); - if(!new) return NULL; - - memset(new,0x00,destsize); - - s=original; - d=new; - - while(*s) { - switch(*s) { - case '>': - strcat(d,">"); - d += 4; - s++; - break; - case '<': - strcat(d,"<"); - d += 4; - s++; - break; - case '"': - strcat(d,"""); - d += 6; - s++; - break; - case '\'': - strcat(d,"'"); - d += 6; - s++; - break; - case '&': - strcat(d,"&"); - d += 5; - s++; - break; - default: - *d++ = *s++; - } - } - - return new; -} diff --git a/src/xml-rpc.h b/src/xml-rpc.h deleted file mode 100644 index 937d2cf7..00000000 --- a/src/xml-rpc.h +++ /dev/null @@ -1,19 +0,0 @@ - -#ifndef _XMLRPC_H_ -#define _XMLRPC_H_ - -#include "webserver.h" - -extern void xml_handle(WS_CONNINFO *pwsc); - -struct tag_xmlstruct; -typedef struct tag_xmlstruct XMLSTRUCT; - -extern XMLSTRUCT *xml_init(WS_CONNINFO *pwsc, int emit_header); -extern void xml_push(XMLSTRUCT *pxml, char *term); -extern void xml_pop(XMLSTRUCT *pxml); -extern void xml_output(XMLSTRUCT *pxml, char *section, char *fmt, ...); -extern void xml_deinit(XMLSTRUCT *pxml); - - -#endif /* _XMLRPC_H_ */