/*
 * Copyright (C) 2009-2011 Julien BLACHE <jb@jblache.org>
 *
 * Adapted from mt-daapd:
 * Copyright (C) 2006-2007 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <limits.h>

#include "httpd_internal.h"
#include "logger.h"
#include "db.h"
#include "conffile.h"
#include "misc.h"
#include "misc_xml.h"
#include "transcode.h"
#include "parsers/rsp_parser.h"

#define RSP_VERSION "1.0"
#define RSP_XML_DECLARATION "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>"

#define F_FULL     (1 << 0)
#define F_BROWSE   (1 << 1)
#define F_ID       (1 << 2)
#define F_DETAILED (1 << 3)
#define F_ALWAYS   (F_FULL | F_BROWSE | F_ID | F_DETAILED)

struct field_map {
  char *field;
  size_t offset;
  int flags;
};

static char rsp_filter_files[32];

static const struct field_map pl_fields[] =
  {
    { "id",           dbpli_offsetof(id),           F_ALWAYS },
    { "title",        dbpli_offsetof(title),        F_FULL | F_BROWSE | F_DETAILED },
    { "type",         dbpli_offsetof(type),         F_DETAILED },
    { "items",        dbpli_offsetof(items),        F_FULL | F_BROWSE | F_DETAILED },
    { "query",        dbpli_offsetof(query),        F_DETAILED },
    { "db_timestamp", dbpli_offsetof(db_timestamp), F_DETAILED },
    { "path",         dbpli_offsetof(path),         F_DETAILED },
    { "index",        dbpli_offsetof(index),        F_DETAILED },
    { NULL,           0,                            0 }
  };

static const struct field_map rsp_fields[] =
  {
    { "id",            dbmfi_offsetof(id),            F_ALWAYS },
    { "path",          dbmfi_offsetof(path),          F_DETAILED },
    { "fname",         dbmfi_offsetof(fname),         F_DETAILED },
    { "title",         dbmfi_offsetof(title),         F_ALWAYS },
    { "artist",        dbmfi_offsetof(artist),        F_DETAILED | F_FULL | F_BROWSE },
    { "album",         dbmfi_offsetof(album),         F_DETAILED | F_FULL | F_BROWSE },
    { "genre",         dbmfi_offsetof(genre),         F_DETAILED | F_FULL },
    { "comment",       dbmfi_offsetof(comment),       F_DETAILED | F_FULL },
    { "type",          dbmfi_offsetof(type),          F_ALWAYS },
    { "composer",      dbmfi_offsetof(composer),      F_DETAILED | F_FULL },
    { "orchestra",     dbmfi_offsetof(orchestra),     F_DETAILED | F_FULL },
    { "conductor",     dbmfi_offsetof(conductor),     F_DETAILED | F_FULL },
    { "url",           dbmfi_offsetof(url),           F_DETAILED | F_FULL },
    { "bitrate",       dbmfi_offsetof(bitrate),       F_DETAILED | F_FULL },
    { "samplerate",    dbmfi_offsetof(samplerate),    F_DETAILED | F_FULL },
    { "song_length",   dbmfi_offsetof(song_length),   F_DETAILED | F_FULL },
    { "file_size",     dbmfi_offsetof(file_size),     F_DETAILED | F_FULL },
    { "year",          dbmfi_offsetof(year),          F_DETAILED | F_FULL },
    { "track",         dbmfi_offsetof(track),         F_DETAILED | F_FULL | F_BROWSE },
    { "total_tracks",  dbmfi_offsetof(total_tracks),  F_DETAILED | F_FULL },
    { "disc",          dbmfi_offsetof(disc),          F_DETAILED | F_FULL | F_BROWSE },
    { "total_discs",   dbmfi_offsetof(total_discs),   F_DETAILED | F_FULL },
    { "bpm",           dbmfi_offsetof(bpm),           F_DETAILED | F_FULL },
    { "compilation",   dbmfi_offsetof(compilation),   F_DETAILED | F_FULL },
    { "rating",        dbmfi_offsetof(rating),        F_DETAILED | F_FULL },
    { "play_count",    dbmfi_offsetof(play_count),    F_DETAILED | F_FULL },
    { "skip_count",    dbmfi_offsetof(skip_count),    F_DETAILED | F_FULL },
    { "data_kind",     dbmfi_offsetof(data_kind),     F_DETAILED },
    { "item_kind",     dbmfi_offsetof(item_kind),     F_DETAILED },
    { "description",   dbmfi_offsetof(description),   F_DETAILED | F_FULL },
    { "time_added",    dbmfi_offsetof(time_added),    F_DETAILED | F_FULL },
    { "time_modified", dbmfi_offsetof(time_modified), F_DETAILED | F_FULL },
    { "time_played",   dbmfi_offsetof(time_played),   F_DETAILED | F_FULL },
    { "time_skipped",  dbmfi_offsetof(time_skipped),  F_DETAILED | F_FULL },
    { "db_timestamp",  dbmfi_offsetof(db_timestamp),  F_DETAILED },
    { "disabled",      dbmfi_offsetof(disabled),      F_ALWAYS },
    { "sample_count",  dbmfi_offsetof(sample_count),  F_DETAILED },
    { "codectype",     dbmfi_offsetof(codectype),     F_ALWAYS },
    { "idx",           dbmfi_offsetof(idx),           F_DETAILED },
    { "has_video",     dbmfi_offsetof(has_video),     F_DETAILED },
    { "contentrating", dbmfi_offsetof(contentrating), F_DETAILED },
    { NULL,            0,                             0 }
  };


/* -------------------------------- HELPERS --------------------------------- */

