link_preview 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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