discodactyl 0.3.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,8 +1,3 @@
1
1
  source :rubygems
2
2
 
3
- gem 'actionpack', '~>2.3.8'
4
- gem 'feedzirra', '~>0.0.23'
5
- gem 'mofo', '~>0.2.16'
6
- gem 'nokogiri', '~>1.4.2'
7
- gem 'rake', '~>0.8.7'
8
- gem 'rr', '~>0.10.11'
3
+ gemspec
data/MANIFEST CHANGED
@@ -5,7 +5,7 @@ Gemfile
5
5
  INSTALL
6
6
  MANIFEST
7
7
  NEWS
8
- README
8
+ README.rdoc
9
9
  Rakefile
10
10
  TODO
11
11
  bin/webfinger
@@ -13,7 +13,13 @@ discodactyl.gemspec
13
13
  lib/discodactyl.rb
14
14
  lib/discodactyl/acct_uri.rb
15
15
  lib/discodactyl/host_meta.rb
16
+ lib/discodactyl/host_meta_jrd.rb
17
+ lib/discodactyl/jrd.rb
18
+ lib/discodactyl/jrd/document.rb
19
+ lib/discodactyl/jrd/link.rb
20
+ lib/discodactyl/known_rels.rb
16
21
  lib/discodactyl/link_header.rb
22
+ lib/discodactyl/log.rb
17
23
  lib/discodactyl/resource_discovery.rb
18
24
  lib/discodactyl/uri_template.rb
19
25
  lib/discodactyl/xrd.rb
@@ -22,6 +28,8 @@ lib/discodactyl/xrd/link.rb
22
28
  test/test_acct_uri.rb
23
29
  test/test_helper.rb
24
30
  test/test_host_meta.rb
31
+ test/test_host_meta_jrd.rb
32
+ test/test_jrd_document.rb
25
33
  test/test_link_header.rb
26
34
  test/test_resource_discovery.rb
27
35
  test/test_uri_template.rb
@@ -1,9 +1,10 @@
1
1
  = Discodactyl
2
2
 
3
- * http://dactylo.us
4
- * http://github.com/josephholsten/discodactyl
5
- * http://josephholsten.lighthouseapp.com/projects/36040-discodactyl
6
- * mailto:joseph@josephholsten.com
3
+ Code :: http://github.com/josephholsten/discodactyl
4
+ Bugs :: http://josephholsten.lighthouseapp.com/projects/36040-discodactyl
5
+ Tests :: http://discodactyl-ci.heroku.com/
6
+ Docs :: http://rdoc.info/projects/josephholsten/discodactyl
7
+ Author :: mailto:joseph@josephholsten.com
7
8
 
8
9
  == DESCRIPTION:
9
10
 
@@ -18,9 +19,22 @@ Discodactyl is an experimental toolkit for XRD service discovery documents and r
18
19
  * acct: URI parsing.
19
20
  * Minimal URI Template parsing and application.
20
21
  * Probably filled with bugs. Don't forget your bug powder.
22
+ * JRD parsing, querying.
23
+ * Yahoo 'webfinger' doesn't work. Sadly, they need to update their format to the most recent one.
21
24
 
22
25
  == SYNOPSIS:
23
26
 
27
+ For command-line fiends:
28
+
29
+ $ webfinger bradfitz@gmail.com
30
+ Profile: http://www.google.com/profiles/bradfitz
31
+ Profile data: http://www-opensocial.googleusercontent.com/api/people/115863474911002159675/
32
+ describedby: http://www.google.com/profiles/bradfitz
33
+ Contacts: http://www-opensocial.googleusercontent.com/api/people/
34
+ Status: Hah... about 4 hours ago
35
+
36
+ For ruby mongers:
37
+
24
38
  # All of WebFinger! In very few, if long and pointy, lines!
25
39
  require 'discodactyl'
26
40
 
@@ -46,7 +60,7 @@ This software is also available in a permissive license. Please contact Joseph H
46
60
 
47
61
  (The GNU Affero General Public License)
48
62
 
49
- Copyright (c) 2009, 2010 Joseph Anthony Pasquale Holsten
63
+ Copyright (c) 2009, 2010, 2011 Joseph Anthony Pasquale Holsten
50
64
 
51
65
  This program is free software: you can redistribute it and/or modify
52
66
  it under the terms of the GNU Affero General Public License as published by
data/TODO CHANGED
@@ -14,11 +14,28 @@
14
14
  * REXML support
15
15
  * collect common rel values into a enum module
16
16
  * put license info in every file
17
+ * find out if ruby has a default debug flag
18
+ * find well known uris via WellKnown.find(uri, :host_meta)
17
19
 
18
20
  Reference Specs
19
- * host meta http://tools.ietf.org/html/draft-hammer-hostmeta-05
21
+ * host meta http://tools.ietf.org/html/draft-hammer-hostmeta-13
20
22
  * webfinger
21
- * xrd http://www.oasis-open.org/committees/download.php/36542/xrd-1.0-wd15.html
22
- * lrdd http://tools.ietf.org/html/draft-hammer-discovery-04
23
- * link header http://tools.ietf.org/html/draft-nottingham-http-link-header-08
24
- * versioning-rels http://tools.ietf.org/html/draft-brown-versioning-link-relations-07
23
+ * xrd http://docs.oasis-open.org/xri/xrd/v1.0/xrd-1.0.html
24
+ * lrdd http://tools.ietf.org/html/draft-hammer-discovery-06
25
+ * link header http://tools.ietf.org/html/rfc5988
26
+ * versioning-rels http://tools.ietf.org/html/rfc5829
27
+
28
+ Interesting webfinger variants
29
+ * joseph@josephholsten.com
30
+ * romeda@gmail.com
31
+ * will@willmeyer.com
32
+ * dclinton@gmail.com
33
+ * bradfitz@gmail.com
34
+ * dclinton@yahoo.com
35
+ * beestage@yahoo.com
36
+ * gffletch@aol.com
37
+
38
+ other clients
39
+ * webfingerclient-dclinton.appspot.com/
40
+ * webfinger.org/lookup/darron@froese.org
41
+ * web.mailana.com/labs/findbyemail/
data/bin/webfinger CHANGED
@@ -7,14 +7,27 @@ require 'open-uri'
7
7
  require 'action_view'
