@@ -72,6 +72,14 @@ def url_for_direct_upload(key, **options)
7272 instrument :url , key : key do |payload |
7373 options = { :resource_type => resource_type ( nil , key ) } . merge ( @options . merge ( options . symbolize_keys ) )
7474 options [ :public_id ] = public_id_internal ( key )
75+ # Provide file format for raw files, since js client does not include original file name.
76+ #
77+ # When the file is uploaded from the server, the request includes original filename. That allows Cloudinary
78+ # to identify file extension and append it to the public id of the file (raw files include file extension
79+ # in their public id, opposed to transformable assets (images/video) that use only basename). When uploading
80+ # through direct upload (client side js), filename is missing, and that leads to inconsistent/broken URLs.
81+ # To avoid that, we explicitly pass file format in options.
82+ options [ :format ] = ext_for_file ( key ) if options [ :resource_type ] == "raw"
7583 options [ :context ] = { active_storage_key : key }
7684 options . delete ( :file )
7785 payload [ :url ] = api_uri ( "upload" , options )
@@ -174,7 +182,7 @@ def api_uri(action, options)
174182 # @param [string] content_type The content type of the file.
175183 #
176184 # @return [string] The extension of the filename.
177- def ext_for_file ( key , filename , content_type )
185+ def ext_for_file ( key , filename = nil , content_type = nil )
178186 if filename . blank?
179187 options = key . respond_to? ( :attributes ) ? key . attributes : { }
180188 filename = ActiveStorage ::Filename . new ( options [ :filename ] ) if options . has_key? ( :filename )
@@ -200,6 +208,8 @@ def public_id_internal(key)
200208 end
201209
202210 def content_type_to_resource_type ( content_type )
211+ return 'image' if content_type . nil?
212+
203213 type , subtype = content_type . split ( '/' )
204214 case type
205215 when 'video' , 'audio'
0 commit comments