{"record":{"id":"392a8759efe71632","repo":"arsduo/koala","slug":"unable-to-determine-mime-type-for-uploadableio","errorCode":null,"errorMessage":"Unable to determine MIME type for UploadableIO","messagePattern":"Unable to determine MIME type for UploadableIO","errorType":"validation","errorClass":"Koala::KoalaError","httpStatus":null,"severity":"error","filePath":"lib/koala/http_service/uploadable_io.rb","lineNumber":18,"sourceCode":"require \"tempfile\"\n\nmodule Koala\n  module HTTPService\n    class UploadableIO\n      attr_reader :io_or_path, :content_type, :filename\n\n      def initialize(io_or_path_or_mixed, content_type = nil, filename = nil)\n        # see if we got the right inputs\n        parse_init_mixed_param io_or_path_or_mixed, content_type\n\n        # filename is used in the Ads API\n        # if it's provided, take precedence over the detected filename\n        # otherwise, fall back to a dummy name\n        @filename = filename || @filename || \"koala-io-file.dum\"\n\n        raise KoalaError.new(\"Invalid arguments to initialize an UploadableIO\") unless @io_or_path\n        raise KoalaError.new(\"Unable to determine MIME type for UploadableIO\") if !@content_type\n      end\n\n      def to_upload_io\n        UploadIO.new(@io_or_path, @content_type, @filename)\n      end\n\n      def to_file\n        @io_or_path.is_a?(String) ? File.open(@io_or_path) : @io_or_path\n      end\n\n      def self.binary_content?(content)\n        content.is_a?(UploadableIO) || DETECTION_STRATEGIES.detect {|method| send(method, content)}\n      end\n\n      private\n      DETECTION_STRATEGIES = [\n        :sinatra_param?,\n        :rails_3_param?,","sourceCodeStart":1,"sourceCodeEnd":36,"githubUrl":"https://github.com/arsduo/koala/blob/47d052063ef8b5644fb59e279da0b52687999f55/lib/koala/http_service/uploadable_io.rb#L1-L36","documentation":"UploadableIO raises Koala::KoalaError ('Unable to determine MIME type for UploadableIO') when @content_type is still nil after parsing (lib/koala/http_service/uploadable_io.rb:18). The content type comes from the explicit second constructor argument, else from the mime-types gem if installed (use_mime_module), else from a small built-in extension table (jpg/jpeg, png, gif, plus a list of video extensions). IO objects such as StringIO get no sniffing at all — parse_io only accepts an explicitly passed content_type.","triggerScenarios":"api.put_picture(StringIO.new(data)) with no content_type argument; a file path with a missing or unrecognized extension (/tmp/xyz123, photo.tiff, photo.webp) when the mime-types gem is not installed; an uploaded param whose :type value is blank.","commonSituations":"Rails Tempfiles whose random paths have no extension; newer formats (.heic, .webp) outside the built-in table; apps that never added the optional mime-types gem; StringIO sources built from downloaded bytes or generated images.","solutions":["Pass the content type explicitly as the second argument: api.put_picture(io, 'image/png') — the documented form is put_picture(file, content_type, args, target_id)","Use a source with a recognized extension (.jpg/.png/.gif, or common video extensions) so the fallback table resolves","Add the mime-types gem to the Gemfile — Koala's detection strategy uses it for any extension","Preserve extensions when materializing temp files (write to 'upload.jpg', not a random name)"],"exampleFix":"# before\napi.put_picture(StringIO.new(png_bytes)) # => KoalaError: Unable to determine MIME type for UploadableIO\n\n# after\napi.put_picture(StringIO.new(png_bytes), 'image/png')\n# or rely on a recognized extension:\napi.put_picture('/tmp/avatar.jpg', {message: 'new profile pic'})","handlingStrategy":"validation","validationCode":"def media_content_type(source, fallback = 'application/octet-stream')\n  return source.content_type if source.respond_to?(:content_type) # Rails uploaded file\n  return fallback if source.respond_to?(:read)                   # IO objects cannot be sniffed — be explicit\n  ext = File.extname(source).downcase.sub('.', '')\n  {'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif',\n   'mp4' => 'video/mp4', 'mov' => 'video/quicktime'}.fetch(ext, fallback)\nend\n\napi.put_picture(file, media_content_type(file))","typeGuard":null,"tryCatchPattern":"begin\n  api.put_picture(media, explicit_type)\nrescue Koala::KoalaError => e\n  # local failure: type undetectable — fall back to an explicit default instead of retrying blind\n  api.put_picture(media, 'application/octet-stream')\nend","preventionTips":["Always pass content_type explicitly when the source is StringIO/IO — there is no filename to sniff","Add mime-types to the Gemfile so extension detection covers more than the tiny built-in table","Preserve file extensions when writing Tempfiles (write to 'upload.jpg', not a random name)","Restrict uploads to formats Facebook handles (jpg, png, gif, mp4, mov, ...) and validate the extension server-side"],"tags":["file-upload","mime-type","uploadable-io","facebook-graph-api"],"backgroundTag":"unknown-mime-type","analyzedSha":"47d052063ef8b5644fb59e279da0b52687999f55","analyzedAt":"2026-08-23T10:19:03.891Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}