8
8
  require 'mofo'
9
9
  require 'feedzirra'
10
+ require 'logger'
11
+
12
+ log = Logger.new(STDOUT)
13
+ Discodactyl.log = log
10
14
 
11
15
  if ARGV.length == 0
12
16
  puts "Usage: #{__FILE__} user@host"
13
17
  exit 0
14
18
  end
15
19
 
20
+ if ARGV[0].match /^-d/
21
+ log.level = Logger::DEBUG
22
+ # ARGV.unshift
23
+ ARGV.shift
24
+ else
25
+ log.level = Logger::WARN
26
+ end
27
+
16
28
  # parse identifier
17
- acct = URI::ACCT.parse(ARGV[0])
29
+ raw_acct = ARGV[0]
30
+ acct = URI::ACCT.parse(raw_acct)
18
31
 
19
32
  # Perform LRDD discovery on acct with the webfinger relation
20
33
  finger_rel = "lrdd"
@@ -22,41 +35,73 @@ begin
22
35
  webfinger_uris = Discodactyl::ResourceDiscovery.get_uris_by_rel(acct, finger_rel, 'uri' => acct)
23
36
  rescue Discodactyl::HostMetaHTTPError => error
24
37
  if error.io.status[1] == 'Not Found'
25
- abort "No host meta webfinger information found at #{error.io.base_uri}"
38
+ abort "No host meta webfinger information found at #{error.io.base_uri}. Is the file there?"
26
39
  else
27
40
  raise
28
41
  end
42
+ rescue Discodactyl::HostMetaSocketError => error
43
+ abort "Could not connect to #{error.host}:#{error.port}. Is your internet connection working?"
29
44
  end
30
45
 
31
46
  abort "URI didn't have any linked webfinger URIs" if webfinger_uris.empty?
32
47
 
33
48
  # retrieve user disco doc
34
- disco_res = open(webfinger_uris.first)
49
+ begin
50
+ disco_uri = webfinger_uris.first
51
+ disco_res = open(disco_uri)
52
+ rescue OpenURI::HTTPError
53
+ abort "Couldn't open the disco doc at #{disco_uri}. Is your host-meta pointed at the right place?"
54
+ end
35
55
 
36
56
  # parse disco doc
37
- disco = Discodactyl::XRD::Document.parse(disco_res)
57
+ begin
58
+ disco = Discodactyl::JRD::Document.parse(disco_res)
59
+ log.debug "disco doc <#{webfinger_uris.first}>:\n#{disco.inspect}\n"
60
+ rescue Discodactyl::JRD::ParseError => e
61
+ log.debug "disco doc doesn't seem to be a JRD #{e.message}"
62
+ disco_res.rewind
63
+ disco = Discodactyl::XRD::Document.parse(disco_res)
64
+ log.debug "disco doc <#{webfinger_uris.first}>:\n#{disco}\n"
65
+ end
66
+
67
+ log.debug "links: #{disco.links.map{|r| [r.rel, r.type, r.href]}.inspect}"
68
+ log.debug "rels: #{disco.rels.inspect}"
69
+
70
+ if disco.subject
71
+ log.debug "subject: #{disco.subject}"
72
+ unless acct.to_s == disco.subject.to_s
73
+ abort "Disco doc's subject <#{disco.subject}> doesn't match account we're webfingering <#{acct}>"
74
+ end
75
+ else
76
+ log.warn("Disco doc didn't list a subject. Assuming everything is well.")
77
+ end
38
78
 
39
79
  hcard_uri = disco.uris_by_rel('http://microformats.org/profile/hcard').first
40
- hcard = hCard.find(:first => hcard_uri)
80
+ hcard = hCard.find(:first => hcard_uri) rescue
41
81
  if hcard
42
82
  puts "Name: #{hcard.fn.gsub(/\s+/, ' ')}" if hcard.properties.include?('fn')
43
83
  puts "Title: #{hcard.title}" if hcard.properties.include?('title')
44
84
  puts "Organization: #{hcard.org}" if hcard.properties.include?('org')
45
85
  end
46
86
 
47
- openid = disco.uris_by_rel('http://specs.openid.net/auth/').first
48
- puts "OpenID: #{openid}" if openid
49
-
50
- profile = disco.uris_by_rel('http://webfinger.net/rel/profile-page').first
51
- puts "Profile: #{profile}" if profile
87
+ links_with_titles = disco.links.select{|l| l.title.present? }
88
+ links_with_titles.each do |link|
89
+ puts "#{link.title}: #{link.to_uri}"
90
+ end
52
91
 
53
- contacts = disco.uris_by_rel('http://portablecontacts.net/spec/1.0').first
54
- puts "Contacts: #{contacts}" if contacts
92
+ Discodactyl::KNOWN_RELS.each do |name, rel|
93
+ uri = disco.uris_by_rel(rel).first
94
+ puts "#{name}: #{uri}" if uri
95
+ end
55
96
 
56
97
  activities = disco.uris_by_rel('http://schemas.google.com/g/2010#updates-from').first
57
98
  if activities
