1/*
2 * Copyright (C) 2009-2011 Julien BLACHE <jb@jblache.org>
3 *
4 * Adapted from mt-daapd:
5 * Copyright (C) 2006-2007 Ron Pedde <ron@pedde.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 */
21
22#ifdef HAVE_CONFIG_H
23# include <config.h>
24#endif
25
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <sys/queue.h>
30#include <sys/types.h>
31#include <regex.h>
32#include <limits.h>
33
34#include <event.h>
35#include "evhttp/evhttp.h"
36
37#include <mxml.h>
38
39#include "logger.h"
40#include "db.h"
41#include "conffile.h"
42#include "misc.h"
43#include "httpd.h"
44#include "transcode.h"
45#include "httpd_rsp.h"
46#include "rsp_query.h"
47
48
49#define RSP_VERSION "1.0"
50#define RSP_XML_ROOT "?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?"
51
52
53#define F_FULL     (1 << 0)
54#define F_BROWSE   (1 << 1)
55#define F_ID       (1 << 2)
56#define F_DETAILED (1 << 3)
57#define F_ALWAYS   (F_FULL | F_BROWSE | F_ID | F_DETAILED)
58
59struct field_map {
60  char *field;
61  size_t offset;
62  int flags;
63};
64
65struct uri_map {
66  regex_t preg;
67  char *regexp;
68  void (*handler)(struct evhttp_request *req, char **uri, struct evkeyvalq *query);
69};
70
71static const struct field_map pl_fields[] =
72  {
73    { "id",           dbpli_offsetof(id),           F_ALWAYS },
74    { "title",        dbpli_offsetof(title),        F_FULL | F_BROWSE | F_DETAILED },
75    { "type",         dbpli_offsetof(type),         F_DETAILED },
76    { "items",        dbpli_offsetof(items),        F_FULL | F_BROWSE | F_DETAILED },
77    { "query",        dbpli_offsetof(query),        F_DETAILED },
78    { "db_timestamp", dbpli_offsetof(db_timestamp), F_DETAILED },
79    { "path",         dbpli_offsetof(path),         F_DETAILED },
80    { "index",        dbpli_offsetof(index),        F_DETAILED },
81    { NULL,           0,                            0 }
82  };
83
84static const struct field_map rsp_fields[] =
85  {
86    { "id",            dbmfi_offsetof(id),            F_ALWAYS },
87    { "path",          dbmfi_offsetof(path),          F_DETAILED },
88    { "fname",         dbmfi_offsetof(fname),         F_DETAILED },
89    { "title",         dbmfi_offsetof(title),         F_ALWAYS },
90    { "artist",        dbmfi_offsetof(artist),        F_DETAILED | F_FULL | F_BROWSE },
91    { "album",         dbmfi_offsetof(album),         F_DETAILED | F_FULL | F_BROWSE },
92    { "genre",         dbmfi_offsetof(genre),         F_DETAILED | F_FULL },
93    { "comment",       dbmfi_offsetof(comment),       F_DETAILED | F_FULL },
94    { "type",          dbmfi_offsetof(type),          F_ALWAYS },
95    { "composer",      dbmfi_offsetof(composer),      F_DETAILED | F_FULL },
96    { "orchestra",     dbmfi_offsetof(orchestra),     F_DETAILED | F_FULL },
97    { "conductor",     dbmfi_offsetof(conductor),     F_DETAILED | F_FULL },
98    { "url",           dbmfi_offsetof(url),           F_DETAILED | F_FULL },
99    { "bitrate",       dbmfi_offsetof(bitrate),       F_DETAILED | F_FULL },
100    { "samplerate",    dbmfi_offsetof(samplerate),    F_DETAILED | F_FULL },
101    { "song_length",   dbmfi_offsetof(song_length),   F_DETAILED | F_FULL },
102    { "file_size",     dbmfi_offsetof(file_size),     F_DETAILED | F_FULL },
103    { "year",          dbmfi_offsetof(year),          F_DETAILED | F_FULL },
104    { "track",         dbmfi_offsetof(track),         F_DETAILED | F_FULL | F_BROWSE },
105    { "total_tracks",  dbmfi_offsetof(total_tracks),  F_DETAILED | F_FULL },
106    { "disc",          dbmfi_offsetof(disc),          F_DETAILED | F_FULL | F_BROWSE },
107    { "total_discs",   dbmfi_offsetof(total_discs),   F_DETAILED | F_FULL },
108    { "bpm",           dbmfi_offsetof(bpm),           F_DETAILED | F_FULL },
109    { "compilation",   dbmfi_offsetof(compilation),   F_DETAILED | F_FULL },
110    { "rating",        dbmfi_offsetof(rating),        F_DETAILED | F_FULL },
111    { "play_count",    dbmfi_offsetof(play_count),    F_DETAILED | F_FULL },
112    { "data_kind",     dbmfi_offsetof(data_kind),     F_DETAILED },
113    { "item_kind",     dbmfi_offsetof(item_kind),     F_DETAILED },
114    { "description",   dbmfi_offsetof(description),   F_DETAILED | F_FULL },
115    { "time_added",    dbmfi_offsetof(time_added),    F_DETAILED | F_FULL },
116    { "time_modified", dbmfi_offsetof(time_modified), F_DETAILED | F_FULL },
117    { "time_played",   dbmfi_offsetof(time_played),   F_DETAILED | F_FULL },
118    { "db_timestamp",  dbmfi_offsetof(db_timestamp),  F_DETAILED },
119    { "disabled",      dbmfi_offsetof(disabled),      F_ALWAYS },
120    { "sample_count",  dbmfi_offsetof(sample_count),  F_DETAILED },
121    { "codectype",     dbmfi_offsetof(codectype),     F_ALWAYS },
122    { "idx",           dbmfi_offsetof(idx),           F_DETAILED },
123    { "has_video",     dbmfi_offsetof(has_video),     F_DETAILED },
124    { "contentrating", dbmfi_offsetof(contentrating), F_DETAILED },
125    { NULL,            0,                             0 }
126  };
127
128
129static struct evbuffer *
130mxml_to_evbuf(mxml_node_t *tree)
131{
132  struct evbuffer *evbuf;
133  char *xml;
134  int ret;
135
136  evbuf = evbuffer_new();
137  if (!evbuf)
138    {
139      DPRINTF(E_LOG, L_RSP, "Could not create evbuffer for RSP reply\n");
140
141      return NULL;
142    }
143
144  xml = mxmlSaveAllocString(tree, MXML_NO_CALLBACK);
145  if (!xml)
146    {
147      DPRINTF(E_LOG, L_RSP, "Could not finalize RSP reply\n");
148
149      evbuffer_free(evbuf);
150      return NULL;
151    }
152
153  ret = evbuffer_add(evbuf, xml, strlen(xml));
154  free(xml);
155  if (ret < 0)
156    {
157      DPRINTF(E_LOG, L_RSP, "Could not load evbuffer for RSP reply\n");
158
159      evbuffer_free(evbuf);
160      return NULL;
161    }
162
163  return evbuf;
164}
165
166/* Forward */
167static void
168rsp_send_error(struct evhttp_request *req, char *errmsg);
169
170static int
171get_query_params(struct evhttp_request *req, struct evkeyvalq *query, struct query_params *qp)
172{
173  const char *param;
174  int ret;
175
176  qp->offset = 0;
177  param = evhttp_find_header(query, "offset");
178  if (param)
179    {
180      ret = safe_atoi32(param, &qp->offset);
181      if (ret < 0)
182	{
183	  rsp_send_error(req, "Invalid offset");
184	  return -1;
185	}
186    }
187
188  qp->limit = 0;
189  param = evhttp_find_header(query, "limit");
190  if (param)
191    {
192      ret = safe_atoi32(param, &qp->limit);
193      if (ret < 0)
194	{
195	  rsp_send_error(req, "Invalid limit");
196	  return -1;
197	}
198    }
199
200  if (qp->offset || qp->limit)
201    qp->idx_type = I_SUB;
202  else
203    qp->idx_type = I_NONE;
204
205  qp->sort = S_NONE;
206
207  param = evhttp_find_header(query, "query");
208  if (param)
209    {
210      DPRINTF(E_DBG, L_RSP, "RSP browse query filter: %s\n", param);
211
212      qp->filter = rsp_query_parse_sql(param);
213      if (!qp->filter)
214	DPRINTF(E_LOG, L_RSP, "Ignoring improper RSP query\n");
215    }
216
217  return 0;
218}
219
220
221static void
222rsp_send_error(struct evhttp_request *req, char *errmsg)
223{
224  struct evbuffer *evbuf;
225  mxml_node_t *reply;
226  mxml_node_t *status;
227  mxml_node_t *node;
228
229  /* We'd use mxmlNewXML(), but then we can't put any attributes
230   * on the root node and we need some.
231   */
232  reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
233
234  node = mxmlNewElement(reply, "response");
235  status = mxmlNewElement(node, "status");
236
237  /* Status block */
238  node = mxmlNewElement(status, "errorcode");
239  mxmlNewText(node, 0, "1");
240
241  node = mxmlNewElement(status, "errorstring");
242  mxmlNewText(node, 0, errmsg);
243
244  node = mxmlNewElement(status, "records");
245  mxmlNewText(node, 0, "0");
246
247  node = mxmlNewElement(status, "totalrecords");
248  mxmlNewText(node, 0, "0");
249
250  evbuf = mxml_to_evbuf(reply);
251  mxmlDelete(reply);
252
253  if (!evbuf)
254    {
255      evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
256
257      return;
258    }
259
260  evhttp_add_header(req->output_headers, "Content-Type", "text/xml; charset=utf-8");
261  evhttp_add_header(req->output_headers, "Connection", "close");
262  evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
263
264  evbuffer_free(evbuf);
265}
266
267static void
268rsp_send_reply(struct evhttp_request *req, mxml_node_t *reply)
269{
270  struct evbuffer *evbuf;
271
272  evbuf = mxml_to_evbuf(reply);
273  mxmlDelete(reply);
274
275  if (!evbuf)
276    {
277      rsp_send_error(req, "Could not finalize reply");
278
279      return;
280    }
281
282  evhttp_add_header(req->output_headers, "Content-Type", "text/xml; charset=utf-8");
283  evhttp_add_header(req->output_headers, "Connection", "close");
284  httpd_send_reply(req, HTTP_OK, "OK", evbuf);
285
286  evbuffer_free(evbuf);
287}
288
289
290static void
291rsp_reply_info(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
292{
293  mxml_node_t *reply;
294  mxml_node_t *status;
295  mxml_node_t *info;
296  mxml_node_t *node;
297  cfg_t *lib;
298  char *library;
299  int songcount;
300
301  songcount = db_files_get_count();
302
303  lib = cfg_getsec(cfg, "library");
304  library = cfg_getstr(lib, "name");
305
306  /* We'd use mxmlNewXML(), but then we can't put any attributes
307   * on the root node and we need some.
308   */
309  reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
310
311  node = mxmlNewElement(reply, "response");
312  status = mxmlNewElement(node, "status");
313  info = mxmlNewElement(node, "info");
314
315  /* Status block */
316  node = mxmlNewElement(status, "errorcode");
317  mxmlNewText(node, 0, "0");
318
319  node = mxmlNewElement(status, "errorstring");
320  mxmlNewText(node, 0, "");
321
322  node = mxmlNewElement(status, "records");
323  mxmlNewText(node, 0, "0");
324
325  node = mxmlNewElement(status, "totalrecords");
326  mxmlNewText(node, 0, "0");
327
328  /* Info block */
329  node = mxmlNewElement(info, "count");
330  mxmlNewTextf(node, 0, "%d", songcount);
331
332  node = mxmlNewElement(info, "rsp-version");
333  mxmlNewText(node, 0, RSP_VERSION);
334
335  node = mxmlNewElement(info, "server-version");
336  mxmlNewText(node, 0, VERSION);
337
338  node = mxmlNewElement(info, "name");
339  mxmlNewText(node, 0, library);
340
341  rsp_send_reply(req, reply);
342}
343
344static void
345rsp_reply_db(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
346{
347  struct query_params qp;
348  struct db_playlist_info dbpli;
349  char **strval;
350  mxml_node_t *reply;
351  mxml_node_t *status;
352  mxml_node_t *pls;
353  mxml_node_t *pl;
354  mxml_node_t *node;
355  int i;
356  int ret;
357
358  memset(&qp, 0, sizeof(struct db_playlist_info));
359
360  qp.type = Q_PL;
361  qp.idx_type = I_NONE;
362
363  ret = db_query_start(&qp);
364  if (ret < 0)
365    {
366      DPRINTF(E_LOG, L_RSP, "Could not start query\n");
367
368      rsp_send_error(req, "Could not start query");
369      return;
370    }
371
372  /* We'd use mxmlNewXML(), but then we can't put any attributes
373   * on the root node and we need some.
374   */
375  reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
376
377  node = mxmlNewElement(reply, "response");
378  status = mxmlNewElement(node, "status");
379  pls = mxmlNewElement(node, "playlists");
380
381  /* Status block */
382  node = mxmlNewElement(status, "errorcode");
383  mxmlNewText(node, 0, "0");
384
385  node = mxmlNewElement(status, "errorstring");
386  mxmlNewText(node, 0, "");
387
388  node = mxmlNewElement(status, "records");
389  mxmlNewTextf(node, 0, "%d", qp.results);
390
391  node = mxmlNewElement(status, "totalrecords");
392  mxmlNewTextf(node, 0, "%d", qp.results);
393
394  /* Playlists block (all playlists) */
395  while (((ret = db_query_fetch_pl(&qp, &dbpli)) == 0) && (dbpli.id))
396    {
397      /* Playlist block (one playlist) */
398      pl = mxmlNewElement(pls, "playlist");
399
400      for (i = 0; pl_fields[i].field; i++)
401	{
402	  if (pl_fields[i].flags & F_FULL)
403	    {
404	      strval = (char **) ((char *)&dbpli + pl_fields[i].offset);
405
406	      node = mxmlNewElement(pl, pl_fields[i].field);
407	      mxmlNewText(node, 0, *strval);
408            }
409        }
410    }
411
412  if (ret < 0)
413    {
414      DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
415
416      mxmlDelete(reply);
417      db_query_end(&qp);
418      rsp_send_error(req, "Error fetching query results");
419      return;
420    }
421
422  /* HACK
423   * Add a dummy empty string to the playlists element if there is no data
424   * to return - this prevents mxml from sending out an empty <playlists/>
425   * tag that the SoundBridge does not handle. It's hackish, but it works.
426   */
427  if (qp.results == 0)
428    mxmlNewText(pls, 0, "");
429
430  db_query_end(&qp);
431
432  rsp_send_reply(req, reply);
433}
434
435static void
436rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
437{
438  struct query_params qp;
439  struct db_media_file_info dbmfi;
440  const char *param;
441  char **strval;
442  mxml_node_t *reply;
443  mxml_node_t *status;
444  mxml_node_t *items;
445  mxml_node_t *item;
446  mxml_node_t *node;
447  int mode;
448  int records;
449  int transcode;
450  int32_t bitrate;
451  int i;
452  int ret;
453
454  memset(&qp, 0, sizeof(struct query_params));
455
456  ret = safe_atoi32(uri[2], &qp.id);
457  if (ret < 0)
458    {
459      rsp_send_error(req, "Invalid playlist ID");
460      return;
461    }
462
463  if (qp.id == 0)
464    qp.type = Q_ITEMS;
465  else
466    qp.type = Q_PLITEMS;
467
468  mode = F_FULL;
469  param = evhttp_find_header(query, "type");
470  if (param)
471    {
472      if (strcasecmp(param, "full") == 0)
473	mode = F_FULL;
474      else if (strcasecmp(param, "browse") == 0)
475	mode = F_BROWSE;
476      else if (strcasecmp(param, "id") == 0)
477	mode = F_ID;
478      else if (strcasecmp(param, "detailed") == 0)
479	mode = F_DETAILED;
480      else
481	DPRINTF(E_LOG, L_RSP, "Unknown browse mode %s\n", param);
482    }
483
484  ret = get_query_params(req, query, &qp);
485  if (ret < 0)
486    return;
487
488  ret = db_query_start(&qp);
489  if (ret < 0)
490    {
491      DPRINTF(E_LOG, L_RSP, "Could not start query\n");
492
493      rsp_send_error(req, "Could not start query");
494
495      if (qp.filter)
496	free(qp.filter);
497      return;
498    }
499
500  if (qp.offset > qp.results)
501    records = 0;
502  else if (qp.limit > (qp.results - qp.offset))
503    records = qp.results - qp.offset;
504  else
505    records = qp.limit;
506
507  /* We'd use mxmlNewXML(), but then we can't put any attributes
508   * on the root node and we need some.
509   */
510  reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
511
512  node = mxmlNewElement(reply, "response");
513  status = mxmlNewElement(node, "status");
514  items = mxmlNewElement(node, "items");
515
516  /* Status block */
517  node = mxmlNewElement(status, "errorcode");
518  mxmlNewText(node, 0, "0");
519
520  node = mxmlNewElement(status, "errorstring");
521  mxmlNewText(node, 0, "");
522
523  node = mxmlNewElement(status, "records");
524  mxmlNewTextf(node, 0, "%d", records);
525
526  node = mxmlNewElement(status, "totalrecords");
527  mxmlNewTextf(node, 0, "%d", qp.results);
528
529  /* Items block (all items) */
530  while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id))
531    {
532      transcode = transcode_needed(req->input_headers, dbmfi.codectype);
533
534      /* Item block (one item) */
535      item = mxmlNewElement(items, "item");
536
537      for (i = 0; rsp_fields[i].field; i++)
538	{
539	  if (!(rsp_fields[i].flags & mode))
540	    continue;
541
542	  strval = (char **) ((char *)&dbmfi + rsp_fields[i].offset);
543
544	  if (!(*strval) || (strlen(*strval) == 0))
545	    continue;
546
547	  node = mxmlNewElement(item, rsp_fields[i].field);
548
549	  if (!transcode)
550	    mxmlNewText(node, 0, *strval);
551	  else
552	    {
553	      switch (rsp_fields[i].offset)
554		{
555		  case dbmfi_offsetof(type):
556		    mxmlNewText(node, 0, "wav");
557		    break;
558
559		  case dbmfi_offsetof(bitrate):
560		    bitrate = 0;
561		    ret = safe_atoi32(dbmfi.samplerate, &bitrate);
562		    if ((ret < 0) || (bitrate == 0))
563		      bitrate = 1411;
564		    else
565		      bitrate = (bitrate * 8) / 250;
566
567		    mxmlNewTextf(node, 0, "%d", bitrate);
568		    break;
569
570		  case dbmfi_offsetof(description):
571		    mxmlNewText(node, 0, "wav audio file");
572		    break;
573
574		  case dbmfi_offsetof(codectype):
575		    mxmlNewText(node, 0, "wav");
576
577		    node = mxmlNewElement(item, "original_codec");
578		    mxmlNewText(node, 0, *strval);
579		    break;
580
581		  default:
582		    mxmlNewText(node, 0, *strval);
583		    break;
584		}
585	    }
586	}
587    }
588
589  if (qp.filter)
590    free(qp.filter);
591
592  if (ret < 0)
593    {
594      DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
595
596      mxmlDelete(reply);
597      db_query_end(&qp);
598      rsp_send_error(req, "Error fetching query results");
599      return;
600    }
601
602  /* HACK
603   * Add a dummy empty string to the items element if there is no data
604   * to return - this prevents mxml from sending out an empty <items/>
605   * tag that the SoundBridge does not handle. It's hackish, but it works.
606   */
607  if (qp.results == 0)
608    mxmlNewText(items, 0, "");
609
610  db_query_end(&qp);
611
612  rsp_send_reply(req, reply);
613}
614
615static void
616rsp_reply_browse(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
617{
618  struct query_params qp;
619  char *browse_item;
620  mxml_node_t *reply;
621  mxml_node_t *status;
622  mxml_node_t *items;
623  mxml_node_t *node;
624  int records;
625  int ret;
626
627  memset(&qp, 0, sizeof(struct query_params));
628
629  if (strcmp(uri[3], "artist") == 0)
630    qp.type = Q_BROWSE_ARTISTS;
631  else if (strcmp(uri[3], "genre") == 0)
632    qp.type = Q_BROWSE_GENRES;
633  else if (strcmp(uri[3], "album") == 0)
634    qp.type = Q_BROWSE_ALBUMS;
635  else if (strcmp(uri[3], "composer") == 0)
636    qp.type = Q_BROWSE_COMPOSERS;
637  else
638    {
639      DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", uri[3]);
640
641      rsp_send_error(req, "Unsupported browse type");
642      return;
643    }
644
645  ret = safe_atoi32(uri[2], &qp.id);
646  if (ret < 0)
647    {
648      rsp_send_error(req, "Invalid playlist ID");
649      return;
650    }
651
652  ret = get_query_params(req, query, &qp);
653  if (ret < 0)
654    return;
655
656  ret = db_query_start(&qp);
657  if (ret < 0)
658    {
659      DPRINTF(E_LOG, L_RSP, "Could not start query\n");
660
661      rsp_send_error(req, "Could not start query");
662
663      if (qp.filter)
664	free(qp.filter);
665      return;
666    }
667
668  if (qp.offset > qp.results)
669    records = 0;
670  else if (qp.limit > (qp.results - qp.offset))
671    records = qp.results - qp.offset;
672  else
673    records = qp.limit;
674
675  /* We'd use mxmlNewXML(), but then we can't put any attributes
676   * on the root node and we need some.
677   */
678  reply = mxmlNewElement(MXML_NO_PARENT, RSP_XML_ROOT);
679
680  node = mxmlNewElement(reply, "response");
681  status = mxmlNewElement(node, "status");
682  items = mxmlNewElement(node, "items");
683
684  /* Status block */
685  node = mxmlNewElement(status, "errorcode");
686  mxmlNewText(node, 0, "0");
687
688  node = mxmlNewElement(status, "errorstring");
689  mxmlNewText(node, 0, "");
690
691  node = mxmlNewElement(status, "records");
692  mxmlNewTextf(node, 0, "%d", records);
693
694  node = mxmlNewElement(status, "totalrecords");
695  mxmlNewTextf(node, 0, "%d", qp.results);
696
697  /* Items block (all items) */
698  while (((ret = db_query_fetch_string(&qp, &browse_item)) == 0) && (browse_item))
699    {
700      node = mxmlNewElement(items, "item");
701      mxmlNewText(node, 0, browse_item);
702    }
703
704  if (qp.filter)
705    free(qp.filter);
706
707  if (ret < 0)
708    {
709      DPRINTF(E_LOG, L_RSP, "Error fetching results\n");
710
711      mxmlDelete(reply);
712      db_query_end(&qp);
713      rsp_send_error(req, "Error fetching query results");
714      return;
715    }
716
717  /* HACK
718   * Add a dummy empty string to the items element if there is no data
719   * to return - this prevents mxml from sending out an empty <items/>
720   * tag that the SoundBridge does not handle. It's hackish, but it works.
721   */
722  if (qp.results == 0)
723    mxmlNewText(items, 0, "");
724
725  db_query_end(&qp);
726
727  rsp_send_reply(req, reply);
728}
729
730static void
731rsp_stream(struct evhttp_request *req, char **uri, struct evkeyvalq *query)
732{
733  int id;
734  int ret;
735
736  ret = safe_atoi32(uri[2], &id);
737  if (ret < 0)
738    evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
739  else
740    httpd_stream_file(req, id);
741}
742
743
744static struct uri_map rsp_handlers[] =
745  {
746    {
747      .regexp = "^/rsp/info$",
748      .handler = rsp_reply_info
749    },
750    {
751      .regexp = "^/rsp/db$",
752      .handler = rsp_reply_db
753    },
754    {
755      .regexp = "^/rsp/db/[[:digit:]]+$",
756      .handler = rsp_reply_playlist
757    },
758    {
759      .regexp = "^/rsp/db/[[:digit:]]+/[^/]+$",
760      .handler = rsp_reply_browse
761    },
762    {
763      .regexp = "^/rsp/stream/[[:digit:]]+$",
764      .handler = rsp_stream
765    },
766    {
767      .regexp = NULL,
768      .handler = NULL
769    }
770  };
771
772
773void
774rsp_request(struct evhttp_request *req)
775{
776  char *full_uri;
777  char *uri;
778  char *ptr;
779  char *uri_parts[5];
780  struct evkeyvalq query;
781  cfg_t *lib;
782  char *libname;
783  char *passwd;
784  int handler;
785  int i;
786  int ret;
787
788  memset(&query, 0, sizeof(struct evkeyvalq));
789
790  full_uri = httpd_fixup_uri(req);
791  if (!full_uri)
792    {
793      rsp_send_error(req, "Server error");
794      return;
795    }
796
797  ptr = strchr(full_uri, '?');
798  if (ptr)
799    *ptr = '\0';
800
801  uri = strdup(full_uri);
802  if (!uri)
803    {
804      rsp_send_error(req, "Server error");
805
806      free(full_uri);
807      return;
808    }
809
810  if (ptr)
811    *ptr = '?';
812
813  ptr = uri;
814  uri = evhttp_decode_uri(uri);
815  free(ptr);
816
817  DPRINTF(E_DBG, L_RSP, "RSP request: %s\n", full_uri);
818
819  handler = -1;
820  for (i = 0; rsp_handlers[i].handler; i++)
821    {
822      ret = regexec(&rsp_handlers[i].preg, uri, 0, NULL, 0);
823      if (ret == 0)
824	{
825	  handler = i;
826	  break;
827	}
828    }
829
830  if (handler < 0)
831    {
832      DPRINTF(E_LOG, L_RSP, "Unrecognized RSP request\n");
833
834      rsp_send_error(req, "Bad path");
835
836      free(uri);
837      free(full_uri);
838      return;
839    }
840
841  /* Check authentication */
842  lib = cfg_getsec(cfg, "library");
843  passwd = cfg_getstr(lib, "password");
844  if (passwd)
845    {
846      libname = cfg_getstr(lib, "name");
847
848      DPRINTF(E_DBG, L_HTTPD, "Checking authentication for library '%s'\n", libname);
849
850      /* We don't care about the username */
851      ret = httpd_basic_auth(req, NULL, passwd, libname);
852      if (ret != 0)
853	{
854	  free(uri);
855	  free(full_uri);
856	  return;
857	}
858
859      DPRINTF(E_DBG, L_HTTPD, "Library authentication successful\n");
860    }
861
862  memset(uri_parts, 0, sizeof(uri_parts));
863
864  uri_parts[0] = strtok_r(uri, "/", &ptr);
865  for (i = 1; (i < sizeof(uri_parts) / sizeof(uri_parts[0])) && uri_parts[i - 1]; i++)
866    {
867      uri_parts[i] = strtok_r(NULL, "/", &ptr);
868    }
869
870  if (!uri_parts[0] || uri_parts[i - 1] || (i < 2))
871    {
872      DPRINTF(E_LOG, L_RSP, "RSP URI has too many/few components (%d)\n", (uri_parts[0]) ? i : 0);
873
874      rsp_send_error(req, "Bad path");
875
876      free(uri);
877      free(full_uri);
878      return;
879    }
880
881  evhttp_parse_query(full_uri, &query);
882
883  rsp_handlers[handler].handler(req, uri_parts, &query);
884
885  evhttp_clear_headers(&query);
886  free(uri);
887  free(full_uri);
888}
889
890int
891rsp_is_request(struct evhttp_request *req, char *uri)
892{
893  if (strncmp(uri, "/rsp/", strlen("/rsp/")) == 0)
894    return 1;
895
896  return 0;
897}
898
899int
900rsp_init(void)
901{
902  char buf[64];
903  int i;
904  int ret;
905
906  for (i = 0; rsp_handlers[i].handler; i++)
907    {
908      ret = regcomp(&rsp_handlers[i].preg, rsp_handlers[i].regexp, REG_EXTENDED | REG_NOSUB);
909      if (ret != 0)
910        {
911          regerror(ret, &rsp_handlers[i].preg, buf, sizeof(buf));
912
913          DPRINTF(E_FATAL, L_RSP, "RSP init failed; regexp error: %s\n", buf);
914	  return -1;
915        }
916    }
917
918  return 0;
919}
920
921void
922rsp_deinit(void)
923{
924  int i;
925
926  for (i = 0; rsp_handlers[i].handler; i++)
927    regfree(&rsp_handlers[i].preg);
928}
929