浏览代码

Only use CSP workaround when actually needed.

Ian Walton 2 年之前
父节点
当前提交
613773fd1e

+ 62 - 26
native/find-webclient.js

@@ -1,3 +1,24 @@
+function loadScript(src) {
+    return new Promise((resolve, reject) => {
+        const s = document.createElement('script');
+        s.src = src;
+        s.onload = resolve;
+        s.onerror = reject;
+        document.head.appendChild(s);
+    });
+}
+
+async function createApi() {
+    await loadScript('qrc:///qtwebchannel/qwebchannel.js');
+    const channel = await new Promise((resolve) => {
+        /*global QWebChannel */
+        new QWebChannel(window.qt.webChannelTransport, resolve);
+    });
+    return channel.objects;
+}
+
+window.apiPromise = createApi();
+
 async function tryConnect(server) {
 async function tryConnect(server) {
     document.getElementById('connect-button').disabled = true;
     document.getElementById('connect-button').disabled = true;
 
 
@@ -14,36 +35,47 @@ async function tryConnect(server) {
                 throw new Error("Status not ok");
                 throw new Error("Status not ok");
             }
             }
 
 
-            // Sigh... If we just navigate to the URL, the server's CSP will block us loading other resources.
-            // So we have to parse the HTML, set a new base href, and then write it back to the page.
-            // We also have to override the history functions to make sure they use the correct URL.
-            const webUrl = htmlResponse.url.replace(/\/[^\/]*$/, "/");
-            const realUrl = window.location.href;
+            if (response.headers.get("content-security-policy")) {
+                // Sigh... If we just navigate to the URL, the server's CSP will block us loading other resources.
+                // So we have to parse the HTML, set a new base href, and then write it back to the page.
+                // We also have to override the history functions to make sure they use the correct URL.
+                console.log("Using CSP workaround");
+                const webUrl = htmlResponse.url.replace(/\/[^\/]*$/, "/");
+                const realUrl = window.location.href;
 
 
-            const html = await htmlResponse.text();
-            const parser = new DOMParser();
-            const doc = parser.parseFromString(html, "text/html");
-            const base = doc.createElement("base");
-            base.href = webUrl
-            doc.head.insertBefore(base, doc.head.firstChild);
-            
-            const oldPushState = window.history.pushState;
-            window.history.pushState = function(state, title, url) {
-                url = (new URL(url, realUrl)).toString();
-                return oldPushState.call(window.history, state, title, url);
-            };
+                const html = await htmlResponse.text();
+                const parser = new DOMParser();
+                const doc = parser.parseFromString(html, "text/html");
+                const base = doc.createElement("base");
+                base.href = webUrl
+                doc.head.insertBefore(base, doc.head.firstChild);
+                
+                const oldPushState = window.history.pushState;
+                window.history.pushState = function(state, title, url) {
+                    url = (new URL(url, realUrl)).toString();
+                    return oldPushState.call(window.history, state, title, url);
+                };
 
 
-            const oldReplaceState = window.history.replaceState;
-            window.history.replaceState = function(state, title, url) {
-                url = (new URL(url, realUrl)).toString();
-                return oldReplaceState.call(window.history, state, title, url);
-            };
+                const oldReplaceState = window.history.replaceState;
+                window.history.replaceState = function(state, title, url) {
+                    url = (new URL(url, realUrl)).toString();
+                    return oldReplaceState.call(window.history, state, title, url);
+                };
 
 
-            document.open();
-            document.write((new XMLSerializer()).serializeToString(doc));
-            document.close();
+                document.open();
+                document.write((new XMLSerializer()).serializeToString(doc));
+                document.close();
+            } else {
+                console.log("Using normal navigation");
+                window.location = server;
+            }
 
 
             window.localStorage.setItem("saved-server", server);
             window.localStorage.setItem("saved-server", server);
+            
+            const api = await window.apiPromise;
+            await new Promise(resolve => {
+                api.settings.setValue('main', 'userWebClient', server, resolve);
+            });
             return true;
             return true;
         }
         }
     } catch (e) {
     } catch (e) {
@@ -66,10 +98,14 @@ document.getElementById('connect-fail-button').addEventListener('click', () => {
     document.getElementById('backdrop').style.display = 'none';
     document.getElementById('backdrop').style.display = 'none';
 });
 });
 
 