58
99
  feed = Feedzirra::Feed.fetch_and_parse(activities)
59
100
  entry = feed.entries.first
60
- include ActionView::Helpers::DateHelper
61
- puts "Status: #{Loofah::Helpers.strip_tags(entry.content)} #{time_ago_in_words(entry.published)} ago"
101
+ if entry
102
+ include ActionView::Helpers::DateHelper
103
+ silence_warnings do
104
+ puts "Status: #{Loofah::Helpers.strip_tags(entry.content)} #{time_ago_in_words(entry.published)} ago"
105
+ end
106
+ end
62
107
  end
data/discodactyl.gemspec CHANGED
@@ -7,17 +7,19 @@ Gem::Specification.new do |spec|
7
7
  spec.email = "joseph@josephholsten.com"
8
8
  spec.homepage = "http://dactylo.us"
9
9
  spec.description = %q{Discodactyl is an experimental toolkit for XRD service discovery documents and related protocols. It includes implementations of XRD URITemplate Link-Patterns, basic site-meta support, HTTP Link header parsing, acct: URIs and a webfinger poking stick.}
10
- spec.extra_rdoc_files = %w[ AUTHORS CHANGELOG COPYING INSTALL NEWS README TODO ]
10
+ spec.extra_rdoc_files = %w[ AUTHORS CHANGELOG COPYING INSTALL NEWS README.rdoc TODO ]
11
11
  spec.rdoc_options << "--charset=UTF-8" <<
12
12
  "--title" << "Discodactyl Documentation" <<
13
- "--main" << "README"
13
+ "--main" << "README.rdoc"
14
14
  spec.version = version
15
15
  spec.summary = spec.description.split(/\.\s+/).first
16
16
  spec.files = File.read("MANIFEST").split(/\r?\n\r?/)
17
17
  spec.executables = spec.files.grep(/^bin/) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(/^test\/.*test_.*\.rb$/)
19
- spec.add_runtime_dependency 'nokogiri', '~>1.4.0'
20
- spec.add_runtime_dependency 'actionpack', '~>2.3.0'
19
+ spec.add_runtime_dependency 'nokogiri', '~>1.4.2'
20
+ spec.add_runtime_dependency 'actionpack', '~>3.0.0'
21
21
  spec.add_runtime_dependency 'feedzirra', '~>0.0.23'
22
- spec.add_runtime_dependency 'mofo', '~>0.2.0'
22
+ spec.add_runtime_dependency 'mofo', '~>0.2.16'
23
+ spec.add_development_dependency 'rr', '~>0.10.11'
24
+ spec.add_development_dependency 'rake', '~>0.8.7'
23
25
  end
@@ -13,7 +13,9 @@ module URI # :nodoc:
13
13
  /^(acct:)?(.*)$/ =~ str
14
14
  scheme = 'acct'
15
15
  opaque = $2
16
- self.new(scheme, nil, nil, nil, nil, nil, opaque, nil, nil)
16
+ acct = self.new(scheme, nil, nil, nil, nil, nil, opaque, nil, nil)
17
+ Discodactyl.log.debug("Parsed #{str.inspect} into URI #{acct.inspect}") if Discodactyl.log
18
+ acct
17
19
  end
18
20
 
19
21
 
@@ -1,5 +1,6 @@
1
1
  require 'discodactyl/xrd'
2
2
  require 'open-uri'
3
+ require 'socket'
3
4
 
4
5
  module Discodactyl # :nodoc
5
6
  # Convienient access to host metadata and individual resources controlled by
@@ -15,6 +16,7 @@ module Discodactyl # :nodoc
15
16
  # Take a URI and retrieve its HostMeta document
16
17
  def from_uri(uri)
17
18
  uri = get_uri_from_uri(uri)
19
+ Discodactyl.log.debug("got xrd host-meta uri: #{uri}") if Discodactyl.log
18
20
  begin
19
21
  raw = uri.open
20
22
  rescue OpenURI::HTTPError => error
@@ -24,6 +26,8 @@ module Discodactyl # :nodoc
24
26
  else
25
27
  raise
26
28
  end
29
+ rescue ::SocketError => error
30
+ raise HostMetaSocketError.new(error.message, uri.host, uri.port)
27
31
  end
28
32
  self.parse raw
29
33
  end
@@ -31,4 +35,12 @@ module Discodactyl # :nodoc
31
35
  end
32
36
  class HostMetaHTTPError < OpenURI::HTTPError # :nodoc:
33
37
  end
38
+ class HostMetaSocketError < StandardError # :nodoc:
39
+ attr_accessor :host, :port
40
+ def initialize(msg = nil, host = nil, port = nil)
41
+ message = msg
42
+ @host = host
43
+ @port = port
44
+ end
45
+ end
34
46
  end