static int
xml_to_evbuf(struct evbuffer *evbuf, xml_node *tree)
{
  char *xml;
  int ret;

  xml = xml_to_string(tree, RSP_XML_DECLARATION);
  if (!xml)
    {
      DPRINTF(E_LOG, L_RSP, "Could not finalize RSP reply\n");
      return -1;
    }

  ret = evbuffer_add(evbuf, xml, strlen(xml));
  free(xml);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_RSP, "Could not load evbuffer for RSP reply\n");
      return -1;
    }

  return 0;
}

static int
rsp_xml_response_new(xml_node **response_ptr, int errorcode, const char *errorstring, int records, int totalrecords)
{
  xml_node *node;
  xml_node *response = xml_new_node(NULL, "response", NULL);
  xml_node *status = xml_new_node(response, "status", NULL);

  if (!response || !status)
    return -1;

  xml_new_node_textf(status, "errorcode", "%d", errorcode);
  node = xml_new_node(status, "errorstring", errorstring);
  if (errorstring && *errorstring == '\0')
    xml_new_text(node, ""); // Prevents sending <errorstring/> which the Soundbridge may not understand

  xml_new_node_textf(status, "records", "%d", records);
  xml_new_node_textf(status, "totalrecords", "%d", totalrecords);

  if (response_ptr)
    *response_ptr = response;

  return 0;
}

static void
rsp_send_error(struct httpd_request *hreq, char *errmsg)
{
  xml_node *response = NULL;
  int ret;

  CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 1, errmsg, 0, 0));

  ret = xml_to_evbuf(hreq->out_body, response);
  if (ret < 0)
    goto error;

  httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8");
  httpd_header_add(hreq->out_headers, "Connection", "close");

  httpd_send_reply(hreq, HTTP_OK, "OK", HTTPD_SEND_NO_GZIP);

  xml_free(response);
  return;

 error:
  httpd_send_error(hreq, HTTP_SERVUNAVAIL, "Internal Server Error");
  xml_free(response);
}

