Switch to unified view

a b/src/httpfs.cxx
1
/* Copyright (C) 2014 J.F.Dockes
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the
14
 *   Free Software Foundation, Inc.,
15
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16
 */
17
18
#include <unordered_map>
19
#include <string>
20
21
#include "libupnpp/log.hxx"
22
23
#include "upmpd.hxx"
24
#include "upmpdutils.hxx"
25
#include "httpfs.hxx"
26
27
using namespace std;
28
using namespace UPnPP;
29
30
// The description XML document is the first thing downloaded by
31
// clients and tells them what services we export, and where to find
32
// them. The base data is in /usr/shared/upmpdcli/description.xml, it
33
// has a number of substitutable fields for optional data, like the
34
// description of OpenHome services
35
static string ohDesc(
36
    "<service>"
37
    "  <serviceType>urn:av-openhome-org:service:Product:1</serviceType>"
38
    "  <serviceId>urn:av-openhome-org:serviceId:Product</serviceId>"
39
    "  <SCPDURL>/upmpd/OHProduct.xml</SCPDURL>"
40
    "  <controlURL>/ctl/OHProduct</controlURL>"
41
    "  <eventSubURL>/evt/OHProduct</eventSubURL>"
42
    "</service>"
43
    "<service>"
44
    "  <serviceType>urn:av-openhome-org:service:Info:1</serviceType>"
45
    "  <serviceId>urn:av-openhome-org:serviceId:Info</serviceId>"
46
    "  <SCPDURL>/upmpd/OHInfo.xml</SCPDURL>"
47
    "  <controlURL>/ctl/OHInfo</controlURL>"
48
    "  <eventSubURL>/evt/OHInfo</eventSubURL>"
49
    "</service>"
50
    "<service>"
51
    "  <serviceType>urn:av-openhome-org:service:Time:1</serviceType>"
52
    "  <serviceId>urn:av-openhome-org:serviceId:Time</serviceId>"
53
    "  <SCPDURL>/upmpd/OHTime.xml</SCPDURL>"
54
    "  <controlURL>/ctl/OHTime</controlURL>"
55
    "  <eventSubURL>/evt/OHTime</eventSubURL>"
56
    "</service>"
57
    "<service>"
58
    "  <serviceType>urn:av-openhome-org:service:Volume:1</serviceType>"
59
    "  <serviceId>urn:av-openhome-org:serviceId:Volume</serviceId>"
60
    "  <SCPDURL>/upmpd/OHVolume.xml</SCPDURL>"
61
    "  <controlURL>/ctl/OHVolume</controlURL>"
62
    "  <eventSubURL>/evt/OHVolume</eventSubURL>"
63
    "</service>"
64
    "<service>"
65
    "  <serviceType>urn:av-openhome-org:service:Playlist:1</serviceType>"
66
    "  <serviceId>urn:av-openhome-org:serviceId:Playlist</serviceId>"
67
    "  <SCPDURL>/upmpd/OHPlaylist.xml</SCPDURL>"
68
    "  <controlURL>/ctl/OHPlaylist</controlURL>"
69
    "  <eventSubURL>/evt/OHPlaylist</eventSubURL>"
70
    "</service>"
71
    );
72
73
// We only advertise the Openhome Receiver service if the sc2mpd
74
// songcast-to-mpd gateway command is available
75
static string ohDescReceive(
76
    "<service>"
77
    "  <serviceType>urn:av-openhome-org:service:Receiver:1</serviceType>"
78
    "  <serviceId>urn:av-openhome-org:serviceId:Receiver</serviceId>"
79
    "  <SCPDURL>/upmpd/OHReceiver.xml</SCPDURL>"
80
    "  <controlURL>/ctl/OHReceiver</controlURL>"
81
    "  <eventSubURL>/evt/OHReceiver</eventSubURL>"
82
    "</service>"
83
    );
84
85
static const string iconDesc(
86
    "<iconList>"
87
    "  <icon>"
88
    "    <mimetype>image/png</mimetype>"
89
    "    <width>64</width>"
90
    "    <height>64</height>"
91
    "    <depth>32</depth>"
92
    "    <url>/upmpd/icon.png</url>"
93
    "  </icon>"
94
    "</iconList>"
95
    );
96
97
static const string presDesc(
98
"<presentationURL>/upmpd/presentation.html</presentationURL>"
99
    );
