diff --git a/src/db-generic.h b/src/db-generic.h index 5ea17f35..217bff3f 100644 --- a/src/db-generic.h +++ b/src/db-generic.h @@ -23,6 +23,7 @@ #define _DB_GENERIC_H_ #include "mp3-scanner.h" /** for MP3FILE */ +#include "smart-parser.h" /** for PARSETREE */ typedef enum { // generic meta data @@ -77,11 +78,11 @@ typedef enum { metaItmsGenreId, metaItmsStorefrontId, metaItunesSmartPlaylist, - + /* iTunes 5.0 + */ metaSongContentRating, metaHasChildContainers, - + /* iTunes 6.0.2+ */ metaItunesHasVideo, @@ -125,7 +126,7 @@ typedef struct tag_dbqueryinfo { int session_id; int uri_count; char *uri_sections[10]; - char *whereclause; + PARSETREE pt; void *output_info; } DBQUERYINFO; diff --git a/src/db-sql.c b/src/db-sql.c index 83be9a6a..9dbe2a0f 100644 --- a/src/db-sql.c +++ b/src/db-sql.c @@ -233,28 +233,28 @@ int db_sql_parse_smart(char **pe, char **clause, char *phrase) { *clause = strdup("1"); return TRUE; } - + pt=sp_init(); if(!pt) { if(pe) *pe = strdup("Could not initialize parse tree"); return FALSE; } - - if(!sp_parse(pt,phrase)) { + + if(!sp_parse(pt,phrase,SP_TYPE_PLAYLIST)) { if(pe) *pe = strdup(sp_get_error(pt)); - + DPRINTF(E_LOG,L_DB,"Error parsing playlist: %s\n",sp_get_error(pt)); - + sp_dispose(pt); return FALSE; } else { *clause = sp_sql_clause(pt); } - + sp_dispose(pt); return TRUE; } - + /** * open sqlite database * @@ -620,7 +620,7 @@ int db_sql_add(char **pe, MP3FILE *pmp3, int *id) { /* Always an add if in song scan on full reload */ - if((!db_sql_reload)||(!db_sql_in_scan)) { + if((!db_sql_reload)||(!db_sql_in_scan)) { err=db_sql_fetch_int(NULL,&count,"select count(*) from songs where " "path='%q'",pmp3->path); @@ -630,7 +630,7 @@ int db_sql_add(char **pe, MP3FILE *pmp3, int *id) { DPRINTF(E_LOG,L_DB,"Error: %s\n",pe); return err; } - + } pmp3->play_count=0; @@ -731,7 +731,7 @@ int db_sql_add(char **pe, MP3FILE *pmp3, int *id) { if(id) *id = insertid; - + DPRINTF(E_SPAM,L_DB,"Exiting db_sql_add\n"); return DB_E_SUCCESS; } @@ -931,6 +931,7 @@ int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo) { char query_count[255]; char query_rest[4096]; char *where_clause; + char *filter; int is_smart; int have_clause=0; @@ -980,13 +981,13 @@ int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo) { if(is_smart) { if(!db_sql_parse_smart(NULL,&where_clause,temprow[1])) where_clause = strdup("0"); - + if(!where_clause) { db_sql_enum_end_fn(NULL); db_get_error(pe,DB_E_PARSE); return DB_E_PARSE; } - + sprintf(query_select,"select * from songs "); sprintf(query_count,"select count(id) from songs "); sprintf(query_rest,"where (%s)",where_clause); @@ -1048,18 +1049,25 @@ int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo) { } /* Apply the query/filter */ - if(pinfo->whereclause) { - if(have_clause) - strcat(query_rest," and "); - else - strcpy(query_rest," where "); + if(pinfo->pt) { + filter = sp_sql_clause(pinfo->pt); - strcat(query_rest,"("); - strcat(query_rest,pinfo->whereclause); - strcat(query_rest,")"); + if(filter) { + if(have_clause) { + strcat(query_rest," and "); + } else { + 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"); + } } - if(pinfo->index_type == indexTypeLast) { /* We don't really care how many items unless we are * doing a "last n items" query */ @@ -1355,14 +1363,14 @@ int db_sql_get_size(DBQUERYINFO *pinfo, SQL_ROW valarray) { if(ISSTR(valarray[37]) && db_wantsmeta(pinfo->meta, metaSongCodecType)) /* ascd */ size += 12; - + if(db_wantsmeta(pinfo->meta,metaSongContentRating)) /* ascr */ size += 9; if(db_wantsmeta(pinfo->meta,metaItunesHasVideo)) /* aeHV */ size += 9; - + return size; break; @@ -1596,10 +1604,10 @@ M3UFILE *db_sql_fetch_playlist(char **pe, char *path, int index) { db_get_error(pe,DB_E_INVALID_PLAYLIST); return NULL; } - + return NULL; /* sql error or something */ } - + pm3u=(M3UFILE*)malloc(sizeof(M3UFILE)); if(!pm3u) DPRINTF(E_FATAL,L_MISC,"malloc error: db_sql_fetch_playlist\n"); @@ -1760,7 +1768,7 @@ int db_sql_get_count(char **pe, int *count, CountType_t type) { */ int db_sql_playcount_increment(char **pe, int id) { time_t now = time(NULL); - + return db_sql_exec_fn(pe,E_INF,"update songs set play_count=play_count + 1" ", time_played=%d where id=%d",now,id); } diff --git a/src/dispatch.c b/src/dispatch.c index 2a3649aa..45af0bce 100644 --- a/src/dispatch.c +++ b/src/dispatch.c @@ -126,7 +126,7 @@ int daap_auth(char *username, char *password) { return 0; if(strcasecmp(password,readpassword)) { - DPRINTF(E_LOG,L_DAAP | L_WS,"Bad password attempt\n"); + DPRINTF(E_LOG,L_DAAP | L_WS,"Bad password attempt\n"); return 0; } @@ -152,13 +152,20 @@ void daap_handler(WS_CONNINFO *pwsc) { memset(pqi,0x00,sizeof(DBQUERYINFO)); + /* we could really pre-parse this to make sure it works */ query=ws_getvar(pwsc,"query"); if(!query) query=ws_getvar(pwsc,"filter"); if(query) { - DPRINTF(E_DBG,L_DAAP,"Getting sql clause for %s\n",query); - pqi->whereclause = query_build_sql(query); + pqi->pt = sp_init(); + 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; + } } + /* Add some default headers */ ws_addresponseheader(pwsc,"Accept-Ranges","bytes"); ws_addresponseheader(pwsc,"DAAP-Server","mt-daapd/" VERSION); diff --git a/src/parser-driver.c b/src/parser-driver.c index 1d3cf511..a78f737d 100644 --- a/src/parser-driver.c +++ b/src/parser-driver.c @@ -11,19 +11,23 @@ #include "smart-parser.h" void usage(void) { - printf("Usage:\n\n parser [-d ] \"phrase\"\n\n"); + printf("Usage:\n\n parser [-t ] [-d ] \"phrase\"\n\n"); exit(0); } int main(int argc, char *argv[]) { int option; + int type=0; PARSETREE pt; - while((option = getopt(argc, argv, "d:")) != -1) { + while((option = getopt(argc, argv, "d:t:")) != -1) { switch(option) { case 'd': err_setlevel(atoi(optarg)); break; + case 't': + type = atoi(optarg); + break; default: fprintf(stderr,"Error: unknown option (%c)\n\n",option); usage(); @@ -34,7 +38,7 @@ int main(int argc, char *argv[]) { printf("Parsing %s\n",argv[optind]); pt=sp_init(); - if(!sp_parse(pt,argv[optind])) { + if(!sp_parse(pt,argv[optind],type)) { printf("%s\n",sp_get_error(pt)); } else { printf("SQL: %s\n",sp_sql_clause(pt)); diff --git a/src/smart-parser.c b/src/smart-parser.c index d52ec084..68a3fc10 100644 --- a/src/smart-parser.c +++ b/src/smart-parser.c @@ -147,6 +147,10 @@ typedef struct tag_sp_node { #define T_ENDSWITH 0x001d #define T_LAST 0x001e +/* specific to query extensions */ +#define T_GREATERAND 0x001f +#define T_EXPRQUOTE 0x0020 + #define T_EOF 0x00fd #define T_BOF 0x00fe #define T_ERROR 0x00ff @@ -211,6 +215,15 @@ FIELDLOOKUP sp_symbols_0[] = { }; FIELDLOOKUP sp_symbols_1[] = { + { T_OPENPAREN, "(", NULL }, + { T_CLOSEPAREN, ")", NULL }, + { T_EXPRQUOTE, "'", NULL }, + { T_GREATERAND, "+", NULL }, + { T_GREATERAND, " ", NULL }, + { T_LESS,"-", NULL }, + { T_OR, ",", NULL }, + { T_EQUAL, ":", NULL }, + { T_NOT, "!", NULL }, { 0, NULL, NULL } }; @@ -283,31 +296,31 @@ FIELDLOOKUP sp_fields_0[] = { }; FIELDLOOKUP sp_fields_1[] = { - { T_STRING_FIELD, "dmap.itemname", "title" }, - { T_INT_FIELD, "dmap.itemid", "id" }, - { T_STRING_FIELD, "daap.songalbum", "album" }, - { T_STRING_FIELD, "daap.songartist", "artist" }, - { T_INT_FIELD, "daap.songbitrate", "bitrate" }, - { T_STRING_FIELD, "daap.songcomment", "comment" }, - { T_INT_FIELD, "daap.songcompilation", "compilation" }, - { T_STRING_FIELD, "daap.songcomposer", "composer" }, - { T_INT_FIELD, "daap.songdatakind", "data_kind" }, - { T_STRING_FIELD, "daap.songdataurl", "url" }, - { T_INT_FIELD, "daap.songdateadded", "time_added" }, - { T_INT_FIELD, "daap.songdatemodified","time_modified" }, - { T_STRING_FIELD, "daap.songdescription", "description" }, - { T_INT_FIELD, "daap.songdisccount", "total_discs" }, - { T_INT_FIELD, "daap.songdiscnumber", "disc" }, - { T_STRING_FIELD, "daap.songformat", "type" }, - { T_STRING_FIELD, "daap.songgenre", "genre" }, - { T_INT_FIELD, "daap.songsamplerate", "samplerate" }, - { T_INT_FIELD, "daap.songsize", "file_size" }, + { T_STRING_FIELD, "dmap.itemname", "title" }, + { T_INT_FIELD, "dmap.itemid", "id" }, + { T_STRING_FIELD, "daap.songalbum", "album" }, + { T_STRING_FIELD, "daap.songartist", "artist" }, + { T_INT_FIELD, "daap.songbitrate", "bitrate" }, + { T_STRING_FIELD, "daap.songcomment", "comment" }, + { T_INT_FIELD, "daap.songcompilation", "compilation" }, + { T_STRING_FIELD, "daap.songcomposer", "composer" }, + { T_INT_FIELD, "daap.songdatakind", "data_kind" }, + { T_STRING_FIELD, "daap.songdataurl", "url" }, + { T_INT_FIELD, "daap.songdateadded", "time_added" }, + { T_INT_FIELD, "daap.songdatemodified", "time_modified" }, + { T_STRING_FIELD, "daap.songdescription", "description" }, + { T_INT_FIELD, "daap.songdisccount", "total_discs" }, + { T_INT_FIELD, "daap.songdiscnumber", "disc" }, + { T_STRING_FIELD, "daap.songformat", "type" }, + { T_STRING_FIELD, "daap.songgenre", "genre" }, + { T_INT_FIELD, "daap.songsamplerate", "samplerate" }, + { T_INT_FIELD, "daap.songsize", "file_size" }, // { T_INT_FIELD, "daap.songstarttime", 0 }, - { T_INT_FIELD, "daap.songstoptime", "song_length" }, - { T_INT_FIELD, "daap.songtime", "song_length" }, - { T_INT_FIELD, "daap.songtrackcount", "total_tracks" }, - { T_INT_FIELD, "daap.songtracknumber", "track" }, - { T_INT_FIELD, "daap.songyear", "year" }, + { T_INT_FIELD, "daap.songstoptime", "song_length" }, + { T_INT_FIELD, "daap.songtime", "song_length" }, + { T_INT_FIELD, "daap.songtrackcount", "total_tracks" }, + { T_INT_FIELD, "daap.songtracknumber", "track" }, + { T_INT_FIELD, "daap.songyear", "year" }, { 0, NULL, NULL } }; @@ -341,6 +354,7 @@ typedef struct tag_parsetree { #define SP_E_BEFOREAFTER 0x0a #define SP_E_TIMEINTERVAL 0x0b #define SP_E_DATE 0x0c +#define SP_E_EXPRQUOTE 0x0d char *sp_errorstrings[] = { "Success", @@ -355,7 +369,8 @@ char *sp_errorstrings[] = { "Expecting date comparison operator (<,<=,>,>=)", "Expecting interval comparison (before, after)", "Expecting time interval (days, weeks, months, years)", - "Expecting date" + "Expecting date", + "Expecting ' (single quote)\n" }; /* Forwards */ @@ -431,7 +446,10 @@ time_t sp_isdate(char *string) { } /** - * scan the input, returning the next available token. + * scan the input, returning the next available token. This is + * kind of a mess, and looking at it with new eyes would probably + * yield a better way of tokenizing the stream, but this seems to + * work. * * @param tree current working parse tree. * @returns next token (token, not the value) @@ -446,6 +464,7 @@ int sp_scan(PARSETREE tree, int hint) { int is_qstr; time_t tval; char *qstr; + char *token_string; if(tree->token.token_id & 0x2000) { if(tree->token.data.cvalue) @@ -483,7 +502,7 @@ int sp_scan(PARSETREE tree, int hint) { DPRINTF(E_SPAM,L_PARSE,"Starting scan - in_string: %d, hint: %d\n", - tree->in_string, hint); + tree->in_string, hint); /* check symbols */ if(!tree->in_string) { @@ -501,17 +520,21 @@ int sp_scan(PARSETREE tree, int hint) { qstr = sp_terminators[tree->token_list][3]; is_qstr = (strchr(qstr,*(tree->current)) != NULL); + + DPRINTF(E_SPAM,L_PARSE,"qstr: %s -- is_quoted: %d\n",qstr,is_qstr); + if(strlen(qstr)) { /* strings ARE quoted */ if(hint == SP_HINT_STRING) { /* MUST be a quote */ if(!is_qstr) { + tree->token.token_id = T_ERROR; return T_ERROR; - } + } } else { - if(is_qstr) { - tree->in_string = 1; /* guess we're in a string */ - terminator=sp_terminators[tree->token_list][1]; - tree->current++; - } + if(is_qstr) { + tree->in_string = 1; /* guess we're in a string */ + terminator=sp_terminators[tree->token_list][1]; + tree->current++; + } } } @@ -534,7 +557,7 @@ int sp_scan(PARSETREE tree, int hint) { /* find it in the token list */ pfield=sp_fields[tree->token_list]; while(pfield->name) { - DPRINTF(E_SPAM,L_PARSE,"Comparing to %s\n",pfield->name); + DPRINTF(E_SPAM,L_PARSE,"Comparing to %s\n",pfield->name); if(strlen(pfield->name) == len) { if(strncasecmp(pfield->name,tree->current,len) == 0) { found=1; @@ -552,12 +575,20 @@ int sp_scan(PARSETREE tree, int hint) { } if(tree->token.token_id & 0x2000) { + token_string=tree->current; + if(found) { + if(pfield->xlat) { + len = strlen(pfield->xlat); + token_string = pfield->xlat; + } + } + tree->token.data.cvalue = malloc(len + 1); if(!tree->token.data.cvalue) { /* fail on malloc error */ DPRINTF(E_FATAL,L_PARSE,"Malloc error.\n"); } - strncpy(tree->token.data.cvalue,tree->current,len); + strncpy(tree->token.data.cvalue,token_string,len); tree->token.data.cvalue[len] = '\x0'; } @@ -591,15 +622,15 @@ int sp_scan(PARSETREE tree, int hint) { is_qstr = (strchr(qstr,*tree->current) != NULL); if((!found) && strlen(qstr) && (tree->in_string)) { - if(is_qstr) { - tree->current++; /* absorb it */ - } else { - DPRINTF(E_INF,L_PARSE,"Missing closing quotes\n"); - if(tree->token.token_id & 0x2000) { - free(tree->token.data.cvalue); - } - tree->token.token_id = T_ERROR; - } + if(is_qstr) { + tree->current++; /* absorb it */ + } else { + DPRINTF(E_INF,L_PARSE,"Missing closing quotes\n"); + if(tree->token.token_id & 0x2000) { + free(tree->token.data.cvalue); + } + tree->token.token_id = T_ERROR; + } } DPRINTF(E_DBG,L_PARSE,"%*s Returning token %04x\n",tree->level," ", @@ -652,10 +683,11 @@ PARSETREE sp_init(void) { * @param term term or phrase to parse * @returns 1 if successful, 0 if not */ -int sp_parse(PARSETREE tree, char *term) { +int sp_parse(PARSETREE tree, char *term, int type) { tree->term = strdup(term); /* will be destroyed by parsing */ tree->current=tree->term; tree->token.token_id=T_BOF; + tree->token_list = type; if(tree->tree) sp_free_node(tree->tree); @@ -716,7 +748,8 @@ SP_NODE *sp_parse_aexpr(PARSETREE tree) { expr = sp_parse_expr(tree); - while(expr && (tree->token.token_id == T_AND)) { + while(expr && ((tree->token.token_id == T_AND) || + (tree->token.token_id == T_GREATERAND))) { pnew = (SP_NODE*)malloc(sizeof(SP_NODE)); if(!pnew) { DPRINTF(E_FATAL,L_PARSE,"Malloc error\n"); @@ -829,6 +862,15 @@ SP_NODE *sp_parse_criterion(PARSETREE tree) { sp_enter_exit(tree,"sp_parse_criterion",1,expr); + if(tree->token_list == 1) { + if(tree->token.token_id != T_EXPRQUOTE) { + sp_set_error(tree,SP_E_EXPRQUOTE); + return NULL; + } else { + sp_scan(tree,SP_HINT_NONE); + } + } + switch(tree->token.token_id) { case T_STRING_FIELD: expr = sp_parse_string_criterion(tree); @@ -849,6 +891,16 @@ SP_NODE *sp_parse_criterion(PARSETREE tree) { break; } + if(tree->token_list == 1) { + if(tree->token.token_id != T_EXPRQUOTE) { + sp_set_error(tree,SP_E_EXPRQUOTE); + sp_free_node(expr); + return NULL; + } else { + sp_scan(tree,SP_HINT_NONE); + } + } + sp_enter_exit(tree,"sp_parse_criterion",0,expr); return expr; } @@ -872,7 +924,7 @@ SP_NODE *sp_parse_criterion(PARSETREE tree) { memset(pnew,0x00,sizeof(SP_NODE)); pnew->left.field = strdup(tree->token.data.cvalue); - sp_scan(tree,SP_HINT_NONE);/* scan past the string field we know is there */ + sp_scan(tree,SP_HINT_NONE); /* scan past the string field we know is there */ if(tree->token.token_id == T_NOT) { pnew->not_flag=1; @@ -895,11 +947,22 @@ SP_NODE *sp_parse_criterion(PARSETREE tree) { } if(result) { - sp_scan(tree,SP_HINT_NONE); + sp_scan(tree,SP_HINT_STRING); /* should be sitting on string literal */ if(tree->token.token_id == T_STRING) { result = 1; pnew->right.cvalue=strdup(tree->token.data.cvalue); + if(tree->token_list == 1) { + if(pnew->right.cvalue[0]=='*') { + pnew->op = T_ENDSWITH; + memcpy(pnew->right.cvalue,&pnew->right.cvalue[1], + (int)strlen(pnew->right.cvalue)); /* with zt */ + } + if(pnew->right.cvalue[strlen(pnew->right.cvalue)-1] == '*') { + pnew->op = (pnew->op==T_ENDSWITH)?T_INCLUDES:T_STARTSWITH; + pnew->right.cvalue[strlen(pnew->right.cvalue)-1] = '\0'; + } + } sp_scan(tree,SP_HINT_NONE); } else { sp_set_error(tree,SP_E_OPENQUOTE); @@ -954,6 +1017,11 @@ SP_NODE *sp_parse_criterion(PARSETREE tree) { pnew->op=tree->token.token_id; pnew->op_type = SP_OPTYPE_INT; break; + case T_GREATERAND: + result = 1; + pnew->op = T_GREATER; + pnew->op_type = SP_OPTYPE_INT; + break; default: /* Error: expecting legal int comparison operator */ sp_set_error(tree,SP_E_INTCMP); @@ -963,7 +1031,7 @@ SP_NODE *sp_parse_criterion(PARSETREE tree) { } if(result) { - sp_scan(tree,SP_HINT_NONE); + sp_scan(tree,SP_HINT_INT); /* should be sitting on a literal string */ if(tree->token.token_id == T_NUMBER) { result = 1; @@ -1023,6 +1091,11 @@ SP_NODE *sp_parse_date_criterion(PARSETREE tree) { pnew->op=tree->token.token_id; pnew->op_type = SP_OPTYPE_DATE; break; + case T_GREATERAND: + result = 1; + pnew->op=T_GREATER; + pnew->op_type = SP_OPTYPE_DATE; + break; case T_BEFORE: result = 1; pnew->op_type = SP_OPTYPE_DATE; @@ -1042,7 +1115,7 @@ SP_NODE *sp_parse_date_criterion(PARSETREE tree) { } if(result) { - sp_scan(tree,SP_HINT_NONE); + sp_scan(tree,SP_HINT_DATE); /* should be sitting on a date */ if((pnew->right.tvalue = sp_parse_date(tree))) { result=1; diff --git a/src/smart-parser.h b/src/smart-parser.h index a7755669..321375d8 100644 --- a/src/smart-parser.h +++ b/src/smart-parser.h @@ -8,10 +8,13 @@ typedef void* PARSETREE; extern PARSETREE sp_init(void); -extern int sp_parse(PARSETREE *tree, char *term); +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); +#define SP_TYPE_PLAYLIST 0 +#define SP_TYPE_QUERY 1 + #endif /* _SMART_PARSER_H_ */