static int
query_params_set(struct query_params *qp, struct httpd_request *hreq)
{
  struct rsp_result parse_result;
  const char *param;
  char query[1024];
  int ret;

  qp->offset = 0;
  param = httpd_query_value_find(hreq->query, "offset");
  if (param)
    {
      ret = safe_atoi32(param, &qp->offset);
      if (ret < 0)
	{
	  rsp_send_error(hreq, "Invalid offset");
	  return -1;
	}
    }

  qp->limit = 0;
  param = httpd_query_value_find(hreq->query, "limit");
  if (param)
    {
      ret = safe_atoi32(param, &qp->limit);
      if (ret < 0)
	{
	  rsp_send_error(hreq, "Invalid limit");
	  return -1;
	}
    }

  if (qp->offset || qp->limit)
    qp->idx_type = I_SUB;
  else
    qp->idx_type = I_NONE;

  qp->filter = NULL;
  param = httpd_query_value_find(hreq->query, "query");
  if (param)
    {
      ret = snprintf(query, sizeof(query), "%s", param);
      if (ret < 0 || ret >= sizeof(query))
	{
	  DPRINTF(E_LOG, L_RSP, "RSP query is too large for buffer: %s\n", param);
	  return -1;
	}

      // This is hack to work around the fact that we return album artists in
      // the artist lists, but the query from the speaker will just be artist.
      // It would probably be better to do this in the RSP lexer/parser.
      ret = safe_snreplace(query, sizeof(query), "artist=\"", "album_artist=\"");
      if (ret < 0)
	{
	  DPRINTF(E_LOG, L_RSP, "RSP query is too large for buffer: %s\n", param);
	  return -1;
	}

      if (rsp_lex_parse(&parse_result, query) != 0)
	DPRINTF(E_LOG, L_RSP, "Ignoring improper RSP query: %s\n", query);
      else
	qp->filter = safe_asprintf("(%s) AND %s", parse_result.str, rsp_filter_files);
    }

  // Always filter to include only files (not streams and Spotify)
  if (!qp->filter)
    qp->filter = strdup(rsp_filter_files);

  return 0;
}

static void
rsp_send_reply(struct httpd_request *hreq, xml_node *reply)
{
  int ret;

  ret = xml_to_evbuf(hreq->out_body, reply);
  xml_free(reply);

  if (ret < 0)
    {
      rsp_send_error(hreq, "Could not finalize reply");
      return;
    }

  httpd_header_add(hreq->out_headers, "Content-Type", "text/xml; charset=utf-8");
  httpd_header_add(hreq->out_headers, "Connection", "close");

  httpd_send_reply(hreq, HTTP_OK, "OK", 0);
}

static int
rsp_request_authorize(struct httpd_request *hreq)
{
  char *passwd;
  int ret;

  if (httpd_request_is_trusted(hreq))
    return 0;

  passwd = cfg_getstr(cfg_getsec(cfg, "library"), "password");
  if (!passwd)
    return 0;

  DPRINTF(E_DBG, L_RSP, "Checking authentication for library\n");

  // We don't care about the username
  ret = httpd_basic_auth(hreq, NULL, passwd, cfg_getstr(cfg_getsec(cfg, "library"), "name"));
  if (ret != 0)
    {
      DPRINTF(E_LOG, L_RSP, "Unsuccessful library authorization attempt from '%s'\n", hreq->peer_address);
      return -1;
    }

  return 0;
}


/* --------------------------- REPLY HANDLERS ------------------------------- */

static int
rsp_reply_info(struct httpd_request *hreq)
{
  xml_node *response;
  xml_node *info;
  cfg_t *lib;
  char *library;
  uint32_t songcount;

  db_files_get_count(&songcount, NULL, rsp_filter_files);

  lib = cfg_getsec(cfg, "library");
  library = cfg_getstr(lib, "name");

  CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", 0, 0));

  info = xml_new_node(response, "info", NULL);

  xml_new_node_textf(info, "count", "%d", (int)songcount);
  xml_new_node(info, "rsp-version", RSP_VERSION);
  xml_new_node(info, "server-version", VERSION);
  xml_new_node(info, "name", library);

  rsp_send_reply(hreq, response);
  return 0;
}

