LCOV - code coverage report
Current view: top level - muse/framework/cloud/internal - oauthhttpserverreplyhandler.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 182 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2022 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "oauthhttpserverreplyhandler.h"
      24              : 
      25              : #include <cctype>
      26              : #include <cstring>
      27              : #include <functional>
      28              : 
      29              : #include <QCoreApplication>
      30              : #include <QNetworkReply>
      31              : #include <QTcpServer>
      32              : #include <QTcpSocket>
      33              : #include <QUrl>
      34              : #include <QUrlQuery>
      35              : 
      36              : #include "modularity/ioc.h"
      37              : #include "icloudconfiguration.h"
      38              : 
      39              : #include "translation.h"
      40              : 
      41              : #include "log.h"
      42              : 
      43              : using namespace muse::cloud;
      44              : 
      45              : class OAuthHttpServerReplyHandler::Impl : public Contextable
      46              : {
      47              :     muse::GlobalInject<ICloudConfiguration> configuration;
      48              : 
      49              : public:
      50              :     explicit Impl(OAuthHttpServerReplyHandler* p, const modularity::ContextPtr& iocCtx);
      51              :     ~Impl();
      52              : 
      53              :     QTcpServer m_httpServer;
      54              :     QHostAddress m_listenAddress = QHostAddress::LocalHost;
      55              :     QString m_path;
      56              : 
      57              :     QUrl m_redirectUrl;
      58              : 
      59              : private:
      60              :     void onClientConnected();
      61              :     void readData(QTcpSocket* socket);
      62              :     void answerClient(QTcpSocket* socket, const QUrl& url);
      63              : 
      64              :     struct HttpRequest {
      65              :         quint16 port = 0;
      66              : 
      67              :         bool readMethod(QTcpSocket* socket);
      68              :         bool readUrl(QTcpSocket* socket);
      69              :         bool readStatus(QTcpSocket* socket);
      70              :         bool readHeader(QTcpSocket* socket);
      71              : 
      72              :         enum class State {
      73              :             ReadingMethod,
      74              :             ReadingUrl,
      75              :             ReadingStatus,
      76              :             ReadingHeader,
      77              :             ReadingBody,
      78              :             AllDone
      79              :         } state = State::ReadingMethod;
      80              : 
      81              :         QByteArray m_fragment;
      82              : 
      83              :         enum class Method {
      84              :             Unknown,
      85              :             Head,
      86              :             Get,
      87              :             Put,
      88              :             Post,
      89              :             Delete,
      90              :         } method = Method::Unknown;
      91              : 
      92              :         QUrl url;
      93              :         QPair<quint8, quint8> version;
      94              :         QMap<QByteArray, QByteArray> headers;
      95              :     };
      96              : 
      97              :     QMap<QTcpSocket*, HttpRequest> m_clients;
      98              : 
      99              :     OAuthHttpServerReplyHandler* m_public = nullptr;
     100              : };
     101              : 
     102            0 : OAuthHttpServerReplyHandler::Impl::Impl(OAuthHttpServerReplyHandler* p, const modularity::ContextPtr& iocCtx)
     103            0 :     : Contextable(iocCtx), m_public(p)
     104              : {
     105            0 :     QObject::connect(&m_httpServer, &QTcpServer::newConnection, [this]() { onClientConnected(); });
     106            0 : }
     107              : 
     108            0 : OAuthHttpServerReplyHandler::Impl::~Impl()
     109              : {
     110            0 :     if (m_httpServer.isListening()) {
     111            0 :         m_httpServer.close();
     112              :     }
     113            0 : }
     114              : 
     115            0 : void OAuthHttpServerReplyHandler::Impl::onClientConnected()
     116              : {
     117            0 :     QTcpSocket* socket = m_httpServer.nextPendingConnection();
     118              : 
     119            0 :     QObject::connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
     120            0 :     QObject::connect(socket, &QTcpSocket::readyRead, [this, socket]() { readData(socket); });
     121            0 : }
     122              : 
     123            0 : void OAuthHttpServerReplyHandler::Impl::readData(QTcpSocket* socket)
     124              : {
     125            0 :     if (!m_clients.contains(socket)) {
     126            0 :         m_clients[socket].port = m_httpServer.serverPort();
     127              :     }
     128              : 
     129            0 :     HttpRequest* request = &m_clients[socket];
     130            0 :     bool error = false;
     131              : 
     132            0 :     if (Q_LIKELY(request->state == HttpRequest::State::ReadingMethod)) {
     133            0 :         if (Q_UNLIKELY(error = !request->readMethod(socket))) {
     134            0 :             LOGW() << "Invalid Method";
     135              :         }
     136              :     }
     137              : 
     138            0 :     if (Q_LIKELY(!error && request->state == HttpRequest::State::ReadingUrl)) {
     139            0 :         if (Q_UNLIKELY(error = !request->readUrl(socket))) {
     140            0 :             LOGW() << "Invalid URL";
     141              :         }
     142              :     }
     143              : 
     144            0 :     if (Q_LIKELY(!error && request->state == HttpRequest::State::ReadingStatus)) {
     145            0 :         if (Q_UNLIKELY(error = !request->readStatus(socket))) {
     146            0 :             LOGW() << "Invalid Status";
     147              :         }
     148              :     }
     149              : 
     150            0 :     if (Q_LIKELY(!error && request->state == HttpRequest::State::ReadingHeader)) {
     151            0 :         if (Q_UNLIKELY(error = !request->readHeader(socket))) {
     152            0 :             LOGW() << "Invalid Header";
     153              :         }
     154              :     }
     155              : 
     156            0 :     if (error) {
     157            0 :         socket->disconnectFromHost();
     158            0 :         m_clients.remove(socket);
     159            0 :     } else if (!request->url.isEmpty()) {
     160            0 :         Q_ASSERT(request->state != HttpRequest::State::ReadingUrl);
     161            0 :         answerClient(socket, request->url);
     162            0 :         m_clients.remove(socket);
     163              :     }
     164            0 : }
     165              : 
     166            0 : void OAuthHttpServerReplyHandler::Impl::answerClient(QTcpSocket* socket, const QUrl& url)
     167              : {
     168            0 :     if (!url.path().startsWith(QLatin1String("/") + m_path)) {
     169            0 :         LOGW() << "Invalid request: " << url;
     170            0 :         socket->disconnectFromHost();
     171            0 :         return;
     172              :     }
     173              : 
     174            0 :     QVariantMap receivedData;
     175            0 :     const QUrlQuery query(url.query());
     176            0 :     const auto items = query.queryItems();
     177            0 :     for (auto it = items.begin(), end = items.end(); it != end; ++it) {
     178            0 :         receivedData.insert(it->first, it->second);
     179              :     }
     180              : 
     181            0 :     Q_EMIT m_public->callbackReceived(receivedData);
     182              : 
     183              :     // Fallback text, shown while redirecting
     184            0 :     const QString text = muse::qtrc("cloud", "Sign in successful! You’re good to go back to MuseScore Studio.");
     185              : 
     186            0 :     const QByteArray html = QByteArrayLiteral("<html><head><title>")
     187            0 :                             + qApp->applicationName().toUtf8()
     188            0 :                             + QByteArrayLiteral("</title></head><body>")
     189            0 :                             + text.toUtf8()
     190            0 :                             + ("</body></html>");
     191              : 
     192            0 :     const QByteArray htmlSize = QByteArray::number(html.size());
     193            0 :     const QByteArray replyMessage = QByteArrayLiteral("HTTP/1.0 301 Moved Permanently \r\n"
     194            0 :                                                       "Location: ") + m_redirectUrl.toString().toUtf8()
     195            0 :                                     + QByteArrayLiteral("\r\nContent-Type: text/html; "
     196              :                                                         "charset=\"utf-8\"\r\n"
     197            0 :                                                         "Content-Length: ") + htmlSize
     198            0 :                                     + QByteArrayLiteral("\r\n\r\n")
     199            0 :                                     + html;
     200              : 
     201            0 :     socket->write(replyMessage);
     202            0 : }
     203              : 
     204            0 : bool OAuthHttpServerReplyHandler::Impl::HttpRequest::readMethod(QTcpSocket* socket)
     205              : {
     206            0 :     bool finished = false;
     207            0 :     while (socket->bytesAvailable() && !finished) {
     208              :         char c;
     209            0 :         socket->getChar(&c);
     210            0 :         if (std::isupper(c) && m_fragment.size() < 6) {
     211            0 :             m_fragment += c;
     212              :         } else {
     213            0 :             finished = true;
     214              :         }
     215              :     }
     216            0 :     if (finished) {
     217            0 :         if (m_fragment == "HEAD") {
     218            0 :             method = Method::Head;
     219            0 :         } else if (m_fragment == "GET") {
     220            0 :             method = Method::Get;
     221            0 :         } else if (m_fragment == "PUT") {
     222            0 :             method = Method::Put;
     223            0 :         } else if (m_fragment == "POST") {
     224            0 :             method = Method::Post;
     225            0 :         } else if (m_fragment == "DELETE") {
     226            0 :             method = Method::Delete;
     227              :         } else {
     228            0 :             LOGW() << "Invalid operation: " << m_fragment;
     229              :         }
     230              : 
     231            0 :         state = State::ReadingUrl;
     232            0 :         m_fragment.clear();
     233              : 
     234            0 :         return method != Method::Unknown;
     235              :     }
     236            0 :     return true;
     237              : }
     238              : 
     239            0 : bool OAuthHttpServerReplyHandler::Impl::HttpRequest::readUrl(QTcpSocket* socket)
     240              : {
     241            0 :     bool finished = false;
     242            0 :     while (socket->bytesAvailable() && !finished) {
     243              :         char c;
     244            0 :         socket->getChar(&c);
     245            0 :         if (std::isspace(c)) {
     246            0 :             finished = true;
     247              :         } else {
     248            0 :             m_fragment += c;
     249              :         }
     250              :     }
     251            0 :     if (finished) {
     252            0 :         if (!m_fragment.startsWith("/")) {
     253            0 :             LOGW() << "Invalid URL path: " << m_fragment;
     254            0 :             return false;
     255              :         }
     256            0 :         url.setUrl(QStringLiteral("http://127.0.0.1:") + QString::number(port)
     257            0 :                    + QString::fromUtf8(m_fragment));
     258            0 :         state = State::ReadingStatus;
     259            0 :         if (!url.isValid()) {
     260            0 :             LOGW() << "Invalid URL " << m_fragment;
     261            0 :             return false;
     262              :         }
     263            0 :         m_fragment.clear();
     264            0 :         return true;
     265              :     }
     266            0 :     return true;
     267              : }
     268              : 
     269            0 : bool OAuthHttpServerReplyHandler::Impl::HttpRequest::readStatus(QTcpSocket* socket)
     270              : {
     271            0 :     bool finished = false;
     272            0 :     while (socket->bytesAvailable() && !finished) {
     273              :         char c;
     274            0 :         socket->getChar(&c);
     275            0 :         m_fragment += c;
     276            0 :         if (m_fragment.endsWith("\r\n")) {
     277            0 :             finished = true;
     278            0 :             m_fragment.resize(m_fragment.size() - 2);
     279              :         }
     280              :     }
     281            0 :     if (finished) {
     282            0 :         if (!std::isdigit(m_fragment.at(m_fragment.size() - 3))
     283            0 :             || !std::isdigit(m_fragment.at(m_fragment.size() - 1))) {
     284            0 :             LOGW() << "Invalid version";
     285            0 :             return false;
     286              :         }
     287            0 :         version = qMakePair(m_fragment.at(m_fragment.size() - 3) - '0',
     288            0 :                             m_fragment.at(m_fragment.size() - 1) - '0');
     289            0 :         state = State::ReadingHeader;
     290            0 :         m_fragment.clear();
     291              :     }
     292            0 :     return true;
     293              : }
     294              : 
     295            0 : bool OAuthHttpServerReplyHandler::Impl::HttpRequest::readHeader(QTcpSocket* socket)
     296              : {
     297            0 :     while (socket->bytesAvailable()) {
     298              :         char c;
     299            0 :         socket->getChar(&c);
     300            0 :         m_fragment += c;
     301            0 :         if (m_fragment.endsWith("\r\n")) {
     302            0 :             if (m_fragment == "\r\n") {
     303            0 :                 state = State::ReadingBody;
     304            0 :                 m_fragment.clear();
     305            0 :                 return true;
     306              :             } else {
     307            0 :                 m_fragment.chop(2);
     308            0 :                 const int index = m_fragment.indexOf(':');
     309            0 :                 if (index == -1) {
     310            0 :                     return false;
     311              :                 }
     312              : 
     313            0 :                 const QByteArray key = m_fragment.mid(0, index).trimmed();
     314            0 :                 const QByteArray value = m_fragment.mid(index + 1).trimmed();
     315            0 :                 headers.insert(key, value);
     316            0 :                 m_fragment.clear();
     317            0 :             }
     318              :         }
     319              :     }
     320            0 :     return false;
     321              : }
     322              : 
     323            0 : OAuthHttpServerReplyHandler::OAuthHttpServerReplyHandler(const modularity::ContextPtr& iocCtx, QObject* parent)
     324            0 :     : OAuthHttpServerReplyHandler(QHostAddress::Any, 0, iocCtx, parent)
     325            0 : {}
     326              : 
     327            0 : OAuthHttpServerReplyHandler::OAuthHttpServerReplyHandler(quint16 port, const modularity::ContextPtr& iocCtx, QObject* parent)
     328            0 :     : OAuthHttpServerReplyHandler(QHostAddress::Any, port, iocCtx, parent)
     329            0 : {}
     330              : 
     331            0 : OAuthHttpServerReplyHandler::OAuthHttpServerReplyHandler(const QHostAddress& address, quint16 port, const modularity::ContextPtr& iocCtx,
     332            0 :                                                          QObject* parent)
     333            0 :     : QOAuthOobReplyHandler(parent), m_impl(std::make_unique<Impl>(this, iocCtx))
     334              : {
     335            0 :     listen(address, port);
     336            0 : }
     337              : 
     338            0 : OAuthHttpServerReplyHandler::~OAuthHttpServerReplyHandler()
     339            0 : {}
     340              : 
     341            0 : QString OAuthHttpServerReplyHandler::callback() const
     342              : {
     343            0 :     Q_ASSERT(m_impl->m_httpServer.isListening());
     344            0 :     const QUrl url(QString::fromLatin1("http://127.0.0.1:%1/%2")
     345            0 :                    .arg(m_impl->m_httpServer.serverPort()).arg(m_impl->m_path));
     346            0 :     return url.toString(QUrl::EncodeDelimiters);
     347            0 : }
     348              : 
     349            0 : QString OAuthHttpServerReplyHandler::callbackPath() const
     350              : {
     351            0 :     return m_impl->m_path;
     352              : }
     353              : 
     354            0 : void OAuthHttpServerReplyHandler::setCallbackPath(const QString& path)
     355              : {
     356            0 :     QString copy = path;
     357            0 :     while (copy.startsWith(QLatin1Char('/'))) {
     358            0 :         copy = copy.mid(1);
     359              :     }
     360              : 
     361            0 :     m_impl->m_path = copy;
     362            0 : }
     363              : 
     364            0 : quint16 OAuthHttpServerReplyHandler::port() const
     365              : {
     366            0 :     return m_impl->m_httpServer.serverPort();
     367              : }
     368              : 
     369            0 : bool OAuthHttpServerReplyHandler::listen(const QHostAddress& address, quint16 port)
     370              : {
     371            0 :     return m_impl->m_httpServer.listen(address, port);
     372              : }
     373              : 
     374            0 : void OAuthHttpServerReplyHandler::close()
     375              : {
     376            0 :     return m_impl->m_httpServer.close();
     377              : }
     378              : 
     379            0 : bool OAuthHttpServerReplyHandler::isListening() const
     380              : {
     381            0 :     return m_impl->m_httpServer.isListening();
     382              : }
     383              : 
     384            0 : void OAuthHttpServerReplyHandler::setRedirectUrl(const QUrl& url)
     385              : {
     386            0 :     m_impl->m_redirectUrl = url;
     387            0 : }
        

Generated by: LCOV version 2.0-1