{"record":{"id":"166c677651f09272","repo":"gitroomhq/postiz-app","slug":"could-not-determine-the-media-size-for-upload","errorCode":null,"errorMessage":"'Could not determine the media size for upload'","messagePattern":"'Could not determine the media size for upload'","errorType":"exception","errorClass":"BadBody","httpStatus":null,"severity":"error","filePath":"libraries/nestjs-libraries/src/integrations/social.abstract.ts","lineNumber":257,"sourceCode":"  // Resolves the total byte size of the media without loading it into memory:\n  // a HEAD request for remote URLs, statSync for local files.\n  protected async mediaSize(path: string, identifier = ''): Promise<number> {\n    if (path.indexOf('http') === 0) {\n      // the media path is user-influenced, keep the SSRF-safe dispatcher that\n      // this.fetch applies to every other outbound request. identity encoding\n      // so content-length matches the bytes a later GET actually streams\n      // (fetch transparently decompresses encoded bodies).\n      setHeartbeatDetails(`media size ${stripQuery(path)}`);\n      const head = await fetch(path, {\n        method: 'HEAD',\n        headers: { 'accept-encoding': 'identity' },\n        dispatcher: getSsrfSafeDispatcher(),\n      } as any);\n      const length = Number(head.headers.get('content-length'));\n      // A failed HEAD can still carry a content-length (of the error body),\n      // and a zero/NaN size would poison chunk-count math downstream.\n      if (!head.ok || !Number.isFinite(length) || length <= 0) {\n        throw new BadBody(\n          identifier,\n          '{}',\n          Buffer.from('{}'),\n          'Could not determine the media size for upload'\n        );\n      }\n      return length;\n    }\n\n    return statSync(path).size;\n  }\n\n  // Reads a single [start, end] byte range into memory. Used by providers whose\n  // chunked-upload APIs require a Buffer per segment: only one small chunk is\n  // resident at a time, never the whole file.\n  protected async mediaChunk(\n    path: string,\n    start: number,","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/gitroomhq/postiz-app/blob/0f1647f7491a217d43eb5ae7a480484bdf0aff3e/libraries/nestjs-libraries/src/integrations/social.abstract.ts#L239-L275","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nposts: [{ integration: { id }, image: ['https://cdn.example.com/expired-signed.jpg'] }]\n// after\nposts: [{ integration: { id }, image: ['https://storage.example.com/media/123.jpg'] }] // stable URL, HEAD returns content-length\n// verify: curl -I <url> -> 200 with content-length > 0","handlingStrategy":"validation","validationCode":"const head = await fetch(mediaUrl, { method: 'HEAD' });\nconst len = Number(head.headers.get('content-length'));\nconst ok = head.ok && Number.isFinite(len) && len > 0;\nif (ok) await submitPostWithMedia(orgId, body); else await rehostMedia(mediaUrl);","typeGuard":null,"tryCatchPattern":"try {\n  await createPost(orgId, body);\n} catch (e) {\n  if (/Could not determine the media size/.test(String(e?.response?.message ?? e))) {\n    body.posts = body.posts.map((p) => ({ ...p, image: p.image.map(rehostToStableUrl) }));\n    await createPost(orgId, body);\n  } else throw e;\n}","preventionTips":["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"],"tags":["integrations","media","upload","http-head","content-length"],"backgroundTag":"media-url-content-length-missing","analyzedSha":"0f1647f7491a217d43eb5ae7a480484bdf0aff3e","analyzedAt":"2026-08-27T12:09:55.020Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}