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