diff --git a/lib/Controller/DownloadController.php b/lib/Controller/DownloadController.php index fcff5264..dcdfbeba 100644 --- a/lib/Controller/DownloadController.php +++ b/lib/Controller/DownloadController.php @@ -90,7 +90,11 @@ class DownloadController extends GenericApiController $session = \OC::$server->get(ISession::class); $key = "memories_download_{$handle}"; $info = $session->get($key); - $session->remove($key); + + // Remove handle from session unless HEAD request + if ('HEAD' !== $this->request->getMethod()) { + $session->remove($key); + } if (null === $info) { throw Exceptions::NotFound('handle'); @@ -171,13 +175,17 @@ class DownloadController extends GenericApiController $out->setHeader('Content-Type: '.$file->getMimeType()); // Make sure the browser downloads the file - $out->setHeader('Content-Disposition: attachment; filename="'.$file->getName().'"'); + $filename = str_replace('"', '\\"', $file->getName()); + $out->setHeader('Content-Disposition: attachment; filename="'.$filename.'"'); // Prevent output from being buffered $out->setHeader('Content-Encoding: none'); $out->setHeader('X-Content-Encoded-By: none'); $out->setHeader('X-Accel-Buffering: no'); + // Quit if HEAD request + if ('HEAD' === $this->request->getMethod()) return; + // Open file to send $res = $file->fopen('rb'); @@ -255,6 +263,9 @@ class DownloadController extends GenericApiController // Create a zip file $streamer->sendHeaders($name); + // Quit if HEAD request + if ('HEAD' === $this->request->getMethod()) return; + // Multiple files might have the same name // So we need to add a number to the end of the name $nameCounts = []; diff --git a/src/native.ts b/src/native.ts index e9726aa5..16cd0a79 100644 --- a/src/native.ts +++ b/src/native.ts @@ -1,3 +1,4 @@ +import axios from '@nextcloud/axios'; import type { IDay, IPhoto } from './types'; const BASE_URL = 'http://127.0.0.1'; @@ -17,7 +18,7 @@ export const API = { export type NativeX = { isNative: () => boolean; setThemeColor: (color: string, isDark: boolean) => void; - downloadFromUrl: (url: string) => void; + downloadFromUrl: (url: string, filename: string) => void; }; /** The native interface is a global object that is injected by the native app. */ @@ -45,7 +46,18 @@ export const setTheme = (color?: string, dark?: boolean) => { /** * Download a file from the given URL. */ -export const downloadFromUrl = (url: string) => nativex?.downloadFromUrl?.(url); +export const downloadFromUrl = async (url: string) => { + // Make HEAD request to get filename + const res = await axios.head(url); + let filename = res.headers['content-disposition']; + if (res.status !== 200 || !filename) return; + + // Extract filename from header without quotes + filename = filename.split('filename="')[1].slice(0, -1); + + // Hand off to download manager + nativex?.downloadFromUrl?.(url, filename); +}; /** * Extend a list of days with local days.