CVE-2024-2206 (published 2024-03-27T01:15Z)

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.


Weakness information:
cwe_id: CWE-918
cwe_name: Server-Side Request Forgery (SSRF)
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.
extended_description: By providing URLs to unexpected hosts or ports, attackers can make it appear that the server is sending the request, possibly bypassing access controls such as firewalls that prevent the attackers from accessing the URLs directly. The server can be used as a proxy to conduct port scanning of hosts in internal networks, use other URLs such as that can access documents on the system (using file://), or use other protocols such as gopher:// or tftp://, which may provide greater control over the contents of requests.
         

Fix information:
repo_url: https://github.com/gradio-app/gradio
commit_details: repo_url: https://github.com/gradio-app/gradio
author: Abubakar Abid
author_date: 2024-02-07 14:39:33-08:00
msg: [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>
repository: repo_url: https://github.com/gradio-app/gradio
repo_name: gradio-app/gradio
description: Build and share delightful machine learning apps, all in Python. 🌟 Star to support our work!
repo_language: Python
owner: gradio-app
file_changes: filename: strong-chefs-study.md
change_type: ModificationType.ADD
diff: @@ -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

programming_language: Python
filename: client.ts
change_type: ModificationType.MODIFY
diff: @@ -28,7 +28,7 @@ import type {
 	SpaceStatusCallback
 } from "./types.js";
 
-import { FileData, normalise_file } from "./upload";
+import { FileData } from "./upload";
 
 import type { Config } from "./types.js";
 
@@ -166,7 +166,6 @@ interface Client {
 		options: {
 			hf_token?: `hf_${string}`;
 			status_callback?: SpaceStatusCallback;
-			normalise_files?: boolean;
 		}
 	) => Promise<client_return>;
 	handle_blob: (
@@ -259,11 +258,10 @@ export function api_factory(
 		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,
@@ -271,7 +269,6 @@ export function api_factory(
 				component_server
 			};
 
-			const transform_files = normalise_files ?? true;
 			if (
 				(typeof window === "undefined" || !("WebSocket" in window)) &&
 				!global.Websocket
@@ -493,14 +490,7 @@ export function api_factory(
 								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",
@@ -628,14 +618,7 @@ export function api_factory(
 									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
 									});
@@ -750,14 +733,7 @@ export function api_factory(
 									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
 									});
@@ -878,14 +854,7 @@ export function api_factory(
 												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
 												});
@@ -1244,28 +1213,6 @@ export const { post_data, upload_files, client, handle_blob } = api_factory(
 	(...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: {

programming_language: TypeScript
filename: index.ts
change_type: ModificationType.MODIFY
diff: @@ -7,7 +7,6 @@ export {
 } from "./client.js";
 export type { SpaceStatus } from "./types.js";
 export {
-	normalise_file,
 	FileData,
 	upload,
 	get_fetchable_url_or_file,

programming_language: TypeScript
filename: upload.ts
change_type: ModificationType.MODIFY
diff: @@ -1,65 +1,5 @@
 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);
@@ -103,9 +43,12 @@ export async function upload(
 				} 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;
 						});
 					}
 

programming_language: TypeScript
filename: utils.py
change_type: ModificationType.MODIFY
diff: @@ -895,7 +895,7 @@ def get_type(schema: dict):
         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:
@@ -1010,6 +1010,12 @@ 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",

programming_language: Python
filename: blocks.py
change_type: ModificationType.MODIFY
diff: @@ -1325,7 +1325,7 @@ Received inputs:
                     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
@@ -1454,6 +1454,7 @@ Received outputs:
                     prediction_value,
                     block,  # type: ignore
                     postprocess=True,
+                    add_urls=True,
                 )
                 output.append(outputs_cached)
 

programming_language: Python
filename: base.py
change_type: ModificationType.MODIFY
diff: @@ -195,7 +195,12 @@ class Component(ComponentBase, Block):
         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)

programming_language: Python
filename: login_button.py
change_type: ModificationType.MODIFY
diff: @@ -88,7 +88,7 @@ class LoginButton(Button):
             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)

programming_language: Python
filename: data_classes.py
change_type: ModificationType.MODIFY
diff: @@ -168,6 +168,7 @@ class FileData(GradioModel):
     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):

programming_language: Python
filename: processing_utils.py
change_type: ModificationType.MODIFY
diff: @@ -236,15 +236,22 @@ def move_resource_to_block_cache(
     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):
@@ -259,6 +266,19 @@ def move_files_to_cache(data: Any, block: Component, postprocess: bool = False):
             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)):
