Przeglądaj źródła

fix(slicer): encode file URL in protocol-handler scheme on Windows/Linux

  "Open in Slicer" emitted `orcaslicer://open?file=<URL>` and
  `bambustudio://open?file=<URL>` by plain string concatenation, relying
  on a stale comment that claimed the browser preserves URLs in the query
  string. That ignores the slicer's own `url_decode()` on the received
  query (BS post_init → url_decode + split_str; OrcaSlicer Downloader
  regex + url_decode), so any already-percent-encoded character — most
  commonly `%20` from filenames with spaces — decoded to a literal space
  and the slicer's subsequent HTTP GET returned 0 bytes or 404.

  All three URL forms now use `encodeURIComponent()` (matching what the
  macOS `bambustudioopen://` branch was already doing, which is why the
  bug didn't surface on macOS). Corrected the file-level comment to
  document the actual invariant.

  Regression test in slicer.test.ts feeds the exact issue reproduction
  URL and asserts `%2520` appears in the generated href.
maziggy 1 miesiąc temu
rodzic
commit
76b997fc8a

Plik diff jest za duży
+ 0 - 0
CHANGELOG.md


+ 11 - 6
frontend/src/__tests__/utils/slicer.test.ts

@@ -51,7 +51,7 @@ describe('slicer utility', () => {
 
 
       expect(appendSpy).toHaveBeenCalled();
       expect(appendSpy).toHaveBeenCalled();
       expect(createdLink.href).toContain('bambustudio://open?file=');
       expect(createdLink.href).toContain('bambustudio://open?file=');
-      expect(createdLink.href).toContain('http://localhost:8000/file.3mf');
+      expect(createdLink.href).toContain(encodeURIComponent('http://localhost:8000/file.3mf'));
       expect(clickSpy).toHaveBeenCalled();
       expect(clickSpy).toHaveBeenCalled();
       expect(removeSpy).toHaveBeenCalled();
       expect(removeSpy).toHaveBeenCalled();
     });
     });
@@ -78,15 +78,20 @@ describe('slicer utility', () => {
       expect(createdLink.href).toContain('open?file=');
       expect(createdLink.href).toContain('open?file=');
     });
     });
 
 
-    it('does not encode the file URL for orcaslicer', () => {
+    it('encodes filenames with spaces so the slicer receives %20 after url_decode (issue #1059)', () => {
       vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue('Mozilla/5.0 (Windows NT 10.0)');
       vi.spyOn(navigator, 'userAgent', 'get').mockReturnValue('Mozilla/5.0 (Windows NT 10.0)');
-      const url = 'http://localhost:8000/api/v1/archives/1/file/My Model.3mf';
+      // A download URL that already contains percent-encoded spaces — this is how
+      // Bambuddy emits archive paths (the filename in the URL is URL-path-encoded).
+      const url = 'http://localhost:8000/api/v1/archives/1/dl/TOKEN/Toothpick%20Launcher%20Print-in-Place.3mf';
       openInSlicer(url, 'orcaslicer');
       openInSlicer(url, 'orcaslicer');
 
 
-      // The href should contain the raw URL (browser may normalize it but it should not be double-encoded)
+      // Each %20 in the input must become %2520 in the href so that after the
+      // slicer's own url_decode() it comes back as %20 (preserving the original
+      // URL). Without this, the slicer would decode %20 → literal space and its
+      // subsequent HTTP fetch would fail.
       expect(createdLink.href).toContain('orcaslicer://open?file=');
       expect(createdLink.href).toContain('orcaslicer://open?file=');
-      // Should NOT contain %253A (double-encoded colon)
-      expect(createdLink.href).not.toContain('%253A');
+      expect(createdLink.href).toContain('Toothpick%2520Launcher%2520Print-in-Place.3mf');
+      expect(createdLink.href).not.toContain('Toothpick%20Launcher');
     });
     });
 
 
     it('defaults to bambu_studio when no slicer specified', () => {
     it('defaults to bambu_studio when no slicer specified', () => {

+ 11 - 10
frontend/src/utils/slicer.ts

@@ -15,9 +15,13 @@
  *   - (orcaslicer|bambustudio|...)://open?file=<URL>
  *   - (orcaslicer|bambustudio|...)://open?file=<URL>
  *   - bambustudioopen://<URL>
  *   - bambustudioopen://<URL>
  *
  *
- * Key insight: Using ?file= query format, the browser's URL parser preserves
- * http:// in the query string without any encoding. Only the macOS-specific
- * bambustudioopen:// format needs encodeURIComponent (BS calls url_decode).
+ * Key insight: every form needs encodeURIComponent on the file URL, because
+ * the slicer calls url_decode() on the received query (post_init calls
+ * url_decode then split_str; MacOpenURL strips the prefix then url_decode;
+ * OrcaSlicer's Downloader regex-extracts then url_decode). Without encoding,
+ * any already-percent-encoded character in the download URL (most commonly
+ * %20 in filenames with spaces) decodes to a literal space and the slicer's
+ * subsequent HTTP fetch fails with a 0-byte body or 404. See issue #1059.
  */
  */
 
 
 export type SlicerType = 'bambu_studio' | 'orcaslicer';
 export type SlicerType = 'bambu_studio' | 'orcaslicer';
@@ -51,22 +55,19 @@ export function detectPlatform(): Platform {
 export function openInSlicer(downloadUrl: string, slicer: SlicerType = 'bambu_studio'): void {
 export function openInSlicer(downloadUrl: string, slicer: SlicerType = 'bambu_studio'): void {
   let url: string;
   let url: string;
 
 
+  const encoded = encodeURIComponent(downloadUrl);
   if (slicer === 'orcaslicer') {
   if (slicer === 'orcaslicer') {
-    // OrcaSlicer: ?file= query format — http:// preserved in query string
-    url = `orcaslicer://open?file=${downloadUrl}`;
+    url = `orcaslicer://open?file=${encoded}`;
   } else {
   } else {
     const platform = detectPlatform();
     const platform = detectPlatform();
     if (platform === 'macos') {
     if (platform === 'macos') {
       // macOS only: bambustudioopen scheme via MacOpenURL() callback.
       // macOS only: bambustudioopen scheme via MacOpenURL() callback.
-      // Must encode because bare http:// in authority gets mangled by browser.
-      // BS calls url_decode() after stripping "bambustudioopen://" prefix.
-      url = `bambustudioopen://${encodeURIComponent(downloadUrl)}`;
+      url = `bambustudioopen://${encoded}`;
     } else {
     } else {
       // Windows/Linux: bambustudio://open?file= via post_init() CLI args.
       // Windows/Linux: bambustudio://open?file= via post_init() CLI args.
-      // The ?file= query format preserves http:// without encoding.
       // IMPORTANT: On Linux, BS only handles "bambustudio://open" prefix —
       // IMPORTANT: On Linux, BS only handles "bambustudio://open" prefix —
       // it does NOT process "bambustudioopen://" (that's macOS-only).
       // it does NOT process "bambustudioopen://" (that's macOS-only).
-      url = `bambustudio://open?file=${downloadUrl}`;
+      url = `bambustudio://open?file=${encoded}`;
     }
     }
   }
   }
 
 

Plik diff jest za duży
+ 0 - 0
static/assets/index-DbCeLLRM.js


+ 1 - 1
static/index.html

@@ -26,7 +26,7 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-B2bhLHKc.js"></script>
+    <script type="module" crossorigin src="/assets/index-DbCeLLRM.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-CiCRNaHx.css">
     <link rel="stylesheet" crossorigin href="/assets/index-CiCRNaHx.css">
   </head>
   </head>
   <body>
   <body>

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików