gitroomhq/postiz-app · error · BadBody

'Could not determine the media size for upload'

Error message

'Could not determine the media size for upload'

What it means

SocialAbstract.mediaSize issues a HEAD request (via an SSRF-safe dispatcher) to determine the content-length of media before chunked upload. If the HEAD response is not ok, or content-length is missing/zero/NaN, it throws BadBody — a failed HEAD carrying the error body's length, or 0, would corrupt downstream chunk math.

Source

Thrown at libraries/nestjs-libraries/src/integrations/social.abstract.ts:257

  // Resolves the total byte size of the media without loading it into memory:
  // a HEAD request for remote URLs, statSync for local files.
  protected async mediaSize(path: string, identifier = ''): Promise<number> {
    if (path.indexOf('http') === 0) {
      // the media path is user-influenced, keep the SSRF-safe dispatcher that
      // this.fetch applies to every other outbound request. identity encoding
      // so content-length matches the bytes a later GET actually streams
      // (fetch transparently decompresses encoded bodies).
      setHeartbeatDetails(`media size ${stripQuery(path)}`);
      const head = await fetch(path, {
        method: 'HEAD',
        headers: { 'accept-encoding': 'identity' },
        dispatcher: getSsrfSafeDispatcher(),
      } as any);
      const length = Number(head.headers.get('content-length'));
      // A failed HEAD can still carry a content-length (of the error body),
      // and a zero/NaN size would poison chunk-count math downstream.
      if (!head.ok || !Number.isFinite(length) || length <= 0) {
        throw new BadBody(
          identifier,
          '{}',
          Buffer.from('{}'),
          'Could not determine the media size for upload'
        );
      }
      return length;
    }

    return statSync(path).size;
  }

  // Reads a single [start, end] byte range into memory. Used by providers whose
  // chunked-upload APIs require a Buffer per segment: only one small chunk is
  // resident at a time, never the whole file.
  protected async mediaChunk(
    path: string,
    start: number,

View on GitHub (pinned to 0f1647f749)

Solutions

  1. Use a direct, publicly accessible media URL that returns a valid content-length on HEAD
  2. Re-generate expired presigned URLs before submitting the post
  3. If the host rejects HEAD, host the file where HEAD works or download it first and upload the bytes directly
  4. For servers without content-length, pre-compute the size and (if extending the code) pass it instead of relying on HEAD

Example fix

// before
posts: [{ integration: { id }, image: ['https://cdn.example.com/expired-signed.jpg'] }]
// after
posts: [{ integration: { id }, image: ['https://storage.example.com/media/123.jpg'] }] // stable URL, HEAD returns content-length
// verify: curl -I <url> -> 200 with content-length > 0
Defensive patterns

Strategy: validation

Validate before calling

const head = await fetch(mediaUrl, { method: 'HEAD' });
const len = Number(head.headers.get('content-length'));
const ok = head.ok && Number.isFinite(len) && len > 0;
if (ok) await submitPostWithMedia(orgId, body); else await rehostMedia(mediaUrl);

Try / catch

try {
  await createPost(orgId, body);
} catch (e) {
  if (/Could not determine the media size/.test(String(e?.response?.message ?? e))) {
    body.posts = body.posts.map((p) => ({ ...p, image: p.image.map(rehostToStableUrl) }));
    await createPost(orgId, body);
  } else throw e;
}

Prevention

When it happens

Trigger: Uploading media by URL where the URL returns 403/404, requires auth/redirects without exposing content-length, responds with chunked encoding (no content-length), or where the value parses to 0/NaN.

Common situations: Signed/expiring CDN URLs (S3 presigned, Google Drive) that have expired; hotlink-protected hosts rejecting HEAD; servers responding only to GET; media URL deleted; proxies stripping content-length.

Related errors


AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27). Data as JSON: /api/errors/166c677651f09272. Report an issue: GitHub.