Переглянути джерело

Fix mixed-content server selection (#855)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sstidl <12804296+sstidl@users.noreply.github.com>
Copilot 6 днів тому
батько
коміт
be1e0a0daa
2 змінених файлів з 118 додано та 1 видалено
  1. 5 1
      speedtest.js
  2. 113 0
      tests/e2e/server-selection.spec.js

+ 5 - 1
speedtest.js

@@ -239,7 +239,11 @@ Speedtest.prototype = {
       const checkServer = function(server, done) {
         let i = 0;
         server.pingT = -1;
-        if (server.server.indexOf(location.protocol) == -1) done();
+        if (
+          location.protocol === "https:" &&
+          server.server.substring(0, 7).toLowerCase() === "http://"
+        )
+          done();
         else {
           const nextPing = function() {
             if (i++ == PINGS) {

+ 113 - 0
tests/e2e/server-selection.spec.js

@@ -0,0 +1,113 @@
+const { test, expect } = require('@playwright/test');
+const fs = require('node:fs');
+const path = require('node:path');
+const vm = require('node:vm');
+
+const speedtestSource = fs.readFileSync(
+  path.resolve(__dirname, '../../speedtest.js'),
+  'utf8'
+);
+
+function createHarness(protocol, responders = {}) {
+  const requests = [];
+
+  function FakeXMLHttpRequest() {
+    this.responseText = '';
+    this.timeout = 0;
+  }
+
+  FakeXMLHttpRequest.prototype.open = function open(method, url) {
+    this._method = method;
+    this._url = url;
+  };
+
+  FakeXMLHttpRequest.prototype.send = function send() {
+    requests.push(this._url);
+    const baseUrl = this._url.replace(/[?&]cors=true$/, '');
+    const outcome = responders[baseUrl] || 'error';
+
+    if (outcome === 'success') {
+      if (typeof this.onload === 'function') {
+        this.onload();
+      }
+      return;
+    }
+
+    if (typeof this.onerror === 'function') {
+      this.onerror(new Error(`Request failed for ${baseUrl}`));
+    }
+  };
+
+  const context = {
+    console: { log() {} },
+    Date,
+    location: { protocol },
+    navigator: { userAgent: 'Playwright' },
+    performance: { getEntriesByName: () => [] },
+    XMLHttpRequest: FakeXMLHttpRequest
+  };
+
+  vm.createContext(context);
+  vm.runInContext(speedtestSource, context, { filename: 'speedtest.js' });
+
+  return { Speedtest: context.Speedtest, requests };
+}
+
+function createServer(server) {
+  return {
+    name: server,
+    server,
+    dlURL: 'garbage.php',
+    ulURL: 'empty.php',
+    pingURL: 'empty.php',
+    getIpURL: 'getIP.php'
+  };
+}
+
+function selectServer(speedtest) {
+  return new Promise((resolve) => {
+    speedtest.selectServer(resolve);
+  });
+}
+
+test.describe('Speedtest server selection protocol handling', () => {
+  test('allows HTTPS backends to be pinged from an HTTP frontend', async () => {
+    const secureServer = 'https://secure.example/';
+    const { Speedtest, requests } = createHarness('http:', {
+      [`${secureServer}empty.php`]: 'success'
+    });
+
+    const speedtest = new Speedtest();
+    speedtest.addTestPoint(createServer(secureServer));
+
+    const selectedServer = await selectServer(speedtest);
+
+    expect(selectedServer.server).toBe(secureServer);
+    expect(
+      requests.some((url) => url.startsWith(`${secureServer}empty.php`))
+    ).toBeTruthy();
+  });
+
+  test('still skips insecure HTTP backends from an HTTPS frontend', async () => {
+    const insecureServer = 'http://insecure.example/';
+    const secureServer = 'https://secure.example/';
+    const { Speedtest, requests } = createHarness('https:', {
+      [`${insecureServer}empty.php`]: 'success',
+      [`${secureServer}empty.php`]: 'success'
+    });
+
+    const speedtest = new Speedtest();
+    speedtest.addTestPoint(createServer(insecureServer));
+    speedtest.addTestPoint(createServer(secureServer));
+
+    const selectedServer = await selectServer(speedtest);
+
+    expect(selectedServer.server).toBe(secureServer);
+    expect(
+      requests.some((url) => url.startsWith(`${insecureServer}empty.php`))
+    ).toBeFalsy();
+    expect(
+      requests.some((url) => url.startsWith(`${secureServer}empty.php`))
+    ).toBeTruthy();
+  });
+});