--- a
+++ b/src/httpfs.cxx
@@ -0,0 +1,234 @@
+/* Copyright (C) 2014 J.F.Dockes
+ * 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 <unordered_map>
+#include <string>
+
+#include "libupnpp/log.hxx"
+
+#include "upmpd.hxx"
+#include "upmpdutils.hxx"
+#include "httpfs.hxx"
+
+using namespace std;
+using namespace UPnPP;
+
+// The description XML document is the first thing downloaded by
+// clients and tells them what services we export, and where to find
+// them. The base data is in /usr/shared/upmpdcli/description.xml, it
+// has a number of substitutable fields for optional data, like the
+// description of OpenHome services
+static string ohDesc(
+ "<service>"
+ " <serviceType>urn:av-openhome-org:service:Product:1</serviceType>"
+ " <serviceId>urn:av-openhome-org:serviceId:Product</serviceId>"
+ " <SCPDURL>/upmpd/OHProduct.xml</SCPDURL>"
+ " <controlURL>/ctl/OHProduct</controlURL>"
+ " <eventSubURL>/evt/OHProduct</eventSubURL>"
+ "</service>"
+ "<service>"
+ " <serviceType>urn:av-openhome-org:service:Info:1</serviceType>"
+ " <serviceId>urn:av-openhome-org:serviceId:Info</serviceId>"
+ " <SCPDURL>/upmpd/OHInfo.xml</SCPDURL>"
+ " <controlURL>/ctl/OHInfo</controlURL>"
+ " <eventSubURL>/evt/OHInfo</eventSubURL>"
+ "</service>"
+ "<service>"
+ " <serviceType>urn:av-openhome-org:service:Time:1</serviceType>"
+ " <serviceId>urn:av-openhome-org:serviceId:Time</serviceId>"
+ " <SCPDURL>/upmpd/OHTime.xml</SCPDURL>"
+ " <controlURL>/ctl/OHTime</controlURL>"
+ " <eventSubURL>/evt/OHTime</eventSubURL>"
+ "</service>"
+ "<service>"
+ " <serviceType>urn:av-openhome-org:service:Volume:1</serviceType>"
+ " <serviceId>urn:av-openhome-org:serviceId:Volume</serviceId>"
+ " <SCPDURL>/upmpd/OHVolume.xml</SCPDURL>"
+ " <controlURL>/ctl/OHVolume</controlURL>"
+ " <eventSubURL>/evt/OHVolume</eventSubURL>"
+ "</service>"
+ "<service>"
+ " <serviceType>urn:av-openhome-org:service:Playlist:1</serviceType>"
+ " <serviceId>urn:av-openhome-org:serviceId:Playlist</serviceId>"
+ " <SCPDURL>/upmpd/OHPlaylist.xml</SCPDURL>"
+ " <controlURL>/ctl/OHPlaylist</controlURL>"
+ " <eventSubURL>/evt/OHPlaylist</eventSubURL>"
+ "</service>"
+ );
+
+// We only advertise the Openhome Receiver service if the sc2mpd
+// songcast-to-mpd gateway command is available
+static string ohDescReceive(
+ "<service>"
+ " <serviceType>urn:av-openhome-org:service:Receiver:1</serviceType>"
+ " <serviceId>urn:av-openhome-org:serviceId:Receiver</serviceId>"
+ " <SCPDURL>/upmpd/OHReceiver.xml</SCPDURL>"
+ " <controlURL>/ctl/OHReceiver</controlURL>"
+ " <eventSubURL>/evt/OHReceiver</eventSubURL>"
+ "</service>"
+ );
+
+static const string iconDesc(
+ "<iconList>"
+ " <icon>"
+ " <mimetype>image/png</mimetype>"
+ " <width>64</width>"
+ " <height>64</height>"
+ " <depth>32</depth>"
+ " <url>/upmpd/icon.png</url>"
+ " </icon>"
+ "</iconList>"
+ );
+
+static const string presDesc(
+"<presentationURL>/upmpd/presentation.html</presentationURL>"
+ );
+
+// The base XML description files. !Keep description.xml first!
+static vector<const char *> xmlfilenames =
+{
+ /* keep first */ "description.xml", /* keep first */
+ "RenderingControl.xml", "AVTransport.xml", "ConnectionManager.xml",
+};
+
+// Optional OpenHome service description files
+static vector<const char *> ohxmlfilenames =
+{
+ "OHProduct.xml", "OHInfo.xml", "OHTime.xml", "OHVolume.xml",
+ "OHPlaylist.xml",
+};
+
+/** Read protocol info file. This contains the connection manager
+ * protocol info data
+ *
+ * We strip white-space from beginning/ends of lines, and allow
+ * #-started comments (on a line alone only, comments after data not allowed).
+ */
+static bool read_protocolinfo(const string& fn, string& out)
+{
+ ifstream input;
+ input.open(fn, ios::in);
+ if (!input.is_open()) {
+ return false;
+ }
+ bool eof = false;
+ for (;;) {
+ string line;
+ getline(input, line);
+ if (!input.good()) {
+ if (input.bad()) {
+ return false;
+ }
+ // Must be eof ? But maybe we have a partial line which
+ // must be processed. This happens if the last line before
+ // eof ends with a backslash, or there is no final \n
+ eof = true;
+ }
+ trimstring(line, " \t\n\r");
+ if (line[0] == '#')
+ continue;
+ out += line;
+ if (eof)
+ break;
+ }
+ return true;
+}
+
+
+// Read and setup our (mostly XML) data to make it available from the
+// virtual directory
+bool initHttpFs(unordered_map<string, VDirContent>& files,
+ const string& datadir,
+ const string& UUID, const string& friendlyname,
+ bool openhome, const string& iconpath,
+ const string& presentationhtml)
+{
+ if (openhome) {
+ if (!g_sc2mpd_path.empty()) {
+ ohxmlfilenames.push_back("OHReceiver.xml");
+ }
+ xmlfilenames.insert(xmlfilenames.end(), ohxmlfilenames.begin(),
+ ohxmlfilenames.end());
+ }
+
+ string protofile(path_cat(datadir, "protocolinfo.txt"));
+ if (!read_protocolinfo(protofile, g_protocolInfo)) {
+ LOGFAT("Failed reading protocol info from " << protofile << endl);
+ return false;
+ }
+
+ string reason;
+ string icondata;
+ if (!iconpath.empty()) {
+ if (!file_to_string(iconpath, icondata, &reason)) {
+ LOGERR("Failed reading " << iconpath << " : " << reason << endl);
+ }
+ }
+ string presentationdata;
+ if (!presentationhtml.empty()) {
+ if (!file_to_string(presentationhtml, presentationdata, &reason)) {
+ LOGERR("Failed reading " << iconpath << " : " << reason << endl);
+ }
+ }
+
+ string dir("/upmpd/");
+ for (unsigned int i = 0; i < xmlfilenames.size(); i++) {
+ string filename = path_cat(datadir, xmlfilenames[i]);
+ string data;
+ if (!file_to_string(filename, data, &reason)) {
+ LOGFAT("Failed reading " << filename << " : " << reason << endl);
+ return false;
+ }
+ if (i == 0) {
+ // Special for description: set UUID and friendlyname
+ data = regsub1("@UUID@", data, UUID);
+ data = regsub1("@FRIENDLYNAME@", data, friendlyname);
+ if (openhome) {
+ if (!g_sc2mpd_path.empty()) {
+ ohDesc += ohDescReceive;
+ }
+ data = regsub1("@OPENHOME@", data, ohDesc);
+ } else {
+ data = regsub1("@OPENHOME@", data, "");
+ }
+
+ if (!icondata.empty())
+ data = regsub1("@ICONLIST@", data, iconDesc);
+ else
+ data = regsub1("@ICONLIST@", data, "");
+ if (!presentationdata.empty())
+ data = regsub1("@PRESENTATION@", data, presDesc);
+ else
+ data = regsub1("@PRESENTATION@", data, "");
+ }
+ files.insert(pair<string, VDirContent>
+ (dir + xmlfilenames[i],
+ VDirContent(data, "application/xml")));
+ }
+
+ if (!icondata.empty()) {
+ files.insert(pair<string, VDirContent>
+ (dir + "icon.png",
+ VDirContent(icondata, "image/png")));
+ }
+ if (!presentationdata.empty()) {
+ files.insert(pair<string, VDirContent>
+ (dir + "presentation.html",
+ VDirContent(presentationdata, "text/html")));
+ }
+ return true;
+}