{"record":{"id":"34df6bf44b27da3d","repo":"instructure/canvas-lms","slug":"unable-to-determine-filename-from-content-disposition-header","errorCode":null,"errorMessage":"Unable to determine filename from Content-Disposition header","messagePattern":"Unable to determine filename from Content-Disposition header","errorType":"exception","errorClass":"Common::InvalidResultError","httpStatus":null,"severity":"error","filePath":"app/services/page_views/fetch_batch_result_service.rb","lineNumber":53,"sourceCode":"\n    private\n\n    def determine_result_format(response)\n      raise InvalidResultError, \"Missing Content-Type header in response.\" unless response.header[\"Content-Type\"]\n\n      # strip any parameters (encoding for example) from the Content-Type\n      content_type = response.header[\"Content-Type\"].split(\";\").first.strip\n      raise Common::InvalidResultError, \"Result format is invalid: #{content_type}\" unless Common::CONTENT_TYPE_MAPPINGS[content_type]\n\n      Common::CONTENT_TYPE_MAPPINGS[content_type]\n    end\n\n    def determine_filename(response)\n      content_disposition = response.header[\"Content-Disposition\"]\n      if content_disposition && content_disposition =~ /filename=\"?([^\";]+)\"?/\n        Regexp.last_match(1)\n      else\n        raise Common::InvalidResultError, \"Unable to determine filename from Content-Disposition header\"\n      end\n    end\n  end\nend\n","sourceCodeStart":35,"sourceCodeEnd":58,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/page_views/fetch_batch_result_service.rb#L35-L58","documentation":"PageViews::FetchBatchResultService#determine_filename raises Common::InvalidResultError when the Content-Disposition header is missing or contains no filename matching /filename=\"?([^\";]+)\"?/. The service names the downloaded batch file from this header, so it refuses to guess.","triggerScenarios":"Response has no Content-Disposition header, or it lacks a filename parameter (e.g. 'attachment' alone, or an inline disposition).","commonSituations":"Proxy stripping Content-Disposition; API version change altering header formatting; error responses that skip attachment headers; CDN configurations that rewrite dispositions.","solutions":["Inspect response.header['Content-Disposition'] to see what the server sends","Fall back to a locally generated filename when the header is absent (wrap determine_filename path in a rescue)","Check upstream/proxy settings that drop or rewrite Content-Disposition","Confirm you are fetching the result download URL, not the status URL"],"exampleFix":"// before\nfilename = service.call(response) # raises when header missing\n// after\nfilename = service.call(response)\nrescue PageViews::Common::InvalidResultError => e\n  raise unless e.message.include?('filename')\n  filename = \"page_views_batch_#{Time.now.to_i}.csv\"","handlingStrategy":"fallback","validationCode":"cd = response.header['Content-Disposition'].to_s\nhas_filename = cd.match?(/filename=\"?([^\";]+)\"?/)","typeGuard":null,"tryCatchPattern":"begin\n  filename = service.call(response)\nrescue PageViews::Common::InvalidResultError => e\n  raise unless e.message.include?('filename')\n  filename = \"page_views_#{Time.now.strftime('%Y%m%d%H%M%S')}.csv\"\nend","preventionTips":["Confirm the download URL returns an attachment disposition","Generate your own fallback filename from query metadata","Check CDN/proxy handling of Content-Disposition","Log the raw header when the parse fails"],"tags":["http","headers","response-parsing"],"backgroundTag":"unexpected-response-shape","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}