{"record":{"id":"c2147b9ca302eea3","repo":"antiwork/gumroad","slug":"one-of-the-uploaded-files-is-not-a-jpg-png-or-pd","errorCode":null,"errorMessage":"One of the uploaded files is not a JPG, PNG, or PDF.","messagePattern":"One of the uploaded files is not a JPG, PNG, or PDF\\.","errorType":"exception","errorClass":"DisputeEvidence::MergeCustomerCommunicationFilesService::MergeError","httpStatus":null,"severity":"error","filePath":"app/services/dispute_evidence/merge_customer_communication_files_service.rb","lineNumber":50,"sourceCode":"  def self.perform(blobs:, max_size:)\n    new(blobs:, max_size:).perform\n  end\n\n  def initialize(blobs:, max_size:)\n    @blobs = blobs\n    @max_size = max_size\n  end\n\n  # Returns a new application/pdf ActiveStorage::Blob. The input blobs are left alone: the\n  # caller purges them once the submission has actually been persisted.\n  def perform\n    if blobs.size > DisputeEvidence::MAX_CUSTOMER_COMMUNICATION_FILES\n      raise MergeError, \"You can attach up to #{DisputeEvidence::MAX_CUSTOMER_COMMUNICATION_FILES} files.\"\n    end\n    # The merged output is always application/pdf, so the model's content-type validation never\n    # sees the inputs. Everything below hands seller-supplied bytes to ImageMagick and qpdf.\n    unless blobs.all? { _1.content_type.in?(DisputeEvidence::ALLOWED_FILE_CONTENT_TYPES) }\n      raise MergeError, UNSUPPORTED_FILE_TYPE_MESSAGE\n    end\n    raise FilesTooLargeError, FILE_TOO_LARGE_MESSAGE if blobs.any? { _1.byte_size > max_size }\n\n    downloaded_files = download_blobs\n    merged_path = merge_within_size_budget(downloaded_files)\n\n    File.open(merged_path) do |file|\n      ActiveStorage::Blob.create_and_upload!(io: file, filename: MERGED_FILENAME, content_type: \"application/pdf\")\n    end\n  ensure\n    downloaded_files&.each { _1[:tempfile].close! }\n    File.unlink(merged_path) if merged_path && File.exist?(merged_path)\n  end\n\n  private\n    attr_reader :blobs, :max_size\n\n    def download_blobs","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/services/dispute_evidence/merge_customer_communication_files_service.rb#L32-L68","documentation":"Every input blob's content_type must be in DisputeEvidence::ALLOWED_FILE_CONTENT_TYPES (image/jpeg, image/png, application/pdf) or MergeError is raised. Because the merged output is always application/pdf, the model-level content-type validation never inspects the raw inputs — this explicit check is the only gate before seller-supplied bytes reach ImageMagick and qpdf.","triggerScenarios":"Attaching a HEIC photo (iPhone default for 'Photos' exports), a WEBP screenshot (many modern capture tools), GIF, TIFF, or any file whose recorded ActiveStorage content_type is outside the allowlist. The previously attached blob also counts as an input, so an older disallowed attachment re-triggers it on a later save.","commonSituations":"iPhone photos dragged out of the Photos app stay HEIC; screenshot/browser tools increasingly emit WEBP; files renamed '.jpg' without conversion keep their original recorded content type.","solutions":["Convert HEIC/WEBP/GIF/TIFF to plain JPG or PNG with a real converter before uploading","Re-export documents as PDF","If a previously saved attachment is the offender, remove it and submit a converted version"],"exampleFix":"# before\n# photo.HEIC uploaded directly from iPhone Photos\nMergeCustomerCommunicationFilesService.perform(blobs: [heic_blob], max_size: budget)\n# => MergeError: One of the uploaded files is not a JPG, PNG, or PDF.\n\n# after\n# 'sips -s format jpeg photo.HEIC --out photo.jpg' (macOS) or any converter, then upload","handlingStrategy":"validation","validationCode":"unless blobs.all? { _1.content_type.in?(DisputeEvidence::ALLOWED_FILE_CONTENT_TYPES) }\n  raise 'only JPG, PNG and PDF are accepted'\nend","typeGuard":"def all_customer_communication_files_allowed?(blobs)\n  blobs.all? { _1.content_type.in?(DisputeEvidence::ALLOWED_FILE_CONTENT_TYPES) }\nend","tryCatchPattern":"begin\n  DisputeEvidence::MergeCustomerCommunicationFilesService.perform(blobs:, max_size: budget)\nrescue DisputeEvidence::MergeCustomerCommunicationFilesService::MergeError => e\n  redirect_back alert: e.message\nend","preventionTips":["Set accept='.jpg,.jpeg,.png,.application/pdf' on the upload input so the picker filters most cases","Convert HEIC/WEBP at the client or server edge; never rely on file extensions — ActiveStorage records the declared content type","Remember the existing attached blob is re-validated on every later save"],"tags":["ruby","rails","file-type","stripe","dispute-evidence"],"backgroundTag":"unsupported-file-type","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}