From 36e477750c015c3681c6ef3da35ad90ba9df9934 Mon Sep 17 00:00:00 2001
From: Robin Ward <robin.ward@gmail.com>
Date: Tue, 23 May 2017 13:31:20 -0400
Subject: [PATCH] FIX: Use same code path for downloading images

---
 lib/file_helper.rb                          | 14 ++------------
 lib/final_destination.rb                    |  7 ++++++-
 spec/components/file_helper_spec.rb         |  1 +
 spec/components/final_destination_spec.rb   | 17 +++++++++++++++++
 spec/controllers/uploads_controller_spec.rb |  4 ++--
 spec/jobs/pull_hotlinked_images_spec.rb     |  1 +
 6 files changed, 29 insertions(+), 15 deletions(-)

diff --git a/lib/file_helper.rb b/lib/file_helper.rb
index e9e6ab54800..1cdddd1b425 100644
--- a/lib/file_helper.rb
+++ b/lib/file_helper.rb
@@ -1,4 +1,5 @@
 require "open-uri"
+require "final_destination"
 
 class FileHelper
 
@@ -10,7 +11,7 @@ class FileHelper
     url = "https:" + url if url.start_with?("//")
     raise Discourse::InvalidParameters.new(:url) unless url =~ /^https?:\/\//
 
-    uri = parse_url(url)
+    uri = FinalDestination.new(url).resolve
     extension = File.extname(uri.path)
     tmp = Tempfile.new([tmp_file_name, extension])
 
@@ -36,15 +37,4 @@ class FileHelper
     @@images_regexp ||= /\.(#{images.to_a.join("|")})$/i
   end
 
-  # HACK to support underscores in URLs
-  # cf. http://stackoverflow.com/a/18938253/11983
-  def self.parse_url(url)
-    URI.parse(url)
-  rescue URI::InvalidURIError
-    host = url.match(".+\:\/\/([^\/]+)")[1]
-    uri = URI.parse(url.sub(host, 'valid-host'))
-    uri.instance_variable_set("@host", host)
-    uri
-  end
-
 end
diff --git a/lib/final_destination.rb b/lib/final_destination.rb
index bf581c91ae4..4e0f4229d87 100644
--- a/lib/final_destination.rb
+++ b/lib/final_destination.rb
@@ -62,7 +62,7 @@ class FinalDestination
   end
 
   def validate_uri
-    validate_uri_format && is_public?
+    validate_uri_format && is_dest_valid?
   end
 
   def validate_uri_format
@@ -75,6 +75,10 @@ class FinalDestination
     (IPAddr.new(@uri.hostname) rescue nil).nil?
   end
 
+  def is_dest_valid?
+    is_public?
+  end
+
   def is_public?
     return false unless @uri && @uri.host
 
@@ -116,4 +120,5 @@ class FinalDestination
     end
     header[1] if header
   end
+
 end
diff --git a/spec/components/file_helper_spec.rb b/spec/components/file_helper_spec.rb
index 49cdbdb5753..7b10d1a510f 100644
--- a/spec/components/file_helper_spec.rb
+++ b/spec/components/file_helper_spec.rb
@@ -7,6 +7,7 @@ describe FileHelper do
   let(:png) { Base64.decode64("R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==") }
 
   before do
+    Excon.stub({ method: :head, hostname: 'eviltrout.com' }, {})
     stub_request(:get, url).to_return(body: png)
   end
 
diff --git a/spec/components/final_destination_spec.rb b/spec/components/final_destination_spec.rb
index 4b0b8b9fd2e..73611a5c45a 100644
--- a/spec/components/final_destination_spec.rb
+++ b/spec/components/final_destination_spec.rb
@@ -10,9 +10,12 @@ describe FinalDestination do
         when 'eviltrout.com' then '52.84.143.152'
         when 'codinghorror.com' then '91.146.108.148'
         when 'discourse.org' then '104.25.152.10'
+        when 'some_thing.example.com' then '104.25.152.10'
         when 'private-host.com' then '192.168.10.1'
         when 'internal-ipv6.com' then '2001:abc:de:01:3:3d0:6a65:c2bf'
         else
+          as_ip = IPAddr.new(host) rescue nil
+          raise "couldn't lookup #{host}" if as_ip.nil?
           host
         end
       end
@@ -54,6 +57,20 @@ describe FinalDestination do
         expect(final.redirected?).to eq(false)
         expect(final.status).to eq(:resolved)
       end
+
+    end
+
+    context "underscores in URLs" do
+      before do
+        Excon.stub({ method: :head, hostname: 'some_thing.example.com' }, doc_response)
+      end
+
+      it "doesn't raise errors with underscores in urls" do
+        final = FinalDestination.new('https://some_thing.example.com', opts)
+        expect(final.resolve.to_s).to eq('https://some_thing.example.com')
+        expect(final.redirected?).to eq(false)
+        expect(final.status).to eq(:resolved)
+      end
     end
 
     context "with a couple of redirects" do
diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb
index f5fbec08d39..daf983ed46c 100644
--- a/spec/controllers/uploads_controller_spec.rb
+++ b/spec/controllers/uploads_controller_spec.rb
@@ -74,7 +74,7 @@ describe UploadsController do
         controller.stubs(:is_api?).returns(true)
 
         Jobs.expects(:enqueue).with(:create_avatar_thumbnails, anything)
-
+        Excon.stub({ method: :head, hostname: 'example.com' }, {})
         stub_request(:get, "http://example.com/image.png").to_return(body: File.read('spec/fixtures/images/logo.png'))
 
         xhr :post, :create, url: 'http://example.com/image.png', type: "avatar", synchronous: true
@@ -183,7 +183,7 @@ describe UploadsController do
 
     it "handles file without extension" do
       SiteSetting.authorized_extensions = "*"
-      upload = Fabricate(:upload, original_filename: "image_file", sha1: sha)
+      Fabricate(:upload, original_filename: "image_file", sha1: sha)
       controller.stubs(:render)
       controller.expects(:send_file)
 
diff --git a/spec/jobs/pull_hotlinked_images_spec.rb b/spec/jobs/pull_hotlinked_images_spec.rb
index bdae4fd3d4d..f0814526499 100644
--- a/spec/jobs/pull_hotlinked_images_spec.rb
+++ b/spec/jobs/pull_hotlinked_images_spec.rb
@@ -6,6 +6,7 @@ describe Jobs::PullHotlinkedImages do
   before do
     png = Base64.decode64("R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==")
     stub_request(:get, "http://wiki.mozilla.org/images/2/2e/Longcat1.png").to_return(body: png)
+    Excon.stub({ method: :head, hostname: 'wiki.mozilla.org' }, {})
     SiteSetting.download_remote_images_to_local = true
     FastImage.expects(:size).returns([100, 100]).at_least_once
   end