@@ -267,6 +287,15 @@ def move_files_to_cache(data: Any, block: Component, postprocess: bool = False):
     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.

programming_language: Python
filename: route_utils.py
change_type: ModificationType.MODIFY
diff: @@ -2,6 +2,7 @@ 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
@@ -545,3 +546,8 @@ class GradioMultiPartParser:
         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)

programming_language: Python
filename: routes.py
change_type: ModificationType.MODIFY
diff: @@ -17,7 +17,6 @@ import mimetypes
 import os
 import posixpath
 import secrets
-import shutil
 import tempfile
 import threading
 import time
@@ -54,6 +53,7 @@ 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,
@@ -63,6 +63,7 @@ from gradio.route_utils import (  # noqa: F401
     GradioUploadFile,
     MultiPartException,
     Request,
+    move_uploaded_files_to_cache,
 )
 from gradio.state_holder import StateHolder
 from gradio.utils import (
@@ -115,12 +116,6 @@ 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()
 
 
@@ -315,14 +310,12 @@ class App(FastAPI):
         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,
@@ -359,13 +352,11 @@ class App(FastAPI):
         @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}")
@@ -580,6 +571,8 @@ class App(FastAPI):
                     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)])
@@ -588,6 +581,7 @@ class App(FastAPI):
             session_hash: str,
         ):
             blocks = app.get_blocks()
+            root_path = app.get_blocks().config.get("root", "")
 
             async def sse_stream(request: fastapi.Request):
                 try:
@@ -633,6 +627,7 @@ class App(FastAPI):
                                 "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[