@@ -0,0 +1,35 @@
1
+ require 'open-uri'
2
+ require 'active_support'
3
+ require 'socket'
4
+ require "discodactyl/uri_template"
5
+ require "discodactyl/jrd"
6
+ require 'discodactyl/log'
7
+
8
+ module Discodactyl
9
+ class HostMetaJRD < Discodactyl::JRD::Document
10
+ # Take a URI and return the URI for its HostMeta document
11
+ def self.get_uri_from_uri(uri)
12
+ host = uri.respond_to?('host') ? uri.host : uri
13
+ URI.parse('http://'+ host + '/.well-known/host-meta.json')
14
+ end
15
+
16
+ # Take a URI and retrieve its HostMeta document
17
+ def self.from_uri(uri)
18
+ uri = get_uri_from_uri(uri)
19
+ Discodactyl.log.debug("got jrd host-meta uri: #{uri}") if Discodactyl.log
20
+ begin
21
+ raw = uri.open
22
+ rescue OpenURI::HTTPError => error
23
+ if error.io.status[1] == 'Not Found'
24
+ error.io.base_uri = uri
25
+ raise HostMetaHTTPError.new(error.message, error.io)
26
+ else
27
+ raise
28
+ end
29
+ rescue ::SocketError => error
30
+ raise HostMetaSocketError.new(error.message, uri.host, uri.port)
31
+ end
32
+ self.parse raw
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,49 @@
1
+ require 'nokogiri'
2
+ require 'active_support'
3
+ require 'discodactyl/jrd/link'
4
+ require "discodactyl/uri_template"
5
+
6
+ module Discodactyl # :nodoc:
7
+ module JRD # :nodoc:
8
+ class Document < Hash
9
+ def self.parse(raw)
10
+ begin
11
+ Discodactyl.log.debug("parsing jrd: #{raw}") if Discodactyl.log
12
+ decoded = ActiveSupport::JSON.decode(raw)
13
+ rescue => e
14
+ raise ParseError.new(e.message)
15
+ end
16
+ self.[](decoded)
17
+ end
18
+
19
+ def uris_by_rel(rel, params={})
20
+ links_by_rel(rel).map do |link|
21
+ link.href || link.template.to_uri(params)
22
+ end
23
+ end
24
+
25
+ def links_by_rel(rel)
26
+ if self.has_key? 'links' and self['links'].has_key? rel
27
+ self['links'][rel].map{|link| Link.parse(link.merge('rel'=>rel)) }
28
+ else
29
+ []
30
+ end
31
+ end
32
+
33
+ def links
34
+ rels.map do |rel, links|
35
+ links.map {|link| Link.parse(link.merge('rel' => rel)) }
36
+ end.flatten
37
+ end
38
+
39
+ def rels
40
+ self['links'] || []
41
+ end
42
+
43
+ def subject
44
+ self['subject']
45
+ end
46
+ end
47
+ class ParseError < StandardError; end
48
+ end
49
+ end
@@ -0,0 +1,25 @@
1
+ require 'discodactyl/uri_template'
2
+
3
+ module Discodactyl # :nodoc:
4
+ module JRD # :nodoc:
5
+ class Link < Struct.new(:href, :template, :rel, :title, :type)
6
+ attr_accessor :raw
7
+ class << self
8
+ def parse(element)
9
+ self.new.tap do |link|
10
+ begin
11
+ link.rel = element['rel']
12
+ link.type = element['type']
13
+ link.href = element['href']
14
+ link.title = element['title']
15
+ link.template = URITemplate.new(element['template']) unless link.href
16
+ link.raw = element
17
+ rescue
18
+ raise "Couldn't parse #{link} into a JRD Link"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,2 @@
1
+ require 'discodactyl/jrd/document'
2
+ require 'discodactyl/jrd/link'
@@ -0,0 +1,12 @@
1
+ module Discodactyl # :nodoc:
2
+ KNOWN_RELS = {
3
+ 'OpenID' => 'http://specs.openid.net/auth/',
4
+ 'OpenID Provider' => 'http://specs.openid.net/auth/2.0/provider',
5
+ 'OpenID Provider' => 'openid2.provider',
6
+ "Profile" => 'http://webfinger.net/rel/profile-page',
7
+ "Profile data" => 'http://portablecontacts.net/spec/1.0#me',
8
+ "Contacts" => 'http://portablecontacts.net/spec/1.0',
9
+ "describedby" => 'describedby', # from POWDER
10
+ "Webfinger/LRDD" => 'lrdd'
11
+ }
12
+ end
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/object/misc'
1
+ # require 'active_support/core_ext/object/misc'
2
2
 
3
3
  module Discodactyl # :nodoc:
4
4
  # Access to web links stored in HTTP Link header-fields. See also:
@@ -14,7 +14,7 @@ module Discodactyl # :nodoc:
14
14
  # link[:href] # '/'
15
15
  # link[:rel] # ['http://example.net/foo']
16
16
  def parse(string)
17
- returning(params = LinkHeader.new) do
17
+ LinkHeader.new.tap do |params|
18
18
  if string =~ /^<([^>]+)>(.*)$/
19
19
  params[:href] = $1
20
20
  $2.split(/;\s*/).each do |part|
@@ -0,0 +1,5 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+
3
+ module Discodactyl # :nodoc:
4
+ mattr_accessor :log
5
+ end
@@ -1,6 +1,7 @@
1
1
  require 'open-uri'
2
2
  require 'nokogiri'
3
3
  require 'discodactyl/link_header'
4
+ require 'discodactyl/log'
4
5
 
5
6
  module Discodactyl # :nodoc:
6
7
  class ResourceDiscovery
@@ -20,6 +21,7 @@ module Discodactyl # :nodoc:
20
21
  def get_uris_by_rel(uri, rel, params = {})
21
22
  begin
22
23
  uri = URI.parse(uri.to_s) unless uri.respond_to?('open')
24
+ Discodactyl.log.debug "Getting uris for uri #{uri.inspect} by rel #{rel.inspect} with params #{params.inspect}" if Discodactyl.log
23
25
  resource = uri.open
24
26
  rescue OpenURI::HTTPError => e
25
27
  status = e.io.status[0] # => 3xx, 4xx, or 5xx
@@ -61,9 +63,28 @@ module Discodactyl # :nodoc:
61
63
  end
62
64
  end
63
65
  unless uris