static int
rsp_reply_db(struct httpd_request *hreq)
{
  struct query_params qp;
  struct db_playlist_info dbpli;
  char **strval;
  xml_node *response;
  xml_node *pls;
  xml_node *pl;
  int i;
  int ret;

  memset(&qp, 0, sizeof(struct query_params));

  qp.type = Q_PL;
  qp.idx_type = I_NONE;

  ret = db_query_start(&qp);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_RSP, "Could not start query\n");

      rsp_send_error(hreq, "Could not start query");
      return -1;
    }

  CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", qp.results, qp.results));

  pls = xml_new_node(response, "playlists", NULL);

  /* Playlists block (all playlists) */
  while (((ret = db_query_fetch_pl(&dbpli, &qp)) == 0) && (dbpli.id))
    {
      // Skip non-local playlists, can't be streamed to the device
      if (!dbpli.path || dbpli.path[0] != '/')
	continue;

      /* Playlist block (one playlist) */
      pl = xml_new_node(pls, "playlist", NULL);

      for (i = 0; pl_fields[i].field; i++)
	{
	  if (pl_fields[i].flags & F_FULL)
	    {
	      strval = (char **) ((char *)&dbpli + pl_fields[i].offset);

	      xml_new_node(pl, pl_fields[i].field, *strval);
            }
        }
    }

  if (ret < 0)
    {
      DPRINTF(E_LOG, L_RSP, "Error fetching results\n");

      xml_free(response);
      db_query_end(&qp);
      rsp_send_error(hreq, "Error fetching query results");
      return -1;
    }

  /* HACK
   * Add a dummy empty string to the playlists element if there is no data
   * to return - this prevents us from sending out an empty <playlists/>
   * tag that the SoundBridge does not handle. It's hackish, but it works.
   */
  if (qp.results == 0)
    xml_new_text(pls, "");

  db_query_end(&qp);

  rsp_send_reply(hreq, response);

  return 0;
}

static int
item_add(xml_node *parent, struct query_params *qp, enum transcode_profile spk_profile, const char *user_agent, const char *accept_codecs, int mode)
{
  struct media_quality quality = { 0 };
  struct db_media_file_info dbmfi;
  struct transcode_metadata_string xcode_metadata;
  enum transcode_profile profile;
  const char *orgcodec = NULL;
  uint32_t len_ms;
  xml_node *item;
  char **strval;
  int ret;
  int i;

  ret = db_query_fetch_file(&dbmfi, qp);
  if (ret != 0)
    return ret;

  profile = transcode_needed(user_agent, accept_codecs, dbmfi.codectype);
  if (profile == XCODE_UNKNOWN)
    {
      DPRINTF(E_LOG, L_DAAP, "Cannot transcode '%s', codec type is unknown\n", dbmfi.fname);
    }
  else if (profile != XCODE_NONE)
    {
      if (spk_profile != XCODE_NONE)
	profile = spk_profile; // User has configured a specific transcode format for this speaker

      orgcodec = dbmfi.codectype;

      if (safe_atou32(dbmfi.song_length, &len_ms) < 0)
        len_ms = 3 * 60 * 1000; // just a fallback default

      safe_atoi32(dbmfi.samplerate, &quality.sample_rate);
      safe_atoi32(dbmfi.bits_per_sample, &quality.bits_per_sample);
      safe_atoi32(dbmfi.channels, &quality.channels);
      quality.bit_rate = cfg_getint(cfg_getsec(cfg, "streaming"), "bit_rate");

      transcode_metadata_strings_set(&xcode_metadata, profile, &quality, len_ms);
      dbmfi.type        = xcode_metadata.type;
      dbmfi.codectype   = xcode_metadata.codectype;
      dbmfi.description = xcode_metadata.description;
      dbmfi.file_size   = xcode_metadata.file_size;
      dbmfi.bitrate     = xcode_metadata.bitrate;
    }

  // Now add block with content
  item = xml_new_node(parent, "item", NULL);

  for (i = 0; rsp_fields[i].field; i++)
    {
      if (!(rsp_fields[i].flags & mode))
	continue;

      strval = (char **) ((char *)&dbmfi + rsp_fields[i].offset);
      if (!(*strval) || (strlen(*strval) == 0))
	continue;

      xml_new_node(item, rsp_fields[i].field, *strval);

      // In case we are transcoding
      if (rsp_fields[i].offset == dbmfi_offsetof(codectype) && orgcodec)
	xml_new_node(item, "original_codec", orgcodec);
    }

  return 0;
}