100
101
// The base XML description files. !Keep description.xml first!
102
static vector<const char *> xmlfilenames = 
103
{
104
    /* keep first */ "description.xml", /* keep first */
105
    "RenderingControl.xml", "AVTransport.xml", "ConnectionManager.xml",
106
};
107
108
// Optional OpenHome service description files
109
static vector<const char *> ohxmlfilenames = 
110
{
111
    "OHProduct.xml", "OHInfo.xml", "OHTime.xml", "OHVolume.xml", 
112
    "OHPlaylist.xml",
113
};
114
115
/** Read protocol info file. This contains the connection manager
116
 * protocol info data
117
 *
118
 * We strip white-space from beginning/ends of lines, and allow
119
 * #-started comments (on a line alone only, comments after data not allowed).
120
 */
121
static bool read_protocolinfo(const string& fn, string& out)
122
{
123
    ifstream input;
124
    input.open(fn, ios::in);
125
    if (!input.is_open()) {
126
  return false;
127
    }     
128
    bool eof = false;
129
    for (;;) {
130
        string line;
131
  getline(input, line);
132
  if (!input.good()) {
133
      if (input.bad()) {
134
      return false;
135
      }
136
      // Must be eof ? But maybe we have a partial line which
137
      // must be processed. This happens if the last line before
138
      // eof ends with a backslash, or there is no final \n
139
            eof = true;
140
  }
141
        trimstring(line, " \t\n\r");
142
        if (line[0] == '#')
143
            continue;
144
        out += line;
145
        if (eof) 
146
            break;
147
    }
148
    return true;
149
}
150
151
152
// Read and setup our (mostly XML) data to make it available from the
153
// virtual directory
154
bool initHttpFs(unordered_map<string, VDirContent>& files,
155
                const string& datadir,
156
                const string& UUID, const string& friendlyname, 
157
                bool openhome, const string& iconpath,
158
                const string& presentationhtml)
159
{
160
    if (openhome) {
161
        if (!g_sc2mpd_path.empty()) {
162
            ohxmlfilenames.push_back("OHReceiver.xml");
163
        }
164
        xmlfilenames.insert(xmlfilenames.end(), ohxmlfilenames.begin(),
165
                            ohxmlfilenames.end());
166
    }
167
    
168
    string protofile(path_cat(datadir, "protocolinfo.txt"));
169
    if (!read_protocolinfo(protofile, g_protocolInfo)) {
170
        LOGFAT("Failed reading protocol info from " << protofile << endl);
171
        return false;
172
    }
173
174
    string reason;
175
    string icondata;
176
    if (!iconpath.empty()) {
177
        if (!file_to_string(iconpath, icondata, &reason)) {
178
            LOGERR("Failed reading " << iconpath << " : " << reason << endl);
179
        }
180
    }
181
    string presentationdata;
182
    if (!presentationhtml.empty()) {
183
        if (!file_to_string(presentationhtml, presentationdata, &reason)) {
184
            LOGERR("Failed reading " << iconpath << " : " << reason << endl);
185
        }
186
    }
187
188
    string dir("/upmpd/");
189
    for (unsigned int i = 0; i < xmlfilenames.size(); i++) {
190
        string filename = path_cat(datadir, xmlfilenames[i]);
191
        string data;
192
        if (!file_to_string(filename, data, &reason)) {
193
            LOGFAT("Failed reading " << filename << " : " << reason << endl);
194
            return false;
195
        }
196
        if (i == 0) {
197
            // Special for description: set UUID and friendlyname
198
            data = regsub1("@UUID@", data, UUID);
199
            data = regsub1("@FRIENDLYNAME@", data, friendlyname);
200
            if (openhome) {
201
                if (!g_sc2mpd_path.empty()) {
202
                    ohDesc += ohDescReceive;
203
                }
204
                data = regsub1("@OPENHOME@", data, ohDesc);
205
            } else {
206
                data = regsub1("@OPENHOME@", data, "");
207
            }
208
209
            if (!icondata.empty())
210
                data = regsub1("@ICONLIST@", data, iconDesc);
211
            else
212
                data = regsub1("@ICONLIST@", data, "");
213
            if (!presentationdata.empty())
214
                data = regsub1("@PRESENTATION@", data, presDesc);
215
            else
216
                data = regsub1("@PRESENTATION@", data, "");
217
        }
218
        files.insert(pair<string, VDirContent>
219
                     (dir + xmlfilenames[i], 
220
                      VDirContent(data, "application/xml")));
221
    }
222
223
    if (!icondata.empty()) {
224
        files.insert(pair<string, VDirContent>
225
                     (dir + "icon.png", 
226
                      VDirContent(icondata, "image/png")));
227
    }
228
    if (!presentationdata.empty()) {
229
        files.insert(pair<string, VDirContent>
230
                     (dir + "presentation.html", 
231
                      VDirContent(presentationdata, "text/html")));
232
    }
233
    return true;
234
}