64
- host_meta = Discodactyl::HostMeta.from_uri uri
66
+ Discodactyl.log.debug("getting jrd host-meta") if Discodactyl.log
67
+ begin
68
+ host_meta = Discodactyl::HostMetaJRD.from_uri uri
69
+ rescue => error
70
+ Discodactyl.log.debug("Error parsing jrd host-meta: #{error}") if Discodactyl.log
71
+ end
72
+ Discodactyl.log.debug("finding uris in jrd host-meta") if Discodactyl.log
73
+ uris = host_meta.uris_by_rel(rel, params) if host_meta
74
+ end
75
+
76
+ unless uris
77
+ Discodactyl.log.debug("Getting xrd host-meta") if Discodactyl.log
78
+ begin
79
+ host_meta = Discodactyl::HostMeta.from_uri uri
80
+ rescue => error
81
+ Discodactyl.log.debug("Error getting xrd host-meta: #{error}") if Discodactyl.log
82
+ raise
83
+ end
84
+ Discodactyl.log.debug("finding uris in host-meta") if Discodactyl.log
65
85
  uris = host_meta.uris_by_rel(rel, params)
66
86
  end
87
+ Discodactyl.log.debug "URIs: #{uris.inspect}" if Discodactyl.log
67
88
  uris
68
89
  end
69
90
 
@@ -8,6 +8,7 @@ module XRD # :nodoc:
8
8
  class << self
9
9
  def parse(string)
10
10
  raw = Nokogiri::XML(string)
11
+ Discodactyl.log.debug("parsing xrd: #{raw}") if Discodactyl.log
11
12
  doc = self.new
12
13
 
13
14
  doc.raw = raw
@@ -29,7 +30,8 @@ module XRD # :nodoc:
29
30
  end
30
31
 
31
32
  def links_by_rel(rel)
32
- linkelems_by_rel(rel).map {|e| Link.parse(e) }
33
+ # linkelems_by_rel(rel).map {|e| Link.parse(e) }
34
+ find_all_links_by_rel(rel)
33
35
  end
34
36
 
35
37
  def uris_by_rel(rel, params = {})
@@ -54,6 +56,10 @@ module XRD # :nodoc:
54
56
  def find_link_by_id(link_id)
55
57
  links.find {|link| link.id == link_id}
56
58
  end
59
+
60
+ def find_all_links_by_rel(rel)
61
+ links.find_all {|link| link.rel == rel }
62
+ end
57
63
 
58
64
  def ids
59
65
  links.map(&:id).reject(&:nil?)
@@ -62,6 +68,10 @@ module XRD # :nodoc:
62
68
  def to_s
63
69
  raw.to_s
64
70
  end
71
+
72
+ def rels
73
+ links.map(&:rel).reject(&:nil?).uniq
74
+ end
65
75
 
66
76
  def generate_tag_uri
67
77
  scheme = 'tag'
@@ -70,6 +80,10 @@ module XRD # :nodoc:
70
80
  specific = "/xrd/link/#{rand(2**10)}"
71
81
  "#{scheme}:#{authority},#{date}:#{specific}"
72
82
  end
83
+
84
+ def subject
85
+ raw.xpath('/xrd:XRD/xrd:Subject', XMLNS).text
86
+ end
73
87
  end
74
88
  end
75
89
  end
@@ -1,26 +1,25 @@
1
1
  require 'nokogiri'
2
- require 'active_support/core_ext/object/misc'
3
2
  require 'discodactyl/uri_template'
4
3
 
5
4
  module Discodactyl # :nodoc:
6
5
  module XRD # :nodoc:
7
- class Link
6
+ class Link < Struct.new(:href, :template, :rel, :title, :type)
7
+ attr_accessor :raw
8
8
  class << self
9
9
  def parse(element)
10
- returning(link = self.new) do
10
+ self.new.tap do |link|
11
11
  link.rel = element['rel']
12
12
  link.type = element['type']
13
13
  link.href = element['href']
14
+ link.title = element['title']
14
15
  link.template = URITemplate.new(element['template']) unless link.href
15
16
  link.raw = element
16
17
  end
17
18
  end
18
19
  end
19
20
 
20
- attr_accessor :href, :template, :rel, :type, :raw
21
-
22
21
  def to_uri(params = {})
23
- @href || @template.to_uri(params)
22
+ href || template.to_uri(params)
24
23
  end
25
24
 
26
25
  def id
data/lib/discodactyl.rb CHANGED
@@ -1,9 +1,13 @@
1
1
  require 'discodactyl/acct_uri'
2
2
  require 'discodactyl/host_meta'
3
+ require 'discodactyl/host_meta_jrd'
4
+ require 'discodactyl/jrd'
5
+ require 'discodactyl/known_rels'
6
+ require 'discodactyl/log'
3
7
  require 'discodactyl/resource_discovery'
4
8
  require 'discodactyl/uri_template'
5
9
  require 'discodactyl/xrd'
6
10
 
7
11
  module Discodactyl # :nodoc:
