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
- Use a direct, publicly accessible media URL that returns a valid content-length on HEAD
- Re-generate expired presigned URLs before submitting the post
- If the host rejects HEAD, host the file where HEAD works or download it first and upload the bytes directly
- 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
- Verify with curl -I that media URLs return 200 with a positive content-length
- Avoid expiring presigned URLs in scheduled posts far in the future
- Re-upload media to your own storage instead of hotlinking third-party hosts
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
- Could not determine the video size for the YouTube upload
- Organization not found
- Integration not allowed
- Invalid body
- Invalid integration
AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27).
Data as JSON: /api/errors/166c677651f09272.
Report an issue: GitHub.