CVE-2024-2206: Server-Side Request Forgery (SSRF) (CWE-918)
value: An SSRF vulnerability exists in the gradio-app/gradio due to insufficient validation of user-supplied URLs in the `/proxy` route. Attackers can exploit this vulnerability by manipulating the `self.replica_urls` set through the `X-Direct-Url` header in requests to the `/` and `/config` routes, allowing the addition of arbitrary URLs for proxying. This flaw enables unauthorized proxying of requests and potential access to internal endpoints within the Hugging Face space. The issue arises from the application's inadequate checking of safe URLs in the `build_proxy_request` function.
CWE description: The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
Published: 2024-03-27T01:15Z
Repository: https://github.com/gradio-app/gradio
Language: Other
Files changed: .changeset/strong-chefs-study.md, client/js/src/client.ts, client/js/src/index.ts, client/js/src/upload.ts, client/python/gradio_client/utils.py, gradio/blocks.py, gradio/components/base.py, gradio/components/login_button.py, gradio/data_classes.py, gradio/processing_utils.py, gradio/route_utils.py, gradio/routes.py, guides/05_custom-components/05_frontend.md, guides/05_custom-components/07_pdf-component-example.md, js/annotatedimage/Index.svelte, js/app/src/Index.svelte, js/audio/Index.svelte, js/chatbot/Chatbot.test.ts, js/chatbot/Index.svelte, js/file/Index.svelte, js/gallery/Gallery.test.ts, js/gallery/Index.svelte, js/gallery/shared/Gallery.svelte, js/image/Index.svelte, js/image/shared/ImagePreview.svelte, js/image/shared/ImageUploader.svelte, js/imageeditor/Index.svelte, js/imageeditor/shared/InteractiveImageEditor.svelte, js/imageeditor/shared/tools/Sources.svelte, js/model3D/Index.svelte, js/simpleimage/Index.svelte, js/simpleimage/shared/ImageUploader.svelte, js/video/Index.svelte, js/video/Video.test.ts, test/test_components.py
Commit date: 2024-02-07 14:39:33 -0800
Commit message: [WIP] Refactor file normalization to be in the backend and remove it from the frontend of each component (#7183)

* processing

* add changeset

* changes

* add changeset

* add changeset

* changes

* changes

* clean

* changes

* add changeset

* add changeset

* root url

* refactor

* testing

* testing

* log

* logs

* fix

* format

* add changeset

* remove

* add root

* format

* apply to everything

* annoying fix

* fixes

* lint

* fixes

* fixes

* fixes

* fix tests

* fix js tests

* format

* fix python tests

* clean guides

* add changeset

* add changeset

* simplify

* add changeset

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: pngwn <hello@pngwn.io>
Diff: diff --git a/.changeset/strong-chefs-study.md b/.changeset/strong-chefs-study.md
new file mode 100644
index 000000000..9bf7b17aa
--- /dev/null
+++ b/.changeset/strong-chefs-study.md
@@ -0,0 +1,18 @@
+---
+"@gradio/annotatedimage": minor
+"@gradio/app": minor
+"@gradio/audio": minor
+"@gradio/chatbot": minor
+"@gradio/client": minor
+"@gradio/file": minor
+"@gradio/gallery": minor
+"@gradio/image": minor
+"@gradio/imageeditor": minor
+"@gradio/model3d": minor
+"@gradio/simpleimage": minor
+"@gradio/video": minor
+"gradio": minor
+"gradio_client": minor
+---
+
+feat:[WIP] Refactor file normalization to be in the backend and remove it from the frontend of each component
diff --git a/client/js/src/client.ts b/client/js/src/client.ts
index 00ae24048..41516ed48 100644
--- a/client/js/src/client.ts
+++ b/client/js/src/client.ts
@@ -26,11 +26,11 @@ import type {
 	Status,
 	SpaceStatus,
 	SpaceStatusCallback
 } from "./types.js";
 
-import { FileData, normalise_file } from "./upload";
+import { FileData } from "./upload";
 
 import type { Config } from "./types.js";
 
 type event = <K extends EventType>(
 	eventType: K,
@@ -164,11 +164,10 @@ interface Client {
 	client: (
 		app_reference: string,
 		options: {
 			hf_token?: `hf_${string}`;
 			status_callback?: SpaceStatusCallback;
-			normalise_files?: boolean;
 		}
 	) => Promise<client_return>;
 	handle_blob: (
 		endpoint: string,
 		data: unknown[],
@@ -257,23 +256,21 @@ export function api_factory(
 	async function client(
 		app_reference: string,
 		options: {
 			hf_token?: `hf_${string}`;
 			status_callback?: SpaceStatusCallback;
-			normalise_files?: boolean;
-		} = { normalise_files: true }
+		} = {}
 	): Promise<client_return> {
 		return new Promise(async (res) => {
-			const { status_callback, hf_token, normalise_files } = options;
+			const { status_callback, hf_token } = options;
 			const return_obj = {
 				predict,
 				submit,
 				view_api,
 				component_server
 			};
 
-			const transform_files = normalise_files ?? true;
 			if (
 				(typeof window === "undefined" || !("WebSocket" in window)) &&
 				!global.Websocket
 			) {
 				const ws = await import("ws");
@@ -491,18 +488,11 @@ export function api_factory(
 									session_hash
 								},
 								hf_token
 							)
 								.then(([output, status_code]) => {
-									const data = transform_files
-										? transform_output(
-												output.data,
-												api_info,
-												config.root,
-												config.root_url
-										  )
-										: output.data;
+									const data = output.data;
 									if (status_code == 200) {
 										fire_event({
 											type: "data",
 											endpoint: _endpoint,
 											fn_index,
@@ -626,18 +616,11 @@ export function api_factory(
 								}
 								if (data) {
 									fire_event({
 										type: "data",
 										time: new Date(),
-										data: transform_files
-											? transform_output(
-													data.data,
-													api_info,
-													config.root,
-													config.root_url
-											  )
-											: data.data,
+										data: data.data,
 										endpoint: _endpoint,
 										fn_index
 									});
 
 									if (complete) {
@@ -748,18 +731,11 @@ export function api_factory(
 								}
 								if (data) {
 									fire_event({
 										type: "data",
 										time: new Date(),
-										data: transform_files
-											? transform_output(
-													data.data,
-													api_info,
-													config.root,
-													config.root_url
-											  )
-											: data.data,
+										data: data.data,
 										endpoint: _endpoint,
 										fn_index
 									});
 
 									if (complete) {
@@ -876,18 +852,11 @@ export function api_factory(
 											}
 											if (data) {
 												fire_event({
 													type: "data",
 													time: new Date(),
-													data: transform_files
-														? transform_output(
-																data.data,
-																api_info,
-																config.root,
-																config.root_url
-														  )
-														: data.data,
+													data: data.data,
 													endpoint: _endpoint,
 													fn_index
 												});
 
 												if (complete) {
@@ -1242,32 +1211,10 @@ export function api_factory(
 export const { post_data, upload_files, client, handle_blob } = api_factory(
 	fetch,
 	(...args) => new EventSource(...args)
 );
 
-function transform_output(
-	data: any[],
-	api_info: any,
-	root_url: string,
-	remote_url?: string
-): unknown[] {
-	return data.map((d, i) => {
-		if (api_info?.returns?.[i]?.component === "File") {
-			return normalise_file(d, root_url, remote_url);
-		} else if (api_info?.returns?.[i]?.component === "Gallery") {
-			return d.map((img) => {
-				return Array.isArray(img)
-					? [normalise_file(img[0], root_url, remote_url), img[1]]
-					: [normalise_file(img, root_url, remote_url), null];
-			});
-		} else if (typeof d === "object" && d.path) {
-			return normalise_file(d, root_url, remote_url);
-		}
-		return d;
-	});
-}
-
 interface ApiData {
 	label: string;
 	type: {
 		type: any;
 		description: string;
diff --git a/client/js/src/index.ts b/client/js/src/index.ts
index 98e248e54..9f8ff1aaa 100644
--- a/client/js/src/index.ts
+++ b/client/js/src/index.ts
@@ -5,11 +5,10 @@ export {
 	duplicate,
 	api_factory
 } from "./client.js";
 export type { SpaceStatus } from "./types.js";
 export {
-	normalise_file,
 	FileData,
 	upload,
 	get_fetchable_url_or_file,
 	prepare_files
 } from "./upload.js";
diff --git a/client/js/src/upload.ts b/client/js/src/upload.ts
index f5011710a..888676509 100644
--- a/client/js/src/upload.ts
+++ b/client/js/src/upload.ts
@@ -1,67 +1,7 @@
 import { upload_files } from "./client";
 
-export function normalise_file(
-	file: FileData | null,
-	server_url: string,
-	proxy_url: string | null
-): FileData | null;
-
-export function normalise_file(
-	file: FileData[] | null,
-	server_url: string,
-	proxy_url: string | null
-): FileData[] | null;
-
-export function normalise_file(
-	file: FileData[] | FileData | null,
-	server_url: string, // root: string,
-	proxy_url: string | null // root_url: string | null
-): FileData[] | FileData | null;
-
-export function normalise_file(
-	file: FileData[] | FileData | null,
-	server_url: string, // root: string,
-	proxy_url: string | null // root_url: string | null
-): FileData[] | FileData | null {
-	if (file == null) {
-		return null;
-	}
-
-	if (Array.isArray(file)) {
-		const normalized_file: (FileData | null)[] = [];
-
-		for (const x of file) {
-			if (x == null) {
-				normalized_file.push(null);
-			} else {
-				normalized_file.push(normalise_file(x, server_url, proxy_url));
-			}
-		}
-
-		return normalized_file as FileData[];
-	}
-
-	if (file.is_stream) {
-		if (proxy_url == null) {
-			return new FileData({
-				...file,
-				url: server_url + "/stream/" + file.path
-			});
-		}
-		return new FileData({
-			...file,
-			url: "/proxy=" + proxy_url + "stream/" + file.path
-		});
-	}
-
-	return new FileData({
-		...file,
-		url: get_fetchable_url_or_file(file.path, server_url, proxy_url)
-	});
-}
-
 function is_url(str: string): boolean {
 	try {
 		const url = new URL(str);
 		return url.protocol === "http:" || url.protocol === "https:";
 	} catch {
@@ -101,13 +41,16 @@ export async function upload(
 				if (response.error) {
 					throw new Error(response.error);
 				} else {
 					if (response.files) {
 						return response.files.map((f, i) => {
-							const file = new FileData({ ...file_data[i], path: f });
-
-							return normalise_file(file, root, null);
+							const file = new FileData({
+								...file_data[i],
+								path: f,
+								url: root + "/file=" + f
+							});
+							return file;
 						});
 					}
 
 					return [];
 				}
diff --git a/client/python/gradio_client/utils.py b/client/python/gradio_client/utils.py
index 60d98ded9..8c3898a74 100644
--- a/client/python/gradio_client/utils.py
+++ b/client/python/gradio_client/utils.py
@@ -893,11 +893,11 @@ def get_type(schema: dict):
         return {}
     else:
         raise APIInfoParseError(f"Cannot parse type for {schema}")
 
 
-FILE_DATA = "Dict(path: str, url: str | None, size: int | None, orig_name: str | None, mime_type: str | None)"
+FILE_DATA = "Dict(path: str, url: str | None, size: int | None, orig_name: str | None, mime_type: str | None, is_stream: bool)"
 
 
 def json_schema_to_python_type(schema: Any) -> str:
     type_ = _json_schema_to_python_type(schema, schema.get("$defs"))
     return type_.replace(FILE_DATA, "filepath")
@@ -1008,10 +1008,16 @@ def is_url(s):
 
 def is_file_obj(d):
     return isinstance(d, dict) and "path" in d
 
 
+def is_file_obj_with_url(d):
+    return (
+        isinstance(d, dict) and "path" in d and "url" in d and isinstance(d["url"], str)
+    )
+
+
 SKIP_COMPONENTS = {
     "state",
     "row",
     "column",
     "tabs",
diff --git a/gradio/blocks.py b/gradio/blocks.py
index 860d419f7..d73bca17b 100644
--- a/gradio/blocks.py
+++ b/gradio/blocks.py
@@ -1323,11 +1323,11 @@ Received inputs:
                     processed_input.append(state[input_id])
                 else:
                     if input_id in state:
                         block = state[input_id]
                     inputs_cached = processing_utils.move_files_to_cache(
-                        inputs[i], block
+                        inputs[i], block, add_urls=True
                     )
                     if getattr(block, "data_model", None) and inputs_cached is not None:
                         if issubclass(block.data_model, GradioModel):  # type: ignore
                             inputs_cached = block.data_model(**inputs_cached)  # type: ignore
                         elif issubclass(block.data_model, GradioRootModel):  # type: ignore
@@ -1452,10 +1452,11 @@ Received outputs:
                     prediction_value = block.postprocess(prediction_value)
                 outputs_cached = processing_utils.move_files_to_cache(
                     prediction_value,
                     block,  # type: ignore
                     postprocess=True,
+                    add_urls=True,
                 )
                 output.append(outputs_cached)
 
         return output
 
diff --git a/gradio/components/base.py b/gradio/components/base.py
index 028ccdf34..de0caa56e 100644
--- a/gradio/components/base.py
+++ b/gradio/components/base.py
@@ -193,11 +193,16 @@ class Component(ComponentBase, Block):
         # load_event is set in the Blocks.attach_load_events method
         self.load_event: None | dict[str, Any] = None
         self.load_event_to_attach: None | tuple[Callable, float | None] = None
         load_fn, initial_value = self.get_load_fn_and_initial_value(value)
         initial_value = self.postprocess(initial_value)
-        self.value = move_files_to_cache(initial_value, self, postprocess=True)  # type: ignore
+        self.value = move_files_to_cache(
+            initial_value,
+            self,  # type: ignore
+            postprocess=True,
+            add_urls=True,
+        )
 
         if callable(load_fn):
             self.attach_load_event(load_fn, every)
 
         self.component_class_id = self.__class__.get_component_class_id()
diff --git a/gradio/components/login_button.py b/gradio/components/login_button.py
index a453fc6fd..e386485cd 100644
--- a/gradio/components/login_button.py
+++ b/gradio/components/login_button.py
@@ -86,11 +86,11 @@ class LoginButton(Button):
         # Each time the page is refreshed or loaded, check if the user is logged in and adapt label
         session = getattr(request, "session", None) or getattr(
             request.request, "session", None
         )
         if session is None or "oauth_info" not in session:
-            return LoginButton(value=self.value, interactive=True)
+            return LoginButton(value=self.value, interactive=True)  # type: ignore
         else:
             username = session["oauth_info"]["userinfo"]["preferred_username"]
             logout_text = self.logout_value.format(username)
             return LoginButton(logout_text, interactive=True)
 
diff --git a/gradio/data_classes.py b/gradio/data_classes.py
index 34542954d..aa7be3ade 100644
--- a/gradio/data_classes.py
+++ b/gradio/data_classes.py
@@ -166,10 +166,11 @@ class FileData(GradioModel):
     path: str  # server filepath
     url: Optional[str] = None  # normalised server url
     size: Optional[int] = None  # size in bytes
     orig_name: Optional[str] = None  # original filename
     mime_type: Optional[str] = None
+    is_stream: bool = False
 
     @property
     def is_none(self):
         return all(
             f is None
diff --git a/gradio/processing_utils.py b/gradio/processing_utils.py
index 4c07df2d4..34023819e 100644
--- a/gradio/processing_utils.py
+++ b/gradio/processing_utils.py
@@ -234,19 +234,26 @@ def move_resource_to_block_cache(
     left here for backwards compatibility for any custom components created in Gradio 4.2.0 or earlier.
     """
     return block.move_resource_to_block_cache(url_or_file_path)
 
 
-def move_files_to_cache(data: Any, block: Component, postprocess: bool = False):
-    """Move files to cache and replace the file path with the cache path.
+def move_files_to_cache(
+    data: Any,
+    block: Component,
+    postprocess: bool = False,
+    add_urls=False,
+) -> dict:
+    """Move any files in `data` to cache and (optionally), adds URL prefixes (/file=...) needed to access the cached file.
+    Also handles the case where the file is on an external Gradio app (/proxy=...).
 
-    Runs after .postprocess(), after .process_example(), and before .preprocess().
+    Runs after .postprocess() and before .preprocess().
 
     Args:
         data: The input or output data for a component. Can be a dictionary or a dataclass
-        block: The component
+        block: The component whose data is being processed
         postprocess: Whether its running from postprocessing
+        root_url: The root URL of the local server, if applicable
     """
 
     def _move_to_cache(d: dict):
         payload = FileData(**d)
         # If the gradio app developer is returning a URL from
@@ -257,18 +264,40 @@ def move_files_to_cache(data: Any, block: Component, postprocess: bool = False):
             temp_file_path = payload.url
         else:
             temp_file_path = move_resource_to_block_cache(payload.path, block)
         assert temp_file_path is not None
         payload.path = temp_file_path
+
+        if add_urls:
+            url_prefix = "/stream/" if payload.is_stream else "/file="
+            if block.proxy_url:
+                url = f"/proxy={block.proxy_url}{url_prefix}{temp_file_path}"
+            elif client_utils.is_http_url_like(
+                temp_file_path
+            ) or temp_file_path.startswith(f"{url_prefix}"):
+                url = temp_file_path
+            else:
+                url = f"{url_prefix}{temp_file_path}"
+            payload.url = url
+
         return payload.model_dump()
 
     if isinstance(data, (GradioRootModel, GradioModel)):
         data = data.model_dump()
 
     return client_utils.traverse(data, _move_to_cache, client_utils.is_file_obj)
 
 
+def add_root_url(data, root_url) -> dict:
+    def _add_root_url(file_dict: dict):
+        if not client_utils.is_http_url_like(file_dict["url"]):
+            file_dict["url"] = f'{root_url}{file_dict["url"]}'
+        return file_dict
+
+    return client_utils.traverse(data, _add_root_url, client_utils.is_file_obj_with_url)
+
+
 def resize_and_crop(img, size, crop_type="center"):
     """
     Resize and crop an image to fit the specified size.
     args:
         size: `(width, height)` tuple. Pass `None` for either width or height
diff --git a/gradio/route_utils.py b/gradio/route_utils.py
index 3045498fc..56b1e5d3c 100644
--- a/gradio/route_utils.py
+++ b/gradio/route_utils.py
@@ -1,9 +1,10 @@
 from __future__ import annotations
 
 import hashlib
 import json
+import shutil
 from collections import deque
 from dataclasses import dataclass as python_dataclass
 from tempfile import NamedTemporaryFile, _TemporaryFileWrapper
 from typing import TYPE_CHECKING, AsyncGenerator, BinaryIO, List, Optional, Tuple, Union
 
@@ -543,5 +544,10 @@ class GradioMultiPartParser:
 
         parser.finalize()
         if self.upload_progress is not None:
             self.upload_progress.set_done(self.upload_id)  # type: ignore
         return FormData(self.items)
+
+
+def move_uploaded_files_to_cache(files: list[str], destinations: list[str]) -> None:
+    for file, dest in zip(files, destinations):
+        shutil.move(file, dest)
diff --git a/gradio/routes.py b/gradio/routes.py
index 2de50b6ef..970fc3c0c 100644
--- a/gradio/routes.py
+++ b/gradio/routes.py
@@ -15,11 +15,10 @@ import inspect
 import json
 import mimetypes
 import os
 import posixpath
 import secrets
-import shutil
 import tempfile
 import threading
 import time
 import traceback
 from pathlib import Path
@@ -52,19 +51,21 @@ import gradio
 from gradio import ranged_response, route_utils, utils, wasm_utils
 from gradio.context import Context
 from gradio.data_classes import ComponentServerBody, PredictBody, ResetBody
 from gradio.exceptions import Error
 from gradio.oauth import attach_oauth
+from gradio.processing_utils import add_root_url
 from gradio.queueing import Estimation
 from gradio.route_utils import (  # noqa: F401
     FileUploadProgress,
     FileUploadProgressNotQueuedError,
     FileUploadProgressNotTrackedError,
     GradioMultiPartParser,
     GradioUploadFile,
     MultiPartException,
     Request,
+    move_uploaded_files_to_cache,
 )
 from gradio.state_holder import StateHolder
 from gradio.utils import (
     get_package_version,
 )
@@ -113,16 +114,10 @@ def toorjson(value):
 templates = Jinja2Templates(directory=STATIC_TEMPLATE_LIB)
 templates.env.filters["toorjson"] = toorjson
 
 client = httpx.AsyncClient()
 
-
-def move_uploaded_files_to_cache(files: list[str], destinations: list[str]) -> None:
-    for file, dest in zip(files, destinations):
-        shutil.move(file, dest)
-
-
 file_upload_statuses = FileUploadProgress()
 
 
 class App(FastAPI):
     """
@@ -313,18 +308,16 @@ class App(FastAPI):
         @app.head("/", response_class=HTMLResponse)
         @app.get("/", response_class=HTMLResponse)
         def main(request: fastapi.Request, user: str = Depends(get_current_user)):
             mimetypes.add_type("application/javascript", ".js")
             blocks = app.get_blocks()
-            root_path = (
-                request.scope.get("root_path")
-                or request.headers.get("X-Direct-Url")
-                or ""
-            )
+            root_path = route_utils.strip_url(str(request.url))
             if app.auth is None or user is not None:
                 config = app.get_blocks().config
-                config["root"] = route_utils.strip_url(root_path)
+                if "root" not in config:
+                    config["root"] = root_path
+                    config = add_root_url(config, root_path)
             else:
                 config = {
                     "auth_required": True,
                     "auth_message": blocks.auth_message,
                     "space_id": app.get_blocks().space_id,
@@ -357,17 +350,15 @@ class App(FastAPI):
             return app.get_blocks().get_api_info()  # type: ignore
 
         @app.get("/config/", dependencies=[Depends(login_check)])
         @app.get("/config", dependencies=[Depends(login_check)])
         def get_config(request: fastapi.Request):
-            root_path = (
-                request.scope.get("root_path")
-                or request.headers.get("X-Direct-Url")
-                or ""
-            )
+            root_path = route_utils.strip_url(str(request.url))[:-7]
             config = app.get_blocks().config
-            config["root"] = route_utils.strip_url(root_path)
+            if "root" not in config:
+                config["root"] = route_utils.strip_url(root_path)
+                config = add_root_url(config, root_path)
             return config
 
         @app.get("/static/{path:path}")
         def static_resource(path: str):
             static_file = safe_join(STATIC_PATH_LIB, path)
@@ -578,18 +569,21 @@ class App(FastAPI):
                 traceback.print_exc()
                 return JSONResponse(
                     content={"error": str(error) if show_error else None},
                     status_code=500,
                 )
+            root_path = app.get_blocks().config.get("root", "")
+            output = add_root_url(output, route_utils.strip_url(root_path))
             return output
 
         @app.get("/queue/data", dependencies=[Depends(login_check)])
         async def queue_data(
             request: fastapi.Request,
             session_hash: str,
         ):
             blocks = app.get_blocks()
+            root_path = app.get_blocks().config.get("root", "")
 
             async def sse_stream(request: fastapi.Request):
                 try:
                     last_heartbeat = time.perf_counter()
                     while True:
@@ -631,10 +625,11 @@ class App(FastAPI):
                                 "msg": "unexpected_error",
                                 "message": "Server stopped unexpectedly.",
                                 "success": False,
                             }
                         if message:
+                            add_root_url(message, route_utils.strip_url(root_path))
                             yield f"data: {json.dumps(message)}\n\n"
                             if message["msg"] == ServerMessage.process_completed:
                                 blocks._queue.pending_event_ids_session[
                                     session_hash
                                 ].remove(message["event_id"])
diff --git a/guides/05_custom-components/05_frontend.md b/guides/05_custom-components/05_frontend.md
index b0a469a67..541537dcd 100644
--- a/guides/05_custom-components/05_frontend.md
+++ b/guides/05_custom-components/05_frontend.md
@@ -145,36 +145,28 @@ This is the `Example.svelte` file for the code `Radio` component:
 ```
 
 ## Handling Files
 
 If your component deals with files, these files **should** be uploaded to the backend server. 
-The `@gradio/client` npm package provides the `upload`, `prepare_files`, and `normalise_file` utility functions to help you do this.
+The `@gradio/client` npm package provides the `upload` and `prepare_files` utility functions to help you do this.
 
 The `prepare_files` function will convert the browser's `File` datatype to gradio's internal `FileData` type.
 You should use the `FileData` data in your component to keep track of uploaded files.
 
 The `upload` function will upload an array of `FileData` values to the server.
 
-The `normalise_file` function will generate the correct URL for your component to fetch the file from and set it to the `data` property of the `FileData.`
-
-
-Tip: Be sure you call `normalise_file` whenever your files are updated!
-
-
 Here's an example of loading files from an `<input>` element when its value changes.
 
 
 ```typescript
 <script lang="ts">
 
-    import { upload, prepare_files, normalise_file, type FileData } from "@gradio/client";
+    import { upload, prepare_files, type FileData } from "@gradio/client";
     export let root;
     export let value;
     let uploaded_files;
 
-    $: value: normalise_file(uploaded_files, root)
-
     async function handle_upload(file_data: FileData[]): Promise<void> {
         await tick();
         uploaded_files = await upload(file_data, root);
     }
 
@@ -229,11 +221,11 @@ This means that you can use them to save yourself time while incorporating commo
 For example, the `@gradio/upload` package has `Upload` and `ModifyUpload` components for properly uploading files to the Gradio server. 
 Here is how you can use them to create a user interface to upload and display PDF files.
 
 ```typescript
 <script>
-	import { type FileData, normalise_file, Upload, ModifyUpload } from "@gradio/upload";
+	import { type FileData, Upload, ModifyUpload } from "@gradio/upload";
 	import { Empty, UploadText, BlockLabel } from "@gradio/atoms";
 </script>
 
 <BlockLabel Icon={File} label={label || "PDF"} />
 {#if value === null && interactive}
diff --git a/guides/05_custom-components/07_pdf-component-example.md b/guides/05_custom-components/07_pdf-component-example.md
index 31b7fe63e..fe2bf9b30 100644
--- a/guides/05_custom-components/07_pdf-component-example.md
+++ b/guides/05_custom-components/07_pdf-component-example.md
@@ -115,11 +115,10 @@ That's ok.
     import { Block, BlockLabel } from "@gradio/atoms";
     import { File } from "@gradio/icons";
     import { StatusTracker } from "@gradio/statustracker";
     import type { LoadingStatus } from "@gradio/statustracker";
     import type { FileData } from "@gradio/client";
-    import { normalise_file } from "@gradio/client";
     import { Upload, ModifyUpload } from "@gradio/upload";
 
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
 	export let visible = true;
@@ -314,14 +313,10 @@ Add the following code to `Index.svelte`:
             canvasRef.height = viewport.height;
             page.render(renderContext);
         });
     }
 
-    // Compute the url to fetch the file from the backend
-    // whenever a new value is passed in.
-    $: _value = normalise_file(value, root, proxy_url);
-
     // If the value changes, render the PDF of the currentPage
     $: if(JSON.stringify(old_value) != JSON.stringify(_value)) {
         if (_value){
             get_doc(_value);
         }
diff --git a/js/annotatedimage/Index.svelte b/js/annotatedimage/Index.svelte
index 1faad939a..98ee13603 100644
--- a/js/annotatedimage/Index.svelte
+++ b/js/annotatedimage/Index.svelte
@@ -3,11 +3,11 @@
 
 	import { Block, BlockLabel, Empty } from "@gradio/atoms";
 	import { Image } from "@gradio/icons";
 	import { StatusTracker } from "@gradio/statustracker";
 	import type { LoadingStatus } from "@gradio/statustracker";
-	import { type FileData, normalise_file } from "@gradio/client";
+	import { type FileData } from "@gradio/client";
 	import { resolve_wasm_src } from "@gradio/wasm/svelte";
 
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
 	export let visible = true;
@@ -34,12 +34,10 @@
 	export let width: number | undefined;
 	export let color_map: Record<string, string>;
 	export let container = true;
 	export let scale: number | null = null;
 	export let min_width: number | undefined = undefined;
-	export let root: string;
-	export let proxy_url: string;
 	let active: string | null = null;
 	export let loading_status: LoadingStatus;
 
 	// `value` can be updated before the Promises from `resolve_wasm_src` are resolved.
 	// In such a case, the resolved values for the old `value` have to be discarded,
@@ -50,13 +48,13 @@
 			old_value = value;
 			gradio.dispatch("change");
 		}
 		if (value) {
 			const normalized_value = {
-				image: normalise_file(value.image, root, proxy_url) as FileData,
+				image: value.image as FileData,
 				annotations: value.annotations.map((ann) => ({
-					image: normalise_file(ann.image, root, proxy_url) as FileData,
+					image: ann.image as FileData,
 					label: ann.label
 				}))
 			};
 			_value = normalized_value;
 
diff --git a/js/app/src/Index.svelte b/js/app/src/Index.svelte
index 5a63f3b7d..6aae52a81 100644
--- a/js/app/src/Index.svelte
+++ b/js/app/src/Index.svelte
@@ -250,12 +250,11 @@
 						typeof server_port === "number" ? server_port : 7860
 				  }`
 				: host || space || src || location.origin;
 
 		app = await client(api_url, {
-			status_callback: handle_status,
-			normalise_files: false
+			status_callback: handle_status
 		});
 		config = app.config;
 		window.__gradio_space__ = config.space_id;
 
 		status = {
@@ -278,12 +277,11 @@
 				let url = new URL(`http://${host}/dev/reload`);
 				eventSource = new EventSource(url);
 				eventSource.onmessage = async function (event) {
 					if (event.data === "CHANGE") {
 						app = await client(api_url, {
-							status_callback: handle_status,
-							normalise_files: false
+							status_callback: handle_status
 						});
 
 						config = app.config;
 						window.__gradio_space__ = config.space_id;
 						await mount_custom_css(config.css);
diff --git a/js/audio/Index.svelte b/js/audio/Index.svelte
index 128a9759c..c6009de53 100644
--- a/js/audio/Index.svelte
+++ b/js/audio/Index.svelte
@@ -9,11 +9,10 @@
 	import StaticAudio from "./static/StaticAudio.svelte";
 	import InteractiveAudio from "./interactive/InteractiveAudio.svelte";
 	import { StatusTracker } from "@gradio/statustracker";
 	import { Block, UploadText } from "@gradio/atoms";
 	import type { WaveformOptions } from "./shared/types";
-	import { normalise_file } from "@gradio/client";
 
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
 	export let visible = true;
 	export let interactive: boolean;
@@ -24,11 +23,10 @@
 		| ["microphone", "upload"]
 		| ["upload", "microphone"];
 	export let label: string;
 	export let root: string;
 	export let show_label: boolean;
-	export let proxy_url: null | string;
 	export let container = true;
 	export let scale: number | null = null;
 	export let min_width: number | undefined = undefined;
 	export let loading_status: LoadingStatus;
 	export let autoplay = false;
@@ -54,13 +52,11 @@
 		upload: never;
 		clear: never;
 		share: ShareData;
 	}>;
 
-	let old_value: null | FileData | string = null;
-	let _value: null | FileData;
-	$: _value = normalise_file(value, root, proxy_url);
+	let old_value: null | FileData = null;
 
 	let active_source: "microphone" | "upload";
 
 	let initial_value: null | FileData = value;
 
@@ -150,11 +146,11 @@
 		<StaticAudio
 			i18n={gradio.i18n}
 			{show_label}
 			{show_download_button}
 			{show_share_button}
-			value={_value}
+			{value}
 			{label}
 			{waveform_settings}
 			{waveform_options}
 			{editable}
 			on:share={(e) => gradio.dispatch("share", e.detail)}
@@ -183,11 +179,11 @@
 		/>
 		<InteractiveAudio
 			{label}
 			{show_label}
 			{show_download_button}
-			value={_value}
+			{value}
 			on:change={({ detail }) => (value = detail)}
 			on:stream={({ detail }) => {
 				value = detail;
 				gradio.dispatch("stream", value);
 			}}
diff --git a/js/chatbot/Chatbot.test.ts b/js/chatbot/Chatbot.test.ts
index db276c95d..e66d293b0 100644
--- a/js/chatbot/Chatbot.test.ts
+++ b/js/chatbot/Chatbot.test.ts
@@ -114,10 +114,11 @@ describe("Chatbot", () => {
 			2
 		).fill([
 			{
 				file: {
 					path: "https://gradio-builds.s3.amazonaws.com/demo-files/cheetah1.jpg",
+					url: "https://gradio-builds.s3.amazonaws.com/demo-files/cheetah1.jpg",
 					mime_type: "image/jpeg",
 					alt_text: null
 				}
 			}
 		]);
@@ -144,10 +145,11 @@ describe("Chatbot", () => {
 		let value: Array<[string | FileData | null, string | FileData | null]> =
 			Array(2).fill([
 				{
 					file: {
 						path: "https://gradio-builds.s3.amazonaws.com/demo-files/video_sample.mp4",
+						url: "https://gradio-builds.s3.amazonaws.com/demo-files/video_sample.mp4",
 						mime_type: "video/mp4",
 						alt_text: null
 					}
 				}
 			]);
@@ -172,10 +174,11 @@ describe("Chatbot", () => {
 
 		let value = Array(2).fill([
 			{
 				file: {
 					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav",
+					url: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav",
 					mime_type: "audio/wav",
 					alt_text: null
 				}
 			}
 		]);
@@ -200,10 +203,11 @@ describe("Chatbot", () => {
 
 		let value = Array(2).fill([
 			{
 				file: {
 					path: "https://gradio-builds.s3.amazonaws.com/demo-files/titanic.csv",
+					url: "https://gradio-builds.s3.amazonaws.com/demo-files/titanic.csv",
 					mime_type: "text/csv",
 					alt_text: null
 				}
 			}
 		]);
diff --git a/js/chatbot/Index.svelte b/js/chatbot/Index.svelte
index 70edc9d57..300245492 100644
--- a/js/chatbot/Index.svelte
+++ b/js/chatbot/Index.svelte
@@ -7,11 +7,11 @@
 
 	import ChatBot from "./shared/ChatBot.svelte";
 	import { Block, BlockLabel } from "@gradio/atoms";
 	import type { LoadingStatus } from "@gradio/statustracker";
 	import { Chat } from "@gradio/icons";
-	import { normalise_file, type FileData } from "@gradio/client";
+	import type { FileData } from "@gradio/client";
 	import { StatusTracker } from "@gradio/statustracker";
 
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
 	export let visible = true;
@@ -62,11 +62,11 @@
 	): { file: FileData; alt_text: string | null } | null {
 		if (message === null) {
 			return message;
 		}
 		return {
-			file: normalise_file(message?.file, root, proxy_url) as FileData,
+			file: message?.file as FileData,
 			alt_text: message?.alt_text
 		};
 	}
 
 	$: _value = value
diff --git a/js/file/Index.svelte b/js/file/Index.svelte
index a4cac65c4..5ecd3838b 100644
--- a/js/file/Index.svelte
+++ b/js/file/Index.svelte
@@ -9,11 +9,11 @@
 
 <script lang="ts">
 	import type { Gradio, SelectData } from "@gradio/utils";
 	import File from "./shared/File.svelte";
 	import FileUpload from "./shared/FileUpload.svelte";
-	import { normalise_file, type FileData } from "@gradio/client";
+	import type { FileData } from "@gradio/client";
 	import { Block, UploadText } from "@gradio/atoms";
 
 	import { StatusTracker } from "@gradio/statustracker";
 	import type { LoadingStatus } from "@gradio/statustracker";
 
@@ -26,11 +26,10 @@
 	export let root: string;
 	export let label: string;
 	export let show_label: boolean;
 	export let height: number | undefined = undefined;
 
-	export let proxy_url: null | string;
 	export let _selectable = false;
 	export let loading_status: LoadingStatus;
 	export let container = true;
 	export let scale: number | null = null;
 	export let min_width: number | undefined = undefined;
@@ -41,16 +40,15 @@
 		clear: never;
 		select: SelectData;
 	}>;
 	export let file_count: string;
 	export let file_types: string[] = ["file"];
-	$: _value = normalise_file(value, root, proxy_url);
 
-	let old_value = _value;
-	$: if (JSON.stringify(old_value) !== JSON.stringify(_value)) {
+	let old_value = value;
+	$: if (JSON.stringify(old_value) !== JSON.stringify(value)) {
 		gradio.dispatch("change");
-		old_value = _value;
+		old_value = value;
 	}
 
 	let dragging = false;
 	let pending_upload = false;
 </script>
@@ -77,21 +75,21 @@
 	/>
 	{#if !interactive}
 		<File
 			on:select={({ detail }) => gradio.dispatch("select", detail)}
 			selectable={_selectable}
-			value={_value}
+			{value}
 			{label}
 			{show_label}
 			{height}
 			i18n={gradio.i18n}
 		/>
 	{:else}
 		<FileUpload
 			{label}
 			{show_label}
-			value={_value}
+			{value}
 			{file_count}
 			{file_types}
 			selectable={_selectable}
 			{root}
 			{height}
diff --git a/js/gallery/Gallery.test.ts b/js/gallery/Gallery.test.ts
index c67b77b2c..9037e8d02 100644
--- a/js/gallery/Gallery.test.ts
+++ b/js/gallery/Gallery.test.ts
@@ -33,12 +33,14 @@ describe("Gallery", () => {
 			root: "",
 			proxy_url: "",
 			value: [
 				{
 					image: {
-						path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
-					}
+						path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
+						url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
+					},
+					caption: null
 				}
 			]
 		});
 		let item = getByTestId("detailed-image") as HTMLImageElement;
 		assert.equal(
@@ -56,23 +58,27 @@ describe("Gallery", () => {
 			root: "",
 			proxy_url: "",
 			value: [
 				{
 					image: {
-						path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
-					}
+						path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
+						url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
+					},
+					caption: null
 				}
 			]
 		});
 		const change_event = listen("change");
 
 		await component.$set({
 			value: [
 				{
 					image: {
-						path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
-					}
+						path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
+						url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
+					},
+					caption: null
 				}
 			]
 		});
 		assert.equal(change_event.callCount, 0);
 	});
diff --git a/js/gallery/Index.svelte b/js/gallery/Index.svelte
index 4b05fabdc..b711dd9d1 100644
--- a/js/gallery/Index.svelte
+++ b/js/gallery/Index.svelte
@@ -14,11 +14,10 @@
 
 	export let loading_status: LoadingStatus;
 	export let show_label: boolean;
 	export let label: string;
 	export let root: string;
-	export let proxy_url: null | string;
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
 	export let visible = true;
 	export let value: { image: FileData; caption: string | null }[] | null = null;
 	export let container = true;
@@ -89,12 +88,10 @@
 			on:select={(e) => gradio.dispatch("select", e.detail)}
 			on:share={(e) => gradio.dispatch("share", e.detail)}
 			on:error={(e) => gradio.dispatch("error", e.detail)}
 			{label}
 			{show_label}
-			{root}
-			{proxy_url}
 			{columns}
 			{rows}
 			{height}
 			{preview}
 			{object_fit}
diff --git a/js/gallery/shared/Gallery.svelte b/js/gallery/shared/Gallery.svelte
index d4f830693..00bf32a15 100644
--- a/js/gallery/shared/Gallery.svelte
+++ b/js/gallery/shared/Gallery.svelte
@@ -6,22 +6,20 @@
 	import { dequal } from "dequal";
 	import { createEventDispatcher, getContext } from "svelte";
 	import { tick } from "svelte";
 
 	import { Download, Image as ImageIcon } from "@gradio/icons";
-	import { normalise_file, type FileData } from "@gradio/client";
+	import { FileData } from "@gradio/client";
 	import { format_gallery_for_sharing } from "./utils";
 	import { IconButton } from "@gradio/atoms";
 	import type { I18nFormatter } from "@gradio/utils";
 
 	type GalleryImage = { image: FileData; caption: string | null };
 	type GalleryData = GalleryImage[];
 
 	export let show_label = true;
 	export let label: string;
-	export let root = "";
-	export let proxy_url: null | string = null;
 	export let value: GalleryData | null = null;
 	export let columns: number | number[] | undefined = [2];
 	export let rows: number | number[] | undefined = undefined;
 	export let height: number | "auto" = "auto";
 	export let preview: boolean;
@@ -47,11 +45,11 @@
 	let resolved_value: GalleryData | null = null;
 	$: resolved_value =
 		value == null
 			? null
 			: value.map((data) => ({
-					image: normalise_file(data.image, root, proxy_url) as FileData,
+					image: data.image as FileData,
 					caption: data.caption
 			  }));
 
 	let prev_value: GalleryData | null = value;
 	if (selected_index == null && preview && value?.length) {
diff --git a/js/image/Index.svelte b/js/image/Index.svelte
index d093fa71f..e7babb6ca 100644
--- a/js/image/Index.svelte
+++ b/js/image/Index.svelte
@@ -16,24 +16,22 @@
 	import { Block, Empty, UploadText } from "@gradio/atoms";
 	import { Image } from "@gradio/icons";
 	import { StatusTracker } from "@gradio/statustracker";
 	import type { FileData } from "@gradio/client";
 	import type { LoadingStatus } from "@gradio/statustracker";
-	import { normalise_file } from "@gradio/client";
 
 	type sources = "upload" | "webcam" | "clipboard" | null;
 
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
 	export let visible = true;
 	export let value: null | FileData = null;
-	$: _value = normalise_file(value, root, proxy_url);
+	let old_value: null | FileData = null;
 	export let label: string;
 	export let show_label: boolean;
 	export let show_download_button: boolean;
 	export let root: string;
-	export let proxy_url: null | string;
 
 	export let height: number | undefined;
 	export let width: number | undefined;
 
 	export let _selectable = false;
@@ -62,12 +60,16 @@
 		clear: never;
 		select: SelectData;
 		share: ShareData;
 	}>;
 
-	$: url = _value?.url;
-	$: url, gradio.dispatch("change");
+	$: {
+		if (JSON.stringify(value) !== JSON.stringify(old_value)) {
+			old_value = value;
+			gradio.dispatch("change");
+		}
+	}
 
 	let dragging: boolean;
 	let active_source: sources = null;
 </script>
 
@@ -93,11 +95,11 @@
 		/>
 		<StaticImage
 			on:select={({ detail }) => gradio.dispatch("select", detail)}
 			on:share={({ detail }) => gradio.dispatch("share", detail)}
 			on:error={({ detail }) => gradio.dispatch("error", detail)}
-			value={_value}
+			{value}
 			{label}
 			{show_label}
 			{show_download_button}
 			selectable={_selectable}
 			{show_share_button}
@@ -105,11 +107,11 @@
 		/>
 	</Block>
 {:else}
 	<Block
 		{visible}
-		variant={_value === null ? "dashed" : "solid"}
+		variant={value === null ? "dashed" : "solid"}
 		border_mode={dragging ? "focus" : "base"}
 		padding={false}
 		{elem_id}
 		{elem_classes}
 		height={height || undefined}
diff --git a/js/image/shared/ImagePreview.svelte b/js/image/shared/ImagePreview.svelte
index fd103504d..d8c954fe3 100644
--- a/js/image/shared/ImagePreview.svelte
+++ b/js/image/shared/ImagePreview.svelte
@@ -7,11 +7,11 @@
 	import { get_coordinates_of_clicked_image } from "./utils";
 	import Image from "./Image.svelte";
 	import { DownloadLink } from "@gradio/wasm/svelte";
 
 	import { Image as ImageIcon } from "@gradio/icons";
-	import { type FileData, normalise_file } from "@gradio/client";
+	import { type FileData } from "@gradio/client";
 	import type { I18nFormatter } from "@gradio/utils";
 
 	export let value: null | FileData;
 	export let label: string | undefined = undefined;
 	export let show_label: boolean;
diff --git a/js/image/shared/ImageUploader.svelte b/js/image/shared/ImageUploader.svelte
index c56b60400..3cba6add8 100644
--- a/js/image/shared/ImageUploader.svelte
+++ b/js/image/shared/ImageUploader.svelte
@@ -5,11 +5,11 @@
 	import type { SelectData, I18nFormatter } from "@gradio/utils";
 	import { get_coordinates_of_clicked_image } from "./utils";
 	import Webcam from "./Webcam.svelte";
 
 	import { Upload } from "@gradio/upload";
-	import { type FileData, normalise_file } from "@gradio/client";
+	import type { FileData } from "@gradio/client";
 	import ClearImage from "./ClearImage.svelte";
 	import { SelectSource } from "@gradio/atoms";
 	import Image from "./Image.svelte";
 
 	export let value: null | FileData;
@@ -29,11 +29,11 @@
 	let upload: Upload;
 	let uploading = false;
 	export let active_source: source_type = null;
 
 	function handle_upload({ detail }: CustomEvent<FileData>): void {
-		value = normalise_file(detail, root, null);
+		value = detail;
 		dispatch("upload");
 	}
 
 	function handle_clear(): void {
 		value = null;
@@ -54,12 +54,10 @@
 	}
 
 	$: active_streaming = streaming && active_source === "webcam";
 	$: if (uploading && !active_streaming) value = null;
 
-	$: value && !value.url && (value = normalise_file(value, root, null));
-
 	const dispatch = createEventDispatcher<{
 		change?: never;
 		stream?: never;
 		clear?: never;
 		drag: boolean;
diff --git a/js/imageeditor/Index.svelte b/js/imageeditor/Index.svelte
index 303b390f3..dbde2229f 100644
--- a/js/imageeditor/Index.svelte
+++ b/js/imageeditor/Index.svelte
@@ -11,11 +11,10 @@
 	import { BaseStaticImage as StaticImage } from "@gradio/image";
 	import InteractiveImageEditor from "./shared/InteractiveImageEditor.svelte";
 	import { Block } from "@gradio/atoms";
 	import { StatusTracker } from "@gradio/statustracker";
 	import type { LoadingStatus } from "@gradio/statustracker";
-	import { normalise_file } from "@gradio/client";
 
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
 	export let visible = true;
 	export let value: EditorData | null = {
@@ -40,11 +39,10 @@
 	export let sources: ("clipboard" | "webcam" | "upload")[] = [
 		"upload",
 		"clipboard",
 		"webcam"
 	];
-	export let proxy_url: string;
 	export let interactive: boolean;
 
 	export let brush: Brush;
 	export let eraser: Eraser;
 	export let crop_size: [number, number] | `${string}:${string}` | null = null;
@@ -118,11 +116,11 @@
 		/>
 		<StaticImage
 			on:select={({ detail }) => gradio.dispatch("select", detail)}
 			on:share={({ detail }) => gradio.dispatch("share", detail)}
 			on:error={({ detail }) => gradio.dispatch("error", detail)}
-			value={normalise_file(value?.composite || null, root, proxy_url)}
+			value={value?.composite || null}
 			{label}
 			{show_label}
 			{show_download_button}
 			selectable={_selectable}
 			{show_share_button}
@@ -172,11 +170,10 @@
 				gradio.dispatch("error", detail);
 			}}
 			on:error
 			{brush}
 			{eraser}
-			{proxy_url}
 			changeable={attached_events.includes("change")}
 			i18n={gradio.i18n}
 			{transforms}
 		></InteractiveImageEditor>
 	</Block>
diff --git a/js/imageeditor/shared/InteractiveImageEditor.svelte b/js/imageeditor/shared/InteractiveImageEditor.svelte
index d71610937..9ee180f8c 100644
--- a/js/imageeditor/shared/InteractiveImageEditor.svelte
+++ b/js/imageeditor/shared/InteractiveImageEditor.svelte
@@ -12,16 +12,11 @@
 	}
 </script>
 
 <script lang="ts">
 	import { type I18nFormatter } from "@gradio/utils";
-	import {
-		prepare_files,
-		upload,
-		normalise_file,
-		type FileData
-	} from "@gradio/client";
+	import { prepare_files, upload, type FileData } from "@gradio/client";
 
 	import ImageEditor from "./ImageEditor.svelte";
 	import Layers from "./layers/Layers.svelte";
 	import { type Brush as IBrush } from "./tools/Brush.svelte";
 	import { type Eraser } from "./tools/Brush.svelte";
@@ -34,11 +29,10 @@
 
 	export let sources: ("clipboard" | "webcam" | "upload")[];
 	export let crop_size: [number, number] | `${string}:${string}` | null = null;
 	export let i18n: I18nFormatter;
 	export let root: string;
-	export let proxy_url: string;
 	export let label: string | undefined = undefined;
 	export let show_label: boolean;
 	export let changeable = false;
 	export let value: EditorData | null = {
 		background: null,
@@ -134,15 +128,11 @@
 			<Sources
 				{i18n}
 				{root}
 				{sources}
 				bind:bg
-				background_file={normalise_file(
-					value?.background || null,
-					root,
-					proxy_url
-				)}
+				background_file={value?.background || null}
 			></Sources>
 		{/if}
 		{#if transforms.includes("crop")}
 			<Crop {crop_constraint} />
 		{/if}
@@ -159,13 +149,11 @@
 		{#if brush && eraser}
 			<Brush default_size={eraser.default_size} mode="erase" />
 		{/if}
 	</Tools>
 
-	<Layers
-		layer_files={normalise_file(value?.layers || null, root, proxy_url)}
-	/>
+	<Layers layer_files={value?.layers || null} />
 
 	{#if !bg && !history}
 		<div class="empty wrap">
 			{#if sources && sources.length}
 				<div>Upload an image</div>
diff --git a/js/imageeditor/shared/tools/Sources.svelte b/js/imageeditor/shared/tools/Sources.svelte
index 83a9e5318..8b34dcfb8 100644
--- a/js/imageeditor/shared/tools/Sources.svelte
+++ b/js/imageeditor/shared/tools/Sources.svelte
@@ -10,11 +10,11 @@
 	import { Upload } from "@gradio/upload";
 	import { Webcam } from "@gradio/image";
 	import { type I18nFormatter } from "@gradio/utils";
 
 	import { add_bg_color, add_bg_image } from "./sources";
-	import type { FileData, normalise_file } from "@gradio/client";
+	import type { FileData } from "@gradio/client";
 
 	export let background_file: FileData | null;
 	export let root: string;
 	export let sources: ("upload" | "webcam" | "clipboard")[] = [
 		"upload",
diff --git a/js/model3D/Index.svelte b/js/model3D/Index.svelte
index fee733ab3..0674ec1e5 100644
--- a/js/model3D/Index.svelte
+++ b/js/model3D/Index.svelte
@@ -3,11 +3,11 @@
 	export { default as BaseModel3DUpload } from "./shared/Model3DUpload.svelte";
 	export { default as BaseExample } from "./Example.svelte";
 </script>
 
 <script lang="ts">
-	import { normalise_file, type FileData } from "@gradio/client";
+	import type { FileData } from "@gradio/client";
 	import Model3D from "./shared/Model3D.svelte";
 	import Model3DUpload from "./shared/Model3DUpload.svelte";
 	import { BlockLabel, Block, Empty, UploadText } from "@gradio/atoms";
 	import { File } from "@gradio/icons";
 
@@ -18,11 +18,10 @@
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
 	export let visible = true;
 	export let value: null | FileData = null;
 	export let root: string;
-	export let proxy_url: null | string;
 	export let clear_color: [number, number, number, number];
 	export let loading_status: LoadingStatus;
 	export let label: string;
 	export let show_label: boolean;
 	export let container = true;
@@ -38,13 +37,10 @@
 		null,
 		null
 	];
 	export let interactive: boolean;
 
-	let _value: null | FileData;
-	$: _value = normalise_file(value, root, proxy_url);
-
 	let dragging = false;
 </script>
 
 {#if !interactive}
 	<Block
@@ -65,11 +61,11 @@
 			{...loading_status}
 		/>
 
 		{#if value}
 			<Model3D
-				value={_value}
+				{value}
 				i18n={gradio.i18n}
 				{clear_color}
 				{label}
 				{show_label}
 				{camera_position}
@@ -106,11 +102,11 @@
 		<Model3DUpload
 			{label}
 			{show_label}
 			{root}
 			{clear_color}
-			value={_value}
+			{value}
 			{camera_position}
 			{zoom_speed}
 			on:change={({ detail }) => (value = detail)}
 			on:drag={({ detail }) => (dragging = detail)}
 			on:change={({ detail }) => gradio.dispatch("change", detail)}
diff --git a/js/simpleimage/Index.svelte b/js/simpleimage/Index.svelte
index 1b2d79479..580da78dd 100644
--- a/js/simpleimage/Index.svelte
+++ b/js/simpleimage/Index.svelte
@@ -13,37 +13,32 @@
 
 	import { Block, UploadText } from "@gradio/atoms";
 	import { StatusTracker } from "@gradio/statustracker";
 	import type { FileData } from "@gradio/client";
 	import type { LoadingStatus } from "@gradio/statustracker";
-	import { normalise_file } from "@gradio/client";
 
 	export let elem_id = "";
 	export let elem_classes: string[] = [];
 	export let visible = true;
 	export let value: null | FileData = null;
-	export let root: string;
-	export let proxy_url: null | string;
 	export let label: string;
 	export let show_label: boolean;
 	export let show_download_button: boolean;
 	export let container = true;
 	export let scale: number | null = null;
 	export let min_width: number | undefined = undefined;
 	export let loading_status: LoadingStatus;
 	export let interactive: boolean;
-
-	$: _value = normalise_file(value, root, proxy_url);
+	export let root: string;
 
 	export let gradio: Gradio<{
 		change: never;
 		upload: never;
 		clear: never;
 	}>;
 
-	$: url = _value?.url;
-	$: url, gradio.dispatch("change");
+	$: value, gradio.dispatch("change");
 
 	let dragging: boolean;
 </script>
 
 {#if !interactive}
@@ -63,21 +58,21 @@
 			autoscroll={gradio.autoscroll}
 			i18n={gradio.i18n}
 			{...loading_status}
 		/>
 		<ImagePreview
-			value={_value}
+			{value}
 			{label}
 			{show_label}
 			{show_download_button}
 			i18n={gradio.i18n}
 		/>
 	</Block>
 {:else}
 	<Block
 		{visible}
-		variant={_value === null ? "dashed" : "solid"}
+		variant={value === null ? "dashed" : "solid"}
 		border_mode={dragging ? "focus" : "base"}
 		padding={false}
 		{elem_id}
 		{elem_classes}
 		allow_overflow={false}
diff --git a/js/simpleimage/shared/ImageUploader.svelte b/js/simpleimage/shared/ImageUploader.svelte
index fe06338c0..2586efaa3 100644
--- a/js/simpleimage/shared/ImageUploader.svelte
+++ b/js/simpleimage/shared/ImageUploader.svelte
@@ -2,11 +2,11 @@
 	import { createEventDispatcher } from "svelte";
 	import { BlockLabel } from "@gradio/atoms";
 	import { Image as ImageIcon } from "@gradio/icons";
 
 	import { Upload } from "@gradio/upload";
-	import { type FileData, normalise_file } from "@gradio/client";
+	import type { FileData } from "@gradio/client";
 	import ClearImage from "./ClearImage.svelte";
 
 	export let value: null | FileData;
 	export let label: string | undefined = undefined;
 	export let show_label: boolean;
@@ -14,15 +14,14 @@
 
 	let upload: Upload;
 	let uploading = false;
 
 	function handle_upload({ detail }: CustomEvent<FileData>): void {
-		value = normalise_file(detail, root, null);
+		value = detail;
 		dispatch("upload");
 	}
 	$: if (uploading) value = null;
-	$: value && !value.url && (value = normalise_file(value, root, null));
 
 	const dispatch = createEventDispatcher<{
 		change?: never;
 		clear?: never;
 		drag: boolean;
diff --git a/js/video/Index.svelte b/js/video/Index.svelte
index d1f4cbcf9..711dff570 100644
--- a/js/video/Index.svelte
+++ b/js/video/Index.svelte
@@ -1,11 +1,11 @@
 <svelte:options accessors={true} />
 
 <script lang="ts">
 	import type { Gradio, ShareData } from "@gradio/utils";
 
-	import { normalise_file, type FileData } from "@gradio/client";
+	import type { FileData } from "@gradio/client";
 	import { Block, UploadText } from "@gradio/atoms";
 	import StaticVideo from "./shared/VideoPreview.svelte";
 	import Video from "./shared/InteractiveVideo.svelte";
 	import { StatusTracker } from "@gradio/statustracker";
 	import type { LoadingStatus } from "@gradio/statustracker";
@@ -22,11 +22,10 @@
 		| ["webcam"]
 		| ["upload"]
 		| ["webcam", "upload"]
 		| ["upload", "webcam"];
 	export let root: string;
-	export let proxy_url: null | string;
 	export let show_label: boolean;
 	export let loading_status: LoadingStatus;
 	export let height: number | undefined;
 	export let width: number | undefined;
 
@@ -78,12 +77,12 @@
 		active_source = sources[0];
 	}
 
 	$: {
 		if (value != null) {
-			_video = normalise_file(value.video, root, proxy_url);
-			_subtitle = normalise_file(value.subtitles, root, proxy_url);
+			_video = value.video;
+			_subtitle = value.subtitles;
 		} else {
 			_video = null;
 			_subtitle = null;
 		}
 	}
diff --git a/js/video/Video.test.ts b/js/video/Video.test.ts
index 3e9378f60..9a41c3440 100644
--- a/js/video/Video.test.ts
+++ b/js/video/Video.test.ts
@@ -39,12 +39,14 @@ describe("Video", () => {
 		const { getByTestId, queryAllByText } = await render(Video, {
 			show_label: true,
 			loading_status,
 			value: {
 				video: {
-					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
-				}
+					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
+					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
+				},
+				subtitles: null
 			},
 			label: "Test Label",
 			root: "foo",
 			proxy_url: null,
 			streaming: false,
@@ -54,23 +56,25 @@ describe("Video", () => {
 			interactive: true
 		});
 		let vid = getByTestId("Test Label-player") as HTMLVideoElement;
 		assert.equal(
 			vid.src,
-			"https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
+			"https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
 		);
 		assert.equal(queryAllByText("Test Label").length, 1);
 	});
 
 	test("hides label", async () => {
 		const { queryAllByText } = await render(Video, {
 			show_label: false,
 			loading_status,
 			value: {
 				video: {
-					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
-				}
+					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
+					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
+				},
+				subtitles: null
 			},
 			label: "Video Component",
 			root: "foo",
 			proxy_url: null,
 			streaming: false,
@@ -86,12 +90,14 @@ describe("Video", () => {
 		const { getByTestId } = await render(Video, {
 			show_label: true,
 			loading_status,
 			value: {
 				video: {
-					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
-				}
+					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
+					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
+				},
+				subtitles: null
 			},
 			root: "foo",
 			proxy_url: null,
 			streaming: false,
 			pending: false,
@@ -100,23 +106,25 @@ describe("Video", () => {
 			mode: "static"
 		});
 		let vid = getByTestId("test-player") as HTMLVideoElement;
 		assert.equal(
 			vid.src,
-			"https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
+			"https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
 		);
 	});
 
 	test("when autoplay is true `media.play` should be called in static mode", async () => {
 		const { getByTestId } = await render(Video, {
 			show_label: true,
 			loading_status,
 			interactive: false,
 			value: {
 				video: {
-					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
-				}
+					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
+					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
+				},
+				subtitles: null
 			},
 			root: "foo",
 			proxy_url: null,
 			streaming: false,
 			pending: false,
@@ -133,12 +141,14 @@ describe("Video", () => {
 		const { getByTestId } = await render(Video, {
 			show_label: true,
 			loading_status,
 			value: {
 				video: {
-					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
-				}
+					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
+					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
+				},
+				subtitles: null
 			},
 			root: "foo",
 			proxy_url: null,
 			streaming: false,
 			pending: false,
@@ -156,12 +166,14 @@ describe("Video", () => {
 			show_label: true,
 			loading_status,
 			interactive: false,
 			value: {
 				video: {
-					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
-				}
+					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
+					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
+				},
+				subtitles: null
 			},
 			root: "foo",
 			proxy_url: null,
 			streaming: false,
 			pending: false,
@@ -187,12 +199,14 @@ describe("Video", () => {
 			show_label: true,
 			loading_status,
 			interactive: true,
 			value: {
 				video: {
-					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
-				}
+					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
+					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
+				},
+				subtitles: null
 			},
 			root: "foo",
 			proxy_url: null,
 			streaming: false,
 			pending: false,
@@ -203,24 +217,24 @@ describe("Video", () => {
 		const fn = spyOn(startButton, "play");
 		startButton.dispatchEvent(new Event("loadeddata"));
 		component.$set({
 			value: {
 				video: {
-					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
-				}
+					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
+					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
+				},
+				subtitles: null
 			}
 		});
 		startButton.dispatchEvent(new Event("loadeddata"));
 		assert.equal(fn.callCount, 2);
 	});
 	test("renders video and download button", async () => {
 		const data = {
 			video: {
-				path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
-			},
-			subtitles: {
-				path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
+				path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
+				url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
 			}
 		};
 		const results = await render(Video, {
 			interactive: false,
 			label: "video",
diff --git a/test/test_components.py b/test/test_components.py
index 33f015ec0..889e77742 100644
--- a/test/test_components.py
+++ b/test/test_components.py
@@ -1535,10 +1535,11 @@ class TestVideo:
                 "path": "video_sample.mp4",
                 "orig_name": "video_sample.mp4",
                 "mime_type": None,
                 "size": None,
                 "url": None,
+                "is_stream": False,
             },
             "subtitles": None,
         }
 
         processed_video_with_subtitle = {
@@ -1546,17 +1547,19 @@ class TestVideo:
                 "path": "video_sample.mp4",
                 "orig_name": "video_sample.mp4",
                 "mime_type": None,
                 "size": None,
                 "url": None,
+                "is_stream": False,
             },
             "subtitles": {
                 "path": "s1.srt",
                 "mime_type": None,
                 "orig_name": None,
                 "size": None,
                 "url": None,
+                "is_stream": False,
             },
         }
         postprocessed_video["video"]["path"] = os.path.basename(
             postprocessed_video["video"]["path"]
         )
@@ -2249,10 +2252,11 @@ class TestGallery:
                     "path": url,
                     "orig_name": "00015-20230906102032-7778-Wonderwoman VintageMagStyle   _lora_SDXL-VintageMagStyle-Lora_1_, Very detailed, clean, high quality, sharp image.jpg",
                     "mime_type": None,
                     "size": None,
                     "url": url,
+                    "is_stream": False,
                 },
                 "caption": None,
             }
         ]
 
@@ -2276,40 +2280,44 @@ class TestGallery:
                     "path": str(Path("test") / "test_files" / "foo.png"),
                     "orig_name": "foo.png",
                     "mime_type": None,
                     "size": None,
                     "url": None,
+                    "is_stream": False,
                 },
                 "caption": "foo_caption",
             },
             {
                 "image": {
                     "path": str(Path("test") / "test_files" / "bar.png"),
                     "orig_name": "bar.png",
                     "mime_type": None,
                     "size": None,
                     "url": None,
+                    "is_stream": False,
                 },
                 "caption": "bar_caption",
             },
             {
                 "image": {
                     "path": str(Path("test") / "test_files" / "baz.png"),
                     "orig_name": "baz.png",
                     "mime_type": None,
                     "size": None,
                     "url": None,
+                    "is_stream": False,
                 },
                 "caption": None,
             },
             {
                 "image": {
                     "path": str(Path("test") / "test_files" / "qux.png"),
                     "orig_name": "qux.png",
                     "mime_type": None,
                     "size": None,
                     "url": None,
+                    "is_stream": False,
                 },
                 "caption": None,
             },
         ]
 

Vulnerable code: import { FileData, normalise_file } from "./upload";
			normalise_files?: boolean;
			normalise_files?: boolean;
		} = { normalise_files: true }
			const { status_callback, hf_token, normalise_files } = options;
			const transform_files = normalise_files ?? true;
									const data = transform_files
										? transform_output(
												output.data,
												api_info,
												config.root,
												config.root_url
										  )
										: output.data;
										data: transform_files
											? transform_output(
													data.data,
													api_info,
													config.root,
													config.root_url
											  )
											: data.data,
										data: transform_files
											? transform_output(
													data.data,
													api_info,
													config.root,
													config.root_url
											  )
											: data.data,
													data: transform_files
														? transform_output(
																data.data,
																api_info,
																config.root,
																config.root_url
														  )
														: data.data,
function transform_output(
	data: any[],
	api_info: any,
	root_url: string,
	remote_url?: string
): unknown[] {
	return data.map((d, i) => {
		if (api_info?.returns?.[i]?.component === "File") {
			return normalise_file(d, root_url, remote_url);
		} else if (api_info?.returns?.[i]?.component === "Gallery") {
			return d.map((img) => {
				return Array.isArray(img)
					? [normalise_file(img[0], root_url, remote_url), img[1]]
					: [normalise_file(img, root_url, remote_url), null];
			});
		} else if (typeof d === "object" && d.path) {
			return normalise_file(d, root_url, remote_url);
		}
		return d;
	});
}

	normalise_file,
export function normalise_file(
	file: FileData | null,
	server_url: string,
	proxy_url: string | null
): FileData | null;

export function normalise_file(
	file: FileData[] | null,
	server_url: string,
	proxy_url: string | null
): FileData[] | null;

export function normalise_file(
	file: FileData[] | FileData | null,
	server_url: string, // root: string,
	proxy_url: string | null // root_url: string | null
): FileData[] | FileData | null;

export function normalise_file(
	file: FileData[] | FileData | null,
	server_url: string, // root: string,
	proxy_url: string | null // root_url: string | null
): FileData[] | FileData | null {
	if (file == null) {
		return null;
	}

	if (Array.isArray(file)) {
		const normalized_file: (FileData | null)[] = [];

		for (const x of file) {
			if (x == null) {
				normalized_file.push(null);
			} else {
				normalized_file.push(normalise_file(x, server_url, proxy_url));
			}
		}

		return normalized_file as FileData[];
	}

	if (file.is_stream) {
		if (proxy_url == null) {
			return new FileData({
				...file,
				url: server_url + "/stream/" + file.path
			});
		}
		return new FileData({
			...file,
			url: "/proxy=" + proxy_url + "stream/" + file.path
		});
	}

	return new FileData({
		...file,
		url: get_fetchable_url_or_file(file.path, server_url, proxy_url)
	});
}

							const file = new FileData({ ...file_data[i], path: f });

							return normalise_file(file, root, null);
FILE_DATA = "Dict(path: str, url: str | None, size: int | None, orig_name: str | None, mime_type: str | None)"
                        inputs[i], block
        self.value = move_files_to_cache(initial_value, self, postprocess=True)  # type: ignore
            return LoginButton(value=self.value, interactive=True)
def move_files_to_cache(data: Any, block: Component, postprocess: bool = False):
    """Move files to cache and replace the file path with the cache path.
    Runs after .postprocess(), after .process_example(), and before .preprocess().
        block: The component
import shutil

def move_uploaded_files_to_cache(files: list[str], destinations: list[str]) -> None:
    for file, dest in zip(files, destinations):
        shutil.move(file, dest)


            root_path = (
                request.scope.get("root_path")
                or request.headers.get("X-Direct-Url")
                or ""
            )
                config["root"] = route_utils.strip_url(root_path)
            root_path = (
                request.scope.get("root_path")
                or request.headers.get("X-Direct-Url")
                or ""
            )
            config["root"] = route_utils.strip_url(root_path)
The `@gradio/client` npm package provides the `upload`, `prepare_files`, and `normalise_file` utility functions to help you do this.
The `normalise_file` function will generate the correct URL for your component to fetch the file from and set it to the `data` property of the `FileData.`


Tip: Be sure you call `normalise_file` whenever your files are updated!


    import { upload, prepare_files, normalise_file, type FileData } from "@gradio/client";
    $: value: normalise_file(uploaded_files, root)

	import { type FileData, normalise_file, Upload, ModifyUpload } from "@gradio/upload";
    import { normalise_file } from "@gradio/client";
    // Compute the url to fetch the file from the backend
    // whenever a new value is passed in.
    $: _value = normalise_file(value, root, proxy_url);

	import { type FileData, normalise_file } from "@gradio/client";
	export let root: string;
	export let proxy_url: string;
				image: normalise_file(value.image, root, proxy_url) as FileData,
					image: normalise_file(ann.image, root, proxy_url) as FileData,
			status_callback: handle_status,
			normalise_files: false
							status_callback: handle_status,
							normalise_files: false
	import { normalise_file } from "@gradio/client";
	export let proxy_url: null | string;
	let old_value: null | FileData | string = null;
	let _value: null | FileData;
	$: _value = normalise_file(value, root, proxy_url);
			value={_value}
			value={_value}
	import { normalise_file, type FileData } from "@gradio/client";
			file: normalise_file(message?.file, root, proxy_url) as FileData,
	import { normalise_file, type FileData } from "@gradio/client";
	export let proxy_url: null | string;
	$: _value = normalise_file(value, root, proxy_url);
	let old_value = _value;
	$: if (JSON.stringify(old_value) !== JSON.stringify(_value)) {
		old_value = _value;
			value={_value}
			value={_value}
						path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
					}
						path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
					}
						path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
					}
	export let proxy_url: null | string;
			{root}
			{proxy_url}
	import { normalise_file, type FileData } from "@gradio/client";
	export let root = "";
	export let proxy_url: null | string = null;
					image: normalise_file(data.image, root, proxy_url) as FileData,
	import { normalise_file } from "@gradio/client";
	$: _value = normalise_file(value, root, proxy_url);
	export let proxy_url: null | string;
	$: url = _value?.url;
	$: url, gradio.dispatch("change");
			value={_value}
		variant={_value === null ? "dashed" : "solid"}
	import { type FileData, normalise_file } from "@gradio/client";
	import { type FileData, normalise_file } from "@gradio/client";
		value = normalise_file(detail, root, null);
	$: value && !value.url && (value = normalise_file(value, root, null));

	import { normalise_file } from "@gradio/client";
	export let proxy_url: string;
			value={normalise_file(value?.composite || null, root, proxy_url)}
			{proxy_url}
	import {
		prepare_files,
		upload,
		normalise_file,
		type FileData
	} from "@gradio/client";
	export let proxy_url: string;
				background_file={normalise_file(
					value?.background || null,
					root,
					proxy_url
				)}
	<Layers
		layer_files={normalise_file(value?.layers || null, root, proxy_url)}
	/>
	import type { FileData, normalise_file } from "@gradio/client";
	import { normalise_file, type FileData } from "@gradio/client";
	export let proxy_url: null | string;
	let _value: null | FileData;
	$: _value = normalise_file(value, root, proxy_url);

				value={_value}
			value={_value}
	import { normalise_file } from "@gradio/client";
	export let root: string;
	export let proxy_url: null | string;

	$: _value = normalise_file(value, root, proxy_url);
	$: url = _value?.url;
	$: url, gradio.dispatch("change");
			value={_value}
		variant={_value === null ? "dashed" : "solid"}
	import { type FileData, normalise_file } from "@gradio/client";
		value = normalise_file(detail, root, null);
	$: value && !value.url && (value = normalise_file(value, root, null));
	import { normalise_file, type FileData } from "@gradio/client";
	export let proxy_url: null | string;
			_video = normalise_file(value.video, root, proxy_url);
			_subtitle = normalise_file(value.subtitles, root, proxy_url);
					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
				}
			"https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
				}
					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
				}
			"https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
				}
					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
				}
					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
				}
					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
				}
					path: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav"
				}
				path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
			},
			subtitles: {
				path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
Fixed code: ---
"@gradio/annotatedimage": minor
"@gradio/app": minor
"@gradio/audio": minor
"@gradio/chatbot": minor
"@gradio/client": minor
"@gradio/file": minor
"@gradio/gallery": minor
"@gradio/image": minor
"@gradio/imageeditor": minor
"@gradio/model3d": minor
"@gradio/simpleimage": minor
"@gradio/video": minor
"gradio": minor
"gradio_client": minor
---

feat:[WIP] Refactor file normalization to be in the backend and remove it from the frontend of each component
import { FileData } from "./upload";
		} = {}
			const { status_callback, hf_token } = options;
									const data = output.data;
										data: data.data,
										data: data.data,
													data: data.data,
							const file = new FileData({
								...file_data[i],
								path: f,
								url: root + "/file=" + f
							});
							return file;
FILE_DATA = "Dict(path: str, url: str | None, size: int | None, orig_name: str | None, mime_type: str | None, is_stream: bool)"
def is_file_obj_with_url(d):
    return (
        isinstance(d, dict) and "path" in d and "url" in d and isinstance(d["url"], str)
    )


                        inputs[i], block, add_urls=True
                    add_urls=True,
        self.value = move_files_to_cache(
            initial_value,
            self,  # type: ignore
            postprocess=True,
            add_urls=True,
        )
            return LoginButton(value=self.value, interactive=True)  # type: ignore
    is_stream: bool = False
def move_files_to_cache(
    data: Any,
    block: Component,
    postprocess: bool = False,
    add_urls=False,
) -> dict:
    """Move any files in `data` to cache and (optionally), adds URL prefixes (/file=...) needed to access the cached file.
    Also handles the case where the file is on an external Gradio app (/proxy=...).
    Runs after .postprocess() and before .preprocess().
        block: The component whose data is being processed
        root_url: The root URL of the local server, if applicable

        if add_urls:
            url_prefix = "/stream/" if payload.is_stream else "/file="
            if block.proxy_url:
                url = f"/proxy={block.proxy_url}{url_prefix}{temp_file_path}"
            elif client_utils.is_http_url_like(
                temp_file_path
            ) or temp_file_path.startswith(f"{url_prefix}"):
                url = temp_file_path
            else:
                url = f"{url_prefix}{temp_file_path}"
            payload.url = url

def add_root_url(data, root_url) -> dict:
    def _add_root_url(file_dict: dict):
        if not client_utils.is_http_url_like(file_dict["url"]):
            file_dict["url"] = f'{root_url}{file_dict["url"]}'
        return file_dict

    return client_utils.traverse(data, _add_root_url, client_utils.is_file_obj_with_url)


import shutil


def move_uploaded_files_to_cache(files: list[str], destinations: list[str]) -> None:
    for file, dest in zip(files, destinations):
        shutil.move(file, dest)
from gradio.processing_utils import add_root_url
    move_uploaded_files_to_cache,
            root_path = route_utils.strip_url(str(request.url))
                if "root" not in config:
                    config["root"] = root_path
                    config = add_root_url(config, root_path)
            root_path = route_utils.strip_url(str(request.url))[:-7]
            if "root" not in config:
                config["root"] = route_utils.strip_url(root_path)
                config = add_root_url(config, root_path)
            root_path = app.get_blocks().config.get("root", "")
            output = add_root_url(output, route_utils.strip_url(root_path))
            root_path = app.get_blocks().config.get("root", "")
                            add_root_url(message, route_utils.strip_url(root_path))
The `@gradio/client` npm package provides the `upload` and `prepare_files` utility functions to help you do this.
    import { upload, prepare_files, type FileData } from "@gradio/client";
	import { type FileData, Upload, ModifyUpload } from "@gradio/upload";
	import { type FileData } from "@gradio/client";
				image: value.image as FileData,
					image: ann.image as FileData,
			status_callback: handle_status
							status_callback: handle_status
	let old_value: null | FileData = null;
			{value}
			{value}
					url: "https://gradio-builds.s3.amazonaws.com/demo-files/cheetah1.jpg",
						url: "https://gradio-builds.s3.amazonaws.com/demo-files/video_sample.mp4",
					url: "https://gradio-builds.s3.amazonaws.com/demo-files/audio_sample.wav",
					url: "https://gradio-builds.s3.amazonaws.com/demo-files/titanic.csv",
	import type { FileData } from "@gradio/client";
			file: message?.file as FileData,
	import type { FileData } from "@gradio/client";
	let old_value = value;
	$: if (JSON.stringify(old_value) !== JSON.stringify(value)) {
		old_value = value;
			{value}
			{value}
						path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
						url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
					},
					caption: null
						path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
						url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
					},
					caption: null
						path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
						url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
					},
					caption: null
	import { FileData } from "@gradio/client";
					image: data.image as FileData,
	let old_value: null | FileData = null;
	$: {
		if (JSON.stringify(value) !== JSON.stringify(old_value)) {
			old_value = value;
			gradio.dispatch("change");
		}
	}
			{value}
		variant={value === null ? "dashed" : "solid"}
	import { type FileData } from "@gradio/client";
	import type { FileData } from "@gradio/client";
		value = detail;
			value={value?.composite || null}
	import { prepare_files, upload, type FileData } from "@gradio/client";
				background_file={value?.background || null}
	<Layers layer_files={value?.layers || null} />
	import type { FileData } from "@gradio/client";
	import type { FileData } from "@gradio/client";
				{value}
			{value}
	export let root: string;
	$: value, gradio.dispatch("change");
			{value}
		variant={value === null ? "dashed" : "solid"}
	import type { FileData } from "@gradio/client";
		value = detail;
	import type { FileData } from "@gradio/client";
			_video = value.video;
			_subtitle = value.subtitles;
					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
				},
				subtitles: null
			"https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
				},
				subtitles: null
					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
				},
				subtitles: null
			"https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
				},
				subtitles: null
					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
				},
				subtitles: null
					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
				},
				subtitles: null
					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
				},
				subtitles: null
					path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
					url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
				},
				subtitles: null
				path: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4",
				url: "https://raw.githubusercontent.com/gradio-app/gradio/main/gradio/demo/video_component/files/a.mp4"
                "is_stream": False,
                "is_stream": False,
                "is_stream": False,
                    "is_stream": False,
                    "is_stream": False,
                    "is_stream": False,
                    "is_stream": False,
                    "is_stream": False,