8
- VERSION = '0.3.0'
12
+ VERSION = '0.4.1'
9
13
  end
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby -w
2
+ libdir = File.expand_path('../../lib', __FILE__)
3
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
4
+
5
+ require "test/unit"
6
+ require "discodactyl/host_meta_jrd"
7
+ require 'discodactyl/acct_uri'
8
+
9
+ class TestHostMetaJRD < Test::Unit::TestCase
10
+ def test_get_uri_from_host
11
+ uri = 'host.example'
12
+ expected = URI.parse 'http://host.example/.well-known/host-meta.json'
13
+ assert_equal expected, Discodactyl::HostMetaJRD.get_uri_from_uri(uri)
14
+ end
15
+
16
+ def test_get_uri_from_http
17
+ uri = URI.parse 'http://host.example/some/path'
18
+ expected = URI.parse 'http://host.example/.well-known/host-meta.json'
19
+ assert_equal expected, Discodactyl::HostMetaJRD.get_uri_from_uri(uri)
20
+ end
21
+
22
+ def test_get_uri_from_acct
23
+ uri = URI.parse 'acct:user@host.example'
24
+ expected = URI.parse 'http://host.example/.well-known/host-meta.json'
25
+ assert_equal expected, Discodactyl::HostMetaJRD.get_uri_from_uri(uri)
26
+ end
27
+
28
+ def test_from_uri
29
+ uri = URI.parse 'acct:josephholsten@localhost'
30
+ assert_equal({"links"=>{"lrdd"=>[{"href"=>"http://localhost/discovery.jrd"}]}}, Discodactyl::HostMetaJRD.from_uri(uri))
31
+ end
32
+
33
+ def test_parse
34
+ json = <<JSON
35
+ { "links": {
36
+ "lrdd": [{
37
+ "href":"http://host.example/discovery.jrd" }]}}
38
+ JSON
39
+ assert_equal({"links"=>{"lrdd"=>[{"href"=>"http://host.example/discovery.jrd"}]}}, Discodactyl::HostMetaJRD.parse(json))
40
+ end
41
+
42
+ def test_links_by_rel
43
+ jrd = Discodactyl::HostMetaJRD[{"links"=>{"lrdd"=>[{"href"=>"http://host.example/discovery.jrd"}]}}]
44
+ expected = [Discodactyl::JRD::Link.parse({'href'=>'http://host.example/discovery.jrd', 'rel' => 'lrdd'})]
45
+ assert_equal(expected, jrd.links_by_rel('lrdd'))
46
+ end
47
+
48
+ # links by rel when links is missing, when rel is missing
49
+ def test_links_by_rel_when_missing
50
+ jrd = Discodactyl::HostMetaJRD[{}]
51
+ assert_equal([], jrd.links_by_rel('lrdd'))
52
+ end
53
+
54
+ def test_uris_by_rel
55
+ jrd = Discodactyl::HostMetaJRD[{"links"=>{"lrdd"=>[{"href"=>"http://host.example/discovery.jrd"}]}}]
56
+ assert_equal(['http://host.example/discovery.jrd'], jrd.uris_by_rel('lrdd'))
57
+ end
58
+
59
+ def test_uris_by_rel_with_template
60
+ jrd = Discodactyl::HostMetaJRD[{"links"=>{"lrdd"=>[{"template"=>"http://host.example/{id}"}]}}]
61
+ assert_equal(['http://host.example/foo'], jrd.uris_by_rel('lrdd', 'id' => 'foo'))
62
+ end
63
+ end
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env ruby -w
2
+ libdir = File.expand_path('../../lib', __FILE__)
3
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
4
+ testdir = File.expand_path('../../test', __FILE__)
5
+ $LOAD_PATH.unshift(testdir) unless $LOAD_PATH.include?(testdir)
6
+
7
+ require 'test_helper'
8
+ require 'discodactyl/jrd/document'
9
+
10
+ class TestJRDParsing < Test::Unit::TestCase
11
+ def setup
12
+ @minimal_jrd_string =<<eos
13
+ {
14
+ "subject":"http://host.example/"
15
+ }
16
+ eos
17
+
18
+ # @full_jrd_string =<<eos
19
+ # {"subject":"http://host.example/","links":{"describedby":[{"href":"http://host.example/endpoint"},{"template":"http://host.example/descriptor?q={%id}"}],"webfinger":[{"href":"http://host.example/endpoint"}],"feed":[{"template":"http://host.example/descriptor?q={%id}"}]}}
20
+ # eos
21
+ # <"{\"subject\":\"http://host.example/\",\"links\":{\"describedby\":[{\"href\":\"http://host.example/endpoint\"},{\"template\":\"http://host.example/descriptor?q={%id}\"}],\"webfinger\":[{\"href\":\"http://host.example/endpoint\"}],\"feed\":[{\"template\":\"http://host.example/descriptor?q={%id}\"}]}}\n"> expected but was
22
+ # <"{\"subject\":\"http://host.example/\",\"links\":{\"webfinger\":[{\"href\":\"http://host.example/endpoint\"}],\"describedby\":[{\"href\":\"http://host.example/endpoint\"},{\"template\":\"http://host.example/descriptor?q={%id}\"}],\"feed\":[{\"template\":\"http://host.example/descriptor?q={%id}\"}]}}">.
23
+
24
+ @full_jrd_string =<<eos
25
+ {"subject":"http://host.example/","links":{"webfinger":[{"href":"http://host.example/endpoint"}],"describedby":[{"href":"http://host.example/endpoint"},{"template":"http://host.example/descriptor?q={%id}"}],"feed":[{"template":"http://host.example/descriptor?q={%id}"}]}}
26
+ eos
27
+
28
+ @raw_link_without_id = '<Link rel="http://oexchange.org/spec/0.8/rel/user-target" type="application/xrd+xml" href="http://www.example.com/linkeater/oexchange.xrd"/>'
29
+
30
+ @raw_link_without_id = '{"http://oexchange.org/spec/0.8/rel/user-target":[{"type":"application/xrd+xml", "href":"http://www.example.com/linkeater/oexchange.xrd"}]}'
31
+ @raw_link_with_id = '{"http://oexchange.org/spec/0.8/rel/user-target":[{"id":"foo","type":"application/xrd+xml", "href":"http://www.example.com/linkeater/oexchange.xrd"}]}'
32
+
33
+ @jrd = Discodactyl::JRD::Document.parse(@full_jrd_string)
34
+ end
35
+ def test_parse
36
+ doc = Discodactyl::JRD::Document.parse(@full_jrd_string)
37
+ assert_not_nil(doc)
38
+ end
39
+ def test_parse_links
40
+ assert_equal(4, @jrd.links.length)
41
+ end
42
+
43
+ # def test_linkelems_by_rel
44
+ # link_elems = @jrd.linkelems_by_rel 'describedby'
45
+ #
46
+ # assert_length(2, link_elems)
47
+ #
48
+ # assert_equal 'http://host.example/endpoint', link_elems[0]['href']
49
+ # assert_equal 'http://host.example/descriptor?q={%id}', link_elems[1]['template']
50
+ # end
51
+
52
+ # def test_linkelems_by_rel_with_multiple_rels
53
+ # link_elems = @jrd.linkelems_by_rel 'describedby'
54
+ #
55
+ # assert_length(2, link_elems)
56
+ #
57
+ # assert_equal 'http://host.example/endpoint', link_elems[0]['href']
58
+ # assert_equal 'http://host.example/descriptor?q={%id}', link_elems[1]['template']
59
+ # end
60
+
61
+ def test_uris_by_rel
62
+ uris = @jrd.uris_by_rel 'describedby', 'id' => 'bradfitz@gmail.com'
63
+
64
+ assert_length(2, uris)
65
+
66
+ assert_equal 'http://host.example/endpoint', uris[0]
67
+ assert_equal 'http://host.example/descriptor?q=bradfitz@gmail.com', uris[1]
68
+ end
69
+
70
+ def test_links_by_rel
71
+ links = @jrd.links_by_rel('feed')
72
+
73
+ assert_length(1, links)
74
+ expected = Discodactyl::URITemplate.new('http://host.example/descriptor?q={%id}')
75
+ assert_equal(expected, links[0].template)
76
+ end
77
+
78
+ def test_uris_by_rel
79
+ links = @jrd.uris_by_rel('feed', 'id' => 'dclinton@gmail.com')
80
+
81
+ assert_length(1, links)
82
+ assert_include?('http://host.example/descriptor?q=dclinton@gmail.com', links)
83
+ end
84
+
85
+ # def test_update
86
+ # jrd_str =<<eos
87
+ # <?xml version=\"1.0\"?>
88
+ # <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
89
+ # <Subject>http://host.example/</Subject>
90
+ # <Link rel="webfinger" href="http://host.example/endpoint"/>
91
+ # <Link rel="describedby" href="http://host.example/endpoint"/>
92
+ # <Link rel="feed" template="http://host.example/descriptor?q={%id}"/>
93
+ # <Link rel="describedby" template="http://host.example/descriptor?q={%id}"/>
94
+ # <Link href="http://www.example.com/linkeater/oexchange.xrd" rel="http://oexchange.org/spec/0.8/rel/user-target" type="application/xrd+xml"/>
95
+ # </XRD>
96
+ # eos
97
+ # doc = Discodactyl::JRD::Document.parse jrd_str
98
+ #
99
+ # link = "<Link rel='http://oexchange.org/spec/0.8/rel/user-target' type='application/xrd+xml' href='updated' />"
100
+ #
101
+ # doc.raw.xpath('//xrd:Link',Discodactyl::XRD::XMLNS).after(link)
102
+ #
103
+ # expected =<<eos
104
+ # <?xml version=\"1.0\"?>
105
+ # <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
106
+ # <Subject>http://host.example/</Subject>
107
+ # <Link rel="webfinger" href="http://host.example/endpoint"/>
108
+ # <Link rel="describedby" href="http://host.example/endpoint"/>
109
+ # <Link rel="feed" template="http://host.example/descriptor?q={%id}"/>
110
+ # <Link rel="describedby" template="http://host.example/descriptor?q={%id}"/>
111
+ # <Link href="updated" rel="http://oexchange.org/spec/0.8/rel/user-target" type="application/xrd+xml"/>
112
+ # </XRD>
113
+ # eos
114
+ #
115
+ # assert_equal expected, doc.to_s
116
+ # end
117
+
118
+ # def test_delete
119
+ # xrd_str =<<eos
120
+ # <?xml version=\"1.0\"?>
121
+ # <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
122
+ # <Subject>http://host.example/</Subject>
123
+ # <Link xml:id='1' href="http://example.com/" rel="user-target" type="application/xrd+xml"/>
124
+ # </XRD>
125
+ # eos
126
+ # doc = Discodactyl::XRD::Document.parse xrd_str
127
+ #
128
+ # link = "<Link rel='http://oexchange.org/spec/0.8/rel/user-target' type='application/xrd+xml' href='updated' />"
129
+ #
130
+ # doc.raw.xpath('//xrd:Link',Discodactyl::XRD::XMLNS).after(link)
131
+ #
132
+ # expected =<<eos
133
+ # <?xml version=\"1.0\"?>
134
+ # <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
135
+ # <Subject>http://host.example/</Subject>
136
+ # </XRD>
137
+ # eos
138
+ #
139
+ # assert_equal expected, doc.to_s
140
+ # assert !doc.include?(link)
141
+ # end
142
+ end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: discodactyl
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 13
5
+ prerelease:
5
6
  segments:
