From 68db2ae7c2739d0e24759cce5d0ab222f82e21f3 Mon Sep 17 00:00:00 2001 From: Julien BLACHE Date: Tue, 2 Jun 2009 15:51:38 +0200 Subject: [PATCH] Add new ANTLR parser for DAAP queries --- configure.in | 21 ++++ src/.gitignore | 6 + src/DAAP.g | 63 ++++++++++ src/DAAP2SQL.g | 294 +++++++++++++++++++++++++++++++++++++++++++++++ src/Makefile.am | 42 ++++++- src/daap_query.c | 262 +++++++++++++++++++++++++++++++++++++++++ src/daap_query.h | 27 +++++ src/db-sql.c | 17 +++ src/db-sql.h | 1 + src/httpd_daap.c | 11 +- 10 files changed, 741 insertions(+), 3 deletions(-) create mode 100644 src/DAAP.g create mode 100644 src/DAAP2SQL.g create mode 100644 src/daap_query.c create mode 100644 src/daap_query.h diff --git a/configure.in b/configure.in index 5b3519ca..d5ac5b97 100644 --- a/configure.in +++ b/configure.in @@ -11,6 +11,23 @@ AC_PROG_CC AM_PROG_CC_C_O LT_INIT([disable-static]) +AC_CHECK_PROG(ANTLR, [antlr3], [antlr3]) +if test "x$ANTLR" = x; then + AC_MSG_NOTICE([antlr3 wrapper not found, checking direct java invocation]) + AC_CHECK_PROG(JAVA, [java], [java]) + if test "x$JAVA" = x; then + AC_MSG_FAILURE([java not found; check your java installation]) + else + if $JAVA org.antlr.Tool > /dev/null 2>&1; then + AC_MSG_NOTICE([Direct java invocation working, using java org.antlr.Tool to invoke antlr]) + ANTLR="$JAVA org.antlr.Tool" + else + AC_MSG_FAILURE([antlr3 wrapper not found and direct java invocation failed; check your antlr3/java installation]) + fi + fi +fi +AC_SUBST(ANTLR) + CFLAGS="$CFLAGS -Wall" AC_CHECK_HEADERS([sys/wait.h]) @@ -68,6 +85,10 @@ AC_CHECK_HEADER(avl.h, , AC_MSG_ERROR([avl.h not found])) AC_CHECK_LIB([avl], [avl_alloc_tree], [LIBAVL_LIBS="-lavl"], AC_MSG_ERROR([libavl not found])) AC_SUBST(LIBAVL_LIBS) +AC_CHECK_HEADER(antlr3.h, , AC_MSG_ERROR([antlr3.h not found])) +AC_CHECK_LIB([antlr3c], [antlr3BaseRecognizerNew], [ANTLR3C_LIBS="-lantlr3c"], AC_MSG_ERROR([ANTLR3 C runtime (libantlr3c) not found])) +AC_SUBST(ANTLR3C_LIBS) + if test x$use_flac = xtrue; then PKG_CHECK_MODULES(FLAC, [ flac ]) fi diff --git a/src/.gitignore b/src/.gitignore index 57fa3dba..5cb311d7 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,2 +1,8 @@ mt-daapd +*.tokens +*Lexer.[ch] +*Parser.[ch] +DAAP2SQL.[ch] + +*.u diff --git a/src/DAAP.g b/src/DAAP.g new file mode 100644 index 00000000..803f21f1 --- /dev/null +++ b/src/DAAP.g @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009 Julien BLACHE + * + * 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 + */ + +grammar DAAP; + +options { + output = AST; + ASTLabelType = pANTLR3_BASE_TREE; + language = C; +} + +query : expr NEWLINE? EOF -> expr + ; + +expr : aexpr (OPOR^ aexpr)* + ; + +aexpr : crit (OPAND^ crit)* + ; + +crit : LPAR expr RPAR -> expr + | STR + ; + +QUOTE : '\''; +LPAR : '('; +RPAR : ')'; + +OPAND : '+'; +OPOR : ','; + +NEWLINE : '\r'? '\n'; + +/* +Unescaping adapted from (ported to the C runtime) + +*/ +STR +@init{ pANTLR3_STRING unesc = GETTEXT()->factory->newRaw(GETTEXT()->factory); } + : QUOTE ( reg = ~('\\' | '\'') { unesc->addc(unesc, reg); } + | esc = ESCAPED { unesc->appendS(unesc, GETTEXT()); } )+ QUOTE { SETTEXT(unesc); }; + +fragment +ESCAPED : '\\' + ( '\\' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\\")); } + | '\'' { SETTEXT(GETTEXT()->factory->newStr8(GETTEXT()->factory, (pANTLR3_UINT8)"\'")); } + ) + ; diff --git a/src/DAAP2SQL.g b/src/DAAP2SQL.g new file mode 100644 index 00000000..892c74d1 --- /dev/null +++ b/src/DAAP2SQL.g @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2009 Julien BLACHE + * + * 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 + */ + +tree grammar DAAP2SQL; + +options { + tokenVocab = DAAP; + ASTLabelType = pANTLR3_BASE_TREE; + language = C; +} + +@header { + #include + #include + #include + #include + + #include "logger.h" + #include "ff-dbstruct.h" + #include "db-generic.h" + #include "db-sql.h" + #include "daap_query.h" +} + +query returns [ pANTLR3_STRING result ] + : e = expr + { + if (!$e.valid) + { + $result = NULL; + } + else + { + $result = $e.result->factory->newRaw($e.result->factory); + $result->append8($result, "("); + $result->appendS($result, $e.result); + $result->append8($result, ")"); + } + } + ; + +expr returns [ pANTLR3_STRING result, int valid ] +@init { $result = NULL; $valid = 1; } + : ^(OPAND a = expr b = expr) + { + if (!$a.valid || !$b.valid) + { + $valid = 0; + } + else + { + $result = $a.result->factory->newRaw($a.result->factory); + $result->append8($result, "("); + $result->appendS($result, $a.result); + $result->append8($result, " AND "); + $result->appendS($result, $b.result); + $result->append8($result, ")"); + } + } + | ^(OPOR a = expr b = expr) + { + if (!$a.valid || !$b.valid) + { + $valid = 0; + } + else + { + $result = $a.result->factory->newRaw($a.result->factory); + $result->append8($result, "("); + $result->appendS($result, $a.result); + $result->append8($result, " OR "); + $result->appendS($result, $b.result); + $result->append8($result, ")"); + } + } + | STR + { + pANTLR3_STRING str; + pANTLR3_UINT8 field; + pANTLR3_UINT8 val; + pANTLR3_UINT8 escaped; + ANTLR3_UINT8 op; + int neg_op; + struct dmap_query_field_map *dqfm; + char *end; + long longval; + + escaped = NULL; + + $result = $STR.text->factory->newRaw($STR.text->factory); + + str = $STR.text->toUTF8($STR.text); + + /* NOTE: the lexer delivers the string without quotes + which may not be obvious from the grammar due to embedded code + */ + field = str->chars; + + val = field; + while ((*val != '\0') && ((*val == '.') + || ((*val >= 'a') && (*val <= 'z')) + || ((*val >= 'A') && (*val <= 'Z')) + || ((*val >= '0') && (*val <= '9')))) + { + val++; + } + + if (*field == '\0') + { + DPRINTF(E_LOG, L_DAAP, "No field name found in clause '\%s'\n", field); + $valid = 0; + goto STR_result_valid_0; /* ABORT */ + } + + if (*val == '\0') + { + DPRINTF(E_LOG, L_DAAP, "No operator found in clause '\%s'\n", field); + $valid = 0; + goto STR_result_valid_0; /* ABORT */ + } + + op = *val; + *val = '\0'; + val++; + + if (*val == '\0') + { + if (op == '!') + DPRINTF(E_LOG, L_DAAP, "Negation found but operator missing in clause '\%s\%c'\n", field, op); + else + DPRINTF(E_LOG, L_DAAP, "No value given in clause '\%s\%c'\n", field, op); + $valid = 0; + goto STR_result_valid_0; /* ABORT */ + } + + if (op == '!') + { + neg_op = 1; + op = *val; + val++; + + if (*val == '\0') + { + DPRINTF(E_LOG, L_DAAP, "No value given in clause '\%s!\%c'\n", field, op); + $valid = 0; + goto STR_result_valid_0; /* ABORT */ + } + } + else + neg_op = 0; + + /* Lookup DMAP field in the query field map */ + dqfm = daap_query_field_lookup((char *)field); + if (!dqfm) + { + DPRINTF(E_LOG, L_DAAP, "DMAP field '\%s' is not a valid field in queries\n", field); + $valid = 0; + goto STR_result_valid_0; /* ABORT */ + } + + $result->append8($result, dqfm->db_col); + + /* Int field: check integer conversion */ + if (dqfm->as_int) + { + errno = 0; + longval = strtol((const char *)val, &end, 10); + + if (((errno == ERANGE) && ((longval == LONG_MAX) || (longval == LONG_MIN))) + || ((errno != 0) && (longval == 0))) + { + DPRINTF(E_LOG, L_DAAP, "Value '\%s' in clause '\%s\%c\%s' does not convert to an integer type\n", + val, field, op, val); + $valid = 0; + goto STR_result_valid_0; /* ABORT */ + } + + if (end == (char *)val) + { + DPRINTF(E_LOG, L_DAAP, "Value '\%s' in clause '\%s\%c\%s' does not represent an integer value\n", + val, field, op, val); + $valid = 0; + goto STR_result_valid_0; /* ABORT */ + } + + *end = '\0'; /* Cut out potential garbage - we're being kind */ + } + /* String field: escape string, check for '*' */ + else + { + if (neg_op) + { + DPRINTF(E_LOG, L_DAAP, "Negation not valid for string operations\n"); + $valid = 0; + goto STR_result_valid_0; /* ABORT */ + } + + if (op != ':') + { + DPRINTF(E_LOG, L_DAAP, "Operation '\%c' not valid for string values\n", op); + $valid = 0; + goto STR_result_valid_0; /* ABORT */ + } + + escaped = (pANTLR3_UINT8)db_sql_escape_dup("\%q", val); + if (!escaped) + { + DPRINTF(E_LOG, L_DAAP, "Could not escape value\n"); + $valid = 0; + goto STR_result_valid_0; /* ABORT */ + } + + val = escaped; + + if (val[0] == '*') + { + op = '\%'; + val[0] = '\%'; + } + + if (val[strlen((char *)val) - 1] == '*') + { + op = '\%'; + val[strlen((char *)val) - 1] = '\%'; + } + } + + switch(op) + { + case ':': + if (neg_op) /* Not valid for strings, checked above */ + $result->append8($result, " <> "); + else + $result->append8($result, " = "); + break; + + case '+': + if (neg_op) + $result->append8($result, " <= "); + else + $result->append8($result, " > "); + break; + + case '-': + if (neg_op) + $result->append8($result, " >= "); + else + $result->append8($result, " < "); + break; + + case '\%': + $result->append8($result, " LIKE "); + break; + + default: + if (neg_op) + DPRINTF(E_LOG, L_DAAP, "Missing or unknown operator '\%c' in clause '\%s!\%c\%s'\n", op, field, op, val); + else + DPRINTF(E_LOG, L_DAAP, "Unknown operator '\%c' in clause '\%s\%c\%s'\n", op, field, op, val); + $valid = 0; + goto STR_result_valid_0; /* ABORT */ + break; + } + + if (!dqfm->as_int) + $result->append8($result, "'"); + + $result->append8($result, (const char *)val); + + if (!dqfm->as_int) + $result->append8($result, "'"); + + STR_result_valid_0: /* bail out label */ + ; + + if (escaped) + free(escaped); + } + ; diff --git a/src/Makefile.am b/src/Makefile.am index 3017c248..b78968c5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,8 +9,17 @@ if COND_MUSEPACK MUSEPACKSRC=scan-mpc.c endif +ANTLR_GRAMMARS = \ + DAAP.g DAAP2SQL.g + +ANTLR_SOURCES = \ + DAAPLexer.c DAAPLexer.h DAAPParser.c DAAPParser.h \ + DAAP2SQL.c DAAP2SQL.h + +ANTLR_PRODUCTS = + 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_LDADD = @AVAHI_LIBS@ @SQLITE3_LIBS@ @FFMPEG_LIBS@ @CONFUSE_LIBS@ @FLAC_LIBS@ @TAGLIB_LIBS@ @LIBEVENT_LIBS@ @LIBAVL_LIBS@ @MINIXML_LIBS@ @ANTLR3C_LIBS@ mt_daapd_SOURCES = main.c \ logger.c logger.h \ conffile.c conffile.h \ @@ -24,6 +33,7 @@ mt_daapd_SOURCES = main.c \ httpd_daap.c httpd_daap.h \ transcode.c transcode.h \ misc.c misc.h \ + daap_query.c daap_query.h \ db-generic.c db-generic.h \ scan-wma.c \ smart-parser.c smart-parser.h \ @@ -31,6 +41,34 @@ mt_daapd_SOURCES = main.c \ db-sql.c db-sql.h db-sql-sqlite3.c db-sql-sqlite3.h\ $(FLACSRC) $(MUSEPACKSRC) -EXTRA_DIST = scan-mpc.c \ +nodist_mt_daapd_SOURCES = \ + $(ANTLR_SOURCES) + +EXTRA_DIST = \ + $(ANTLR_GRAMMARS) \ + scan-mpc.c \ scan-flac.c \ ff-dbstruct.h + + +# Let's help the dependencies a little. +daap_query.c: DAAPLexer.h DAAPParser.h DAAP2SQL.h + + +SUFFIXES = .g .u + +%.tokens %.c %Lexer.c %Parser.c %Lexer.h %Parser.h %.h: %.g + @ANTLR@ $(ANTLR_OPTIONS) $< + +%.u: %.g + @ANTLR@ -depend $< > $@ + @echo -n "ANTLR_PRODUCTS += " > $@.tmp + @grep : $@ | cut -d : -f 1 | tr -d ' ' | { while read f; do test "$$f" != "$<" && echo -n "$$f "; done } >> $@.tmp + @cat $@.tmp >> $@ + rm $@.tmp + +clean-local: + -rm $(ANTLR_PRODUCTS) + rm $(ANTLR_GRAMMARS:.g=.u) + +-include $(ANTLR_GRAMMARS:.g=.u) diff --git a/src/daap_query.c b/src/daap_query.c new file mode 100644 index 00000000..cc9318ab --- /dev/null +++ b/src/daap_query.c @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2009 Julien BLACHE + * + * 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 + */ + +#include +#include +#include +#include + +#include + +#include "logger.h" +#include "misc.h" +#include "daap_query.h" + +#include "DAAPLexer.h" +#include "DAAPParser.h" +#include "DAAP2SQL.h" + + +static struct dmap_query_field_map dmap_query_fields[] = +{ + { 0, 0, "dmap.itemname", "title" }, + { 0, 1, "dmap.itemid", "id" }, + { 0, 0, "daap.songalbum", "album" }, + { 0, 0, "daap.songartist", "artist" }, + { 0, 1, "daap.songbitrate", "bitrate" }, + { 0, 0, "daap.songcomment", "comment" }, + { 0, 1, "daap.songcompilation", "compilation" }, + { 0, 0, "daap.songcomposer", "composer" }, + { 0, 1, "daap.songdatakind", "data_kind" }, + { 0, 0, "daap.songdataurl", "url" }, + { 0, 1, "daap.songdateadded", "time_added" }, + { 0, 1, "daap.songdatemodified", "time_modified" }, + { 0, 0, "daap.songdescription", "description" }, + { 0, 1, "daap.songdisccount", "total_discs" }, + { 0, 1, "daap.songdiscnumber", "disc" }, + { 0, 0, "daap.songformat", "type" }, + { 0, 0, "daap.songgenre", "genre" }, + { 0, 1, "daap.songsamplerate", "samplerate" }, + { 0, 1, "daap.songsize", "file_size" }, + { 0, 1, "daap.songstoptime", "song_length" }, + { 0, 1, "daap.songtime", "song_length" }, + { 0, 1, "daap.songtrackcount", "total_tracks" }, + { 0, 1, "daap.songtracknumber", "track" }, + { 0, 1, "daap.songyear", "year" }, + + { -1, -1, NULL, NULL } +}; + +static avl_tree_t *dmap_query_fields_hash; + + +static int +dmap_query_field_map_compare(const void *aa, const void *bb) +{ + struct dmap_query_field_map *a = (struct dmap_query_field_map *)aa; + struct dmap_query_field_map *b = (struct dmap_query_field_map *)bb; + + if (a->hash < b->hash) + return -1; + + if (a->hash > b->hash) + return 1; + + return 0; +} + + +struct dmap_query_field_map * +daap_query_field_lookup(char *field) +{ + struct dmap_query_field_map dqfm; + avl_node_t *node; + + dqfm.hash = djb_hash(field, strlen(field)); + + node = avl_search(dmap_query_fields_hash, &dqfm); + if (!node) + return NULL; + + return (struct dmap_query_field_map *)node->item; +} + +char * +daap_query_parse_sql(const char *daap_query) +{ + /* Input DAAP query, fed to the lexer */ + pANTLR3_INPUT_STREAM query; + + /* Lexer and the resulting token stream, fed to the parser */ + pDAAPLexer lxr; + pANTLR3_COMMON_TOKEN_STREAM tkstream; + + /* Parser and the resulting AST, fed to the tree parser */ + pDAAPParser psr; + DAAPParser_query_return qtree; + pANTLR3_COMMON_TREE_NODE_STREAM nodes; + + /* Tree parser and the resulting SQL query string */ + pDAAP2SQL sqlconv; + pANTLR3_STRING sql; + + char *ret = NULL; + + DPRINTF(E_DBG, L_DAAP, "Trying DAAP query -%s-\n", daap_query); + + query = antlr3NewAsciiStringInPlaceStream ((pANTLR3_UINT8)daap_query, (ANTLR3_UINT64)strlen(daap_query), (pANTLR3_UINT8)"DAAP query"); + if (!query) + { + DPRINTF(E_DBG, L_DAAP, "Could not create input stream\n"); + return NULL; + } + + lxr = DAAPLexerNew(query); + if (!lxr) + { + DPRINTF(E_DBG, L_DAAP, "Could not create DAAP lexer\n"); + goto lxr_fail; + } + + tkstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr)); + if (!tkstream) + { + DPRINTF(E_DBG, L_DAAP, "Could not create DAAP token stream\n"); + goto tkstream_fail; + } + + psr = DAAPParserNew(tkstream); + if (!psr) + { + DPRINTF(E_DBG, L_DAAP, "Could not create DAAP parser\n"); + goto psr_fail; + } + + qtree = psr->query(psr); + + /* Check for parser errors */ + if (psr->pParser->rec->state->errorCount > 0) + { + DPRINTF(E_LOG, L_DAAP, "DAAP query parser terminated with %d errors\n", psr->pParser->rec->state->errorCount); + goto psr_error; + } + + DPRINTF(E_SPAM, L_DAAP, "DAAP query AST:\n\t%s\n", qtree.tree->toStringTree(qtree.tree)->chars); + + nodes = antlr3CommonTreeNodeStreamNewTree(qtree.tree, ANTLR3_SIZE_HINT); + if (!nodes) + { + DPRINTF(E_DBG, L_DAAP, "Could not create node stream\n"); + goto psr_error; + } + + sqlconv = DAAP2SQLNew(nodes); + if (!sqlconv) + { + DPRINTF(E_DBG, L_DAAP, "Could not create SQL converter\n"); + goto sql_fail; + } + + sql = sqlconv->query(sqlconv); + + /* Check for tree parser errors */ + if (sqlconv->pTreeParser->rec->state->errorCount > 0) + { + DPRINTF(E_LOG, L_DAAP, "DAAP query tree parser terminated with %d errors\n", sqlconv->pTreeParser->rec->state->errorCount); + goto sql_error; + } + + if (sql) + { + DPRINTF(E_DBG, L_DAAP, "DAAP SQL query: -%s-\n", sql->chars); + ret = strdup((char *)sql->chars); + } + else + { + DPRINTF(E_LOG, L_DAAP, "Invalid DAAP query\n"); + ret = NULL; + } + + sql_error: + sqlconv->free(sqlconv); + sql_fail: + nodes->free(nodes); + psr_error: + psr->free(psr); + psr_fail: + tkstream->free(tkstream); + tkstream_fail: + lxr->free(lxr); + lxr_fail: + query->close(query); + + return ret; +} + +int +daap_query_init(void) +{ + avl_node_t *node; + struct dmap_query_field_map *dqfm; + int i; + + dmap_query_fields_hash = avl_alloc_tree(dmap_query_field_map_compare, NULL); + if (!dmap_query_fields_hash) + { + DPRINTF(E_FATAL, L_DAAP, "DAAP query init could not allocate AVL tree\n"); + + return -1; + } + + for (i = 0; dmap_query_fields[i].hash == 0; i++) + { + dmap_query_fields[i].hash = djb_hash(dmap_query_fields[i].dmap_field, strlen(dmap_query_fields[i].dmap_field)); + + node = avl_insert(dmap_query_fields_hash, &dmap_query_fields[i]); + if (!node) + { + if (errno != EEXIST) + DPRINTF(E_FATAL, L_DAAP, "DAAP query init failed; AVL insert error: %s\n", strerror(errno)); + else + { + node = avl_search(dmap_query_fields_hash, &dmap_query_fields[i]); + dqfm = node->item; + + DPRINTF(E_FATAL, L_DAAP, "DAAP query init failed; WARNING: duplicate hash key\n"); + DPRINTF(E_FATAL, L_DAAP, "Hash %x, string %s\n", dmap_query_fields[i].hash, dmap_query_fields[i].dmap_field); + + DPRINTF(E_FATAL, L_DAAP, "Hash %x, string %s\n", dqfm->hash, dqfm->dmap_field); + } + + goto avl_insert_fail; + } + } + + return 0; + + avl_insert_fail: + avl_free_tree(dmap_query_fields_hash); + + return -1; +} + +void +daap_query_deinit(void) +{ + avl_free_tree(dmap_query_fields_hash); +} diff --git a/src/daap_query.h b/src/daap_query.h new file mode 100644 index 00000000..9fa54c0b --- /dev/null +++ b/src/daap_query.h @@ -0,0 +1,27 @@ + +#ifndef __DAAP_QUERY_H__ +#define __DAAP_QUERY_H__ + +#include + +struct dmap_query_field_map { + uint32_t hash; + int as_int; + char *dmap_field; + char *db_col; +}; + + +struct dmap_query_field_map * +daap_query_field_lookup(char *field); + +char * +daap_query_parse_sql(const char *daap_query); + +int +daap_query_init(void); + +void +daap_query_deinit(void); + +#endif /* !__DAAP_QUERY_H__ */ diff --git a/src/db-sql.c b/src/db-sql.c index 467d657a..ef47cfda 100644 --- a/src/db-sql.c +++ b/src/db-sql.c @@ -150,6 +150,23 @@ int db_sql_escape(char *buffer, int *size, char *fmt, ...) { return DB_E_SUCCESS; } +char * +db_sql_escape_dup(char *fmt, ...) +{ + va_list ap; + char *escaped; + char *ret; + + va_start(ap, fmt); + escaped = db_sql_vmquery_fn(fmt, ap); + va_end(ap); + + ret = strdup(escaped); + + db_sql_vmfree_fn(escaped); + + return ret; +} /** * fetch a single row, using the underlying database enum diff --git a/src/db-sql.h b/src/db-sql.h index ee88e984..73ab7b92 100644 --- a/src/db-sql.h +++ b/src/db-sql.h @@ -31,6 +31,7 @@ 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 char * db_sql_escape_dup(char *fmt, ...); extern int db_sql_add(char **pe, MP3FILE *pmp3, int *id); extern int db_sql_enum_start(char **pe, DBQUERYINFO *pinfo); diff --git a/src/httpd_daap.c b/src/httpd_daap.c index c795ca96..84f520a4 100644 --- a/src/httpd_daap.c +++ b/src/httpd_daap.c @@ -46,6 +46,7 @@ #include "httpd.h" #include "transcode.h" #include "httpd_daap.h" +#include "daap_query.h" struct uri_map { @@ -1949,6 +1950,10 @@ daap_init(void) session_id = 100; /* gotta start somewhere, right? */ + ret = daap_query_init(); + if (ret < 0) + return ret; + for (i = 0; daap_handlers[i].handler; i++) { ret = regcomp(&daap_handlers[i].preg, daap_handlers[i].regexp, REG_EXTENDED | REG_NOSUB); @@ -1957,7 +1962,7 @@ daap_init(void) regerror(ret, &daap_handlers[i].preg, buf, sizeof(buf)); DPRINTF(E_FATAL, L_DAAP, "DAAP init failed; regexp error: %s\n", buf); - return -1; + goto regexp_fail; } } @@ -2000,6 +2005,8 @@ daap_init(void) avl_alloc_fail: for (i = 0; daap_handlers[i].handler; i++) regfree(&daap_handlers[i].preg); + regexp_fail: + daap_query_deinit(); return -1; } @@ -2009,6 +2016,8 @@ daap_deinit(void) { int i; + daap_query_deinit(); + for (i = 0; daap_handlers[i].handler; i++) regfree(&daap_handlers[i].preg);