29

I'm looking to save a file (in this case an image) located on another http web server using rails 5.2 active storage.

I have an object with a string parameter for source url. Then on a before_save I want to grab the remote image and save it.

Example: URL of an image http://www.example.com/image.jpg.

require 'open-uri'

class User < ApplicationRecord
  has_one_attached :avatar
  before_save :grab_image

  def grab_image
    #this indicates what I want to do but doesn't work
    downloaded_image = open("http://www.example.com/image.jpg")
    self.avatar.attach(downloaded_image)
  end

end

Thanks in advance for any suggestions.

Ed_
  • 1,676
  • 2
  • 13
  • 22

4 Answers4

53

Just found the answer to my own question. My first instinct was pretty close...

require 'open-uri'

class User < ApplicationRecord
  has_one_attached :avatar
  before_save :grab_image

  def grab_image
    downloaded_image = open("http://www.example.com/image.jpg")
    self.avatar.attach(io: downloaded_image  , filename: "foo.jpg")
  end

end

Update: please note comment below, "you have to be careful not to pass user input to open, it can execute arbitrary code, e.g. open("|date")"

Ed_
  • 1,676
  • 2
  • 13
  • 22
  • 1
    I tried to use this code, but i cannot store and image which came to and URL because i receive this error: **ActiveSupport::MessageVerifier::InvalidSignature**, but when i drive myself to the show view from my record, the image actually is stored. I got the URL from a param from my controller and connect with model with this method (the unique difference is i send between ( ) the param i got to the controller. Does any one knows why i have this problem? – William Romero Jun 29 '18 at 05:35
  • 2
    Came here after looking through github issues on activestorage. Can confirm at least in development that this is working great for me, on multiple filetypes of varying sizes. – Nick Schwaderer Nov 29 '18 at 13:38
  • 3
    you have to be careful not to pass user input to `open`, it can execute arbitrary code, e.g. `open("|date")` – Dorian Sep 02 '20 at 19:07
  • Note: Often the file you're trying to attach has required permission headers to make the request; if you came here looking for how to attach/open files with permission headers like I did, then combine this answer with the following thread and you will have your perfect answer. https://stackoverflow.com/questions/7478841/how-to-specify-http-request-header-in-openuri – Shawn Deprey Jan 28 '21 at 17:20
18

Like said in the comments, the use of open or URI.open is very dangerous, since it can not only access files but also process invocation by prefixing a pipe symbol (e.g. open("| ls")).

Kernel#open and URI.open enable not only file access but also process invocation by prefixing a pipe symbol (e.g., open("| ls")). So, it may lead to a serious security risk by using variable input to the argument of Kernel#open and URI.open. It would be better to use File.open, IO.popen or URI.parse#open explicitly.

Extracted from the Rubocop documentation: https://docs.rubocop.org/rubocop/1.8/cops_security.html#securityopen

So, a safer solution would be:

class User < ApplicationRecord
  has_one_attached :avatar
  before_save :grab_image

  def grab_image
    downloaded_image = URI.parse("http://www.example.com/image.jpg").open
    avatar.attach(io: downloaded_image, filename: "foo.jpg")
  end
end
Repolês
  • 1,563
  • 12
  • 14
7

using the down gem to avoid the security issues of using open-uri:

image = Down.download(image_url)
user.image.attach(io: image, filename: "image.jpg")
Dorian
  • 7,749
  • 4
  • 38
  • 57
4

The simplest way to do this without having to enter filename explicitly is:

url = URI.parse("https://your-url.com/abc.mp3")
filename = File.basename(url.path)
file = URI.open(url)
user = User.first
user.avatar.attach(io: file, filename: filename)

This automatically saves the avatar against that particular user object.

In case you are using a remote service like S3 the URL can be retrieved by:

user.avatar.service_url
Tim Kozak
  • 4,026
  • 39
  • 44
Pushp Raj Saurabh
  • 1,174
  • 12
  • 16
  • Just a note: `DEPRECATION WARNING: service_url is deprecated and will be removed from Rails 6.2 (use url instead)` – sebkkom Apr 19 '21 at 04:13