Allow Subsonic to follow redirects when logging in, as described in issue 3747
Fixes issue #3747
This commit is contained in:
parent
106e9a5dbd
commit
27c017626b
@ -33,6 +33,8 @@ const char* SubsonicService::kApiClientName = "Clementine";
|
|||||||
const char* SubsonicService::kSongsTable = "subsonic_songs";
|
const char* SubsonicService::kSongsTable = "subsonic_songs";
|
||||||
const char* SubsonicService::kFtsTable = "subsonic_songs_fts";
|
const char* SubsonicService::kFtsTable = "subsonic_songs_fts";
|
||||||
|
|
||||||
|
const int SubsonicService::kMaxRedirects = 10;
|
||||||
|
|
||||||
SubsonicService::SubsonicService(Application* app, InternetModel* parent)
|
SubsonicService::SubsonicService(Application* app, InternetModel* parent)
|
||||||
: InternetService(kServiceName, app, parent, parent),
|
: InternetService(kServiceName, app, parent, parent),
|
||||||
network_(new QNetworkAccessManager(this)),
|
network_(new QNetworkAccessManager(this)),
|
||||||
@ -46,7 +48,8 @@ SubsonicService::SubsonicService(Application* app, InternetModel* parent)
|
|||||||
library_filter_(NULL),
|
library_filter_(NULL),
|
||||||
library_sort_model_(new QSortFilterProxyModel(this)),
|
library_sort_model_(new QSortFilterProxyModel(this)),
|
||||||
total_song_count_(0),
|
total_song_count_(0),
|
||||||
login_state_(LoginState_OtherError) {
|
login_state_(LoginState_OtherError),
|
||||||
|
redirect_count_(0) {
|
||||||
app_->player()->RegisterUrlHandler(url_handler_);
|
app_->player()->RegisterUrlHandler(url_handler_);
|
||||||
|
|
||||||
connect(scanner_, SIGNAL(ScanFinished()),
|
connect(scanner_, SIGNAL(ScanFinished()),
|
||||||
@ -148,7 +151,7 @@ void SubsonicService::ReloadSettings() {
|
|||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
|
|
||||||
server_ = s.value("server").toString();
|
UpdateServer(s.value("server").toString());
|
||||||
username_ = s.value("username").toString();
|
username_ = s.value("username").toString();
|
||||||
password_ = s.value("password").toString();
|
password_ = s.value("password").toString();
|
||||||
usesslv3_ = s.value("usesslv3").toBool();
|
usesslv3_ = s.value("usesslv3").toBool();
|
||||||
@ -157,7 +160,7 @@ void SubsonicService::ReloadSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool SubsonicService::IsConfigured() const {
|
bool SubsonicService::IsConfigured() const {
|
||||||
return !server_.isEmpty() &&
|
return !configured_server_.isEmpty() &&
|
||||||
!username_.isEmpty() &&
|
!username_.isEmpty() &&
|
||||||
!password_.isEmpty();
|
!password_.isEmpty();
|
||||||
}
|
}
|
||||||
@ -181,7 +184,7 @@ void SubsonicService::Login() {
|
|||||||
|
|
||||||
void SubsonicService::Login(
|
void SubsonicService::Login(
|
||||||
const QString& server, const QString& username, const QString& password, const bool& usesslv3) {
|
const QString& server, const QString& username, const QString& password, const bool& usesslv3) {
|
||||||
server_ = server;
|
UpdateServer(server);
|
||||||
username_ = username;
|
username_ = username;
|
||||||
password_ = password;
|
password_ = password;
|
||||||
usesslv3_ = usesslv3;
|
usesslv3_ = usesslv3;
|
||||||
@ -196,7 +199,7 @@ void SubsonicService::Ping() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QUrl SubsonicService::BuildRequestUrl(const QString& view) const {
|
QUrl SubsonicService::BuildRequestUrl(const QString& view) const {
|
||||||
QUrl url(server_ + "/rest/" + view + ".view");
|
QUrl url(working_server_ + "/rest/" + view + ".view");
|
||||||
url.addQueryItem("v", kApiVersion);
|
url.addQueryItem("v", kApiVersion);
|
||||||
url.addQueryItem("c", kApiClientName);
|
url.addQueryItem("c", kApiClientName);
|
||||||
url.addQueryItem("u", username_);
|
url.addQueryItem("u", username_);
|
||||||
@ -204,6 +207,16 @@ QUrl SubsonicService::BuildRequestUrl(const QString& view) const {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl SubsonicService::ScrubUrl(const QUrl& url) {
|
||||||
|
QUrl return_url(url);
|
||||||
|
QString path = url.path();
|
||||||
|
int rest_location = path.lastIndexOf("/rest", -1, Qt::CaseInsensitive);
|
||||||
|
if (rest_location >= 0) {
|
||||||
|
return_url.setPath(path.left(rest_location));
|
||||||
|
}
|
||||||
|
return return_url;
|
||||||
|
}
|
||||||
|
|
||||||
QNetworkReply* SubsonicService::Send(const QUrl& url) {
|
QNetworkReply* SubsonicService::Send(const QUrl& url) {
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
// Don't try and check the authenticity of the SSL certificate - it'll almost
|
// Don't try and check the authenticity of the SSL certificate - it'll almost
|
||||||
@ -271,8 +284,32 @@ void SubsonicService::OnPingFinished(QNetworkReply* reply) {
|
|||||||
QXmlStreamReader reader(reply);
|
QXmlStreamReader reader(reply);
|
||||||
reader.readNextStartElement();
|
reader.readNextStartElement();
|
||||||
QStringRef status = reader.attributes().value("status");
|
QStringRef status = reader.attributes().value("status");
|
||||||
|
int http_status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
if (status == "ok") {
|
if (status == "ok") {
|
||||||
login_state_ = LoginState_Loggedin;
|
login_state_ = LoginState_Loggedin;
|
||||||
|
} else if (http_status_code >= 300 && http_status_code <= 399) {
|
||||||
|
// Received a redirect status code, follow up on it.
|
||||||
|
QUrl redirect_url =
|
||||||
|
reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
|
||||||
|
if (redirect_url.isEmpty()) {
|
||||||
|
qLog(Debug) << "Received HTTP code " << http_status_code << ", but no URL";
|
||||||
|
login_state_ = LoginState_RedirectNoUrl;
|
||||||
|
} else {
|
||||||
|
redirect_count_++;
|
||||||
|
qLog(Debug) << "Redirect receieved to "
|
||||||
|
<< redirect_url.toString(QUrl::RemoveQuery)
|
||||||
|
<< ", current redirect count is "
|
||||||
|
<< redirect_count_;
|
||||||
|
if (redirect_count_ <= kMaxRedirects) {
|
||||||
|
working_server_ = ScrubUrl(redirect_url).toString(QUrl::RemoveQuery);
|
||||||
|
Ping();
|
||||||
|
// To avoid the LoginStateChanged, as it will come from the recursive request.
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Redirect limit exceeded
|
||||||
|
login_state_ = LoginState_RedirectLimitExceeded;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
reader.readNextStartElement();
|
reader.readNextStartElement();
|
||||||
int error = reader.attributes().value("code").toString().toInt();
|
int error = reader.attributes().value("code").toString().toInt();
|
||||||
@ -309,6 +346,12 @@ void SubsonicService::ShowConfig() {
|
|||||||
app_->OpenSettingsDialogAtPage(SettingsDialog::Page_Subsonic);
|
app_->OpenSettingsDialogAtPage(SettingsDialog::Page_Subsonic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SubsonicService::UpdateServer(const QString& server) {
|
||||||
|
configured_server_ = server;
|
||||||
|
working_server_ = server;
|
||||||
|
redirect_count_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const int SubsonicLibraryScanner::kAlbumChunkSize = 500;
|
const int SubsonicLibraryScanner::kAlbumChunkSize = 500;
|
||||||
const int SubsonicLibraryScanner::kConcurrentRequests = 8;
|
const int SubsonicLibraryScanner::kConcurrentRequests = 8;
|
||||||
|
@ -38,6 +38,8 @@ class SubsonicService : public InternetService
|
|||||||
LoginState_Timeout,
|
LoginState_Timeout,
|
||||||
LoginState_SslError,
|
LoginState_SslError,
|
||||||
LoginState_IncompleteCredentials,
|
LoginState_IncompleteCredentials,
|
||||||
|
LoginState_RedirectLimitExceeded,
|
||||||
|
LoginState_RedirectNoUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ApiError {
|
enum ApiError {
|
||||||
@ -66,21 +68,27 @@ class SubsonicService : public InternetService
|
|||||||
bool IsConfigured() const;
|
bool IsConfigured() const;
|
||||||
|
|
||||||
QStandardItem* CreateRootItem();
|
QStandardItem* CreateRootItem();
|
||||||
void LazyPopulate(QStandardItem *item);
|
void LazyPopulate(QStandardItem* item);
|
||||||
void ShowContextMenu(const QPoint &global_pos);
|
void ShowContextMenu(const QPoint& global_pos);
|
||||||
QWidget* HeaderWidget() const;
|
QWidget* HeaderWidget() const;
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
void Login();
|
void Login();
|
||||||
void Login(
|
void Login(
|
||||||
const QString &server, const QString &username, const QString &password, const bool &usesslv3);
|
const QString& server,
|
||||||
|
const QString& username,
|
||||||
|
const QString& password,
|
||||||
|
const bool& usesslv3);
|
||||||
|
|
||||||
LoginState login_state() const { return login_state_; }
|
LoginState login_state() const { return login_state_; }
|
||||||
|
|
||||||
// Subsonic API methods
|
// Subsonic API methods
|
||||||
void Ping();
|
void Ping();
|
||||||
|
|
||||||
QUrl BuildRequestUrl(const QString& view) const;
|
QUrl BuildRequestUrl(const QString& view) const;
|
||||||
// Convenience function to reduce QNetworkRequest/QSslConfiguration boilerplate
|
// Scrubs the part of the path that we re-add in BuildRequestUrl().
|
||||||
|
static QUrl ScrubUrl(const QUrl& url);
|
||||||
|
// Convenience function to reduce QNetworkRequest/QSslConfiguration boilerplate.
|
||||||
QNetworkReply* Send(const QUrl& url);
|
QNetworkReply* Send(const QUrl& url);
|
||||||
|
|
||||||
static const char* kServiceName;
|
static const char* kServiceName;
|
||||||
@ -91,11 +99,15 @@ class SubsonicService : public InternetService
|
|||||||
static const char* kSongsTable;
|
static const char* kSongsTable;
|
||||||
static const char* kFtsTable;
|
static const char* kFtsTable;
|
||||||
|
|
||||||
|
static const int kMaxRedirects;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void LoginStateChanged(SubsonicService::LoginState newstate);
|
void LoginStateChanged(SubsonicService::LoginState newstate);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void EnsureMenuCreated();
|
void EnsureMenuCreated();
|
||||||
|
// Update configured and working server state
|
||||||
|
void UpdateServer(const QString& server);
|
||||||
|
|
||||||
QNetworkAccessManager* network_;
|
QNetworkAccessManager* network_;
|
||||||
SubsonicUrlHandler* url_handler_;
|
SubsonicUrlHandler* url_handler_;
|
||||||
@ -113,12 +125,15 @@ class SubsonicService : public InternetService
|
|||||||
int total_song_count_;
|
int total_song_count_;
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
QString server_;
|
// The server that shows up in the GUI (use UpdateServer() to update)
|
||||||
|
QString configured_server_;
|
||||||
QString username_;
|
QString username_;
|
||||||
QString password_;
|
QString password_;
|
||||||
bool usesslv3_;
|
bool usesslv3_;
|
||||||
|
|
||||||
LoginState login_state_;
|
LoginState login_state_;
|
||||||
|
QString working_server_; // The actual server, possibly post-redirect
|
||||||
|
int redirect_count_;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void UpdateTotalSongCount(int count);
|
void UpdateTotalSongCount(int count);
|
||||||
|
@ -127,6 +127,16 @@ void SubsonicSettingsPage::LoginStateChanged(SubsonicService::LoginState newstat
|
|||||||
ui_->login_state->SetAccountTypeText(tr("Incomplete configuration, please ensure all fields are populated."));
|
ui_->login_state->SetAccountTypeText(tr("Incomplete configuration, please ensure all fields are populated."));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case SubsonicService::LoginState_RedirectLimitExceeded:
|
||||||
|
ui_->login_state->SetAccountTypeText(tr("Redirect limit exceeded, verify server configuration."));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SubsonicService::LoginState_RedirectNoUrl:
|
||||||
|
ui_->login_state->SetAccountTypeText(tr("HTTP 3xx status code received without URL, "
|
||||||
|
"verify server configuration."));
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -141,12 +151,8 @@ void SubsonicSettingsPage::ServerEditingFinished() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A direct paste of the server URL will probably include the trailing index.view, so remove it
|
// If the user specified a /rest location, remove it since we're going to re-add it later
|
||||||
if (url.path().endsWith("index.view")) {
|
url = SubsonicService::ScrubUrl(url);
|
||||||
QString newpath = url.path();
|
|
||||||
newpath.chop(10);
|
|
||||||
url.setPath(newpath);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui_->server->setText(url.toString());
|
ui_->server->setText(url.toString());
|
||||||
qLog(Debug) << "URL fixed:" << input << "to" << url;
|
qLog(Debug) << "URL fixed:" << input << "to" << url;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user