1 /* IcyWebAccess.cpp */
2 
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4  *
5  * This file is part of sayonara player
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11 
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16 
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "IcyWebAccess.h"
22 #include "Utils/Logger/Logger.h"
23 #include "Utils/Macros.h"
24 
25 #include <QTcpSocket>
26 #include <QUrl>
27 
28 struct IcyWebAccess::Private
29 {
30 		IcyWebAccess::Status status;
31 		QTcpSocket* tcp=nullptr;
32 		QString hostname;
33 		QString directory;
34 		QString filename;
35 		int port;
36 
PrivateIcyWebAccess::Private37 		Private()
38 		{
39 			status = IcyWebAccess::Status::Success;
40 			port = 80;
41 		}
42 
close_tcpIcyWebAccess::Private43 		void close_tcp()
44 		{
45 			if(tcp->isOpen()){
46 				tcp->close();
47 			}
48 
49 			tcp->deleteLater();
50 		}
51 
concat_dir_and_filenameIcyWebAccess::Private52 		QString concat_dir_and_filename() const
53 		{
54 			QString ret = directory + "/" + filename;
55 			while(ret.contains("//")){
56 				ret.replace("//", "/");
57 			}
58 
59 			if(!ret.startsWith("/")){
60 				ret.prepend("/");
61 			}
62 
63 			return ret;
64 		}
65 };
66 
IcyWebAccess(QObject * parent)67 IcyWebAccess::IcyWebAccess(QObject* parent) :
68 	QObject(parent)
69 {
70 	m = Pimpl::make<Private>();
71 
72 }
73 
~IcyWebAccess()74 IcyWebAccess::~IcyWebAccess() {}
75 
check(const QUrl & url)76 void IcyWebAccess::check(const QUrl& url)
77 {
78 	m->tcp = new QTcpSocket(nullptr);
79 	m->hostname = url.host(QUrl::PrettyDecoded);
80 	m->port = url.port(80);
81 	m->directory = url.path();
82 	m->filename = url.fileName();
83 	m->status = IcyWebAccess::Status::NotExecuted;
84 
85 	connect(m->tcp, &QTcpSocket::connected, this, &IcyWebAccess::connected);
86 	connect(m->tcp, &QTcpSocket::disconnected, this, &IcyWebAccess::disconnected);
87 	connect(m->tcp, &QTcpSocket::readyRead, this, &IcyWebAccess::dataAvailable);
88 
89 	connect(m->tcp, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(errorReceived(QAbstractSocket::SocketError)));
90 
91 	m->tcp->connectToHost(m->hostname,
92 						   m->port,
93 						   QTcpSocket::ReadWrite,
94 						   QAbstractSocket::AnyIPProtocol
95 	);
96 
97 	spLog(Log::Develop, this) << "Start ICY Request";
98 }
99 
stop()100 void IcyWebAccess::stop()
101 {
102 	if(m->tcp && m->tcp->isOpen() && m->tcp->isValid()){
103 		m->tcp->abort();
104 		m->tcp->close();
105 	}
106 }
107 
status() const108 IcyWebAccess::Status IcyWebAccess::status() const
109 {
110 	return m->status;
111 }
112 
113 
connected()114 void IcyWebAccess::connected()
115 {
116 	QString user_agent = QString("Sayonara/") + SAYONARA_VERSION;
117 	QByteArray data(
118 				"GET " + m->concat_dir_and_filename().toLocal8Bit() + " HTTP/1.1\r\n"
119 				"User-Agent: " + user_agent.toLocal8Bit() + "\r\n"
120 				"Connection: Keep-Alive\r\n"
121 				"Accept-Encoding: gzip, deflate\r\n"
122 				"Accept-Language: en-US,*\r\n"
123 				"Host: " +
124 				m->hostname.toLocal8Bit() + ":" +
125 				QString::number(m->port).toLocal8Bit() + "\r\n\r\n"
126 	);
127 
128 	spLog(Log::Develop, this) << data;
129 
130 	int64_t bytes_written = m->tcp->write(data.data(), data.size());
131 	if(bytes_written != data.size())
132 	{
133 		spLog(Log::Warning, this) << "Could only write " << bytes_written << " bytes";
134 		m->status = IcyWebAccess::Status::WriteError;
135 		emit sigFinished();
136 		m->close_tcp();
137 	}
138 }
139 
disconnected()140 void IcyWebAccess::disconnected()
141 {
142 	spLog(Log::Develop, this) << "Disconnected";
143 	if(m->status == IcyWebAccess::Status::NotExecuted) {
144 		m->status = IcyWebAccess::Status::OtherError;
145 		emit sigFinished();
146 	}
147 
148 	m->close_tcp();
149 
150 	sender()->deleteLater();
151 }
152 
errorReceived(QAbstractSocket::SocketError socket_state)153 void IcyWebAccess::errorReceived(QAbstractSocket::SocketError socket_state)
154 {
155 	Q_UNUSED(socket_state)
156 
157 	spLog(Log::Warning, this) << "Icy Webaccess Error: " << m->tcp->errorString();
158 
159 	m->status = IcyWebAccess::Status::OtherError;
160 	m->close_tcp();
161 
162 	emit sigFinished();
163 }
164 
dataAvailable()165 void IcyWebAccess::dataAvailable()
166 {
167 	QByteArray arr = m->tcp->read(20);
168 	if(arr.contains("ICY 200 OK")){
169 		m->status = IcyWebAccess::Status::Success;
170 	}
171 
172 	else {
173 		spLog(Log::Warning, this) << "Icy Answer Error: " << arr;
174 		m->status = IcyWebAccess::Status::WrongAnswer;
175 	}
176 
177 	m->close_tcp();
178 
179 	emit sigFinished();
180 }
181