6
7
  - 0
7
- - 3
8
- - 0
9
- version: 0.3.0
8
+ - 4
9
+ - 1
10
+ version: 0.4.1
10
11
  platform: ruby
11
12
  authors:
12
13
  - Joseph Anthony Pasquale Holsten
@@ -14,44 +15,50 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-07-08 00:00:00 -05:00
18
+ date: 2011-03-14 00:00:00 -05:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
21
22
  name: nokogiri
22
23
  prerelease: false
23
24
  requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
24
26
  requirements:
25
27
  - - ~>
26
28
  - !ruby/object:Gem::Version
29
+ hash: 3
27
30
  segments:
28
31
  - 1
29
32
  - 4
30
- - 0
31
- version: 1.4.0
33
+ - 2
34
+ version: 1.4.2
32
35
  type: :runtime
33
36
  version_requirements: *id001
34
37
  - !ruby/object:Gem::Dependency
35
38
  name: actionpack
36
39
  prerelease: false
37
40
  requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
38
42
  requirements:
39
43
  - - ~>
40
44
  - !ruby/object:Gem::Version
45
+ hash: 7
41
46
  segments:
42
- - 2
43
47
  - 3
44
48
  - 0
45
- version: 2.3.0
49
+ - 0
50
+ version: 3.0.0
46
51
  type: :runtime
