Files
cantata/http/httpsocket.cpp

303 lines
10 KiB
C++

/*
* Cantata
*
* Copyright (c) 2011-2013 Craig Drummond <craig.p.drummond@gmail.com>
*
* ----
*
* 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; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "httpsocket.h"
#include <QTcpSocket>
#include <QNetworkInterface>
#include <QStringList>
#include <QTextStream>
#include <QFile>
#include <QUrl>
#ifdef ENABLE_KDE_SUPPORT
#include <KDE/KMimeType>
#endif
#include <QFileInfo>
#ifdef TAGLIB_FOUND
#include <taglib/oggflacfile.h>
#include <taglib/trueaudiofile.h>
#include <taglib/vorbisfile.h>
#include <taglib/audioproperties.h>
#endif
static QString detectMimeType(const QString &file)
{
#ifdef ENABLE_KDE_SUPPORT
QString km=KMimeType::findByPath(file)->name();
if (!km.isEmpty() && (km.startsWith("audio/") || km.startsWith("application/"))) {
return QLatin1String("audio/x-ape")==km ? QLatin1String("audio/x-monkeys-audio") : km;
}
#endif
QString suffix = QFileInfo(file).suffix().toLower();
if (suffix == QLatin1String("mp3")) {
return QLatin1String("audio/mpeg");
}
#ifdef TAGLIB_FOUND
if (suffix == QLatin1String("ogg")) {
#ifdef Q_OS_WIN32
const wchar_t *encodedName = reinterpret_cast< const wchar_t * >(file.utf16());
#elif defined COMPLEX_TAGLIB_FILENAME
const wchar_t *encodedName = reinterpret_cast< const wchar_t * >(file.utf16());
#else
QByteArray fileName = QFile::encodeName(file);
const char *encodedName = fileName.constData(); // valid as long as fileName exists
#endif
QString mime;
TagLib::File *result = new TagLib::Ogg::Vorbis::File(encodedName, false, TagLib::AudioProperties::Fast);
if (result->isValid()) {
mime=QLatin1String("audio/x-vorbis+ogg");
}
delete result;
if (mime.isEmpty()) {
result = new TagLib::Ogg::FLAC::File(encodedName, false, TagLib::AudioProperties::Fast);
if (result->isValid()) {
mime=QLatin1String("audio/x-flac+ogg");
}
delete result;
}
if (mime.isEmpty()) {
result = new TagLib::TrueAudio::File(encodedName, false, TagLib::AudioProperties::Fast);
if (result->isValid()) {
mime=QLatin1String("audio/x-speex+ogg");
}
delete result;
}
return QLatin1String("audio/ogg");
}
#endif
else if (suffix == QLatin1String("flac")) {
return QLatin1String("audio/x-flac");
} else if (suffix == QLatin1String("wma")) {
return QLatin1String("audio/x-ms-wma");
} else if (suffix == QLatin1String("m4a") || suffix == QLatin1String("m4b") || suffix == QLatin1String("m4p") || suffix == QLatin1String("mp4")) {
return QLatin1String("audio/mp4");
} else if (suffix == QLatin1String("wav")) {
return QLatin1String("audio/x-wav");
} else if (suffix == QLatin1String("wv") || suffix == QLatin1String("wvp")) {
return QLatin1String("audio/x-wavpack");
} else if (suffix == QLatin1String("ape")) {
return QLatin1String("audio/x-monkeys-audio"); // "audio/x-ape";
} else if (suffix == QLatin1String("spx")) {
return QLatin1String("audio/x-speex");
} else if (suffix == QLatin1String("tta")) {
return QLatin1String("audio/x-tta");
} else if (suffix == QLatin1String("aiff") || suffix == QLatin1String("aif") || suffix == QLatin1String("aifc")) {
return QLatin1String("audio/x-aiff");
} else if (suffix == QLatin1String("mpc") || suffix == QLatin1String("mpp") || suffix == QLatin1String("mp+")) {
return QLatin1String("audio/x-musepack");
} else if (suffix == QLatin1String("dff")) {
return QLatin1String("application/x-dff");
} else if (suffix == QLatin1String("dsf")) {
return QLatin1String("application/x-dsf");
}
return QString();
}
// static int level(const QString &s)
// {
// return QLatin1String("Link-local")==s
// ? 1
// : QLatin1String("Site-local")==s
// ? 2
// : QLatin1String("Global")==s
// ? 3
// : 0;
// }
HttpSocket::HttpSocket(const QString &addr, quint16 p)
: QTcpServer(0)
, cfgAddr(addr)
, terminated(false)
{
QHostAddress a;
if (!addr.isEmpty()) {
a=QHostAddress(addr);
if (a.isNull()) {
QString ifaceName=addr;
// bool ipV4=true;
//
// if (ifaceName.endsWith("::6")) {
// ifaceName=ifaceName.left(ifaceName.length()-3);
// ipV4=false;
// }
QNetworkInterface iface=QNetworkInterface::interfaceFromName(ifaceName);
if (iface.isValid()) {
QList<QNetworkAddressEntry> addresses=iface.addressEntries();
// int ip6Scope=-1;
foreach (const QNetworkAddressEntry &addr, addresses) {
QHostAddress ha=addr.ip();
if (QAbstractSocket::IPv4Protocol==ha.protocol()) {
// if ((ipV4 && QAbstractSocket::IPv4Protocol==ha.protocol()) || (!ipV4 && QAbstractSocket::IPv6Protocol==ha.protocol())) {
// if (ipV4) {
a=ha;
break;
// } else {
// int scope=level(a.scopeId());
// if (scope>ip6Scope) {
// ip6Scope=scope;
// a=ha;
// }
// }
}
}
}
}
}
listen(a.isNull() ? QHostAddress::LocalHost : a, p);
}
void HttpSocket::terminate()
{
terminated=true;
deleteLater();
}
void HttpSocket::incomingConnection(int socket)
{
QTcpSocket *s = new QTcpSocket(this);
connect(s, SIGNAL(readyRead()), this, SLOT(readClient()));
connect(s, SIGNAL(disconnected()), this, SLOT(discardClient()));
s->setSocketDescriptor(socket);
}
int getSep(const QByteArray &a, int pos)
{
for (int i=pos+1; i<a.length(); ++i) {
if ('\n'==a[i] || '\r'==a[i] || ' '==a[i]) {
return i;
}
}
return -1;
}
QList<QByteArray> split(const QByteArray &a)
{
QList<QByteArray> rv;
int lastPos=-1;
for (;;) {
int pos=getSep(a, lastPos);
if (pos==(lastPos+1)) {
lastPos++;
} else if (pos>-1) {
lastPos++;
rv.append(a.mid(lastPos, pos-lastPos));
lastPos=pos;
} else {
lastPos++;
rv.append(a.mid(lastPos));
break;
}
}
return rv;
}
void HttpSocket::readClient()
{
if (terminated) {
return;
}
QTcpSocket *socket = (QTcpSocket*)sender();
if (socket->canReadLine()) {
QList<QByteArray> tokens = split(socket->readLine()); // QRegExp("[ \r\n][ \r\n]*"));
if (tokens.length()>=2 && "GET"==tokens[0]) {
QUrl url(QUrl::fromEncoded(tokens[1]));
bool ok=false;
if (url.hasQueryItem("cantata")) {
QFile f(url.path());
if (f.open(QIODevice::ReadOnly)) {
QString mimeType=detectMimeType(url.path());
if (!mimeType.isEmpty()) {
QTextStream os(socket);
os.setAutoDetectUnicode(true);
os << "HTTP/1.0 200 OK\r\nContent-Type: " << mimeType << "\r\n\r\n";
}
ok=true;
static const int constChunkSize=8192;
char buffer[constChunkSize];
qint64 totalBytes = f.size();
qint64 readPos = 0;
qint64 bytesRead = 0;
do {
if (terminated) {
break;
}
bytesRead = f.read(buffer, constChunkSize);
readPos+=bytesRead;
if (bytesRead<0 || terminated) {
break;
}
qint64 writePos=0;
do {
qint64 bytesWritten = socket->write(&buffer[writePos], bytesRead - writePos);
if (terminated) {
break;
}
if (-1==bytesWritten) {
ok=false;
break;
}
writePos+=bytesWritten;
} while (writePos<bytesRead);
if (f.atEnd()) {
break;
}
} while ((readPos+bytesRead)<totalBytes);
}
}
if (!ok) {
QTextStream os(socket);
os.setAutoDetectUnicode(true);
os << "HTTP/1.0 404 Ok\r\n"
"Content-Type: text/html; charset=\"utf-8\"\r\n"
"\r\n"
"<h1>Nothing to see here</h1>\n";
}
socket->close();
if (QTcpSocket::UnconnectedState==socket->state()) {
delete socket;
}
}
}
}
void HttpSocket::discardClient()
{
QTcpSocket *socket = (QTcpSocket*)sender();
socket->deleteLater();
}