From 90dc66110a031bf362957baa9a676f1507d07cc7 Mon Sep 17 00:00:00 2001 From: Ron Pedde Date: Sun, 12 Mar 2006 11:30:58 +0000 Subject: [PATCH] Complete unifying smart playlists and query/filters --- src/db-generic.c | 3 ++- src/db-generic.h | 2 ++ src/db-sql.c | 38 ++++++++++++++++++++++++++++++++--- src/db-sql.h | 1 + src/dispatch.c | 46 ++++++++++++++++++++++++++++++------------ src/mp3-scanner.c | 4 +++- src/parser-driver.c | 33 ++++++++++++++++++++++++++++-- src/parser.mk | 5 +++-- src/smart-parser.c | 49 ++++++++++++++++++++++++++++++++++++++++----- src/smart-parser.h | 2 +- 10 files changed, 155 insertions(+), 28 deletions(-) diff --git a/src/db-generic.c b/src/db-generic.c index 6ed7e518..978acb48 100644 --- a/src/db-generic.c +++ b/src/db-generic.c @@ -341,7 +341,8 @@ char *db_error_list[] = { "Invalid song id: %d", "Parse error: %s", "No backend database support for type: %s", - "Could not initialize thread pool" + "Could not initialize thread pool", + "Passed buffer too small for result" }; /* Globals */ diff --git a/src/db-generic.h b/src/db-generic.h index 217bff3f..a5bf644c 100644 --- a/src/db-generic.h +++ b/src/db-generic.h @@ -209,6 +209,8 @@ extern void db_dispose_playlist(M3UFILE *pm3u); #define DB_E_PARSE 0x08 /**< could not parse result */ #define DB_E_BADPROVIDER 0x09 /**< requested db backend not there */ #define DB_E_PROC 0x0A /**< could not start threadpool */ +#define DB_E_SIZE 0x0B /**< passed buffer too small */ + /* describes the individual database handlers */ typedef struct tag_dbinfo { char *handler_name; diff --git a/src/db-sql.c b/src/db-sql.c index 9dbe2a0f..c920b96a 100644 --- a/src/db-sql.c +++ b/src/db-sql.c @@ -45,7 +45,6 @@ #ifdef HAVE_LIBSQLITE #include "db-sql-sqlite2.h" #endif - #ifdef HAVE_LIBSQLITE3 #include "db-sql-sqlite3.h" #endif @@ -122,6 +121,38 @@ int db_sql_open_sqlite3(char **pe, char *parameters) { } #endif +/** + * escape a sql string, returning it the supplied buffer. + * note that this uses the sqlite escape format -- use %q for quoted + * sql. + * + * @param buffer buffer to throw the escaped sql into + * @param size size of buffer (or size required, if DB_E_SIZE) + * @param fmt printf style format + * @returns DB_E_SUCCESS with buffer filled, or DB_E_SIZE, with size + * set to the required size + */ +int db_sql_escape(char *buffer, int *size, char *fmt, ...) { + va_list ap; + char *escaped; + + va_start(ap,fmt); + escaped = db_sql_vmquery_fn(fmt,ap); + va_end(ap); + + if(*size < (int)strlen(escaped)) { + *size = strlen(escaped) + 1; + db_sql_vmfree_fn(escaped); + return DB_E_SIZE; + } + + strcpy(buffer,escaped); + *size = strlen(escaped); + db_sql_vmfree_fn(escaped); + + return DB_E_SUCCESS; +} + /** * fetch a single row, using the underlying database enum @@ -1050,8 +1081,8 @@ int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo) { /* Apply the query/filter */ if(pinfo->pt) { + DPRINTF(E_DBG,L_DB,"Got query/filter\n"); filter = sp_sql_clause(pinfo->pt); - if(filter) { if(have_clause) { strcat(query_rest," and "); @@ -1059,13 +1090,14 @@ int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo) { strcpy(query_rest," where "); } strcat(query_rest,"("); - strcat(query_rest,filter); strcat(query_rest,")"); free(filter); } else { DPRINTF(E_LOG,L_DB,"Error getting sql for parse tree\n"); } + } else { + DPRINTF(E_DBG,L_DB,"No query/filter\n"); } if(pinfo->index_type == indexTypeLast) { diff --git a/src/db-sql.h b/src/db-sql.h index 27a42369..1404d7d3 100644 --- a/src/db-sql.h +++ b/src/db-sql.h @@ -34,6 +34,7 @@ extern int db_sql_open_sqlite3(char **pe, char *parameters); extern int db_sql_open(char **pe, char *parameters); extern int db_sql_init(int reload); extern int db_sql_deinit(void); +extern int db_sql_escape(char *buffer, int *size, char *fmt, ...); extern int db_sql_add(char **pe, MP3FILE *pmp3, int *id); extern int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo); extern int db_sql_enum_size(char **pe, DBQUERYINFO *pinfo, int *count, int *total_size); diff --git a/src/dispatch.c b/src/dispatch.c index 55deb584..e7bb3856 100644 --- a/src/dispatch.c +++ b/src/dispatch.c @@ -77,6 +77,7 @@ static DAAP_ITEMS *dispatch_xml_lookup_tag(char *tag); static char *dispatch_xml_encode(char *original, int len); static int dispatch_output_xml_write(WS_CONNINFO *pwsc, DBQUERYINFO *pqi, unsigned char *block, int len); +static void dispatch_cleanup(DBQUERYINFO *pqi); /** * Hold the inf for the output serializer @@ -96,6 +97,17 @@ typedef struct tag_output_info { } OUTPUT_INFO; +/** + * do cleanup on the pqi structure... free any allocated memory, etc + */ +void dispatch_cleanup(DBQUERYINFO *pqi) { + if(pqi->pt) { + sp_dispose(pqi->pt); + } + free(pqi); +} + + /** * Handles authentication for the daap server. This isn't the * authenticator for the web admin page, but rather the iTunes @@ -157,12 +169,13 @@ void daap_handler(WS_CONNINFO *pwsc) { if(!query) query=ws_getvar(pwsc,"filter"); if(query) { pqi->pt = sp_init(); - if(!sp_parse(&pqi->pt,query,SP_TYPE_QUERY)) { + if(!sp_parse(pqi->pt,query,SP_TYPE_QUERY)) { DPRINTF(E_LOG,L_DAAP,"Ignoring bad query/filter (%s): %s\n", query,sp_get_error(pqi->pt)); sp_dispose(pqi->pt); pqi->pt = NULL; } + DPRINTF(E_DBG,L_DAAP,"Parsed query successfully\n"); } @@ -173,15 +186,6 @@ void daap_handler(WS_CONNINFO *pwsc) { ws_addresponseheader(pwsc,"Cache-Control","no-cache"); /* anti-ie defense */ ws_addresponseheader(pwsc,"Expires","-1"); - /* This we should put in a quirks file or something, but here might - * be a decent workaround for various failures on different clients */ - /* nm... backing this out. Really do need a "quirks" mode - pwsc->close=0; - if(ws_testrequestheader(pwsc,"Connection","Close")) { - pwsc->close = 1; - } - */ - if(ws_getvar(pwsc,"session-id")) pqi->session_id = atoi(ws_getvar(pwsc,"session-id")); @@ -195,26 +199,31 @@ void daap_handler(WS_CONNINFO *pwsc) { /* Start dispatching */ if(!strcasecmp(pqi->uri_sections[0],"server-info")) { dispatch_server_info(pwsc,pqi); + dispatch_cleanup(pqi); return; } if(!strcasecmp(pqi->uri_sections[0],"content-codes")) { dispatch_content_codes(pwsc,pqi); + dispatch_cleanup(pqi); return; } if(!strcasecmp(pqi->uri_sections[0],"login")) { dispatch_login(pwsc,pqi); + dispatch_cleanup(pqi); return; } if(!strcasecmp(pqi->uri_sections[0],"update")) { dispatch_update(pwsc,pqi); + dispatch_cleanup(pqi); return; } if(!strcasecmp(pqi->uri_sections[0],"logout")) { dispatch_logout(pwsc,pqi); + dispatch_cleanup(pqi); return; } @@ -228,6 +237,7 @@ void daap_handler(WS_CONNINFO *pwsc) { if(!strcasecmp(pqi->uri_sections[0],"databases")) { if(pqi->uri_count == 1) { dispatch_dbinfo(pwsc,pqi); + dispatch_cleanup(pqi); return; } pqi->db_id=atoi(pqi->uri_sections[1]); @@ -235,16 +245,18 @@ void daap_handler(WS_CONNINFO *pwsc) { if(!strcasecmp(pqi->uri_sections[2],"items")) { /* /databases/id/items */ dispatch_items(pwsc,pqi); + dispatch_cleanup(pqi); return; } if(!strcasecmp(pqi->uri_sections[2],"containers")) { /* /databases/id/containers */ dispatch_playlists(pwsc,pqi); + dispatch_cleanup(pqi); return; } pwsc->close=1; - free(pqi); + dispatch_cleanup(pqi); ws_returnerror(pwsc,404,"Page not found"); return; } @@ -252,34 +264,39 @@ void daap_handler(WS_CONNINFO *pwsc) { if(!strcasecmp(pqi->uri_sections[2],"browse")) { /* /databases/id/browse/something */ dispatch_browse(pwsc,pqi); + dispatch_cleanup(pqi); return; } if(!strcasecmp(pqi->uri_sections[2],"items")) { /* /databases/id/items/id.mp3 */ dispatch_stream(pwsc,pqi); + dispatch_cleanup(pqi); return; } if((!strcasecmp(pqi->uri_sections[2],"containers")) && (!strcasecmp(pqi->uri_sections[3],"add"))) { /* /databases/id/containers/add */ dispatch_addplaylist(pwsc,pqi); + dispatch_cleanup(pqi); return; } if((!strcasecmp(pqi->uri_sections[2],"containers")) && (!strcasecmp(pqi->uri_sections[3],"del"))) { /* /databases/id/containers/del */ dispatch_deleteplaylist(pwsc,pqi); + dispatch_cleanup(pqi); return; } if((!strcasecmp(pqi->uri_sections[2],"containers")) && (!strcasecmp(pqi->uri_sections[3],"edit"))) { /* /databases/id/contaienrs/edit */ dispatch_editplaylist(pwsc,pqi); + dispatch_cleanup(pqi); return; } pwsc->close=1; - free(pqi); + dispatch_cleanup(pqi); ws_returnerror(pwsc,404,"Page not found"); return; } @@ -288,6 +305,7 @@ void daap_handler(WS_CONNINFO *pwsc) { (!strcasecmp(pqi->uri_sections[4],"items"))) { pqi->playlist_id=atoi(pqi->uri_sections[3]); dispatch_playlistitems(pwsc,pqi); + dispatch_cleanup(pqi); return; } if((!strcasecmp(pqi->uri_sections[2],"containers")) && @@ -295,6 +313,7 @@ void daap_handler(WS_CONNINFO *pwsc) { /* /databases/id/containers/id/del */ pqi->playlist_id=atoi(pqi->uri_sections[3]); dispatch_deleteplaylistitems(pwsc,pqi); + dispatch_cleanup(pqi); return; } } @@ -304,13 +323,14 @@ void daap_handler(WS_CONNINFO *pwsc) { (!strcasecmp(pqi->uri_sections[5],"add"))) { pqi->playlist_id=atoi(pqi->uri_sections[3]); dispatch_addplaylistitems(pwsc,pqi); + dispatch_cleanup(pqi); return; } } } pwsc->close=1; - free(pqi); + dispatch_cleanup(pqi); ws_returnerror(pwsc,404,"Page not found"); return; } diff --git a/src/mp3-scanner.c b/src/mp3-scanner.c index 84525ee7..985176e0 100644 --- a/src/mp3-scanner.c +++ b/src/mp3-scanner.c @@ -203,7 +203,9 @@ void scan_process_playlistlist(void) { } if(strcasecmp(ext,".xml") == 0) { - scan_xml_playlist(pnext->path); + if(conf_get_int("scanning","process xml",1)) { + scan_xml_playlist(pnext->path); + } } else if(strcasecmp(ext,".m3u") == 0) { scan_static_playlist(pnext->path); } else { diff --git a/src/parser-driver.c b/src/parser-driver.c index a78f737d..eab241fd 100644 --- a/src/parser-driver.c +++ b/src/parser-driver.c @@ -2,11 +2,14 @@ * Test harness for the parser */ +#include #include #include #include #include +#include "conf.h" +#include "db-generic.h" #include "err.h" #include "smart-parser.h" @@ -19,9 +22,18 @@ int main(int argc, char *argv[]) { int option; int type=0; PARSETREE pt; - - while((option = getopt(argc, argv, "d:t:")) != -1) { + char *configfile = "/etc/mt-daapd.conf"; + char db_type[40]; + char db_parms[PATH_MAX]; + int size; + int err; + char *perr; + + while((option = getopt(argc, argv, "d:t:c:")) != -1) { switch(option) { + case 'c': + configfile = optarg; + break; case 'd': err_setlevel(atoi(optarg)); break; @@ -34,6 +46,22 @@ int main(int argc, char *argv[]) { } } + if(conf_read(configfile) != CONF_E_SUCCESS) { + fprintf(stderr,"could not read config file: %s\n",configfile); + exit(1); + } + + size = sizeof(db_type); + conf_get_string("general","db_type","sqlite",db_type,&size); + size = sizeof(db_parms); + conf_get_string("general","db_parms","/var/cache/mt-daapd",db_parms,&size); + + err=db_open(&perr,db_type,db_parms); + if(err != DB_E_SUCCESS) { + fprintf(stderr,"Error opening db: %s\n",perr); + free(perr); + exit(1); + } printf("Parsing %s\n",argv[optind]); @@ -45,6 +73,7 @@ int main(int argc, char *argv[]) { } sp_dispose(pt); + conf_close(); printf("Done!\n"); return 0; diff --git a/src/parser.mk b/src/parser.mk index 88fe08b4..9dc0f773 100644 --- a/src/parser.mk +++ b/src/parser.mk @@ -1,7 +1,8 @@ CC=gcc -CFLAGS := $(CFLAGS) -g -I/sw/include -DHAVE_CONFIG_H -I. -I.. -Wall -DERR_LEAN +CFLAGS := $(CFLAGS) -g -I/opt/local/include -DHAVE_CONFIG_H -I. -I.. -Wall -DERR_LEAN -DHAVE_SQL +LDFLAGS := $(LDFLAGS) -L/opt/local/lib -lsqlite -lsqlite3 TARGET=parser -OBJECTS=parser-driver.o smart-parser.o err.o +OBJECTS=parser-driver.o smart-parser.o err.o db-sql.o db-generic.o ssc.o db-sql-sqlite3.o db-sql-sqlite2.o conf.o ll.o $(TARGET): $(OBJECTS) $(CC) -o $(TARGET) $(LDFLAGS) $(OBJECTS) diff --git a/src/smart-parser.c b/src/smart-parser.c index b5025e6d..02168038 100644 --- a/src/smart-parser.c +++ b/src/smart-parser.c @@ -23,6 +23,11 @@ #include "err.h" +#ifdef HAVE_SQL +extern int db_sql_escape(char *buffer, int *size, char *fmt, ...); +#endif + + typedef struct tag_token { int token_id; union { @@ -197,7 +202,7 @@ typedef struct tag_fieldlookup { /* normal terminators, in-string terminators, escapes, quotes */ char *sp_terminators[2][4] = { { " \t\n\r\"<>=()|&!", "\"","\"","\"" }, - { "()'+: -,", "')", "\\*'","" } + { "()'+: -,", "'", "\\*'","" } }; FIELDLOOKUP sp_symbols_0[] = { @@ -465,6 +470,7 @@ int sp_scan(PARSETREE tree, int hint) { time_t tval; char *qstr; char *token_string; + char *dst, *src; if(tree->token.token_id & 0x2000) { if(tree->token.data.cvalue) @@ -544,8 +550,10 @@ int sp_scan(PARSETREE tree, int hint) { /* walk to a terminator */ tail = tree->current; - /* FIXME: escaped characters */ while((*tail) && (!strchr(terminator,*tail))) { + /* skip escaped characters -- will be unescaped later */ + if((*tail == '\\')&&(*(tail+1) != '\0')) + tail++; tail++; } @@ -634,6 +642,19 @@ int sp_scan(PARSETREE tree, int hint) { } } + /* escape string */ + if(tree->token.token_id == T_STRING) { + src = dst = tree->token.data.cvalue; + while(*src) { + if(*src != '\\') { + *dst++ = *src++; + } else { + src++; + } + } + *dst = '\0'; + } + DPRINTF(E_DBG,L_PARSE,"%*s Returning token %04x\n",tree->level," ", tree->token.token_id); if(tree->token.token_id & 0x2000) @@ -1304,6 +1325,7 @@ int sp_dispose(PARSETREE tree) { return 1; } +#ifdef HAVE_SQL /** * calculate the size required to render the tree as a * sql query. @@ -1313,6 +1335,7 @@ int sp_dispose(PARSETREE tree) { */ int sp_node_size(SP_NODE *node) { int size; + int string_size; if(node->op_type == SP_OPTYPE_ANDOR) { size = sp_node_size(node->left.node); @@ -1329,7 +1352,9 @@ int sp_node_size(SP_NODE *node) { } if(node->op_type == SP_OPTYPE_STRING) { - size += (2 + (int) strlen(node->right.cvalue)); + string_size = 0; + db_sql_escape(NULL,&string_size,"%q",node->right.cvalue); + size += (2 + string_size); if(node->op == T_INCLUDES) { size += 2; /* extra %'s */ } @@ -1358,6 +1383,7 @@ int sp_node_size(SP_NODE *node) { */ void sp_serialize_sql(SP_NODE *node, char *string) { char buffer[40]; + int size; if(node->op_type == SP_OPTYPE_ANDOR) { strcat(string,"("); @@ -1379,7 +1405,15 @@ void sp_serialize_sql(SP_NODE *node, char *string) { strcat(string,"'"); if((node->op == T_INCLUDES) || (node->op == T_ENDSWITH)) strcat(string,"%"); - strcat(string,node->right.cvalue); + size = 0; + db_sql_escape(NULL,&size,"%q",node->right.cvalue); + + /* we don't have a way to verify we have that much + * room, but we must... we allocated it earlier. + */ + db_sql_escape(&string[strlen(string)],&size,"%q",node->right.cvalue); + +// strcat(string,node->right.cvalue); if((node->op == T_INCLUDES) || (node->op == T_STARTSWITH)) strcat(string,"%"); strcat(string,"'"); @@ -1411,15 +1445,20 @@ char *sp_sql_clause(PARSETREE tree) { int size; char *sql; + DPRINTF(E_DBG,L_PARSE,"Fetching sql statement size\n"); size = sp_node_size(tree->tree); + DPRINTF(E_DBG,L_PARSE,"Size: %d\n",size); + sql = (char*)malloc(size+1); memset(sql,0x00,size+1); sp_serialize_sql(tree->tree,sql); + DPRINTF(E_DBG,L_PARSE,"Serialized to : %s\n",sql); + return sql; } - +#endif /* HAVE_SQL */ /** diff --git a/src/smart-parser.h b/src/smart-parser.h index 321375d8..2d15f568 100644 --- a/src/smart-parser.h +++ b/src/smart-parser.h @@ -8,7 +8,7 @@ typedef void* PARSETREE; extern PARSETREE sp_init(void); -extern int sp_parse(PARSETREE *tree, char *term, int type); +extern int sp_parse(PARSETREE tree, char *term, int type); extern int sp_dispose(PARSETREE tree); extern char *sp_get_error(PARSETREE tree); extern char *sp_sql_clause(PARSETREE tree);