1 /*
2 * Copyright 2003-2021 The Music Player Daemon Project
3 * http://www.musicpd.org
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "Device.hxx"
21 #include "Util.hxx"
22 #include "lib/expat/ExpatParser.hxx"
23
24 #include <string.h>
25
26 /* this destructor exists here just so it won't get inlined */
27 UPnPDevice::~UPnPDevice() noexcept = default;
28
29 /**
30 * An XML parser which constructs an UPnP device object from the
31 * device descriptor.
32 */
33 class UPnPDeviceParser final : public CommonExpatParser {
34 UPnPDevice &m_device;
35
36 std::string *value;
37
38 UPnPService m_tservice;
39
40 public:
UPnPDeviceParser(UPnPDevice & device)41 explicit UPnPDeviceParser(UPnPDevice& device)
42 :m_device(device),
43 value(nullptr) {}
44
45 protected:
StartElement(const XML_Char * name,const XML_Char **)46 void StartElement(const XML_Char *name, const XML_Char **) override {
47 value = nullptr;
48
49 switch (name[0]) {
50 case 'c':
51 if (strcmp(name, "controlURL") == 0)
52 value = &m_tservice.controlURL;
53 break;
54 case 'd':
55 if (strcmp(name, "deviceType") == 0)
56 value = &m_device.deviceType;
57 break;
58 case 'f':
59 if (strcmp(name, "friendlyName") == 0)
60 value = &m_device.friendlyName;
61 break;
62 case 'm':
63 if (strcmp(name, "manufacturer") == 0)
64 value = &m_device.manufacturer;
65 else if (strcmp(name, "modelName") == 0)
66 value = &m_device.modelName;
67 break;
68 case 's':
69 if (strcmp(name, "serviceType") == 0)
70 value = &m_tservice.serviceType;
71 break;
72 case 'U':
73 if (strcmp(name, "UDN") == 0)
74 value = &m_device.UDN;
75 else if (strcmp(name, "URLBase") == 0)
76 value = &m_device.URLBase;
77 break;
78 }
79 }
80
EndElement(const XML_Char * name)81 void EndElement(const XML_Char *name) override {
82 if (value != nullptr) {
83 trimstring(*value);
84 value = nullptr;
85 } else if (!strcmp(name, "service")) {
86 m_device.services.emplace_back(std::move(m_tservice));
87 m_tservice = {};
88 }
89 }
90
CharacterData(const XML_Char * s,int len)91 void CharacterData(const XML_Char *s, int len) override {
92 if (value != nullptr)
93 value->append(s, len);
94 }
95 };
96
97 void
Parse(const std::string & url,const char * description)98 UPnPDevice::Parse(const std::string &url, const char *description)
99 {
100 {
101 UPnPDeviceParser mparser(*this);
102 mparser.Parse(description, strlen(description), true);
103 }
104
105 if (URLBase.empty()) {
106 // The standard says that if the URLBase value is empty, we should use
107 // the url the description was retrieved from. However this is
108 // sometimes something like http://host/desc.xml, sometimes something
109 // like http://host/
110
111 if (url.size() < 8) {
112 // ???
113 URLBase = url;
114 } else {
115 auto hostslash = url.find('/', 7);
116 if (hostslash == std::string::npos || hostslash == url.size()-1) {
117 URLBase = url;
118 } else {
119 URLBase = path_getfather(url);
120 }
121 }
122 }
123 }
124