programming_language: Python
filename: 05_frontend.md
change_type: ModificationType.MODIFY
diff: @@ -147,32 +147,24 @@ 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);
@@ -231,7 +223,7 @@ Here is how you can use them to create a user interface to upload and display PD
 
 ```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>
 

programming_language: Markdown
filename: 07_pdf-component-example.md
change_type: ModificationType.MODIFY
diff: @@ -117,7 +117,6 @@ That's ok.
     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 = "";
@@ -316,10 +315,6 @@ Add the following code to `Index.svelte`:
         });
     }
 
-    // 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){

programming_language: Markdown
filename: Index.svelte
change_type: ModificationType.MODIFY
diff: @@ -5,7 +5,7 @@
 	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 = "";
@@ -36,8 +36,6 @@
 	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;
 
@@ -52,9 +50,9 @@
 		}
 		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
 				}))
 			};

programming_language: TypeScript
filename: Index.svelte
change_type: ModificationType.MODIFY
diff: @@ -252,8 +252,7 @@
 				: 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;
@@ -280,8 +279,7 @@
 				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;

programming_language: TypeScript
filename: Index.svelte
change_type: ModificationType.MODIFY
diff: @@ -11,7 +11,6 @@
 	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[] = [];
@@ -26,7 +25,6 @@
 	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;
@@ -56,9 +54,7 @@
 		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";
 
@@ -152,7 +148,7 @@
 			{show_label}
 			{show_download_button}
 			{show_share_button}
-			value={_value}
+			{value}
 			{label}
 			{waveform_settings}
 			{waveform_options}
@@ -185,7 +181,7 @@
 			{label}
 			{show_label}
 			{show_download_button}
-			value={_value}
+			{value}
 			on:change={({ detail }) => (value = detail)}
 			on:stream={({ detail }) => {
 				value = detail;

programming_language: TypeScript
filename: Chatbot.test.ts
change_type: ModificationType.MODIFY
diff: @@ -116,6 +116,7 @@ describe("Chatbot", () => {
 			{
 				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
 				}
@@ -146,6 +147,7 @@ describe("Chatbot", () => {
 				{
 					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
 					}
@@ -174,6 +176,7 @@ describe("Chatbot", () => {
 			{
 				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
 				}
@@ -202,6 +205,7 @@ describe("Chatbot", () => {
 			{
 				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
 				}

programming_language: TypeScript
filename: Index.svelte
change_type: ModificationType.MODIFY
diff: @@ -9,7 +9,7 @@
 	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 = "";
@@ -64,7 +64,7 @@
 			return message;
 		}
 		return {
-			file: normalise_file(message?.file, root, proxy_url) as FileData,
+			file: message?.file as FileData,
 			alt_text: message?.alt_text
 		};
 	}

programming_language: TypeScript
filename: Index.svelte
change_type: ModificationType.MODIFY
diff: @@ -11,7 +11,7 @@
 	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";
@@ -28,7 +28,6 @@
 	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;
@@ -43,12 +42,11 @@
 	}>;
 	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;
@@ -79,7 +77,7 @@
 		<File
 			on:select={({ detail }) => gradio.dispatch("select", detail)}
 			selectable={_selectable}
-			value={_value}
+			{value}
 			{label}
 			{show_label}
 			{height}
@@ -89,7 +87,7 @@
 		<FileUpload
 			{label}
 			{show_label}
-			value={_value}
+			{value}
 			{file_count}
 			{file_types}
 			selectable={_selectable}

programming_language: TypeScript
filename: Gallery.test.ts
change_type: ModificationType.MODIFY
diff: @@ -35,8 +35,10 @@ describe("Gallery", () => {
 			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
 				}
 			]
 		});
@@ -58,8 +60,10 @@ describe("Gallery", () => {
 			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
 				}
 			]
 		});
@@ -69,8 +73,10 @@ describe("Gallery", () => {
 			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
 				}
 			]
 		});

programming_language: TypeScript
filename: Index.svelte
change_type: ModificationType.MODIFY
diff: @@ -16,7 +16,6 @@
 	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;
@@ -91,8 +90,6 @@
 			on:error={(e) => gradio.dispatch("error", e.detail)}
 			{label}
 			{show_label}
-			{root}
-			{proxy_url}
 			{columns}
 			{rows}
 			{height}

programming_language: TypeScript
filename: Gallery.svelte
change_type: ModificationType.MODIFY
diff: @@ -8,7 +8,7 @@
 	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";
@@ -18,8 +18,6 @@
 
 	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;
@@ -49,7 +47,7 @@
 		value == null
 			? null
 			: value.map((data) => ({
-					image: normalise_file(data.image, root, proxy_url) as FileData,
+					image: data.image as FileData,
 					caption: data.caption
 			  }));
 

programming_language: TypeScript
filename: Index.svelte
change_type: ModificationType.MODIFY
diff: @@ -18,7 +18,6 @@
 	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;
 
@@ -26,12 +25,11 @@
 	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;
@@ -64,8 +62,12 @@
 		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;
@@ -95,7 +97,7 @@
 			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}
@@ -107,7 +109,7 @@
 {:else}
 	<Block
 		{visible}
-		variant={_value === null ? "dashed" : "solid"}
+		variant={value === null ? "dashed" : "solid"}
 		border_mode={dragging ? "focus" : "base"}
 		padding={false}
 		{elem_id}

programming_language: TypeScript
filename: ImagePreview.svelte
change_type: ModificationType.MODIFY
diff: @@ -9,7 +9,7 @@
 	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;

programming_language: TypeScript
filename: ImageUploader.svelte
change_type: ModificationType.MODIFY
diff: @@ -7,7 +7,7 @@
 	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";
@@ -31,7 +31,7 @@
 	export let active_source: source_type = null;
 
 	function handle_upload({ detail }: CustomEvent<FileData>): void {
-		value = normalise_file(detail, root, null);
+		value = detail;
 		dispatch("upload");
 	}
 
@@ -56,8 +56,6 @@
 	$: 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;

programming_language: TypeScript
filename: Index.svelte
change_type: ModificationType.MODIFY
diff: @@ -13,7 +13,6 @@
 	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[] = [];
@@ -42,7 +41,6 @@
 		"clipboard",
 		"webcam"
 	];
-	export let proxy_url: string;
 	export let interactive: boolean;
 
 	export let brush: Brush;
@@ -120,7 +118,7 @@
 			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}
@@ -174,7 +172,6 @@
 			on:error
 			{brush}
 			{eraser}
-			{proxy_url}
 			changeable={attached_events.includes("change")}
 			i18n={gradio.i18n}
 			{transforms}

programming_language: TypeScript
filename: InteractiveImageEditor.svelte
change_type: ModificationType.MODIFY
diff: @@ -14,12 +14,7 @@
 
 <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";
@@ -36,7 +31,6 @@
 	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;
@@ -136,11 +130,7 @@
 				{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")}
@@ -161,9 +151,7 @@
 		{/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">

programming_language: TypeScript
filename: Sources.svelte
change_type: ModificationType.MODIFY
diff: @@ -12,7 +12,7 @@
 	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;

programming_language: TypeScript
filename: Index.svelte
change_type: ModificationType.MODIFY
diff: @@ -5,7 +5,7 @@
 </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";
@@ -20,7 +20,6 @@
 	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;
@@ -40,9 +39,6 @@
 	];
 	export let interactive: boolean;
 
-	let _value: null | FileData;
-	$: _value = normalise_file(value, root, proxy_url);
-
 	let dragging = false;
 </script>
 
@@ -67,7 +63,7 @@
 
 		{#if value}
 			<Model3D
-				value={_value}
+				{value}
 				i18n={gradio.i18n}
 				{clear_color}
 				{label}
@@ -108,7 +104,7 @@
 			{show_label}
 			{root}
 			{clear_color}
-			value={_value}
+			{value}
 			{camera_position}
 			{zoom_speed}
 			on:change={({ detail }) => (value = detail)}

programming_language: TypeScript
filename: Index.svelte
change_type: ModificationType.MODIFY
diff: @@ -15,14 +15,11 @@
 	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;
@@ -31,8 +28,7 @@
 	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;
@@ -40,8 +36,7 @@
 		clear: never;
 	}>;
 
-	$: url = _value?.url;
-	$: url, gradio.dispatch("change");
+	$: value, gradio.dispatch("change");
 
 	let dragging: boolean;
 </script>
@@ -65,7 +60,7 @@
 			{...loading_status}
 		/>
 		<ImagePreview
-			value={_value}
+			{value}
 			{label}
 			{show_label}
 			{show_download_button}
@@ -75,7 +70,7 @@
 {:else}
 	<Block
 		{visible}
-		variant={_value === null ? "dashed" : "solid"}
+		variant={value === null ? "dashed" : "solid"}
 		border_mode={dragging ? "focus" : "base"}
 		padding={false}
 		{elem_id}

programming_language: TypeScript
filename: ImageUploader.svelte
change_type: ModificationType.MODIFY
diff: @@ -4,7 +4,7 @@
 	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;
@@ -16,11 +16,10 @@
 	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;

programming_language: TypeScript
filename: Index.svelte
change_type: ModificationType.MODIFY
diff: @@ -3,7 +3,7 @@
 <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";
@@ -24,7 +24,6 @@
 		| ["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;
@@ -80,8 +79,8 @@
 
 	$: {
 		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;

programming_language: TypeScript
filename: Video.test.ts
change_type: ModificationType.MODIFY
diff: @@ -41,8 +41,10 @@ describe("Video", () => {
 			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",
@@ -56,7 +58,7 @@ describe("Video", () => {
 		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);
 	});
@@ -67,8 +69,10 @@ describe("Video", () => {
 			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",
@@ -88,8 +92,10 @@ describe("Video", () => {
 			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,
@@ -102,7 +108,7 @@ describe("Video", () => {
 		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"
 		);
 	});
 
@@ -113,8 +119,10 @@ describe("Video", () => {
 			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,
@@ -135,8 +143,10 @@ describe("Video", () => {
 			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,
@@ -158,8 +168,10 @@ describe("Video", () => {
 			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,
@@ -189,8 +201,10 @@ describe("Video", () => {
 			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,
@@ -205,8 +219,10 @@ describe("Video", () => {
 		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"));
@@ -215,10 +231,8 @@ describe("Video", () => {
 	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, {

programming_language: JavaScript
filename: test_components.py
change_type: ModificationType.MODIFY
diff: @@ -1537,6 +1537,7 @@ class TestVideo:
                 "mime_type": None,
                 "size": None,
                 "url": None,
+                "is_stream": False,
             },
             "subtitles": None,
         }
@@ -1548,6 +1549,7 @@ class TestVideo:
                 "mime_type": None,
                 "size": None,
                 "url": None,
+                "is_stream": False,
             },
             "subtitles": {
                 "path": "s1.srt",
@@ -1555,6 +1557,7 @@ class TestVideo:
                 "orig_name": None,
                 "size": None,
                 "url": None,
+                "is_stream": False,
             },
         }
         postprocessed_video["video"]["path"] = os.path.basename(
@@ -2251,6 +2254,7 @@ class TestGallery:
                     "mime_type": None,
                     "size": None,
                     "url": url,
+                    "is_stream": False,
                 },
                 "caption": None,
             }
@@ -2278,6 +2282,7 @@ class TestGallery:
                     "mime_type": None,
                     "size": None,
                     "url": None,
+                    "is_stream": False,
                 },
                 "caption": "foo_caption",
             },
@@ -2288,6 +2293,7 @@ class TestGallery:
                     "mime_type": None,
                     "size": None,
                     "url": None,
+                    "is_stream": False,
                 },
                 "caption": "bar_caption",
             },
@@ -2298,6 +2304,7 @@ class TestGallery:
                     "mime_type": None,
                     "size": None,
                     "url": None,
+                    "is_stream": False,
                 },
                 "caption": None,
             },
@@ -2308,6 +2315,7 @@ class TestGallery:
                     "mime_type": None,
                     "size": None,
                     "url": None,
+                    "is_stream": False,
                 },
                 "caption": None,
             },

programming_language: Python