static int
rsp_reply_playlist(struct httpd_request *hreq)
{
  struct query_params qp;
  const char *param;
  const char *accept_codecs;
  enum transcode_profile spk_profile;
  xml_node *response;
  xml_node *items;
  int mode;
  int records;
  int ret;

  memset(&qp, 0, sizeof(struct query_params));

  accept_codecs = httpd_header_find(hreq->in_headers, "Accept-Codecs");
  spk_profile = httpd_xcode_profile_get(hreq);

  ret = safe_atoi32(hreq->path_parts[2], &qp.id);
  if (ret < 0)
    {
      rsp_send_error(hreq, "Invalid playlist ID");
      return -1;
    }

  if (qp.id == 0)
    qp.type = Q_ITEMS;
  else
    qp.type = Q_PLITEMS;

  qp.sort = S_NAME;

  mode = F_FULL;
  param = httpd_query_value_find(hreq->query, "type");
  if (param)
    {
      if (strcasecmp(param, "full") == 0)
	mode = F_FULL;
      else if (strcasecmp(param, "browse") == 0)
	mode = F_BROWSE;
      else if (strcasecmp(param, "id") == 0)
	mode = F_ID;
      else if (strcasecmp(param, "detailed") == 0)
	mode = F_DETAILED;
      else
	DPRINTF(E_LOG, L_RSP, "Unknown browse mode %s\n", param);
    }

  ret = query_params_set(&qp, hreq);
  if (ret < 0)
    return -1;

  ret = db_query_start(&qp);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_RSP, "Could not start query\n");

      rsp_send_error(hreq, "Could not start query");

      if (qp.filter)
	free(qp.filter);
      return -1;
    }

  if (qp.offset > qp.results)
    records = 0;
  else
    records = qp.results - qp.offset;

  if (qp.limit && (records > qp.limit))
    records = qp.limit;

  CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", records, qp.results));

  // Add a parent items block (all items), and then one item per file
  items = xml_new_node(response, "items", NULL);
  do
    {
      ret = item_add(items, &qp, spk_profile, hreq->user_agent, accept_codecs, mode);
    }
  while (ret == 0);

  free(qp.filter);

  if (ret < 0)
    {
      DPRINTF(E_LOG, L_RSP, "Error fetching results\n");

      xml_free(response);
      db_query_end(&qp);
      rsp_send_error(hreq, "Error fetching query results");
      return -1;
    }

  /* HACK
   * Add a dummy empty string to the items element if there is no data
   * to return - this prevents us from sending out an empty <items/>
   * tag that the SoundBridge does not handle. It's hackish, but it works.
   */
  if (qp.results == 0)
    xml_new_text(items, "");

  db_query_end(&qp);

  rsp_send_reply(hreq, response);

  return 0;
}

static int
rsp_reply_browse(struct httpd_request *hreq)
{
  struct query_params qp;
  char *browse_item;
  xml_node *response;
  xml_node *items;
  int records;
  int ret;

  memset(&qp, 0, sizeof(struct query_params));

  if (strcmp(hreq->path_parts[3], "artist") == 0)
    {
      qp.type = Q_BROWSE_ARTISTS;
    }
  else if (strcmp(hreq->path_parts[3], "genre") == 0)
    {
      qp.type = Q_BROWSE_GENRES;
    }
  else if (strcmp(hreq->path_parts[3], "album") == 0)
    {
      qp.type = Q_BROWSE_ALBUMS;
    }
  else if (strcmp(hreq->path_parts[3], "composer") == 0)
    {
      qp.type = Q_BROWSE_COMPOSERS;
    }
  else
    {
      DPRINTF(E_LOG, L_RSP, "Unsupported browse type '%s'\n", hreq->path_parts[3]);

      rsp_send_error(hreq, "Unsupported browse type");
      return -1;
    }

  ret = safe_atoi32(hreq->path_parts[2], &qp.id);
  if (ret < 0)
    {
      rsp_send_error(hreq, "Invalid playlist ID");
      return -1;
    }

  ret = query_params_set(&qp, hreq);
  if (ret < 0)
    return -1;

  ret = db_query_start(&qp);
  if (ret < 0)
    {
      DPRINTF(E_LOG, L_RSP, "Could not start query\n");

      rsp_send_error(hreq, "Could not start query");

      if (qp.filter)
	free(qp.filter);
      return -1;
    }

  if (qp.offset > qp.results)
    records = 0;
  else
    records = qp.results - qp.offset;

  if (qp.limit && (records > qp.limit))
    records = qp.limit;

  CHECK_ERR(L_RSP, rsp_xml_response_new(&response, 0, "", records, qp.results));

  items = xml_new_node(response, "items", NULL);

  /* Items block (all items) */
  while (((ret = db_query_fetch_string(&browse_item, &qp)) == 0) && (browse_item))
    {
      xml_new_node(items, "item", browse_item);
    }

  if (qp.filter)
    free(qp.filter);

  if (ret < 0)
    {
      DPRINTF(E_LOG, L_RSP, "Error fetching results\n");

      xml_free(response);
      db_query_end(&qp);
      rsp_send_error(hreq, "Error fetching query results");
      return -1;
    }

  /* HACK
   * Add a dummy empty string to the items element if there is no data
   * to return - this prevents us from sending out an empty <items/>
   * tag that the SoundBridge does not handle. It's hackish, but it works.
   */
  if (qp.results == 0)
    xml_new_text(items, "");

  db_query_end(&qp);

  rsp_send_reply(hreq, response);

  return 0;
}