-const savedServer = window.localStorage.getItem("saved-server");
 
 
 // load the server if we have one
 // load the server if we have one
 (async() => {
 (async() => {
+    const api = await window.apiPromise;
+    const savedServer = await new Promise(resolve => {
+        api.settings.value('main', 'userWebClient', resolve);
+    });
+
     if (!savedServer || !(await tryConnect(savedServer))) {
     if (!savedServer || !(await tryConnect(savedServer))) {
         document.getElementById('splash').style.display = 'none';
         document.getElementById('splash').style.display = 'none';
         document.getElementById('main').style.display = 'block';
         document.getElementById('main').style.display = 'block';

+ 28 - 7
native/nativeshell.js

@@ -133,7 +133,23 @@ function getDeviceProfile() {
 }
 }
 
 
 async function createApi() {
 async function createApi() {
-    await loadScript('qrc:///qtwebchannel/qwebchannel.js');
+    try {
+        await loadScript('qrc:///qtwebchannel/qwebchannel.js');
+    } catch (e) {
+        // try clearing out any cached CSPs
+        let foundCache = false;
+        for (const cache of await caches.keys()) {
+            const dataDeleted = await caches.delete(cache);
+            if (dataDeleted) {
+                foundCache = true;
+            }
+        }
+        if (foundCache) {
+            window.location.reload();
+        }
+        throw e;
+    }
+
     const channel = await new Promise((resolve) => {
     const channel = await new Promise((resolve) => {
         /*global QWebChannel */
         /*global QWebChannel */
         new QWebChannel(window.qt.webChannelTransport, resolve);
         new QWebChannel(window.qt.webChannelTransport, resolve);
@@ -291,7 +307,11 @@ async function showSettingsModal() {
         createSection();
         createSection();
     }
     }
 
 
-    if (window.localStorage.getItem("saved-server") != null) {
+    const savedServer = await new Promise(resolve => {
+        window.api.settings.value('main', 'userWebClient', resolve);
+    });
+
+    if (savedServer) {
         const group = document.createElement("fieldset");
         const group = document.createElement("fieldset");
         group.className = "editItemMetadataForm editMetadataForm dialog-content-centered";
         group.className = "editItemMetadataForm editMetadataForm dialog-content-centered";
         group.style.border = 0;
         group.style.border = 0;
@@ -303,7 +323,7 @@ async function showSettingsModal() {
         legend.appendChild(legendHeader);
         legend.appendChild(legendHeader);
         const legendSubHeader = document.createElement("h4");
         const legendSubHeader = document.createElement("h4");
         legendSubHeader.textContent = (
         legendSubHeader.textContent = (
-            "The server you first connected to is your saved server. " + 
+            "The server you first connected to is your saved server. " +
             "It provides the web client for Jellyfin Media Player in the absence of a bundled one. " +
             "It provides the web client for Jellyfin Media Player in the absence of a bundled one. " +
             "You can use this option to change it to another one. This does NOT log you off."
             "You can use this option to change it to another one. This does NOT log you off."
         );
         );
@@ -316,10 +336,11 @@ async function showSettingsModal() {
         resetSavedServer.style.marginLeft = "auto";
         resetSavedServer.style.marginLeft = "auto";
         resetSavedServer.style.marginRight = "auto";
         resetSavedServer.style.marginRight = "auto";
         resetSavedServer.style.maxWidth = "50%";
         resetSavedServer.style.maxWidth = "50%";
-        resetSavedServer.addEventListener("click", () => {
-            window.localStorage.removeItem("saved-server");
-            window.location.hash = "";
-            window.location.reload();
+        resetSavedServer.addEventListener("click", async () => {
+            await new Promise(resolve => {
+                window.api.settings.setValue('main', 'userWebClient', '', resolve);
+            });
+            window.location.href = viewdata.scriptPath + "/find-webclient.html";
         });
         });
         group.appendChild(resetSavedServer);
         group.appendChild(resetSavedServer);
     }
     }

+ 5 - 0
resources/settings/settings_description.json

@@ -132,6 +132,11 @@
       {
       {
         "value": "forceExternalWebclient",
         "value": "forceExternalWebclient",
         "default": false
         "default": false
+      },
+      {
+        "value": "userWebClient",
+        "default": "",
+        "hidden": true
       }
       }
     ]
     ]
   },
   },

+ 22 - 0
src/settings/SettingsComponent.cpp

@@ -717,6 +717,28 @@ bool SettingsComponent::resetAndSaveOldConfiguration()
   return settingsFile.rename(Paths::dataDir("jellyfinmediaplayer.conf.old"));
   return settingsFile.rename(Paths::dataDir("jellyfinmediaplayer.conf.old"));
 }
 }
 
 
+
+/////////////////////////////////////////////////////////////////////////////////////////
+bool SettingsComponent::isUsingExternalWebClient()
+{
+  QString url;
+
+  url = SettingsComponent::Get().value(SETTINGS_SECTION_PATH, "startupurl_desktop").toString();
+
+  if (url == "bundled")
+  {
+    auto path = Paths::webClientPath("desktop");
+    QFileInfo check_file(path);
+    if (SettingsComponent::Get().value(SETTINGS_SECTION_MAIN, "forceExternalWebclient").toBool() ||
+       !(check_file.exists() && check_file.isFile())) {
+      // use built-in fallback
+      return true;
+    }
+  }
+
+  return false;
+}
+
 /////////////////////////////////////////////////////////////////////////////////////////
 /////////////////////////////////////////////////////////////////////////////////////////
 QString SettingsComponent::getWebClientUrl(bool desktop)
 QString SettingsComponent::getWebClientUrl(bool desktop)
 {
 {

+ 1 - 0
src/settings/SettingsComponent.h

@@ -59,6 +59,7 @@ public:
   Q_INVOKABLE QVariantList settingDescriptions();
   Q_INVOKABLE QVariantList settingDescriptions();
   Q_INVOKABLE QString getWebClientUrl(bool desktop);
   Q_INVOKABLE QString getWebClientUrl(bool desktop);
   Q_INVOKABLE QString getExtensionPath();
   Q_INVOKABLE QString getExtensionPath();
+  Q_INVOKABLE bool isUsingExternalWebClient();
   Q_INVOKABLE QString getClientName();
   Q_INVOKABLE QString getClientName();
   Q_INVOKABLE bool ignoreSSLErrors();
   Q_INVOKABLE bool ignoreSSLErrors();
 
 

+ 4 - 1
src/ui/webview.qml

@@ -1,6 +1,6 @@
 import QtQuick 2.4
 import QtQuick 2.4
 import Konvergo 1.0
 import Konvergo 1.0
-import QtWebEngine 1.1
+import QtWebEngine 1.7
 import QtWebChannel 1.0
 import QtWebChannel 1.0
 import QtQuick.Window 2.2
 import QtQuick.Window 2.2
 import QtQuick.Controls 1.4
 import QtQuick.Controls 1.4
@@ -145,6 +145,9 @@ KonvergoWindow
     objectName: "web"
     objectName: "web"
     settings.errorPageEnabled: false
     settings.errorPageEnabled: false
     settings.localContentCanAccessRemoteUrls: true
     settings.localContentCanAccessRemoteUrls: true
+    settings.localContentCanAccessFileUrls: true
+    settings.allowRunningInsecureContent: components.settings.isUsingExternalWebClient()
+    settings.playbackRequiresUserGesture: false
     profile.httpUserAgent: components.system.getUserAgent()
     profile.httpUserAgent: components.system.getUserAgent()
     url: mainWindow.webUrl
     url: mainWindow.webUrl
     focus: true
     focus: true