Commit c3564669 authored by Jean-Francois Dockes's avatar Jean-Francois Dockes
Browse files

Perform special url translation in avtransport too

parent dabc26a2
...@@ -106,6 +106,8 @@ upmpdcli_SOURCES = \ ...@@ -106,6 +106,8 @@ upmpdcli_SOURCES = \
src/upmpd.hxx \ src/upmpd.hxx \
src/upmpdutils.cxx \ src/upmpdutils.cxx \
src/upmpdutils.hxx \ src/upmpdutils.hxx \
src/urlmorph.cxx \
src/urlmorph.hxx \
src/workqueue.h src/workqueue.h
upmpdcli_LDADD = $(UPMPDCLI_LIBS) upmpdcli_LDADD = $(UPMPDCLI_LIBS)
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
#include "upmpdutils.hxx" #include "upmpdutils.hxx"
#include "smallut.h" #include "smallut.h"
#include "conftree.h" #include "conftree.h"
#include "urlmorph.hxx"
// For testing upplay with a dumb renderer. // For testing upplay with a dumb renderer.
// #define NO_SETNEXT // #define NO_SETNEXT
...@@ -427,6 +428,13 @@ int UpMpdAVTransport::setAVTransportURI(const SoapIncoming& sc, ...@@ -427,6 +428,13 @@ int UpMpdAVTransport::setAVTransportURI(const SoapIncoming& sc,
if (!found) { if (!found) {
return UPNP_E_INVALID_PARAM; return UPNP_E_INVALID_PARAM;
} }
bool forcenocheck;
if (!morphSpecialUrl(uri, forcenocheck)) {
LOGERR("OHPlaylist::insert: bad uri: " << uri << endl);
return UPNP_E_INVALID_PARAM;
}
string metadata; string metadata;
found = setnext ? sc.get("NextURIMetaData", &metadata) : found = setnext ? sc.get("NextURIMetaData", &metadata) :
sc.get("CurrentURIMetaData", &metadata); sc.get("CurrentURIMetaData", &metadata);
......
...@@ -27,7 +27,6 @@ ...@@ -27,7 +27,6 @@
#include <map> #include <map>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <regex>
#include <sstream> #include <sstream>
#include "conftree.h" #include "conftree.h"
...@@ -62,85 +61,6 @@ static const map<string, string> idmap { ...@@ -62,85 +61,6 @@ static const map<string, string> idmap {
{"qobuz.com", "qobuz"} {"qobuz.com", "qobuz"}
}; };
// The URLs from kazoo look like:
// <service>://track?version=2&trackId=<trkid>
// We translate them to something which points to our proxy server, and
// that MPD will accept/use:
// http://<upnphost>:<sport>/<servicename>/track?version=1&trackId=<trkid>
// Where upnphost is the host used by libupnp, and sport the port on
// which the microhttpd listens.
// We retrieve upnphost from the upnp device during init, and sport by a
// call to the CDPluginServices.
//
// The media server, which is used to run the microhttpd and for
// getting the real media URLs, must run on this host (for one thing
// the creds are passed either through shared memory or through a
// local file).
static string upnphost;
// Called from OHPlaylist. The CP (Kazoo/Lumin mostly) will send URIs
// like qobuz:// tidal:// and expect the renderer to know what to do
// with them. We transform them so that they point to our media server
// gateway (which should be running of course for this to work).
//
// Also some control points don't like urls like cdda:///dev/sr0, they
// want everything to be http. We get these through with
// http://_protoescape/proto/path which we transform into
// proto:///path
static const string protoescape{"http://_protoescape/"};
bool OHCredsMaybeMorphSpecialUri(string& uri, bool& isStreaming)
{
// We accept special cloaked cdda URLs and translate them because
// some control points can't grok cdda:///1 and forbid cd-based
// playlists
if (uri.find(protoescape) == 0) {
auto protoend = uri.find('/', protoescape.size());
if (protoend != string::npos) {
auto protoname = uri.substr(protoescape.size(),
protoend-protoescape.size());
uri.replace(0, protoescape.size() + protoname.size(),
protoname + "://");
}
isStreaming = true;
return true;
}
isStreaming = false;
if (uri.find("http://") == 0 || uri.find("https://") == 0) {
return true;
}
// Possibly retrieve the IP port used by our proxy server
static string sport;
if (sport.empty()) {
sport = SoapHelp::i2s(CDPluginServices::microhttpport());
}
// http://wiki.openhome.org/wiki/Av:Developer:Eriskay:StreamingServices
// Tidal and qobuz tracks added by Kazoo / Lumin:
// tidal://track?version=1&trackId=[tidal_track_id]
// qobuz://track?version=2&trackId=[qobuz_track_id]
string se =
"(tidal|qobuz)://track\\?version=([[:digit:]]+)&trackId=([[:digit:]]+)";
std::regex e(se);
std::smatch mr;
bool found = std::regex_match(uri, mr, e);
if (found) {
string pathprefix = CDPluginServices::getpathprefix(mr[1]);
// The microhttpd code actually only cares about getting a
// trackId parameter. Make it look what the plugins normally
// generate anyway:
string path = path_cat(pathprefix,
"track?version=1&trackId=" + mr[3].str());
uri = string("http://") + upnphost + ":" + sport + path;
isStreaming = true;
}
return true;
}
// We might want to derive this into ServiceCredsQobuz, // We might want to derive this into ServiceCredsQobuz,
// ServiceCredsTidal, there is a lot in common and a few diffs. // ServiceCredsTidal, there is a lot in common and a few diffs.
...@@ -480,9 +400,6 @@ OHCredentials::OHCredentials(UpMpd *dev, UpMpdOpenHome *udev, ...@@ -480,9 +400,6 @@ OHCredentials::OHCredentials(UpMpd *dev, UpMpdOpenHome *udev,
udev->addActionMapping( udev->addActionMapping(
this, "GetSequenceNumber", this, "GetSequenceNumber",
bind(&OHCredentials::actGetSequenceNumber, this, _1, _2)); bind(&OHCredentials::actGetSequenceNumber, this, _1, _2));
unsigned short usport;
udev->ipv4(&upnphost, &usport);
} }
OHCredentials::~OHCredentials() OHCredentials::~OHCredentials()
......
...@@ -50,11 +50,5 @@ private: ...@@ -50,11 +50,5 @@ private:
Internal *m{0}; Internal *m{0};
}; };
// Check uri for special qobuz/tidal scheme and transform it to point
// to our proxy in this case.
// @param[out] isStreaming will be true if the uri was transformed, else false.
// @return false for error, uri should not be used
extern bool OHCredsMaybeMorphSpecialUri(std::string& uri, bool& isStreaming);
#endif #endif
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
#include "pathut.h" #include "pathut.h"
#include "conftree.h" #include "conftree.h"
#include "ohcredentials.hxx" #include "ohcredentials.hxx"
#include "urlmorph.hxx"
using namespace std; using namespace std;
using namespace std::placeholders; using namespace std::placeholders;
...@@ -54,6 +55,12 @@ static const string sIdProduct("urn:av-openhome-org:serviceId:Playlist"); ...@@ -54,6 +55,12 @@ static const string sIdProduct("urn:av-openhome-org:serviceId:Playlist");
// mpc/mpd control. If set we don't touch it. // mpc/mpd control. If set we don't touch it.
static bool keepconsume; static bool keepconsume;
// For OHCreds/morphSpecialUrl: the media server, which is used to run
// the microhttpd and for getting the real media URLs, must run on
// this host (for one thing the creds are passed either through shared
// memory or through a local file).
static string upnphost;
// Playlist is the default oh service, so it's active when starting up // Playlist is the default oh service, so it's active when starting up
OHPlaylist::OHPlaylist(UpMpd *dev, UpMpdOpenHome *udev, unsigned int cssleep) OHPlaylist::OHPlaylist(UpMpd *dev, UpMpdOpenHome *udev, unsigned int cssleep)
: OHService(sTpProduct, sIdProduct, "OHPlaylist.xml", dev, udev), : OHService(sTpProduct, sIdProduct, "OHPlaylist.xml", dev, udev),
...@@ -120,6 +127,9 @@ OHPlaylist::OHPlaylist(UpMpd *dev, UpMpdOpenHome *udev, unsigned int cssleep) ...@@ -120,6 +127,9 @@ OHPlaylist::OHPlaylist(UpMpd *dev, UpMpdOpenHome *udev, unsigned int cssleep)
m_dev->getmpdcli()->subscribe( m_dev->getmpdcli()->subscribe(
MPDCli::MpdQueueEvt|MPDCli::MpdPlayerEvt|MPDCli::MpdOptsEvt, MPDCli::MpdQueueEvt|MPDCli::MpdPlayerEvt|MPDCli::MpdOptsEvt,
std::bind(&OHService::onEvent, this, _1)); std::bind(&OHService::onEvent, this, _1));
unsigned short usport;
udev->ipv4(&upnphost, &usport);
} }
static const int tracksmax = 16384; static const int tracksmax = 16384;
...@@ -707,10 +717,10 @@ int OHPlaylist::insert(const SoapIncoming& sc, SoapOutgoing& data) ...@@ -707,10 +717,10 @@ int OHPlaylist::insert(const SoapIncoming& sc, SoapOutgoing& data)
} }
// Maybe transform a qobuz:// or tidal:// uri if we're doing this // Maybe transform a qobuz:// or tidal:// uri if we're doing this
// isStreaming is used to disable content format check in this // forcenocheck is used to disable content format check in this
// case (there is no valid protocolinfo in general). // case (there is no valid protocolinfo in general).
bool isStreaming; bool forcenocheck;
if (!OHCredsMaybeMorphSpecialUri(uri, isStreaming)) { if (!morphSpecialUrl(uri, forcenocheck, upnphost)) {
LOGERR("OHPlaylist::insert: bad uri: " << uri << endl); LOGERR("OHPlaylist::insert: bad uri: " << uri << endl);
return UPNP_E_INVALID_PARAM; return UPNP_E_INVALID_PARAM;
} }
...@@ -727,7 +737,7 @@ int OHPlaylist::insert(const SoapIncoming& sc, SoapOutgoing& data) ...@@ -727,7 +737,7 @@ int OHPlaylist::insert(const SoapIncoming& sc, SoapOutgoing& data)
uri << " Metadata " << metadata << endl); uri << " Metadata " << metadata << endl);
int newid; int newid;
ok = insertUri(afterid, uri, metadata, &newid, isStreaming); ok = insertUri(afterid, uri, metadata, &newid, forcenocheck);
if (ok) { if (ok) {
data.addarg("NewId", SoapHelp::i2s(newid)); data.addarg("NewId", SoapHelp::i2s(newid));
LOGDEB("OHPlaylist::insert: new id: " << newid << endl); LOGDEB("OHPlaylist::insert: new id: " << newid << endl);
......
/* Copyright (C) 2018-2021 J.F.Dockes
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include "urlmorph.hxx"
#include "libupnpp/log.hxx"
#include "libupnpp/soaphelp.hxx"
#include "mediaserver/cdplugins/cdplugin.hxx"
#include "pathut.h"
#include <regex>
using namespace std;
static const string protoescape{"http://_protoescape/"};
bool morphSpecialUrl(string& url, bool& forcenocheck,
const std::string& upnphost)
{
forcenocheck = false;
// We accept special cloaked cdda URLs and translate them because
// some control points can't grok cdda:///1 and forbid cd-based
// playlists
if (url.find(protoescape) == 0) {
auto protoend = url.find('/', protoescape.size());
if (protoend != string::npos) {
auto protoname = url.substr(protoescape.size(),
protoend-protoescape.size());
url.replace(0, protoescape.size() + protoname.size(),
protoname + "://");
}
forcenocheck = true;
return true;
}
if (url.find("http://") == 0 || url.find("https://") == 0) {
return true;
}
// Possibly retrieve the IP port used by our proxy server
static string sport;
if (sport.empty()) {
sport = UPnPP::SoapHelp::i2s(CDPluginServices::microhttpport());
}
// http://wiki.openhome.org/wiki/Av:Developer:Eriskay:StreamingServices
// Tidal and qobuz tracks added by Kazoo / Lumin:
// tidal://track?version=1&trackId=[tidal_track_id]
// qobuz://track?version=2&trackId=[qobuz_track_id]
string se =
"(tidal|qobuz)://track\\?version=([[:digit:]]+)&trackId=([[:digit:]]+)";
std::regex e(se);
std::smatch mr;
bool found = std::regex_match(url, mr, e);
if (found) {
string pathprefix = CDPluginServices::getpathprefix(mr[1]);
// The microhttpd code actually only cares about getting a
// trackId parameter. Make it look what the plugins normally
// generate anyway:
string path = path_cat(pathprefix,
"track?version=1&trackId=" + mr[3].str());
url = string("http://") + upnphost + ":" + sport + path;
forcenocheck = true;
}
return true;
}
/* Copyright (C) 2016-2021 J.F.Dockes
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef _URLMORPH_H_INCLUDED_
#define _URLMORPH_H_INCLUDED_
// Translating special form URLs.
//
// OHCredentials:
// Called from OHPlaylist. The CP (Kazoo/Lumin mostly) will send URIs
// like qobuz:// tidal:// and expect the renderer to know what to do
// with them. We transform them so that they point to our media server
// gateway (which should be running of course for this to work).
// The URLs from kazoo look like:
// <service>://track?version=2&trackId=<trkid>
// We translate them to something which points to our proxy server, and
// that MPD will accept/use:
// http://<upnphost>:<sport>/<servicename>/track?version=1&trackId=<trkid>
// Where upnphost is the host used by libupnp, and sport the port on
// which the microhttpd listens.
// We retrieve upnphost from the upnp device during init, and sport by a
// call to the CDPluginServices.
//
// CDDA:
// Some control points don't like urls like cdda:///dev/sr0, they
// want everything to be http. We get these through with
// http://_protoescape/proto/path
// which we transform into
// proto:///path
#include <string>
bool morphSpecialUrl(std::string& url, bool& forcenocheck,
const std::string& upnphost = std::string());
#endif // _URLMORPH_H_INCLUDED
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment