1 //=============================================================================
2 //  MusE Score
3 //  Linux Music Score Editor
4 //
5 //  The webview is shown on startup with a local file inviting user
6 //  to start connecting with the community. They can press start and
7 //  MuseScore will go online. If no connection, display a can't connect message
8 //  On next startup, if no connection, the panel is closed. If connection, the
9 //  MuseScore goes online directly. If the autoclose panel is reopen, the user
10 //  can retry; retry should not close the panel.
11 //
12 //  Copyright (C) 2011 Werner Schweer and others
13 //
14 //  This program is free software; you can redistribute it and/or modify
15 //  it under the terms of the GNU General Public License version 2.
16 //
17 //  This program is distributed in the hope that it will be useful,
18 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 //  GNU General Public License for more details.
21 //
22 //  You should have received a copy of the GNU General Public License
23 //  along with this program; if not, write to the Free Software
24 //  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 //=============================================================================
26 
27 #include "webpage.h"
28 #include "musescore.h"
29 #include "preferences.h"
30 #include "libmscore/score.h"
31 
32 
33 namespace Ms {
34 
35 static const char* staticUrl = "http://connect.musescore.com";
36 
37 //---------------------------------------------------------
38 //   MyNetworkAccessManager
39 //---------------------------------------------------------
40 
createRequest(Operation op,const QNetworkRequest & req,QIODevice * outgoingData)41 QNetworkReply * MyNetworkAccessManager::createRequest(Operation op,
42                                           const QNetworkRequest & req,
43                                           QIODevice * outgoingData)
44       {
45       QNetworkRequest new_req(req);
46       new_req.setRawHeader("User-Agent",  QString("MuseScore %1").arg(VERSION).toAscii());
47       new_req.setRawHeader("Accept-Language",  QString("%1;q=0.8,en-US;q=0.6,en;q=0.4").arg(mscore->getLocaleISOCode()).toAscii());
48       return QNetworkAccessManager::createRequest(op, new_req, outgoingData);
49       }
50 
51 //---------------------------------------------------------
52 //   MyWebPage
53 //---------------------------------------------------------
54 
MyWebPage(QObject * parent)55 MyWebPage::MyWebPage(QObject *parent)
56    : QWebPage(parent)
57       {
58       // Enable plugin support
59       settings()->setAttribute(QWebSettings::PluginsEnabled, true);
60       }
61 
62 //---------------------------------------------------------
63 //   createPlugin
64 //---------------------------------------------------------
65 
createPlugin(const QString &,const QUrl &,const QStringList &,const QStringList &)66 QObject* MyWebPage::createPlugin(
67    const QString &/*classid*/,
68    const QUrl &/*url*/,
69    const QStringList &/*paramNames*/,
70    const QStringList &/*paramValues*/)
71       {
72       // Create the widget using QUiLoader.
73       // This means that the widgets don't need to be registered
74       // with the meta object system.
75       // On the other hand, non-gui objects can't be created this
76       // way. When we'd like to create non-visual objects in
77       // Html to use them via JavaScript, we'd use a different
78       // mechanism than this.
79 #if 0
80       if (classid == "WebScoreView") {
81             WebScoreView* sv = new WebScoreView(view());
82             int idx = paramNames.indexOf("score");
83             if (idx != -1) {
84                   QString score = paramValues[idx];
85                   sv->setScore(paramValues[idx]);
86                   }
87             else {
88                   qDebug("create WebScoreView: property score not found(%d)",
89                      paramNames.size());
90                   }
91             return sv;
92             }
93 #endif
94       return 0;
95 
96       /*QUiLoader loader;
97       return loader.createWidget(classid, view());*/
98       }
99 
100 //---------------------------------------------------------
101 //   MyWebView
102 //---------------------------------------------------------
103 
MyWebView(QWidget * parent)104 MyWebView::MyWebView(QWidget *parent):
105    QWebView(parent),
106    m_page(this)
107       {
108       // Set the page of our own PageView class, MyPageView,
109       // because only objects of this class will handle
110       // object-tags correctly.
111 
112       m_page.setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
113       QNetworkAccessManager *networkManager = new MyNetworkAccessManager(this);
114 #ifndef QT_NO_OPENSSL
115       connect(networkManager,SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)),this, SLOT(ignoreSSLErrors(QNetworkReply*,QList<QSslError>)));
116 #endif
117       m_page.setNetworkAccessManager(networkManager);
118       setPage(&m_page);
119       setZoomFactor(guiScaling);
120 
121       //set cookie jar for persistent cookies
122       CookieJar* jar = new CookieJar(QString(dataPath + "/cookies.txt"));
123       page()->networkAccessManager()->setCookieJar(jar);
124 
125       progressBar = 0;
126       connect(this, SIGNAL(linkClicked(const QUrl&)), SLOT(link(const QUrl&)));
127       }
128 
129 //---------------------------------------------------------
130 //   ~MyWebView
131 //---------------------------------------------------------
132 
~MyWebView()133 MyWebView::~MyWebView()
134       {
135       disconnect(this, SIGNAL(loadFinished(bool)), this, SLOT(stopBusy(bool)));
136       }
137 
138 #ifndef QT_NO_OPENSSL
139 /**
140 Slot connected to the sslErrors signal of QNetworkAccessManager
141 When this slot is called, call ignoreSslErrors method of QNetworkReply
142 */
ignoreSSLErrors(QNetworkReply * reply,QList<QSslError> sslErrors)143 void MyWebView::ignoreSSLErrors(QNetworkReply *reply, QList<QSslError> sslErrors)
144       {
145       foreach (const QSslError &error, sslErrors)
146             qDebug("Ignore SSL error: %d %s", error.error(), qPrintable(error.errorString()));
147       reply->ignoreSslErrors(sslErrors);
148       }
149 #endif
150 
151 //---------------------------------------------------------
152 //   stopBusy
153 //---------------------------------------------------------
154 
stopBusy(bool val)155 void MyWebView::stopBusy(bool val)
156       {
157       if (!val) {
158             setHtml(QString("<html><head>"
159                  "<script type=\"text/javascript\">"
160                   "function closePermanently() { mscore.closeWebPanelPermanently(); return false;}"
161                   "</script>"
162                   "<link rel=\"stylesheet\" href=\"data/webview.css\" type=\"text/css\" /></head>"
163             "<body>"
164             "<div id=\"content\">"
165             "<div id=\"middle\">"
166             "  <div class=\"title\" align=\"center\"><h2>%1</h2></div>"
167             "  <ul><li>%2</li></ul>"
168             "  <div align=\"center\"><a class=\"button\" href=\"#\" onclick=\"return panel.load();\">%3</a></div>"
169             "  <div align=\"center\"><a class=\"close\" href=\"#\" onclick=\"return closePermanently();\">%4</div>"
170             "</div></div>"
171             "</body></html>")
172             .arg(tr("Could not\nconnect"))
173             .arg(tr("To connect with the community,\nyou need to have internet\nconnection enabled")                 )
174             .arg(tr("Retry"))
175             .arg(tr("Close this permanently")).replace("\n", "<br/>"),
176             QUrl("qrc:/"));
177             }
178       mscore->hideProgressBar();
179       setCursor(Qt::ArrowCursor);
180       }
181 
182 //---------------------------------------------------------
183 //   setBusy
184 //---------------------------------------------------------
185 
setBusy()186 void MyWebView::setBusy()
187       {
188       setCursor(Qt::WaitCursor);
189       }
190 
191 //---------------------------------------------------------
192 //   link
193 //---------------------------------------------------------
194 
link(const QUrl & url)195 void MyWebView::link(const QUrl& url)
196       {
197       QString path(url.path());
198       QFileInfo fi(path);
199       if (fi.suffix() == "mscz" || fi.suffix() == "xml"
200           || fi.suffix() == "musicxml" || fi.suffix() == "mxl")
201             mscore->loadFile(url);
202       else if(url.host().startsWith("connect."))
203             load(QNetworkRequest(url));
204       else
205             QDesktopServices::openUrl(url);
206       }
207 
208 //---------------------------------------------------------
209 //   sizeHint
210 //---------------------------------------------------------
211 
sizeHint() const212 QSize MyWebView::sizeHint() const
213       {
214       return QSize(300 , 300);
215       }
216 
217 //---------------------------------------------------------
218 //   WebPageDockWidget
219 //---------------------------------------------------------
220 
WebPageDockWidget(MuseScore *,QWidget * parent)221 WebPageDockWidget::WebPageDockWidget(MuseScore* /*mscore*/, QWidget* parent)
222    : QDockWidget(parent)
223       {
224       setWindowTitle("MuseScore Connect");
225       setFloating(false);
226       setFeatures(QDockWidget::DockWidgetClosable);
227 
228       setObjectName("webpage");
229       setAllowedAreas(Qt::LeftDockWidgetArea);
230 
231 #if defined(WIN_PORTABLE)
232       QWebEngineProfile* defaultProfile = QWebEngineProfile::defaultProfile();
233       defaultProfile->setCachePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath())));
234       defaultProfile->setPersistentStoragePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath())));
235 #endif
236       web = new MyWebView;
237       web->setContextMenuPolicy(Qt::PreventContextMenu);
238       QWebFrame* frame = web->webPage()->mainFrame();
239       connect(frame, SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(addToJavascript()));
240 
241       connect(web, SIGNAL(loadFinished(bool)), web, SLOT(stopBusy(bool)));
242       web->setBusy();
243       web->load(QNetworkRequest(webUrl()));
244 
245       setWidget(web);
246 
247       //removing every widget from the tabbing order until support for
248       //accessibility is provided
249       QList<QWidget*> widgets = this->findChildren<QWidget*>();
250       for(int i = 0; i < widgets.size(); i++){
251             QWidget* currentWidget = widgets.at(i);
252             switch (currentWidget->focusPolicy()){
253                  case Qt::TabFocus:
254                        currentWidget->setFocusPolicy(Qt::NoFocus);
255                        break;
256                  case Qt::WheelFocus:
257                  case Qt::StrongFocus:
258                        currentWidget->setFocusPolicy(Qt::ClickFocus);
259                        break;
260                  case Qt::ClickFocus:
261                  case Qt::NoFocus:
262                        break;
263                  }
264            }
265       }
266 
267 //---------------------------------------------------------
268 //   addToJavascript
269 //---------------------------------------------------------
270 
addToJavascript()271 void WebPageDockWidget::addToJavascript()
272       {
273       QWebFrame* frame = web->webPage()->mainFrame();
274       frame->addToJavaScriptWindowObject("panel", this);
275       frame->addToJavaScriptWindowObject("mscore", mscore);
276       }
277 
currentScore()278 QObject* WebPageDockWidget::currentScore() {
279   QObject* score = mscore->currentScore();
280   return score;
281 }
282 
283 //---------------------------------------------------------
284 //   load
285 //---------------------------------------------------------
286 
load()287 void WebPageDockWidget::load()
288       {
289       web->setBusy();
290       web->load(QNetworkRequest(webUrl()));
291       }
292 
saveCurrentScoreOnline(QString action,QVariantMap parameters,QString fileFieldName)293 bool WebPageDockWidget::saveCurrentScoreOnline(QString action, QVariantMap parameters, QString fileFieldName)
294       {
295       qDebug("saveCurrentOnline");
296       QWebPage * page = web->webPage();
297       QNetworkAccessManager* manager = page->networkAccessManager();
298 
299       QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
300 
301       QMap<QString, QVariant>::const_iterator i = parameters.constBegin();
302       while (i != parameters.constEnd()) {
303             QHttpPart part;
304             part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(QString("form-data; name=\"%1\"").arg(i.key())));
305             part.setBody(i.value().toString().toLatin1());
306             multiPart->append(part);
307             //qDebug("%s ", qPrintable(i.key()));
308             //qDebug("%s ", qPrintable(i.value().toString()));
309             ++i;
310             }
311 
312       if(!fileFieldName.isEmpty()) {
313             QDir dir;
314             QFile *file = new QFile(dir.tempPath() + "/temp.mscz");
315             Score* score = mscore->currentScore();
316             if(score) {
317                   mscore->saveAs(score, true, file->fileName(), "mscz");
318                   }
319             else {
320                   delete multiPart;
321                   return false;
322                   }
323             QHttpPart filePart;
324             filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
325             filePart.setRawHeader("Content-Transfer-Encoding", "binary");
326             filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant(QString("form-data; name=\"%1\"; filename=\"temp.mscz\"").arg(fileFieldName)));
327             file->open(QIODevice::ReadOnly);
328             filePart.setBodyDevice(file);
329             file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart
330             multiPart->append(filePart);
331             }
332 
333       QUrl url(action);
334       QNetworkRequest request(url);
335 
336       //QNetworkAccessManager manager;
337       QNetworkReply *reply = manager->post(request, multiPart);
338       multiPart->setParent(reply); // delete the multiPart with the reply
339       // here connect signals etc.
340       connect(reply, SIGNAL(finished()),
341          this, SLOT(saveOnlineFinished()));
342 
343       return true;
344       }
345 
saveOnlineFinished()346 void WebPageDockWidget::saveOnlineFinished() {
347       qDebug("Save online finished");
348       // delete file
349       QDir dir;
350       QFile file(dir.tempPath() + "/temp.mscz");
351       file.remove();
352 
353       QNetworkReply *reply = (QNetworkReply *)sender();
354       // Reading attributes of the reply
355       // e.g. the HTTP status code
356       int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
357       QString message = reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
358 
359       // no error received?
360       if (reply->error() == QNetworkReply::NoError) {
361             //deal with redirect
362             if (300 <= httpStatus && httpStatus < 400) {
363                   qDebug("Redirecting to: %s", qPrintable(reply->url().toString()));
364                   web->load(QNetworkRequest(reply->url()));
365                   }
366             else if (httpStatus == 200) {
367                   //Reading bytes form the reply
368                   QByteArray bytes = reply->readAll();
369                   QString string(bytes);
370                   web->setHtml(string);
371                   }
372             else {
373                   qDebug("Unknown HTTP status: %d - %s", httpStatus, qPrintable(message));
374                   }
375             }
376       else { //error received
377             qDebug("Save online error %d, HTTP status: %d - %s", reply->error(), httpStatus, qPrintable(message));
378             }
379       reply->deleteLater();
380       }
381 
setCurrentScoreSource(QString)382 bool WebPageDockWidget::setCurrentScoreSource(QString /*source*/)
383       {
384       Score* score = mscore->currentScore();
385       if(score) {
386             score->metaTags().insert("source", "");
387             return true;
388             }
389       else {
390             return false;
391             }
392       }
393 
394 //---------------------------------------------------------
395 //   webUrl
396 //---------------------------------------------------------
webUrl()397 QUrl WebPageDockWidget::webUrl()
398     {
399     return QUrl(staticUrl);
400     }
401 
402 //---------------------------------------------------------
403 //   CookieJar
404 //
405 //   Once the QNetworkCookieJar object is deleted, all cookies it held will be
406 //   discarded as well. If you want to save the cookies, you should derive from
407 //   this class and implement the saving to disk to your own storage format.
408 //   (From QNetworkCookieJar documentation.)
409 //---------------------------------------------------------
410 
CookieJar(QString path,QObject * parent)411 CookieJar::CookieJar(QString path, QObject *parent)
412     : QNetworkCookieJar(parent)
413       {
414       file = path;
415       QFile cookieFile(this->file);
416 
417       if (cookieFile.exists() && cookieFile.open(QIODevice::ReadOnly)) {
418             QList<QNetworkCookie> list;
419             QByteArray line;
420 
421             while(!(line = cookieFile.readLine()).isNull()) {
422                   list.append(QNetworkCookie::parseCookies(line));
423                   }
424             setAllCookies(list);
425             }
426       else {
427             if (MScore::debugMode)
428                   qDebug() << "Can't open "<< this->file << " to read cookies";
429             }
430       }
431 
432 //---------------------------------------------------------
433 //   ~CookieJar
434 //---------------------------------------------------------
435 
~CookieJar()436 CookieJar::~CookieJar()
437       {
438       QList <QNetworkCookie> cookieList;
439       cookieList = allCookies();
440 
441       QFile file(this->file);
442 
443       if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
444             if (MScore::debugMode)
445                   qDebug() << "Can't open "<< this->file << " to save cookies";
446             return;
447             }
448 
449       QTextStream out(&file);
450       for(int i = 0 ; i < cookieList.size() ; i++) {
451                 //get cookie data
452                 QNetworkCookie cookie = cookieList.at(i);
453                 if (!cookie.isSessionCookie()) {
454                       QByteArray line =  cookieList.at(i).toRawForm(QNetworkCookie::Full);
455                       out << line << "\n";
456                       }
457             }
458       file.close();
459       }
460 
461 #if 0
462 //---------------------------------------------------------
463 //   WebScoreView
464 //---------------------------------------------------------
465 
466 WebScoreView::WebScoreView(QWidget* parent)
467    : ScoreView(parent)
468       {
469       networkManager = 0;
470       }
471 
472 WebScoreView::WebScoreView(const WebScoreView& wsv)
473    : ScoreView((QWidget*)(wsv.parent()))
474       {
475       networkManager = 0;
476       }
477 
478 //---------------------------------------------------------
479 //   setScore
480 //---------------------------------------------------------
481 
482 void WebScoreView::setScore(const QString& url)
483       {
484       if (!networkManager) {
485             networkManager = new QNetworkAccessManager(this);
486             connect(networkManager, SIGNAL(finished(QNetworkReply*)),
487                SLOT(networkFinished(QNetworkReply*)));
488             }
489       networkManager->get(QNetworkRequest(QUrl(url)));
490       }
491 
492 //---------------------------------------------------------
493 //   networkFinished
494 //---------------------------------------------------------
495 
496 void WebScoreView::networkFinished(QNetworkReply* reply)
497       {
498       if (reply->error() != QNetworkReply::NoError) {
499             if (MScore::debugMode)
500                   qDebug("Error while checking update [%s]", qPrintable(reply->errorString()));
501             return;
502             }
503       QByteArray ha = reply->rawHeader("Content-Disposition");
504       QString s(ha);
505       QString name;
506       QRegExp re(".*filename=\"(.*)\"");
507       if (s.isEmpty() || re.indexIn(s) == -1)
508             name = "unknown.mscz";
509       else
510             name = re.cap(1);
511 
512       QByteArray data = reply->readAll();
513       QString tmpName = QDir::tempPath () + "/"+ name;
514       QFile f(tmpName);
515       f.open(QIODevice::WriteOnly);
516       f.write(data);
517       f.close();
518 
519       Score* score = mscore->readScore(tmpName);
520       if (!score) {
521             qDebug("readScore failed");
522             return;
523             }
524       ScoreView::setScore(score);
525       update();
526       }
527 
528 #endif
529 }
530 
531