static int
rsp_stream(struct httpd_request *hreq)
{
  int id;
  int ret;

  ret = safe_atoi32(hreq->path_parts[2], &id);
  if (ret < 0)
    {
      httpd_send_error(hreq, HTTP_BADREQUEST, "Bad Request");
      return -1;
    }

  httpd_stream_file(hreq, id);

  return 0;
}

// Sample RSP requests:
//  /rsp/info
//  /rsp/db
//  /rsp/db/13?type=id
//  /rsp/db/0/artist?type=browse
//  /rsp/db/0/album?query=artist%3D%22Sting%22&type=browse
//  /rsp/db/0?query=artist%3D%22Sting%22%20and%20album%3D%22...All%20This%20Time%22&type=browse
//  /rsp/db/0?query=id%3D36364&type=full
//  /rsp/stream/36364
//  /rsp/db/0?query=id%3D36365&type=full
//  /rsp/stream/36365
//
// Headers sent from Roku M2000 and M1001 in stream requests (and other?):
//
// 'User-Agent': 'Roku SoundBridge/3.0'
// 'Host': '192.168.1.119:3689'
// 'Accept': '*/*'
// 'Pragma': 'no-cache'
// 'accept-codecs': 'wma,mpeg,wav,mp4a,alac'
// 'rsp-version': '0.1'
// 'transcode-codecs': 'wav,mp3'
static struct httpd_uri_map rsp_handlers[] =
  {
    {
      .regexp = "^/rsp/info$",
      .handler = rsp_reply_info
    },
    {
      .regexp = "^/rsp/db$",
      .handler = rsp_reply_db
    },
    {
      .regexp = "^/rsp/db/[[:digit:]]+$",
      .handler = rsp_reply_playlist
    },
    {
      .regexp = "^/rsp/db/[[:digit:]]+/[^/]+$",
      .handler = rsp_reply_browse
    },
    {
      .regexp = "^/rsp/stream/[[:digit:]]+$",
      .handler = rsp_stream,
    },
    { 
      .regexp = NULL,
      .handler = NULL
    }
  };


/* -------------------------------- RSP API --------------------------------- */

static void
rsp_request(struct httpd_request *hreq)
{
  int ret;

  if (!hreq->handler)
    {
      DPRINTF(E_LOG, L_RSP, "Unrecognized path in RSP request: '%s'\n", hreq->uri);

      rsp_send_error(hreq, "Server error");
      return;
    }

  ret = rsp_request_authorize(hreq);
  if (ret < 0)
    {
      rsp_send_error(hreq, "Access denied");
      free(hreq);
      return;
    }

  hreq->handler(hreq);
}

static int
rsp_init(void)
{
  snprintf(rsp_filter_files, sizeof(rsp_filter_files), "f.data_kind = %d", DATA_KIND_FILE);

  return 0;
}

struct httpd_module httpd_rsp =
{
  .name = "RSP",
  .type = MODULE_RSP,
  .logdomain = L_RSP,
  .subpaths = { "/rsp/", NULL },
  .handlers = rsp_handlers,
  .init = rsp_init,
  .request = rsp_request,
};
