Skip to content

DownloadFilter throws "Guid can't be empty" -> HTTP 400 on /Items/{id}/Download for all local items #187

Description

@nkt

Summary

The plugin's global DownloadFilter (registered on Jellyfin's GetDownload action) throws ArgumentException: Guid can't be empty (Parameter 'id') for every /Items/{id}/Download request, returning HTTP 400. This breaks the native "Download" feature for all local (non-Stremio) content — audio, movies, and episodes — not just Gelato items.

Environment

  • Gelato 0.26.15.1 (latest)
  • Jellyfin 10.11.11 (Linux, native)

Repro

curl -i -H 'Authorization: MediaBrowser Token="<API_KEY>"' \
  "http://<jellyfin>:8096/Items/<any-local-item-id>/Download"
# HTTP/1.1 400 Bad Request
# Error processing request.

Confirmed identical 400 for Movie, Episode and Audio items that play and stream fine (/Audio/{id}/stream?static=true -> 200). Disabling the Gelato plugin makes /Download work again.

Server log

[ERR] Error processing request. URL "GET" "/Items/<id>/Download".
System.ArgumentException: Guid can't be empty (Parameter 'id')
   at Gelato.Filters.DownloadFilter.OnActionExecutionAsync(ActionExecutingContext ctx, ActionExecutionDelegate next)
   at Jellyfin.Api.Middleware.ExceptionMiddleware.Invoke(HttpContext context)

Likely cause

In Filters/DownloadFilter.cs:

var mediaSourceIdStr = ctx.HttpContext.Items["MediaSourceId"] as string;
var hasMediaSourceId = Guid.TryParse(mediaSourceIdStr, out var mediaSourceId);
var item = library.GetItemById<Video>(hasMediaSourceId ? mediaSourceId : guid, user);

When MediaSourceId is present but parses to an empty GUID (00000000-...), hasMediaSourceId is true while mediaSourceId is Guid.Empty, so GetItemById(Guid.Empty, ...) is called and Jellyfin throws ArgumentException. The exception is uncaught, so the filter aborts the request with a 400 instead of letting a normal (non-Stremio) download fall through to next().

Suggested fix

  • Only use mediaSourceId when it is non-empty: hasMediaSourceId = Guid.TryParse(...) && mediaSourceId != Guid.Empty;
  • And/or wrap the resolve in try/catch (or early-return) so a non-Gelato item always falls through to await next(); — the filter should never break core downloads for items it doesn't own.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions