lotus 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.travis.yml +9 -0
- data/Gemfile +16 -0
- data/README.md +233 -0
- data/Rakefile +7 -0
- data/lib/lotus.rb +232 -0
- data/lib/lotus/activity.rb +134 -0
- data/lib/lotus/atom/account.rb +50 -0
- data/lib/lotus/atom/address.rb +56 -0
- data/lib/lotus/atom/author.rb +167 -0
- data/lib/lotus/atom/category.rb +41 -0
- data/lib/lotus/atom/entry.rb +159 -0
- data/lib/lotus/atom/feed.rb +174 -0
- data/lib/lotus/atom/generator.rb +40 -0
- data/lib/lotus/atom/link.rb +79 -0
- data/lib/lotus/atom/name.rb +57 -0
- data/lib/lotus/atom/organization.rb +62 -0
- data/lib/lotus/atom/portable_contacts.rb +117 -0
- data/lib/lotus/atom/source.rb +168 -0
- data/lib/lotus/atom/thread.rb +60 -0
- data/lib/lotus/author.rb +177 -0
- data/lib/lotus/category.rb +45 -0
- data/lib/lotus/crypto.rb +146 -0
- data/lib/lotus/feed.rb +190 -0
- data/lib/lotus/generator.rb +53 -0
- data/lib/lotus/identity.rb +59 -0
- data/lib/lotus/link.rb +56 -0
- data/lib/lotus/notification.rb +220 -0
- data/lib/lotus/publisher.rb +40 -0
- data/lib/lotus/subscription.rb +117 -0
- data/lib/lotus/version.rb +3 -0
- data/lotus.gemspec +27 -0
- data/spec/activity_spec.rb +84 -0
- data/spec/atom/feed_spec.rb +681 -0
- data/spec/author_spec.rb +150 -0
- data/spec/crypto_spec.rb +138 -0
- data/spec/feed_spec.rb +252 -0
- data/spec/helper.rb +8 -0
- data/spec/identity_spec.rb +67 -0
- data/spec/link_spec.rb +30 -0
- data/spec/notification_spec.rb +77 -0
- data/test/example_feed.atom +393 -0
- data/test/example_feed_empty_author.atom +336 -0
- data/test/example_feed_false_connected.atom +359 -0
- data/test/example_feed_link_without_href.atom +134 -0
- data/test/example_page.html +4 -0
- data/test/mime_type_bug_feed.atom +874 -0
- metadata +204 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in ostatus.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
group :test do
|
7
|
+
gem "rake" # rakefile
|
8
|
+
gem "minitest" # test framework (specified here for prior rubies)
|
9
|
+
gem "ansi" # minitest colors
|
10
|
+
gem "turn" # minitest output
|
11
|
+
gem "mocha" # stubs
|
12
|
+
|
13
|
+
gem "awesome_print"
|
14
|
+
end
|
15
|
+
|
16
|
+
gem "redfinger", :git => "git://github.com/hotsh/redfinger.git"
|
data/README.md
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
# Lotus
|
2
|
+
|
3
|
+
This gem implements federated protocols which let your website connect and interact with any website implementing a federated space.
|
4
|
+
Users of a federated service can communicate, produce and consume with users of all sites within that federation as one large community while also allowing them
|
5
|
+
to host such websites on their own servers or choice of hosting provider.
|
6
|
+
Specifically, this gem deals with handling the data streams and the technologies that are related to Lotus/pump.io such as ActivityStreams, PortableContacts, Webfinger, PubSubHubbub, and Salmon.
|
7
|
+
|
8
|
+
One such application of this library (in its older form) is [rstat.us](https://rstat.us), which is a Twitter-like service that you can host yourself.
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
Currently, only the immutable interface is available.
|
13
|
+
|
14
|
+
```
|
15
|
+
require 'ostatus'
|
16
|
+
|
17
|
+
author = Lotus::Author.new(:uri => "https://rstat.us/users/wilkie",
|
18
|
+
:email => "wilkie@xomb.org",
|
19
|
+
:name => "wilkie")
|
20
|
+
|
21
|
+
blog_post = Lotus::Activity.new(:activity => :post,
|
22
|
+
:title => "Lotus gem",
|
23
|
+
:actor => author,
|
24
|
+
:content => "Long blog post",
|
25
|
+
:content_type => "text/html",
|
26
|
+
:id => "1",
|
27
|
+
:uri => "http://blog.davewilkinsonii.com/posts/ostatus_gem",
|
28
|
+
:published => Time.now)
|
29
|
+
|
30
|
+
feed = Lotus::Feed.new(:title => "wilkie writes a thing",
|
31
|
+
:url => "http://blog.davewilkinsonii.com",
|
32
|
+
:rights => "CC0",
|
33
|
+
:hubs => ["http://hub.davewilkinsonii.com"],
|
34
|
+
:authors => [author],
|
35
|
+
:published => Time.now,
|
36
|
+
:entries => [blog_post])
|
37
|
+
```
|
38
|
+
|
39
|
+
To generate an Atom representation:
|
40
|
+
|
41
|
+
```
|
42
|
+
feed.to_atom
|
43
|
+
```
|
44
|
+
|
45
|
+
To generate a collection of Lotus objects from Atom:
|
46
|
+
|
47
|
+
```
|
48
|
+
Lotus.feed_from_url("https://rstat.us/users/wilkieii/feed")
|
49
|
+
```
|
50
|
+
|
51
|
+
## Object Documentation
|
52
|
+
|
53
|
+
### Feed
|
54
|
+
|
55
|
+
The Feed is the aggregate. It holds a collection of entries written by a set of authors or contributors. It's the container for content.
|
56
|
+
|
57
|
+
#### Usage
|
58
|
+
|
59
|
+
```
|
60
|
+
author = Lotus::Author.new(:name => "Kelly")
|
61
|
+
feed = Lotus::Feed.new(:title => "My Feed",
|
62
|
+
:id => "1",
|
63
|
+
:url => "http://example.com/feeds/1",
|
64
|
+
:authors => [author])
|
65
|
+
```
|
66
|
+
|
67
|
+
#### Fields
|
68
|
+
```
|
69
|
+
id => The unique identifier for this feed.
|
70
|
+
url => The url that represents this feed.
|
71
|
+
title => The title for this feed. Defaults: "Untitled"
|
72
|
+
title_type => The content type for the title.
|
73
|
+
subtitle => The subtitle for this feed.
|
74
|
+
subtitle_type => The content type for the subtitle.
|
75
|
+
authors => The list of Lotus::Author's for this feed.
|
76
|
+
Defaults: []
|
77
|
+
contributors => The list of Lotus::Author's that contributed to this
|
78
|
+
feed. Defaults: []
|
79
|
+
entries => The list of Lotus::Activity's for this feed.
|
80
|
+
Defaults: []
|
81
|
+
icon => The url of the icon that represents this feed. It
|
82
|
+
should have an aspect ratio of 1 horizontal to 1
|
83
|
+
vertical and optimized for presentation at a
|
84
|
+
small size.
|
85
|
+
logo => The url of the logo that represents this feed. It
|
86
|
+
should have an aspect ratio of 2 horizontal to 1
|
87
|
+
vertical.
|
88
|
+
categories => An array of Lotus::Category's that describe how to
|
89
|
+
categorize and describe the content of the feed.
|
90
|
+
Defaults: []
|
91
|
+
rights => A String depicting the rights of entries without
|
92
|
+
explicit rights of their own. SHOULD NOT be machine
|
93
|
+
interpreted.
|
94
|
+
updated => The DateTime representing when this feed was last
|
95
|
+
modified.
|
96
|
+
published => The DateTime representing when this feed was originally
|
97
|
+
published.
|
98
|
+
salmon_url => The url of the salmon endpoint, if one exists, for this
|
99
|
+
feed.
|
100
|
+
links => An array of Lotus::Link that adds relations to other
|
101
|
+
resources.
|
102
|
+
generator => An Lotus::Generator representing the agent
|
103
|
+
responsible for generating this feed.
|
104
|
+
```
|
105
|
+
|
106
|
+
### Activity
|
107
|
+
|
108
|
+
Something that is done by a person. It has a verb, which suggests what was done
|
109
|
+
(e.g. :follow, or :unfollow, or :post) and it has an object, which is the
|
110
|
+
content. The content type is what the entity represents and governs how to
|
111
|
+
interpret the object. It can be a :note or :post or :image, etc.
|
112
|
+
|
113
|
+
#### Usage
|
114
|
+
```
|
115
|
+
entry = Lotus::Activity.new(:type => :note,
|
116
|
+
:title => "wilkie's Daily Update",
|
117
|
+
:content => "My day is going really well!",
|
118
|
+
:id => "123",
|
119
|
+
:url => "http://example.com/entries/123")
|
120
|
+
```
|
121
|
+
|
122
|
+
#### Fields
|
123
|
+
```
|
124
|
+
:title => The title of the entry. Defaults: "Untitled"
|
125
|
+
:actor => An Lotus::Author responsible for generating this entry.
|
126
|
+
:content => The content of the entry. Defaults: ""
|
127
|
+
:content_type => The MIME type of the content.
|
128
|
+
:published => The DateTime depicting when the entry was originally
|
129
|
+
published.
|
130
|
+
:updated => The DateTime depicting when the entry was modified.
|
131
|
+
:url => The canonical url of the entry.
|
132
|
+
:id => The unique id that identifies this entry.
|
133
|
+
:activity => The activity this entry represents. Either a single string
|
134
|
+
denoting what type of object this entry represents, or an
|
135
|
+
entire Lotus::Activity when a more detailed description is
|
136
|
+
appropriate.
|
137
|
+
:in_reply_to => An Lotus::Activity for which this entry is a response.
|
138
|
+
Or an array of Lotus::Activity's that this entry is a
|
139
|
+
response to. Use this when this Activity is a reply
|
140
|
+
to an existing Activity.
|
141
|
+
```
|
142
|
+
|
143
|
+
### Author
|
144
|
+
|
145
|
+
This represents a person that creates or contributes content in the feed.
|
146
|
+
Feed and Activity can both have one or more Authors or Contributors. One can
|
147
|
+
represent a great deal of information about a person.
|
148
|
+
|
149
|
+
#### Usage
|
150
|
+
|
151
|
+
```
|
152
|
+
author = Lotus::Author.new(:name => "wilkie",
|
153
|
+
:uri => "https://rstat.us/users/wilkie",
|
154
|
+
:email => "wilkie@xomb.org",
|
155
|
+
:preferredUsername => "wilkie",
|
156
|
+
:organization => {:name => "Hackers of the Severed Hand",
|
157
|
+
:department => "Software",
|
158
|
+
:title => "Founder"},
|
159
|
+
:gender => "androgynous",
|
160
|
+
:display_name => "Dave Wilkinson",
|
161
|
+
:birthday => Time.new(1987, 2, 9))
|
162
|
+
```
|
163
|
+
|
164
|
+
#### Fields
|
165
|
+
|
166
|
+
```
|
167
|
+
:name => The name of the author. Defaults: "anonymous"
|
168
|
+
:id => The identifier that uniquely identifies the
|
169
|
+
contact.
|
170
|
+
:nickname => The nickname of the contact.
|
171
|
+
:gender => The gender of the contact.
|
172
|
+
:note => A note for this contact.
|
173
|
+
:display_name => The display name for this contact.
|
174
|
+
:preferred_username => The preferred username for this contact.
|
175
|
+
:updated => A DateTime representing when this contact was
|
176
|
+
last updated.
|
177
|
+
:published => A DateTime representing when this contact was
|
178
|
+
originally created.
|
179
|
+
:birthday => A DateTime representing a birthday for this
|
180
|
+
contact.
|
181
|
+
:anniversary => A DateTime representing an anniversary for this
|
182
|
+
contact.
|
183
|
+
:extended_name => A Hash representing the name of the contact.
|
184
|
+
:formatted => The full name of the contact
|
185
|
+
:family_name => The family name. "Last name" in Western contexts.
|
186
|
+
:given_name => The given name. "First name" in Western contexts.
|
187
|
+
:middle_name => The middle name.
|
188
|
+
:honorific_prefix => "Title" in Western contexts. (e.g. "Mr." "Mrs.")
|
189
|
+
:honorific_suffix => "Suffix" in Western contexts. (e.g. "Esq.")
|
190
|
+
:organization => A Hash representing the organization of which the
|
191
|
+
contact belongs.
|
192
|
+
:name => The name of the organization (e.g. company, school,
|
193
|
+
etc) This field is required. Will be used for sorting.
|
194
|
+
:department => The department within the organization.
|
195
|
+
:title => The title or role within the organization.
|
196
|
+
:type => The type of organization. Canonical values include
|
197
|
+
"job" or "school"
|
198
|
+
:start_date => A DateTime representing when the contact joined
|
199
|
+
the organization.
|
200
|
+
:end_date => A DateTime representing when the contact left the
|
201
|
+
organization.
|
202
|
+
:location => The physical location of this organization.
|
203
|
+
:description => A free-text description of the role this contact
|
204
|
+
played in this organization.
|
205
|
+
:account => A Hash describing the authorative account for the
|
206
|
+
author.
|
207
|
+
:domain => The top-most authoriative domain for this account. (e.g.
|
208
|
+
"twitter.com") This is the primary field. Is required.
|
209
|
+
Used for sorting.
|
210
|
+
:username => An alphanumeric username, typically chosen by the user.
|
211
|
+
:userid => A user id, typically assigned, that uniquely refers to
|
212
|
+
the user.
|
213
|
+
:address => A Hash describing the address of the contact.
|
214
|
+
:formatted => A formatted representating of the address. May
|
215
|
+
contain newlines.
|
216
|
+
:street_address => The full street address. May contain newlines.
|
217
|
+
:locality => The city or locality component.
|
218
|
+
:region => The state or region component.
|
219
|
+
:postal_code => The zipcode or postal code component.
|
220
|
+
:country => The country name component.
|
221
|
+
:uri => The uri that uniquely identifies this author.
|
222
|
+
:email => The email of the author.
|
223
|
+
```
|
224
|
+
|
225
|
+
## TODO
|
226
|
+
|
227
|
+
* General cleanup and abstraction of elements of Atom et al that aren't very consistent or useful.
|
228
|
+
* Add a persistence layer and allow this to be mixed with Rails and Sinatra style models.
|
229
|
+
* Add rails/sinatra aides to allow rapid development of Lotus powered websites.
|
230
|
+
* Add already written osub/opub functionality to allow this gem to subscribe directly to other Lotus powered websites.
|
231
|
+
* Add Webfinger user identity and verification written in the Salmon library and pull the remaining logic out of rstat.us.
|
232
|
+
* Add JSON backend.
|
233
|
+
* Write a PuSH hub to aid in small-scale deployment. (Possibly as a detached project. Lotus talks to the hub via a socket.)
|
data/Rakefile
ADDED
data/lib/lotus.rb
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'lotus/feed'
|
2
|
+
require 'lotus/author'
|
3
|
+
require 'lotus/activity'
|
4
|
+
require 'lotus/identity'
|
5
|
+
require 'lotus/notification'
|
6
|
+
require 'lotus/link'
|
7
|
+
|
8
|
+
require 'lotus/crypto'
|
9
|
+
|
10
|
+
require 'lotus/subscription'
|
11
|
+
require 'lotus/publisher'
|
12
|
+
|
13
|
+
# This module contains elements that allow federated interaction. It also
|
14
|
+
# contains methods to construct these objects from external sources.
|
15
|
+
module Lotus
|
16
|
+
require 'libxml'
|
17
|
+
|
18
|
+
# This module isolates Atom generation.
|
19
|
+
module Atom; end
|
20
|
+
|
21
|
+
require 'lotus/atom/feed'
|
22
|
+
require 'net/http'
|
23
|
+
require 'redfinger'
|
24
|
+
|
25
|
+
# The order to respect atom links
|
26
|
+
MIME_ORDER = ['application/atom+xml',
|
27
|
+
'application/rss+xml',
|
28
|
+
'application/xml']
|
29
|
+
|
30
|
+
# Will yield an OStatus::Identity for the given fully qualified name
|
31
|
+
# (i.e. "user@domain.tld")
|
32
|
+
def self.discover_identity(name)
|
33
|
+
xrd = Redfinger.finger(name)
|
34
|
+
|
35
|
+
# magic-envelope public key
|
36
|
+
public_key = find_link(xrd, 'magic-public-key')
|
37
|
+
public_key = public_key.split(",")[1] || ""
|
38
|
+
|
39
|
+
# ostatus notification endpoint
|
40
|
+
salmon_url = find_link(xrd, 'salmon')
|
41
|
+
|
42
|
+
# pump.io authentication endpoint
|
43
|
+
dialback_url = find_link(xrd, 'dialback')
|
44
|
+
|
45
|
+
# pump.io activity endpoints
|
46
|
+
activity_inbox_endpoint = find_link(xrd, 'activity-inbox')
|
47
|
+
activity_outbox_endpoint = find_link(xrd, 'activity-outbox')
|
48
|
+
|
49
|
+
# profile page
|
50
|
+
profile_page = find_link(xrd, 'http://webfinger.net/rel/profile-page')
|
51
|
+
|
52
|
+
Identity.new(:public_key => public_key,
|
53
|
+
:profile_page => profile_page,
|
54
|
+
:salmon_endpoint => salmon_url,
|
55
|
+
:dialback_endpoint => dialback_url,
|
56
|
+
:activity_inbox_endpoint => activity_inbox_endpoint,
|
57
|
+
:activity_outbox_endpoint => activity_outbox_endpoint)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Will yield an Lotus::Author for the given person.
|
61
|
+
#
|
62
|
+
# identity: Can be a String containing a fully qualified name (i.e.
|
63
|
+
# "user@domain.tld") or a previously resolved Lotus::Identity.
|
64
|
+
def self.discover_author(identity)
|
65
|
+
if identity.is_a? String
|
66
|
+
identity = self.discover_identity(identity)
|
67
|
+
end
|
68
|
+
|
69
|
+
return nil if identity.nil? || identity.profile_page.nil?
|
70
|
+
|
71
|
+
# Discover Author information
|
72
|
+
|
73
|
+
# Pull profile page
|
74
|
+
# Look for a feed to pull
|
75
|
+
feed = self.discover_feed(identity.profile_page)
|
76
|
+
feed.authors.first
|
77
|
+
end
|
78
|
+
|
79
|
+
# Will yield a Lotus::Feed object representing the feed at the given url
|
80
|
+
# or identity.
|
81
|
+
#
|
82
|
+
# Usage:
|
83
|
+
# feed = Lotus.discover_feed("https://rstat.us/users/wilkieii/feed")
|
84
|
+
#
|
85
|
+
# i = Lotus.discover_identity("wilkieii@rstat.us")
|
86
|
+
# feed = Lotus.discover_feed(i)
|
87
|
+
def self.discover_feed(url_or_identity, content_type = "application/atom+xml")
|
88
|
+
if url_or_identity =~ /^(?:acct:)?[^@]+@[^@]+\.[^@]+$/
|
89
|
+
url_or_identity = Lotus::discover_identity(url_or_identity)
|
90
|
+
end
|
91
|
+
|
92
|
+
if url_or_identity.is_a? Lotus::Identity
|
93
|
+
return self.discover_feed(url_or_identity.profile_page)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Atom is default type to attempt to retrieve
|
97
|
+
content_type ||= "application/atom+xml"
|
98
|
+
|
99
|
+
url = url_or_identity
|
100
|
+
|
101
|
+
if url =~ /^http[s]?:\/\//
|
102
|
+
# Url is an internet resource
|
103
|
+
response = Lotus::pull_url(url, content_type)
|
104
|
+
|
105
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
106
|
+
|
107
|
+
content_type = response.content_type
|
108
|
+
str = response.body
|
109
|
+
else
|
110
|
+
str = open(url).read
|
111
|
+
end
|
112
|
+
|
113
|
+
case content_type
|
114
|
+
when 'application/atom+xml', 'application/rss+xml', 'application/xml',
|
115
|
+
'xml', 'atom', 'rss', 'atom+xml', 'rss+xml'
|
116
|
+
xml_str = str
|
117
|
+
|
118
|
+
self.feed_from_string(xml_str, content_type)
|
119
|
+
when 'text/html'
|
120
|
+
html_str = str
|
121
|
+
|
122
|
+
# Discover the feed
|
123
|
+
doc = Nokogiri::HTML::Document.parse(html_str)
|
124
|
+
links = doc.xpath("//link[@rel='alternate']").map {|el|
|
125
|
+
{:type => el.attributes['type'].to_s,
|
126
|
+
:href => el.attributes['href'].to_s}
|
127
|
+
}.select{|e|
|
128
|
+
MIME_ORDER.include? e[:type]
|
129
|
+
}.sort {|a, b|
|
130
|
+
MIME_ORDER.index(a[:type]) <=>
|
131
|
+
MIME_ORDER.index(b[:type])
|
132
|
+
}
|
133
|
+
|
134
|
+
return nil if links.empty?
|
135
|
+
|
136
|
+
# Resolve relative links
|
137
|
+
link = URI::parse(links.first[:href]) rescue URI.new
|
138
|
+
|
139
|
+
unless link.scheme
|
140
|
+
link.scheme = URI::parse(url).scheme
|
141
|
+
end
|
142
|
+
|
143
|
+
unless link.host
|
144
|
+
link.host = URI::parse(url).host rescue nil
|
145
|
+
end
|
146
|
+
|
147
|
+
unless link.absolute?
|
148
|
+
link.path = File::dirname(URI::parse(url).path) \
|
149
|
+
+ '/' + link.path rescue nil
|
150
|
+
end
|
151
|
+
|
152
|
+
url = link.to_s
|
153
|
+
self.discover_feed(url, links.first[:type])
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Yield a Lotus::Feed from the given string content.
|
158
|
+
def self.feed_from_string(string, content_type = nil)
|
159
|
+
# Atom is default type to attempt to retrieve
|
160
|
+
content_type ||= "application/atom+xml"
|
161
|
+
|
162
|
+
case content_type
|
163
|
+
when 'application/atom+xml', 'application/rss+xml', 'application/xml'
|
164
|
+
Lotus::Atom::Feed.new(XML::Reader.string(string)).to_canonical
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.discover_activity(url)
|
169
|
+
self.activity_from_url(url)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Yield a Lotus::Activity from the given url.
|
173
|
+
def self.activity_from_url(url, content_type = nil)
|
174
|
+
# Atom is default type to attempt to retrieve
|
175
|
+
content_type ||= "application/atom+xml"
|
176
|
+
|
177
|
+
response = Lotus.pull_url(url, content_type)
|
178
|
+
|
179
|
+
return nil unless response.is_a?(Net::HTTPSuccess)
|
180
|
+
|
181
|
+
content_type = response.content_type
|
182
|
+
|
183
|
+
case content_type
|
184
|
+
when 'application/atom+xml', 'application/rss+xml', 'application/xml'
|
185
|
+
xml_str = response.body
|
186
|
+
self.entry_from_string(xml_str, response.content_type)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Yield a Lotus::Activity from the given string content.
|
191
|
+
def self.activity_from_string(string, content_type = "application/atom+xml")
|
192
|
+
content_type ||= "application/atom+xml"
|
193
|
+
|
194
|
+
case content_type
|
195
|
+
when 'application/atom+xml', 'application/rss+xml', 'application/xml'
|
196
|
+
Lotus::Atom::Entry.new(XML::Reader.string(string)).to_canonical
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
# :nodoc:
|
203
|
+
def self.pull_url(url, content_type = nil, limit = 10)
|
204
|
+
# Atom is default type to attempt to retrieve
|
205
|
+
content_type ||= "application/atom+xml"
|
206
|
+
|
207
|
+
uri = URI(url)
|
208
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
209
|
+
request.content_type = content_type
|
210
|
+
|
211
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
212
|
+
if uri.scheme == 'https'
|
213
|
+
http.use_ssl = true
|
214
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
215
|
+
end
|
216
|
+
|
217
|
+
response = http.request(request)
|
218
|
+
|
219
|
+
if response.is_a?(Net::HTTPRedirection) && limit > 0
|
220
|
+
location = response['location']
|
221
|
+
Lotus.pull_url(location, content_type, limit - 1)
|
222
|
+
else
|
223
|
+
response
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# :nodoc:
|
228
|
+
def self.find_link(xrd, rel)
|
229
|
+
link = xrd.links.find {|l| l['rel'].downcase == rel} || {}
|
230
|
+
link.fetch("href") { nil }
|
231
|
+
end
|
232
|
+
end
|