favicon_party 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 04c20f8f3afd2ba553493eaccc622a53e8475d52
4
+ data.tar.gz: 9236896799c20478321d54209c26c6bdd88bf9b7
5
+ SHA512:
6
+ metadata.gz: 675b832b0921b0570a077627428da752f3272d35b500798d72e7539727da2c4153b6e474bdd2307776bcd1842b20252e0cfc3a4062574988dfe883b9fe6192e2
7
+ data.tar.gz: 764055135af45b2d82ae4cc1f30150942e2ec89c46ea6a65051bf51921370a3dd28b2806a53d6c0bab3716ff492fd1838ba54fe07023dc8861ed317ffa6b31c9
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .ruby-version
2
+ *.gem
3
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Linmiao Xu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # Favicon Party
2
+
3
+ Fetch favicons from websites and convert them into other image formats!
4
+
5
+
6
+ ## Fetching favicon images
7
+
8
+ Favicon Party parses the html response of the query url to find favicon
9
+ links, and falls back to checking /favicon.ico for valid favicon images.
10
+
11
+ ```ruby
12
+ image = FaviconParty.fetch! "https://github.com"
13
+
14
+ # => #<FaviconParty::Image mime_type: "image/x-icon", size: 6518>
15
+
16
+ image.valid? # true
17
+ image.source_data # binary favicon image data
18
+ image.to_png # binary 16x16 png data
19
+ image.base64_png # base64-encoded 16x16 png data
20
+
21
+
22
+ FaviconParty.fetch! "http://example.com" # raises FaviconNotFound
23
+ FaviconParty.fetch "http://example.com" # nil
24
+ ```
25
+
26
+
27
+ ## Loading favicon images
28
+
29
+ You can load favicon files directly as well.
30
+
31
+ ```ruby
32
+ image = FaviconParty.load "/path/to/favicon.ico"
33
+
34
+ # => #<FaviconParty::Image mime_type: "image/x-icon", size: 6518>
35
+ ```
36
+
37
+ Same for loading favicon URLs.
38
+
39
+ ```ruby
40
+ image = FaviconParty.load "https://github.com/favicon.ico"
41
+
42
+ # => #<FaviconParty::Image mime_type: "image/x-icon", size: 6518>
43
+ ```
44
+
45
+
46
+ ## Command-line client
47
+
48
+ Favicon Party also provides a command-line script for fetching favicons. By
49
+ default, it attempts to fetch a favicon from the given URL and prints it out.
50
+ Run `fetch_favicon --help` to see the list of options.
51
+
52
+ ```bash
53
+ fetch_favicon github.com # prints favicon data to STDOUT
54
+ fetch_favicon --format png github.com # prints 16x16 favicon png to STDOUT
55
+ fetch_favicon example.com # prints an error to STDERR
56
+ ```
57
+
58
+
59
+ ## Installation
60
+
61
+ The best way to install Favicon Party is through [RubyGems](https://rubygems.org/)
62
+
63
+ ```
64
+ $ gem install favicon_party
65
+ ```
66
+
67
+
68
+ ## Requirements
69
+
70
+ * Ruby 2.0.0+
71
+ * [Nokogiri](https://github.com/sparklemotion/nokogiri) - parsing html
72
+ * [Imagemagick](http://www.imagemagick.org/) - validating and converting favicons
73
+ * [Curl](http://curl.haxx.se/) - http client
74
+ * [File](http://darwinsys.com/file/) - mime-type detection
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "favicon_party"
5
+
6
+ # require "irb"
7
+ # IRB.start
8
+
9
+ require "pry"
10
+ Pry.start
data/bin/fetch_favicon ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift "./lib"
4
+
5
+ require 'optparse'
6
+ require 'favicon_party'
7
+
8
+
9
+ options = {}
10
+ OptionParser.new do |opts|
11
+ opts.banner = "Usage: ./fetch_favicon <url>"
12
+
13
+ opts.on("--png", "Outputs favicon as a 16x16 png") do
14
+ options[:format] = :png
15
+ end
16
+
17
+ opts.on("-o", "--output-file FILE", "Saves favicon to FILE") do |output|
18
+ options[:output] = output
19
+ end
20
+
21
+ opts.on_tail("-h", "--help", "Show this help text") do
22
+ puts opts
23
+ exit
24
+ end
25
+
26
+ if ARGV.length == 0
27
+ puts opts
28
+ exit
29
+ end
30
+
31
+ end.parse!
32
+
33
+
34
+ image = FaviconParty.fetch!(ARGV[0])
35
+ case options[:format]
36
+ when :png
37
+ favicon = image.to_png
38
+ else
39
+ favicon = image.source_data
40
+ end
41
+
42
+
43
+ if options[:output]
44
+ open(options[:output], "wb").write {|f| f.write favicon }
45
+ else
46
+ puts favicon
47
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $:.unshift(lib) unless $:.include?(lib)
5
+
6
+ require 'favicon_party/version'
7
+
8
+
9
+ Gem::Specification.new do |s|
10
+ s.name = "favicon_party"
11
+ s.version = FaviconParty::VERSION
12
+ s.authors = ["Linmiao Xu"]
13
+ s.email = ["linmiao.xu@gmail.com"]
14
+
15
+ s.summary = "Fetch favicons from websites and have a blast!"
16
+ s.description = "Fetch favicons from websites and have a blast!"
17
+ s.homepage = "https://github.com/linrock/favicon_party"
18
+ s.license = "MIT"
19
+
20
+ s.files = `git ls-files -z`.split("\0")
21
+ s.bindir = "bin"
22
+ s.executables = ["fetch_favicon"]
23
+ s.require_paths = ["lib"]
24
+
25
+ s.add_development_dependency "rake", "~> 10.0"
26
+ s.add_development_dependency "minitest", "~> 5.0"
27
+ s.add_development_dependency "pry", "~> 0.10"
28
+
29
+ s.add_dependency "nokogiri", "~> 1.6"
30
+ end
@@ -0,0 +1,34 @@
1
+ require 'favicon_party/utils'
2
+ require 'favicon_party/errors'
3
+ require 'favicon_party/http_client'
4
+ require 'favicon_party/image'
5
+ require 'favicon_party/fetcher'
6
+ require 'favicon_party/loader'
7
+ require 'favicon_party/version'
8
+
9
+
10
+ module FaviconParty
11
+
12
+ class << self
13
+
14
+ # @return [FaviconParty::Image]
15
+ #
16
+ def fetch!(url, options = {})
17
+ Fetcher.new(url, options).fetch
18
+ end
19
+
20
+ # @return [FaviconParty::Image]
21
+ #
22
+ def fetch(url)
23
+ fetch!(url) rescue nil
24
+ end
25
+
26
+ # @return [FaviconParty::Image]
27
+ #
28
+ def load(url_or_filename)
29
+ Loader.load url_or_filename
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,32 @@
1
+ module FaviconParty
2
+
3
+ class CurlError < StandardError; end
4
+
5
+ module Curl
6
+ class DNSError < CurlError; end
7
+ class SSLError < CurlError; end
8
+ end
9
+
10
+ class FaviconNotFound < StandardError; end
11
+
12
+ class Error < StandardError
13
+ attr_accessor :meta
14
+
15
+ def initialize(message)
16
+ @meta = {}
17
+ super(message)
18
+ end
19
+
20
+ def to_h
21
+ {
22
+ :error_class => self.class.to_s,
23
+ :error_message => self.message
24
+ }.merge(meta)
25
+ end
26
+
27
+ end
28
+
29
+ class InvalidData < StandardError; end
30
+ class ImageMagickError < Error; end
31
+
32
+ end
@@ -0,0 +1,153 @@
1
+ require 'base64'
2
+ require 'nokogiri'
3
+
4
+
5
+ module FaviconParty
6
+
7
+ # Actually go and grab the favicon from the given url
8
+ #
9
+ class Fetcher
10
+ include FaviconParty::Utils
11
+
12
+ ICON_SELECTORS = [ 'link[rel="shortcut icon"]',
13
+ 'link[rel="icon"]',
14
+ 'link[type="image/x-icon"]',
15
+ 'link[rel="fluid-icon"]',
16
+ 'link[rel="apple-touch-icon"]'
17
+ ]
18
+
19
+ attr_accessor :query_url, :final_url, :favicon_url, :candidate_urls, :html, :data
20
+
21
+ def initialize(url, options = {})
22
+ @query_url = prefix_url(url)
23
+ @final_url = nil
24
+ @favicon_url = nil
25
+ @html = nil
26
+ @data = nil
27
+ @candidate_urls = []
28
+ end
29
+
30
+ # Encodes output as utf8 - Not for binary http responses
31
+ #
32
+ def http_get(url)
33
+ FaviconParty::HTTPClient.get(url)
34
+ end
35
+
36
+ def fetch
37
+ @html = http_get final_url
38
+ set_candidate_favicon_urls
39
+ get_favicon_data
40
+ end
41
+
42
+ def get_favicon_data
43
+ return @data if !@data.nil?
44
+ @data = get_favicon_data_from_candidate_urls
45
+ raise FaviconParty::FaviconNotFound.new(@query_url) unless @data
46
+ @data
47
+ end
48
+
49
+ def get_favicon_data_from_candidate_urls
50
+ @candidate_urls.each do |url|
51
+ data = FaviconParty::Image.new(get_favicon_data_from_url(url))
52
+ begin
53
+ if data.valid?
54
+ @favicon_url = url
55
+ return data
56
+ end
57
+ rescue FaviconParty::ImageMagickError => error
58
+ error.meta = get_urls
59
+ error.meta[:favicon_url] ||= url
60
+ error.meta[:base64_favicon_data] = data.base64_source_data
61
+ raise error
62
+ end
63
+ end
64
+ nil
65
+ end
66
+
67
+ # Tries to find favicon urls from the html content of query_url
68
+ #
69
+ def find_favicon_urls_in_html(html)
70
+ doc = Nokogiri.parse html
71
+ candidate_urls = doc.css(ICON_SELECTORS.join(",")).map {|e| e.attr('href') }.compact
72
+ candidate_urls.sort_by! {|href|
73
+ if href =~ /\.ico/
74
+ 0
75
+ elsif href =~ /\.png/
76
+ 1
77
+ else
78
+ 2
79
+ end
80
+ }
81
+ uri = URI final_url
82
+ candidate_urls.map! do |href|
83
+ href = URI.encode(href.strip)
84
+ if href =~ /\A\/\//
85
+ href = "#{uri.scheme}:#{href}"
86
+ elsif href !~ /\Ahttp/
87
+ # Ignore invalid URLS - ex. {http://i50.tinypic.com/wbuzcn.png}
88
+ href = URI.join(url_root, href).to_s rescue nil
89
+ end
90
+ href
91
+ end.compact
92
+ end
93
+
94
+ def set_candidate_favicon_urls
95
+ @candidate_urls = find_favicon_urls_in_html(@html)
96
+ @candidate_urls << URI.join(url_root, "favicon.ico").to_s
97
+ @candidate_urls << URI.join(url_root, "favicon.png").to_s
98
+ end
99
+
100
+ # Follow redirects from the query url to get to the last url
101
+ #
102
+ def final_url
103
+ return @final_url if !@final_url.nil?
104
+ output = FaviconParty::HTTPClient.head(@query_url)
105
+ final_location = output.scan(/\ALocation: (.*)/)[-1]
106
+ final = final_location && final_location[0].strip
107
+ if !final.nil?
108
+ if final =~ /\Ahttp/
109
+ @final_url = URI.encode final
110
+ else
111
+ uri = URI @query_url
112
+ root = "#{uri.scheme}://#{uri.host}"
113
+ @final_url = URI.encode URI.join(root, final).to_s
114
+ end
115
+ end
116
+ if !@final_url.nil?
117
+ if %w( 127.0.0.1 localhost ).any? {|host| @final_url.include? host }
118
+ # TODO Exception for invalid final urls
119
+ @final_url = @query_url
120
+ end
121
+ return @final_url
122
+ end
123
+ @final_url = @query_url
124
+ end
125
+
126
+ def url_root
127
+ uri = URI final_url
128
+ "#{uri.scheme}://#{uri.host}"
129
+ end
130
+
131
+ def get_favicon_data_from_url(url)
132
+ if url =~ /^data:/
133
+ data = url.split(',')[1]
134
+ return data && Base64.decode64(data)
135
+ end
136
+ FaviconParty::HTTPClient.bin_get url
137
+ end
138
+
139
+ def get_urls
140
+ {
141
+ :query_url => @query_url,
142
+ :final_url => final_url,
143
+ :favicon_url => @favicon_url
144
+ }
145
+ end
146
+
147
+ def has_data?
148
+ !@data.nil? && !@data.empty?
149
+ end
150
+
151
+ end
152
+
153
+ end
@@ -0,0 +1,50 @@
1
+ require 'open3'
2
+
3
+
4
+ module FaviconParty
5
+
6
+ module HTTPClient
7
+ include FaviconParty::Utils
8
+
9
+ TIMEOUT = 5
10
+
11
+ # For now, wrap command-line curl rather than using net/http or open-uri
12
+ # because of easier/more reliable SSL handling
13
+ #
14
+ def curl_cmd(url)
15
+ "curl -sL -k --compressed -m #{TIMEOUT} --ciphers 'RC4,3DES,ALL' --fail --show-error '#{url}'"
16
+ end
17
+
18
+ # Encodes output as utf8 - Not for binary http responses
19
+ #
20
+ def get(url)
21
+ stdin, stdout, stderr, t = Open3.popen3(curl_cmd(url))
22
+ output = encode_utf8(stdout.read).strip
23
+ error = encode_utf8(stderr.read).strip
24
+ if !error.nil? && !error.empty?
25
+ if error.include? "SSL"
26
+ raise FaviconParty::Curl::SSLError.new(error)
27
+ elsif error.include? "Couldn't resolve host"
28
+ raise FaviconParty::Curl::DNSError.new(error)
29
+ else
30
+ raise FaviconParty::CurlError.new(error)
31
+ end
32
+ end
33
+ output
34
+ end
35
+
36
+ # Get binary data from url and ignore errors
37
+ #
38
+ def bin_get(url)
39
+ `#{curl_cmd(url)} 2>/dev/null`
40
+ end
41
+
42
+ def head(url)
43
+ headers = `curl -sIL -1 --ciphers 'RC4,3DES,ALL' -m #{TIMEOUT} "#{url}"`
44
+ encode_utf8 headers
45
+ end
46
+
47
+ extend self
48
+ end
49
+
50
+ end
@@ -0,0 +1,178 @@
1
+ require 'base64'
2
+ require 'open3'
3
+
4
+
5
+ module FaviconParty
6
+
7
+ # For handling anything related to the image data of the favicon itself
8
+ #
9
+ class Image
10
+ include FaviconParty::Utils
11
+
12
+ # Threshold for stdev of color values under which the image data
13
+ # is considered one color
14
+ #
15
+ STDEV_THRESHOLD = 0.005
16
+
17
+ MAX_FILE_SIZE = 1024 * 1024
18
+
19
+ VALID_MIME_TYPES = %w(
20
+ image/x-ico
21
+ image/x-icon
22
+ image/png
23
+ image/gif
24
+ image/svg+xml
25
+ image/jpeg
26
+ )
27
+
28
+ attr_accessor :source_data, :png_data, :error
29
+
30
+ def initialize(source_data)
31
+ @source_data = source_data
32
+ @png_data = nil
33
+ @error = nil
34
+ end
35
+
36
+ def imagemagick_run(cmd, binmode = false)
37
+ stdin, stdout, stderr, t = Open3.popen3(cmd)
38
+ stdout.binmode if binmode
39
+ output = stdout.read.strip
40
+ error = stderr.read.strip
41
+ if output.empty? && !error.nil? && !error.empty?
42
+ raise FaviconParty::ImageMagickError.new(error)
43
+ end
44
+ output
45
+ end
46
+
47
+ def mime_type
48
+ return @mime_type if defined? @mime_type
49
+ @mime_type = get_mime_type(@source_data)
50
+ end
51
+
52
+ def identify(verbose = false)
53
+ with_temp_data_file(@source_data) do |t|
54
+ imagemagick_run("identify #{"-verbose" if verbose} #{t.path.to_s}")
55
+ end
56
+ end
57
+
58
+ # Does the data look like a valid favicon?
59
+ #
60
+ def valid?
61
+ @error =
62
+ if blank?
63
+ "source_data is blank"
64
+ elsif size_too_big?
65
+ "source_data file size too big"
66
+ elsif !valid_mime_type?
67
+ "source_data mime-type is invalid - #{mime_type}"
68
+ elsif transparent?
69
+ "source_data is a transparent image"
70
+ elsif one_pixel?
71
+ "source_data is a 1x1 image"
72
+ elsif one_color?
73
+ "png_data is one color (or close to it)"
74
+ end
75
+ @error.nil?
76
+ end
77
+
78
+ def blank?
79
+ @source_data.nil? || @source_data.length <= 1
80
+ end
81
+
82
+ def size_too_big?
83
+ size >= MAX_FILE_SIZE
84
+ end
85
+
86
+ # TODO set an option to decide how mime-type validity is handled
87
+ #
88
+ def invalid_mime_type?
89
+ mime_type =~ /(text|html|x-empty|octet-stream|ERROR|zip|jar)/
90
+ end
91
+
92
+ def valid_mime_type?
93
+ VALID_MIME_TYPES.include? mime_type
94
+ end
95
+
96
+ def transparent?
97
+ with_temp_data_file(@source_data) do |t|
98
+ cmd = "convert #{t.path.to_s} -channel a -negate -format '%[mean]' info:"
99
+ imagemagick_run(cmd).to_i == 0
100
+ end
101
+ end
102
+
103
+ def one_pixel?
104
+ files = identify.split(/\n/)
105
+ files.length == 1 && files[0].include?(" 1x1 ")
106
+ end
107
+
108
+ def one_color?
109
+ colors_stdev < STDEV_THRESHOLD
110
+ end
111
+
112
+ def colors_stdev
113
+ with_temp_data_file(to_png) do |t|
114
+ cmd = "identify -format '%[fx:image.standard_deviation]' #{t.path.to_s}"
115
+ imagemagick_run(cmd).to_f
116
+ end
117
+ end
118
+
119
+ def n_colors
120
+ with_temp_data_file(@source_data) do |t|
121
+ cmd = "identify -format '%k' #{t.path.to_s}"
122
+ imagemagick_run(cmd).to_i
123
+ end
124
+ end
125
+
126
+ def dimensions
127
+ with_temp_data_file(@source_data) do |t|
128
+ cmd = "convert #{t.path.to_s}[0] -format '%wx%h' info:"
129
+ imagemagick_run(cmd)
130
+ end
131
+ end
132
+
133
+ # number of bytes in the raw data
134
+ #
135
+ def size
136
+ @source_data && @source_data.size || 0
137
+ end
138
+
139
+ def info_str
140
+ "#{mime_type}, #{dimensions}, #{size} bytes"
141
+ end
142
+
143
+ # Export source_data as a 16x16 png
144
+ #
145
+ def to_png
146
+ return @png_data if !@png_data.nil?
147
+ with_temp_data_file(@source_data) do |t|
148
+ sizes = imagemagick_run("identify #{t.path.to_s}").split(/\n/)
149
+ images = []
150
+ %w(16x16 32x32 64x64).each do |dims|
151
+ %w(32-bit 24-bit 16-bit 8-bit).each do |bd|
152
+ images += sizes.select {|x| x.include?(dims) and x.include?(bd) }.
153
+ map {|x| x.split(' ')[0] }
154
+ end
155
+ end
156
+ image_to_convert = images.uniq[0] || "#{t.path.to_s}[0]"
157
+ cmd = "convert -strip -resize 16x16! #{image_to_convert} png:fd:1"
158
+ @png_data = imagemagick_run(cmd, true)
159
+ raise FaviconParty::InvalidData.new("Empty png") if @png_data.empty?
160
+ @png_data
161
+ end
162
+ end
163
+
164
+ def base64_source_data
165
+ Base64.encode64(@source_data).split(/\s+/).join
166
+ end
167
+
168
+ def base64_png
169
+ Base64.encode64(to_png).split(/\s+/).join
170
+ end
171
+
172
+ def inspect
173
+ %(#<FaviconParty::Image mime_type: "#{mime_type}", size: #{size}>)
174
+ end
175
+
176
+ end
177
+
178
+ end
@@ -0,0 +1,14 @@
1
+ require 'open-uri'
2
+
3
+
4
+ module FaviconParty
5
+
6
+ module Loader
7
+
8
+ def self.load(url_or_filename)
9
+ Image.new open(url_or_filename, "rb").read
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,47 @@
1
+ require 'uri'
2
+ require 'tempfile'
3
+
4
+
5
+ module FaviconParty
6
+
7
+ module Utils
8
+
9
+ def prefix_url(url)
10
+ url = URI.encode url.strip.downcase
11
+ if url =~ /https?:\/\//
12
+ url
13
+ else
14
+ "http://#{url}"
15
+ end
16
+ end
17
+
18
+ def encode_utf8(text)
19
+ return text if text.valid_encoding?
20
+ text.encode("UTF-8", :invalid => :replace, :undef => :replace, :replace => '')
21
+ end
22
+
23
+ def get_mime_type(data, use_file_cmd = true)
24
+ if use_file_cmd
25
+ with_temp_data_file(data) {|t| `file -b --mime-type #{t.path.to_s}`.strip }
26
+ else
27
+ FileMagic.new(:mime_type).buffer(data)
28
+ end
29
+ end
30
+
31
+ def with_temp_data_file(data, &block)
32
+ begin
33
+ t = Tempfile.new(["favicon", ".ico"])
34
+ t.binmode
35
+ t.write data
36
+ t.close
37
+ result = block.call(t)
38
+ ensure
39
+ t.unlink
40
+ end
41
+ result
42
+ end
43
+
44
+ extend self
45
+ end
46
+
47
+ end
@@ -0,0 +1,3 @@
1
+ module FaviconParty
2
+ VERSION = "0.7.2"
3
+ end
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+
4
+ class FetcherTest < Minitest::Test
5
+
6
+ def setup
7
+ test_url = "http://localhost"
8
+ @fetcher = FaviconParty::Fetcher.new(test_url)
9
+ @fetcher.final_url = test_url
10
+ end
11
+
12
+ def test_fetcher_raises_error_if_no_favicon_found
13
+ @fetcher.candidate_urls = []
14
+ assert_raises(FaviconParty::FaviconNotFound) do
15
+ @fetcher.get_favicon_data
16
+ end
17
+ end
18
+
19
+ def test_fetcher_finds_favicon_urls_in_html
20
+ html = open("#{File.dirname(__FILE__)}/fixtures/html/page1.html").read
21
+ assert @fetcher.find_favicon_urls_in_html(html).length == 1
22
+ end
23
+
24
+ def test_fetcher_decodes_base64_data_uri_links
25
+ filename = "#{File.dirname(__FILE__)}/fixtures/favicons/transparent-16x16.png"
26
+ image = FaviconParty::Image.new(open(filename, "rb").read)
27
+ data_uri = "data:image/png;base64,#{image.base64_png}"
28
+ assert @fetcher.get_favicon_data_from_url(data_uri) == image.source_data
29
+ end
30
+
31
+ end
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> <image id="image0" width="16" height="16" x="0" y="0"
4
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQAAAAA3iMLMAAAABGdBTUEAALGPC/xhBQAAACBjSFJN
5
+ AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAd2KE6QAAAAHdElN
6
+ RQffCxwWHC8XrmKqAAAADklEQVQI12P4/5+BFAQA/U4f4XOLpqwAAAAldEVYdGRhdGU6Y3JlYXRl
7
+ ADIwMTUtMTEtMjhUMjI6Mjg6NDctMDg6MDC802/MAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE1LTEx
8
+ LTI4VDIyOjI4OjQ3LTA4OjAwzY7XcAAAAABJRU5ErkJggg==" />
9
+ </svg>
@@ -0,0 +1,7 @@
1
+ <html>
2
+ <head>
3
+ <link rel="shortcut icon" href="/assets/favicon.ico">
4
+ </head>
5
+ <body>
6
+ </body>
7
+ </html>
@@ -0,0 +1,59 @@
1
+ require 'test_helper'
2
+
3
+
4
+ class ImageTest < Minitest::Test
5
+
6
+ def setup
7
+ @klass = FaviconParty::Image
8
+ end
9
+
10
+ def test_nil_data_is_invalid
11
+ @image = @klass.new nil
12
+ assert @image.valid? == false
13
+ end
14
+
15
+ def test_transparent_png_is_invalid
16
+ filename = "#{File.dirname(__FILE__)}/fixtures/favicons/transparent-16x16.png"
17
+ @image = @klass.new open(filename, "rb").read
18
+ assert @image.mime_type == "image/png"
19
+ assert @image.transparent? == true
20
+ assert @image.one_color? == true
21
+ assert @image.valid? == false
22
+ end
23
+
24
+ def test_1x1_gif_is_invalid
25
+ filename = "#{File.dirname(__FILE__)}/fixtures/favicons/transparent-1x1.gif"
26
+ @image = @klass.new open(filename, "rb").read
27
+ assert @image.mime_type == "image/gif"
28
+ assert @image.one_pixel? == true
29
+ assert @image.valid? == false
30
+ end
31
+
32
+ def test_all_white_ico_is_invalid
33
+ filename = "#{File.dirname(__FILE__)}/fixtures/favicons/white-16x16.ico"
34
+ @image = @klass.new open(filename, "rb").read
35
+ assert %w( image/x-ico image/x-icon ).include? @image.mime_type
36
+ assert @image.one_color? == true
37
+ assert @image.valid? == false
38
+ end
39
+
40
+ def test_svg_mime_type_is_valid
41
+ filename = "#{File.dirname(__FILE__)}/fixtures/favicons/white-16x16.svg"
42
+ @image = @klass.new open(filename, "rb").read
43
+ assert @image.one_color?
44
+ assert @image.valid_mime_type?
45
+ end
46
+
47
+ def test_base64_png_works
48
+ filename = "#{File.dirname(__FILE__)}/fixtures/favicons/white-16x16.ico"
49
+ @image = @klass.new open(filename, "rb").read
50
+ assert !@image.base64_png.nil?
51
+ end
52
+
53
+ def test_dimensions_returns_correct_dimensions
54
+ filename = "#{File.dirname(__FILE__)}/fixtures/favicons/white-16x16.ico"
55
+ @image = @klass.new open(filename, "rb").read
56
+ assert @image.dimensions == "16x16"
57
+ end
58
+
59
+ end
@@ -0,0 +1,16 @@
1
+ require 'test_helper'
2
+
3
+
4
+ class LoaderTest < Minitest::Test
5
+
6
+ def setup
7
+ @klass = FaviconParty::Loader
8
+ end
9
+
10
+ def test_loader_loads_favicon_images
11
+ filename = "#{File.dirname(__FILE__)}/fixtures/favicons/transparent-16x16.png"
12
+ image = @klass.load filename
13
+ assert image.class == FaviconParty::Image
14
+ end
15
+
16
+ end
@@ -0,0 +1,4 @@
1
+ require 'minitest/autorun'
2
+
3
+ $:.unshift File.expand_path('../../lib', __FILE__)
4
+ require 'favicon_party'
@@ -0,0 +1,43 @@
1
+ require 'test_helper'
2
+
3
+
4
+ class UtilsTest < Minitest::Test
5
+ include FaviconParty::Utils
6
+
7
+ def test_get_mime_type_detects_png_correctly
8
+ filename = "#{File.dirname(__FILE__)}/fixtures/favicons/transparent-16x16.png"
9
+ assert get_mime_type(open(filename, "rb").read) == "image/png"
10
+ end
11
+
12
+ def test_get_mime_type_detects_gif_correctly
13
+ filename = "#{File.dirname(__FILE__)}/fixtures/favicons/transparent-1x1.gif"
14
+ assert get_mime_type(open(filename, "rb").read) == "image/gif"
15
+ end
16
+
17
+ def test_get_mime_type_detects_ico_correctly
18
+ filename = "#{File.dirname(__FILE__)}/fixtures/favicons/white-16x16.ico"
19
+ assert %w( image/x-ico image/x-icon ).include? get_mime_type(open(filename, "rb").read)
20
+ end
21
+
22
+ def test_get_mime_type_detects_svg_correctly
23
+ filename = "#{File.dirname(__FILE__)}/fixtures/favicons/white-16x16.svg"
24
+ assert get_mime_type(open(filename, "rb").read) == "image/svg+xml"
25
+ end
26
+
27
+ def test_get_mime_type_detects_html_correctly
28
+ filename = "#{File.dirname(__FILE__)}/fixtures/html/page1.html"
29
+ assert get_mime_type(open(filename, "rb").read) == "text/html"
30
+ end
31
+
32
+ def test_prefix_url_always_returns_prefixed_urls
33
+ assert prefix_url("localhost") =~ /\Ahttp:\/\//
34
+ assert prefix_url("http://localhost") =~ /\Ahttp:\/\//
35
+ end
36
+
37
+ def test_encode_utf8_returns_valid_utf8_string
38
+ string = "\x12\x61\xA3"
39
+ assert !string.valid_encoding?
40
+ assert encode_utf8(string).valid_encoding?
41
+ end
42
+
43
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: favicon_party
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.2
5
+ platform: ruby
6
+ authors:
7
+ - Linmiao Xu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '10.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '10.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.10'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.10'
55
+ - !ruby/object:Gem::Dependency
56
+ name: nokogiri
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ description: Fetch favicons from websites and have a blast!
70
+ email:
71
+ - linmiao.xu@gmail.com
72
+ executables:
73
+ - fetch_favicon
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - LICENSE
80
+ - README.md
81
+ - Rakefile
82
+ - bin/console
83
+ - bin/fetch_favicon
84
+ - favicon_party.gemspec
85
+ - lib/favicon_party.rb
86
+ - lib/favicon_party/errors.rb
87
+ - lib/favicon_party/fetcher.rb
88
+ - lib/favicon_party/http_client.rb
89
+ - lib/favicon_party/image.rb
90
+ - lib/favicon_party/loader.rb
91
+ - lib/favicon_party/utils.rb
92
+ - lib/favicon_party/version.rb
93
+ - test/fetcher_test.rb
94
+ - test/fixtures/favicons/transparent-16x16.png
95
+ - test/fixtures/favicons/transparent-1x1.gif
96
+ - test/fixtures/favicons/transparent-1x1.png
97
+ - test/fixtures/favicons/white-16x16.ico
98
+ - test/fixtures/favicons/white-16x16.svg
99
+ - test/fixtures/html/page1.html
100
+ - test/image_test.rb
101
+ - test/loader_test.rb
102
+ - test/test_helper.rb
103
+ - test/utils_test.rb
104
+ homepage: https://github.com/linrock/favicon_party
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.4.8
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: Fetch favicons from websites and have a blast!
128
+ test_files: []