47
52
  version_requirements: *id002
48
53
  - !ruby/object:Gem::Dependency
49
54
  name: feedzirra
50
55
  prerelease: false
51
56
  requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
52
58
  requirements:
53
59
  - - ~>
54
60
  - !ruby/object:Gem::Version
61
+ hash: 49
55
62
  segments:
56
63
  - 0
57
64
  - 0
@@ -63,16 +70,50 @@ dependencies:
63
70
  name: mofo
64
71
  prerelease: false
65
72
  requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
66
74
  requirements:
67
75
  - - ~>
68
76
  - !ruby/object:Gem::Version
77
+ hash: 55
69
78
  segments:
70
79
  - 0
71
80
  - 2
72
- - 0
73
- version: 0.2.0
81
+ - 16
82
+ version: 0.2.16
74
83
  type: :runtime
75
84
  version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: rr
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ hash: 33
94
+ segments:
95
+ - 0
96
+ - 10
97
+ - 11
98
+ version: 0.10.11
99
+ type: :development
100
+ version_requirements: *id005
101
+ - !ruby/object:Gem::Dependency
102
+ name: rake
103
+ prerelease: false
104
+ requirement: &id006 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ hash: 49
110
+ segments:
111
+ - 0
112
+ - 8
113
+ - 7
114
+ version: 0.8.7
115
+ type: :development
116
+ version_requirements: *id006
76
117
  description: "Discodactyl is an experimental toolkit for XRD service discovery documents and related protocols. It includes implementations of XRD URITemplate Link-Patterns, basic site-meta support, HTTP Link header parsing, acct: URIs and a webfinger poking stick."
77
118
  email: joseph@josephholsten.com
78
119
  executables:
@@ -85,7 +126,7 @@ extra_rdoc_files:
85
126
  - COPYING
86
127
  - INSTALL
87
128
  - NEWS
88
- - README
129
+ - README.rdoc
89
130
  - TODO
90
131
  files:
91
132
  - AUTHORS
@@ -95,7 +136,7 @@ files:
95
136
  - INSTALL
96
137
  - MANIFEST
97
138
  - NEWS
98
- - README
139
+ - README.rdoc
99
140
  - Rakefile
100
141
  - TODO
101
142
  - bin/webfinger
@@ -103,7 +144,13 @@ files:
103
144
  - lib/discodactyl.rb
104
145
  - lib/discodactyl/acct_uri.rb
105
146
  - lib/discodactyl/host_meta.rb
147
+ - lib/discodactyl/host_meta_jrd.rb
148
+ - lib/discodactyl/jrd.rb
149
+ - lib/discodactyl/jrd/document.rb
150
+ - lib/discodactyl/jrd/link.rb
151
+ - lib/discodactyl/known_rels.rb
106
152
  - lib/discodactyl/link_header.rb
153
+ - lib/discodactyl/log.rb
107
154
  - lib/discodactyl/resource_discovery.rb
108
155
  - lib/discodactyl/uri_template.rb
109
156
  - lib/discodactyl/xrd.rb
@@ -112,6 +159,8 @@ files:
112
159
  - test/test_acct_uri.rb
113
160
  - test/test_helper.rb
114
161
  - test/test_host_meta.rb
162
+ - test/test_host_meta_jrd.rb
163
+ - test/test_jrd_document.rb
115
164
  - test/test_link_header.rb
116
165
  - test/test_resource_discovery.rb
117
166
  - test/test_uri_template.rb
@@ -128,27 +177,31 @@ rdoc_options:
128
177
  - --title
129
178
  - Discodactyl Documentation
130
179
  - --main
131
- - README
180
+ - README.rdoc
132
181
  require_paths:
133
182
  - lib
134
183
  required_ruby_version: !ruby/object:Gem::Requirement
184
+ none: false
135
185
  requirements:
136
186
  - - ">="
137
187
  - !ruby/object:Gem::Version
188
+ hash: 3
138
189
  segments:
139
190
  - 0
140
191
  version: "0"
141
192
  required_rubygems_version: !ruby/object:Gem::Requirement
193
+ none: false
142
194
  requirements:
143
195
  - - ">="
144
196
  - !ruby/object:Gem::Version
197
+ hash: 3
145
198
  segments:
146
199
  - 0
147
200
  version: "0"
148
201
  requirements: []
149
202
 
150
203
  rubyforge_project:
151
- rubygems_version: 1.3.6
204
+ rubygems_version: 1.5.0
152
205
  signing_key:
153
206
  specification_version: 3
154
207
  summary: Discodactyl is an experimental toolkit for XRD service discovery documents and related protocols
@@ -156,6 +209,8 @@ test_files:
156
209
  - test/test_acct_uri.rb
157
210
  - test/test_helper.rb
158
211
  - test/test_host_meta.rb
212
+ - test/test_host_meta_jrd.rb
213
+ - test/test_jrd_document.rb
159
214
  - test/test_link_header.rb
160
215
  - test/test_resource_discovery.rb
161
216
  - test/test_uri_template.rb