link_preview 0.2.7

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.
@@ -0,0 +1,172 @@
1
+ # Copyright (c) 2014, VMware, Inc. All Rights Reserved.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights to
6
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ # of the Software, and to permit persons to whom the Software is furnished to do
8
+ # so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ require 'multi_json'
22
+ require 'nokogiri'
23
+ require 'set'
24
+
25
+ module LinkPreview
26
+ class Parser
27
+ attr_accessor :discovered_uris
28
+
29
+ def initialize(config, options = {})
30
+ @config = config
31
+ @options = options
32
+ self.discovered_uris = Set.new
33
+ end
34
+
35
+ def parse(data)
36
+ return {} unless data && data.headers[:content_type] && data.body
37
+ case data.headers[:content_type]
38
+ when /image/, 'binary/octet-stream'
39
+ parse_image(data)
40
+ when %r{\Atext/html.*}
41
+ parse_html(data)
42
+ when %r{\Atext/xml.*}
43
+ parse_oembed(data)
44
+ when %r{\Aapplication/json.*}
45
+ parse_oembed(data)
46
+ else
47
+ {}
48
+ end
49
+ end
50
+
51
+ def parse_image(data)
52
+ {
53
+ :image => {
54
+ :image_url => data.url,
55
+ :image_data => parse_image_data(data),
56
+ :image_content_type => data.headers[:content_type],
57
+ :image_file_name => parse_image_file_name(data)
58
+ }
59
+ }
60
+ end
61
+
62
+ # FIXME currently secure_url is favored over url via implicit ordering of keys
63
+ def parse_html(data)
64
+ if doc = Nokogiri::HTML.parse(data.body, nil, 'UTF-8')
65
+ enum_oembed_link(doc) do |link_rel|
66
+ discovered_uris << LinkPreview::URI.parse(link_rel, @options)
67
+ end
68
+ {
69
+ :opengraph => {
70
+ :title => find_meta_property(doc, 'og:title'),
71
+ :description => find_meta_property(doc, 'og:description'),
72
+ :image_secure_url => find_meta_property(doc, 'og:image:secure_url'),
73
+ :image => find_meta_property(doc, 'og:image'),
74
+ :image_url => find_meta_property(doc, 'og:image:url'),
75
+ :tag => find_meta_property(doc, 'og:tag'),
76
+ :url => find_meta_property(doc, 'og:url'),
77
+ :type => find_meta_property(doc, 'og:type'),
78
+ :site_name => find_meta_property(doc, 'og:site_name'),
79
+ :video_secure_url => find_meta_property(doc, 'og:video:secure_url'),
80
+ :video => find_meta_property(doc, 'og:video'),
81
+ :video_url => find_meta_property(doc, 'og:video:url'),
82
+ :video_type => find_meta_property(doc, 'og:video:type'),
83
+ :video_width => find_meta_property(doc, 'og:video:width'),
84
+ :video_height => find_meta_property(doc, 'og:video:height')
85
+ },
86
+ :html => {
87
+ :title => find_title(doc),
88
+ :description => find_meta_description(doc),
89
+ :tags => Array.wrap(find_rel_tags(doc))
90
+ }
91
+ }
92
+ end
93
+ end
94
+
95
+ def parse_oembed(data)
96
+ oembed_data = case data.headers[:content_type]
97
+ when /xml/
98
+ Hash.from_xml(Nokogiri::XML.parse(data.body, nil, 'UTF-8').to_s)['oembed']
99
+ when /json/
100
+ MultiJson.load(data.body)
101
+ end
102
+ # TODO validate oembed response
103
+ { :oembed => (oembed_data || {}).merge(:url => parse_oembed_content_url(data)) }
104
+ end
105
+
106
+ def parse_oembed_content_url(data)
107
+ if data.url
108
+ parsed_uri = LinkPreview::URI.parse(data.url, @options)
109
+ parsed_uri.as_content_uri.to_s
110
+ end
111
+ end
112
+
113
+ def parse_image_data(data)
114
+ StringIO.new(data.body.dup) if data.body
115
+ end
116
+
117
+ def parse_image_file_name(data)
118
+ if filename = parse_content_disposition_filename(data)
119
+ filename
120
+ elsif data.url
121
+ parsed_uri = LinkPreview::URI.parse(data.url, @options)
122
+ parsed_uri.path.split('/').last || parsed_uri.hostname.gsub('.', '_')
123
+ end
124
+ end
125
+
126
+ # see http://www.ietf.org/rfc/rfc1806.txt
127
+ def parse_content_disposition_filename(data)
128
+ if data.headers[:'content-disposition'] =~ /filename=(.*?)\z/
129
+ $1.gsub(/\A['"]+|['"]+\z/, '')
130
+ end
131
+ end
132
+
133
+ def enum_oembed_link(doc, &block)
134
+ doc.search("//head/link[@rel='alternate'][@type='application/json+oembed']", "//head/link[@rel='alternate'][@type='text/xml+oembed']").each do |node|
135
+ next unless node && node.respond_to?(:attributes) && node.attributes['href']
136
+ yield node.attributes['href'].value
137
+ end
138
+ end
139
+
140
+ def find_title(doc)
141
+ doc.at('head/title').try(:inner_text)
142
+ end
143
+
144
+ # See http://microformats.org/wiki/rel-tag
145
+ def find_rel_tags(doc)
146
+ doc.search("//a[@rel='tag']").map(&:inner_text).reject(&:blank?)
147
+ end
148
+
149
+ def enum_meta_pair(doc, key, value)
150
+ Enumerator.new do |e|
151
+ doc.search('head/meta').each do |node|
152
+ next unless node
153
+ next unless node.respond_to?(:attributes)
154
+ next unless node.attributes[key]
155
+ next unless node.attributes[key].value
156
+ next unless node.attributes[key].value.downcase == value.downcase
157
+ next unless node.attributes['content']
158
+ next unless node.attributes['content'].value
159
+ e.yield node.attributes['content'].value
160
+ end
161
+ end
162
+ end
163
+
164
+ def find_meta_description(doc)
165
+ enum_meta_pair(doc, 'name', 'description').detect(&:present?)
166
+ end
167
+
168
+ def find_meta_property(doc, property)
169
+ enum_meta_pair(doc, 'property', property).detect(&:present?)
170
+ end
171
+ end
172
+ end
@@ -0,0 +1 @@
1
+ Dir["#{File.dirname(__FILE__)}/../../spec/support/**/*.rb"].each { |f| require f }
@@ -0,0 +1,149 @@
1
+ # Copyright (c) 2014, VMware, Inc. All Rights Reserved.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights to
6
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ # of the Software, and to permit persons to whom the Software is furnished to do
8
+ # so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ require 'uri'
22
+ require 'delegate'
23
+ require 'oembed'
24
+
25
+ require 'addressable/uri'
26
+ class Addressable::URI
27
+ alias :normalize_without_encoded_query :normalize
28
+ # NOTE hack to correctly escape URI query parameters after normalization
29
+ # see https://github.com/sporkmonger/addressable/issues/50
30
+ def normalize_with_encoded_query
31
+ normalize_without_encoded_query.tap do |uri|
32
+ if uri.query_values.present?
33
+ uri.query_values = uri.query_values.map { |key, value| [key, value] }
34
+ end
35
+ uri
36
+ end
37
+ end
38
+ alias :normalize :normalize_with_encoded_query
39
+ end
40
+
41
+ module LinkPreview
42
+ class URI < SimpleDelegator
43
+ def initialize(addressable_uri, options)
44
+ super addressable_uri
45
+ @options = options
46
+ if kaltura_uri?
47
+ merge_query(kaltura_query)
48
+ elsif oembed_uri?
49
+ merge_query(oembed_query)
50
+ end
51
+ end
52
+
53
+ def normalize
54
+ super
55
+ normalize_path
56
+ self
57
+ end
58
+
59
+ def as_oembed_uri
60
+ return self if kaltura_uri? || oembed_uri?
61
+ register_default_oembed_providers!
62
+ if provider = OEmbed::Providers.find(self.to_s)
63
+ self.class.parse(provider.build(self.to_s), @options)
64
+ end
65
+ end
66
+
67
+ def as_content_uri
68
+ return self unless kaltura_uri? || oembed_uri?
69
+ if content_url = self.query_values['url']
70
+ self.class.parse(content_url, @options)
71
+ end
72
+ end
73
+
74
+ def to_absolute(reference_uri)
75
+ return self if absolute?
76
+ self.class.parse(::URI.join(reference_uri, self.path), @options)
77
+ end
78
+
79
+ def for_display
80
+ self.path.sub!(%r{/\z}, '')
81
+ self.path = nil if self.path.blank?
82
+ self.query = nil if self.query.blank?
83
+ self.fragment = nil if self.fragment.blank?
84
+ self
85
+ end
86
+
87
+ class << self
88
+ def parse(uri, options = {})
89
+ return unless uri
90
+ self.new(Addressable::URI.parse(safe_escape(uri)), options).normalize
91
+ end
92
+
93
+ def unescape(uri)
94
+ Addressable::URI.unescape(uri, Addressable::URI)
95
+ end
96
+
97
+ def escape(uri)
98
+ Addressable::URI.escape(uri, Addressable::URI)
99
+ end
100
+
101
+ def safe_escape(uri)
102
+ parsed_uri = Addressable::URI.parse(uri)
103
+ unescaped = self.unescape(parsed_uri)
104
+ if unescaped.to_s == parsed_uri.to_s
105
+ self.escape(parsed_uri)
106
+ else
107
+ parsed_uri
108
+ end
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ def merge_query(query)
115
+ self.query_values ||= {}
116
+ self.query_values = self.query_values.merge(query.stringify_keys).to_a
117
+ end
118
+
119
+ def normalize_path
120
+ self.path += '/' if self.path.empty?
121
+ if kaltura_uri?
122
+ self.path += '/' unless self.path =~ %r{/\z}
123
+ end
124
+ self
125
+ end
126
+
127
+ def oembed_uri?
128
+ query_values.present? && path =~ /oembed/i && query_values['url']
129
+ end
130
+
131
+ def kaltura_uri?
132
+ query_values.present? && query_values['playerId'] && query_values['entryId']
133
+ end
134
+
135
+ def oembed_query
136
+ {:maxwidth => @options[:width], :maxheight => @options[:height]}.reject { |_,value| value.nil? }
137
+ end
138
+
139
+ def kaltura_query
140
+ {:width => @options[:width], :height => @options[:height]}.reject { |_,value| value.nil? }
141
+ end
142
+
143
+ def register_default_oembed_providers!
144
+ return if OEmbed::Providers.urls.any?
145
+ OEmbed::Providers.register_all
146
+ OEmbed::Providers.register_fallback(OEmbed::ProviderDiscovery)
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,23 @@
1
+ # Copyright (c) 2014, VMware, Inc. All Rights Reserved.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights to
6
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ # of the Software, and to permit persons to whom the Software is furnished to do
8
+ # so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ module LinkPreview
22
+ VERSION = '0.2.7'
23
+ end
@@ -0,0 +1,51 @@
1
+ # Copyright (c) 2014, VMware, Inc. All Rights Reserved.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights to
6
+ # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ # of the Software, and to permit persons to whom the Software is furnished to do
8
+ # so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ require 'link_preview/configuration'
22
+ require 'link_preview/content'
23
+
24
+ module LinkPreview
25
+ class Client
26
+ def configure
27
+ yield configuration
28
+ end
29
+
30
+ def configuration
31
+ @configuration ||= LinkPreview::Configuration.new
32
+ end
33
+
34
+ def fetch(uri, options = {}, sources = {})
35
+ LinkPreview::Content.new(configuration, uri, options, sources)
36
+ end
37
+
38
+ def load_content(uri, options = {}, sources = {})
39
+ LinkPreview::Content.new(configuration, uri, options.merge(allow_requests: false), sources)
40
+ end
41
+ end
42
+
43
+ extend Forwardable
44
+ extend self
45
+
46
+ def default_client
47
+ @default_client ||= Client.new
48
+ end
49
+
50
+ def_delegators :default_client, :fetch, :load_content, :configure, :configuration
51
+ end
@@ -0,0 +1,186 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://s.taobao.com/
6
+ body:
7
+ encoding: US-ASCII
8
+ base64_string: ''
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.9.0
12
+ Accept-Encoding:
13
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
14
+ Accept:
15
+ - ! '*/*'
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: !binary |-
20
+ T0s=
21
+ headers:
22
+ !binary "U2VydmVy":
23
+ - !binary |-
24
+ VGVuZ2luZQ==
25
+ !binary "RGF0ZQ==":
26
+ - !binary |-
27
+ TW9uLCAyNCBNYXIgMjAxNCAwMTo1NzoxMSBHTVQ=
28
+ !binary "Q29udGVudC1UeXBl":
29
+ - !binary |-
30
+ dGV4dC9odG1sOyBjaGFyc2V0PUdCSw==
31
+ !binary "VHJhbnNmZXItRW5jb2Rpbmc=":
32
+ - !binary |-
33
+ Y2h1bmtlZA==
34
+ !binary "Q29ubmVjdGlvbg==":
35
+ - !binary |-
36
+ Y2xvc2U=
37
+ !binary "VmFyeQ==":
38
+ - !binary |-
39
+ QWNjZXB0LUVuY29kaW5n
40
+ !binary "Q29udGVudC1FbmNvZGluZw==":
41
+ - !binary |-
42
+ Z3ppcA==
43
+ body:
44
+ encoding: ASCII-8BIT
45
+ base64_string: ! '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
180
+
181
+ 95QZKZnMRr6nhNHsAxd29b/a/S+k7EWd0CsAAA==
182
+
183
+ '
184
+ http_version:
185
+ recorded_at: Mon, 24 Mar 2014 01:57:12 GMT
186
+ recorded_with: VCR 2.8.0