gottfrois/link_thumbnailer

View on GitHub
lib/link_thumbnailer/processor.rb

Summary

Maintainability
B
4 hrs
Test Coverage
# frozen_string_literal: true

require 'delegate'
require 'uri'
require 'net/http/persistent'

module LinkThumbnailer
  class Processor < ::SimpleDelegator

    attr_accessor :url
    attr_reader   :config, :http, :redirect_count

    def initialize
      @config = ::LinkThumbnailer.page.config
      @http   = ::Net::HTTP::Persistent.new

      super(config)
    end

    def start(url)
      result = call(url)
      shutdown
      result
    end

    def call(url = '', redirect_count = 0, headers = {})
      self.url        = url
      @redirect_count = redirect_count

      raise ::LinkThumbnailer::RedirectLimit if too_many_redirections?

      with_valid_url do
        set_http_headers(headers)
        set_http_options
        perform_request
      end
    rescue ::Net::HTTPExceptions, ::SocketError, ::Timeout::Error, ::Net::HTTP::Persistent::Error => e
      raise ::LinkThumbnailer::HTTPError.new(e.message)
    end

    private

    def shutdown
      http.shutdown
    end

    def with_valid_url
      raise ::LinkThumbnailer::BadUriFormat unless valid_url_format?
      yield if block_given?
    end

    def set_http_headers(headers = {})
      headers.each { |k, v| http.headers[k] = v }
      http.override_headers['User-Agent'] = user_agent
      config.http_override_headers.each { |k, v| http.override_headers[k] = v }
    end

    def set_http_options
      http.verify_mode  = ::OpenSSL::SSL::VERIFY_NONE unless ssl_required?
      http.open_timeout = http_open_timeout
      http.read_timeout = http_read_timeout
      http.proxy = :ENV
    end

    def perform_request
      response          = request_in_chunks
      headers           = {}
      headers['Cookie'] = response['Set-Cookie'] if response['Set-Cookie'].present?

      raise ::LinkThumbnailer::FormatNotSupported.new(response['Content-Type']) unless valid_response_format?(response)

      case response
      when ::Net::HTTPSuccess
        Response.new(response).body
      when ::Net::HTTPRedirection
        call(
          resolve_relative_url(response['location'].to_s),
          redirect_count + 1,
          headers
        )
      else
        response.error!
      end
    end

    def request_in_chunks
      body     = String.new
      response = http.request(url) do |resp|
        raise ::LinkThumbnailer::DownloadSizeLimit if too_big_download_size?(resp.content_length)
        resp.read_body do |chunk|
          body.concat(chunk)
          raise ::LinkThumbnailer::DownloadSizeLimit if too_big_download_size?(body.length)
        end
      end
      response.body = body
      response
    end

    def resolve_relative_url(location)
      location.start_with?('http') ? location : build_absolute_url_for(location)
    end

    def build_absolute_url_for(relative_url)
      ::URI.parse("#{url.scheme}://#{url.host}#{relative_url}")
    end

    def redirect_limit
      config.redirect_limit
    end

    def user_agent
      config.user_agent
    end

    def http_open_timeout
      config.http_open_timeout
    end

    def http_read_timeout
      config.http_read_timeout
    end

    def ssl_required?
      config.verify_ssl
    end

    def download_size_limit
      config.download_size_limit
    end

    def too_many_redirections?
      redirect_count > redirect_limit
    end

    def valid_url_format?
      url.is_a?(::URI::HTTP)
    end

    def valid_response_format?(response)
      return true unless config.raise_on_invalid_format
      return true if response['Content-Type'] =~ /text\/html/
      return true if response['Content-Type'] =~ /application\/html/
      return true if response['Content-Type'] =~ /application\/xhtml\+xml/
      return true if response['Content-Type'] =~ /application\/xml/
      return true if response['Content-Type'] =~ /text\/xml/
      return true if response['Content-Type'] =~ /text\/plain/
      false
    end

    def too_big_download_size?(size)
      size.to_i > download_size_limit.to_i
    end

    def url=(url)
      @url = ::URI.parse(url.to_s)
    end

  end
end