How to save an image from a url with rails active storage?
Asked Answered
N

4

31

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.

Newmodel answered 25/6, 2018 at 16:26 Comment(0)
N
51

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")"

Newmodel answered 25/6, 2018 at 16:52 Comment(4)
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?Backbend
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.Cohberg
you have to be careful not to pass user input to open, it can execute arbitrary code, e.g. open("|date")Crean
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. #7479341Couchant
M
27

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
Mornings answered 5/4, 2021 at 21:12 Comment(1)
Since the currently accepted answer is extremely unsafe and bad advice, I've flagged it for review so that this safer solution is marked as the accepted answer instead.Ridenour
C
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")
Crean answered 2/9, 2020 at 19:18 Comment(0)
C
5

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
Cinerarium answered 15/7, 2020 at 18:29 Comment(2)
Just a note: DEPRECATION WARNING: service_url is deprecated and will be removed from Rails 6.2 (use url instead)Harbin
This solution works, though a substitution for URI.open(url) would be great! (security flaw)Alrzc

© 2022 - 2024 — McMap. All rights reserved.