ape 1.0.0

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.
Files changed (47) hide show
  1. data/CHANGELOG +1 -0
  2. data/LICENSE +19 -0
  3. data/Manifest +45 -0
  4. data/README +12 -0
  5. data/ape.gemspec +57 -0
  6. data/bin/ape_server +28 -0
  7. data/lib/ape.rb +982 -0
  8. data/lib/ape/atomURI.rb +73 -0
  9. data/lib/ape/auth/google_login_credentials.rb +96 -0
  10. data/lib/ape/auth/wsse_credentials.rb +25 -0
  11. data/lib/ape/authent.rb +42 -0
  12. data/lib/ape/categories.rb +95 -0
  13. data/lib/ape/collection.rb +51 -0
  14. data/lib/ape/crumbs.rb +39 -0
  15. data/lib/ape/entry.rb +151 -0
  16. data/lib/ape/escaper.rb +29 -0
  17. data/lib/ape/feed.rb +117 -0
  18. data/lib/ape/handler.rb +34 -0
  19. data/lib/ape/html.rb +17 -0
  20. data/lib/ape/invoker.rb +54 -0
  21. data/lib/ape/invokers/deleter.rb +31 -0
  22. data/lib/ape/invokers/getter.rb +80 -0
  23. data/lib/ape/invokers/poster.rb +57 -0
  24. data/lib/ape/invokers/putter.rb +46 -0
  25. data/lib/ape/layout/ape.css +56 -0
  26. data/lib/ape/layout/ape_logo.png +0 -0
  27. data/lib/ape/layout/index.html +54 -0
  28. data/lib/ape/layout/info.png +0 -0
  29. data/lib/ape/names.rb +24 -0
  30. data/lib/ape/print_writer.rb +21 -0
  31. data/lib/ape/samples.rb +180 -0
  32. data/lib/ape/samples/atom_schema.txt +338 -0
  33. data/lib/ape/samples/basic_entry.eruby +16 -0
  34. data/lib/ape/samples/categories_schema.txt +69 -0
  35. data/lib/ape/samples/mini_entry.eruby +8 -0
  36. data/lib/ape/samples/service_schema.txt +187 -0
  37. data/lib/ape/samples/unclean_xhtml_entry.eruby +21 -0
  38. data/lib/ape/server.rb +32 -0
  39. data/lib/ape/service.rb +12 -0
  40. data/lib/ape/validator.rb +65 -0
  41. data/lib/ape/version.rb +9 -0
  42. data/scripts/go.rb +29 -0
  43. data/test/test_helper.rb +17 -0
  44. data/test/unit/authent_test.rb +35 -0
  45. data/test/unit/invoker_test.rb +25 -0
  46. data/test/unit/samples_test.rb +36 -0
  47. metadata +111 -0
@@ -0,0 +1,29 @@
1
+ # Copyright © 2006 Sun Microsystems, Inc. All rights reserved
2
+ # Use is subject to license terms - see file "LICENSE"
3
+ module Ape
4
+ class Escaper
5
+ def Escaper.escape(text)
6
+ text.gsub(/([&<'">])/) do
7
+ case $1
8
+ when '&' then '&amp;'
9
+ when '<' then '&lt;'
10
+ when "'" then '&apos;'
11
+ when '"' then '&quot;'
12
+ when '>' then '&gt;'
13
+ end
14
+ end
15
+ end
16
+
17
+ def Escaper.unescape(text)
18
+ text.gsub(/&([^;]*);/) do
19
+ case $1
20
+ when 'lt' then '<'
21
+ when 'amp' then '&'
22
+ when 'gt' then '>'
23
+ when 'apos' then "'"
24
+ when 'quot' then '"'
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,117 @@
1
+ # Copyright © 2006 Sun Microsystems, Inc. All rights reserved
2
+ # Use is subject to license terms - see file "LICENSE"
3
+
4
+ require 'rexml/document'
5
+ require 'rexml/xpath'
6
+
7
+ module Ape
8
+ class Feed
9
+ # Load up a collection feed from its URI. Return an array of <entry> objects.
10
+ # follow <link rel="next" /> pointers as required to get the whole
11
+ # collection
12
+ def Feed.read(uri, name, ape, report=true)
13
+
14
+ entries = []
15
+ uris = []
16
+ next_page = uri
17
+ page_num = 1
18
+
19
+ while (next_page) && (page_num < 10) do
20
+
21
+ label = "Page #{page_num} of #{name}"
22
+ uris << next_page
23
+ page = ape.check_resource(next_page, label, Names::AtomMediaType, report)
24
+ break unless page
25
+
26
+ # * Validate it
27
+ Validator.validate(Samples.atom_RNC, page.body, label, ape) if report
28
+
29
+ # XML-parse the feed
30
+ error = "not well-formed"
31
+ feed = nil
32
+ begin
33
+ feed = REXML::Document.new(page.body, { :raw => nil })
34
+ rescue Exception
35
+ error = $!.to_s
36
+ feed = nil
37
+ end
38
+ if feed == nil
39
+ ape.error "Can't parse #{label} at #{next_page}, Parser said: #{$!}" if report
40
+ break
41
+ end
42
+
43
+ feed = feed.root
44
+ if feed == nil
45
+ ape.warning "#{label} is empty."
46
+ break
47
+ end
48
+
49
+ page_entries = REXML::XPath.match(feed, "./atom:entry", Names::XmlNamespaces)
50
+ if page_entries.empty? && report
51
+ ape.info "#{label} has no entries."
52
+ end
53
+
54
+ entries += page_entries.map { |e| Entry.new(e, next_page)}
55
+
56
+ next_link = REXML::XPath.first(feed, "./atom:link[@rel=\"next\"]", Names::XmlNamespaces)
57
+ if next_link
58
+ next_link = next_link.attributes['href']
59
+ base = AtomURI.new(next_page)
60
+ next_link = base.absolutize(next_link, feed)
61
+ if uris.index(next_link)
62
+ ape.error "Collection contains circular 'next' linkage: #{next_link}" if report
63
+ break
64
+ end
65
+ page_num += 1
66
+ end
67
+ next_page = next_link
68
+ end
69
+
70
+ if report && next_page
71
+ ape.warning "Stopped reading collection after #{page_num} pages."
72
+ end
73
+
74
+ # all done unless we're error-checking
75
+ return entries unless report
76
+
77
+ # Ensure that entries are ordered by app:edited
78
+ last_date = nil
79
+ with_app_date = 0
80
+ clean = true
81
+ entries.each do |e|
82
+ datestr = e.child_content("edited", Names::AppNamespace)
83
+ error = nil
84
+ if datestr
85
+ if datestr =~ /\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(Z|([-+]\d\d:\d\d))/
86
+ begin
87
+ date = Time.parse(datestr)
88
+ with_app_date += 1
89
+ if last_date && (date > last_date)
90
+ error = "app:edited values out of order, d #{date} ld #{last_date}"
91
+ end
92
+ last_date = date
93
+ rescue ArgumentError
94
+ error = "invalid app:edited value: #{datestr}"
95
+ end
96
+ else
97
+ error = "invalid app:edited child: #{datestr}"
98
+ end
99
+ if error
100
+ title = e.child_content "title"
101
+ ape.error "In entry with title '#{title}', #{error}."
102
+ clean = false
103
+ end
104
+ end
105
+ end
106
+ if with_app_date < entries.size
107
+ ape.error "#{entries.size - with_app_date} of #{entries.size} entries in #{name} lack app:edited elements."
108
+ clean = false
109
+ end
110
+
111
+ ape.good "#{name} has correct app:edited value order." if clean
112
+
113
+ entries
114
+ end
115
+ end
116
+ end
117
+
@@ -0,0 +1,34 @@
1
+ require 'rubygems'
2
+ require 'mongrel'
3
+ require 'ape'
4
+
5
+ module Ape
6
+ class Handler < Mongrel::HttpHandler
7
+ def process(request, response)
8
+ cgi = Mongrel::CGIWrapper.new(request, response)
9
+
10
+ uri = cgi['uri'].strip
11
+ user = cgi['username'].strip
12
+ pass = cgi['password'].strip
13
+
14
+ # invoke_ape uri, user, pass, request, response
15
+
16
+ if uri.empty?
17
+ response.start(200, true) do |header, body|
18
+ header['Content-Type'] = 'text/plain'
19
+ body << 'URI argument is required'
20
+ end
21
+ return
22
+ end
23
+
24
+ format = request.params['HTTP_ACCEPT'] == 'text/plain' ? 'text' : 'html'
25
+ ape = Ape.new({ :crumbs => true, :output => format })
26
+ (user && pass) ? ape.check(uri, user, pass) : ape.check(uri)
27
+
28
+ response.start(200, true) do |header, body|
29
+ header['Content-Type'] = 'text/html'
30
+ ape.report(body)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ # Copyright © 2006 Sun Microsystems, Inc. All rights reserved
2
+ # Use is subject to license terms - see file "LICENSE"
3
+
4
+ module Ape
5
+ class HTML
6
+ def HTML.error(message, output=STDOUT)
7
+ headers(output)
8
+ output.puts <<EndOfText
9
+ <title>Error: #{message}</title>
10
+ </head>
11
+ <body>
12
+ <h2>Error</h2>
13
+ <p>#{message}.</p>
14
+ EndOfText
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,54 @@
1
+ # Copyright © 2006 Sun Microsystems, Inc. All rights reserved
2
+ # Use is subject to license terms - see file "LICENSE"
3
+ require 'net/https'
4
+
5
+ module Ape
6
+ class Invoker
7
+ attr_reader :last_error, :crumbs, :response
8
+
9
+ def initialize(uriString, authent)
10
+ @last_error = nil
11
+ @crumbs = Crumbs.new
12
+ @uri = AtomURI.check(uriString)
13
+ if (@uri.class == String)
14
+ @last_error = @uri
15
+ end
16
+ @authent = authent
17
+ @authent_checker = 0
18
+ end
19
+
20
+ def header(which)
21
+ @response[which]
22
+ end
23
+
24
+ def prepare_http
25
+ http = Net::HTTP.new(@uri.host, @uri.port)
26
+ if @uri.scheme == 'https'
27
+ http.use_ssl = true
28
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
29
+ end
30
+ http.set_debug_output @crumbs if @crumbs
31
+ http
32
+ end
33
+
34
+ def need_authentication?(req)
35
+ if @response.instance_of?(Net::HTTPUnauthorized) && @authent
36
+ #tries to authenticate just two times in order to avoid infinite loops
37
+ raise AuthenticationError, 'Authentication is required' unless @authent_checker <= 1
38
+ @authent_checker += 1
39
+
40
+ @authent.add_to req, header('WWW-Authenticate')
41
+ #clean the request body attribute, if we don't do it http.request(req, body) will raise an exception
42
+ req.body = nil unless req.body.nil?
43
+ return true
44
+ end
45
+ return false
46
+ end
47
+
48
+ def restart_authent_checker
49
+ @authent_checker = 0
50
+ end
51
+ end
52
+ end
53
+
54
+ Dir[File.dirname(__FILE__) + '/invokers/*.rb'].each { |l| require l }
@@ -0,0 +1,31 @@
1
+ # Copyright © 2006 Sun Microsystems, Inc. All rights reserved
2
+ # Use is subject to license terms - see file "LICENSE"
3
+ require 'net/http'
4
+
5
+ module Ape
6
+ class Deleter < Invoker
7
+
8
+ def delete( req = nil )
9
+ req = Net::HTTP::Delete.new(AtomURI.on_the_wire(@uri)) unless req
10
+
11
+ begin
12
+ http = prepare_http
13
+
14
+ http.start do |connection|
15
+ @response = connection.request(req)
16
+
17
+ return delete(req) if need_authentication?(req)
18
+ restart_authent_checker
19
+
20
+ return true if @response.kind_of? Net::HTTPSuccess
21
+
22
+ @last_error = @response.message
23
+ return false
24
+ end
25
+ rescue Exception
26
+ @last_error = "Can't connect to #{@uri.host} on port #{@uri.port}: #{$!}"
27
+ return nil
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,80 @@
1
+ # Copyright © 2006 Sun Microsystems, Inc. All rights reserved
2
+ # Use is subject to license terms - see file "LICENSE"
3
+ require 'net/http'
4
+
5
+ module Ape
6
+ class Getter < Invoker
7
+ attr_reader :contentType, :charset, :body, :security_warning
8
+
9
+ def get(contentType = nil, depth = 0, req = nil)
10
+ req = Net::HTTP::Get.new(AtomURI.on_the_wire(@uri)) unless req
11
+ @last_error = nil
12
+
13
+ return false if document_failed?(depth, req)
14
+
15
+ begin
16
+ http = prepare_http
17
+
18
+ http.start do |connection|
19
+ @response = connection.request(req)
20
+
21
+ if need_authentication?(req)
22
+ @security_warning = true unless http.use_ssl?
23
+ return get(contentType, depth + 1, req)
24
+ end
25
+ restart_authent_checker
26
+
27
+ case @response
28
+ when Net::HTTPSuccess
29
+ return getBody(contentType)
30
+
31
+ when Net::HTTPRedirection
32
+ redirect_to = @uri.merge(@response['location'])
33
+ @uri = AtomURI.check(redirect_to)
34
+ return get(contentType, depth + 1)
35
+
36
+ else
37
+ @last_error = @response.message
38
+ return false
39
+ end
40
+ end
41
+ rescue Exception
42
+ @last_error = "Can't connect to #{@uri.host} on port #{@uri.port}: #{$!}"
43
+ return false
44
+ end
45
+ end
46
+
47
+ def document_failed?(depth, req)
48
+ if (depth > 10)
49
+ if need_authentication?(req)
50
+ #Authentication required
51
+ @last_error = "Authentication is required"
52
+ else
53
+ # too many redirects
54
+ @last_error = "Too many redirects"
55
+ end
56
+ return true
57
+ end
58
+ return false
59
+ end
60
+
61
+ def getBody contentType
62
+
63
+ if contentType
64
+ @contentType = @response['Content-Type']
65
+ # XXX TODO - better regex
66
+ if @contentType =~ /^([^;]*);/
67
+ @contentType = $1
68
+ end
69
+
70
+ if contentType != @contentType
71
+ @last_error = "Content-type must be '#{contentType}', not '#{@contentType}'"
72
+ end
73
+ end
74
+
75
+ @body = @response.body
76
+ return true
77
+ end
78
+ end
79
+ end
80
+
@@ -0,0 +1,57 @@
1
+ # Copyright © 2006 Sun Microsystems, Inc. All rights reserved
2
+ # Use is subject to license terms - see file "LICENSE"
3
+ require 'net/http'
4
+
5
+ module Ape
6
+ class Poster < Invoker
7
+ attr_reader :entry, :uri
8
+
9
+ def initialize(uriString, authent)
10
+ super uriString, authent
11
+ @headers = {}
12
+ @entry = nil
13
+ end
14
+
15
+ def set_header(name, val)
16
+ @headers[name] = val
17
+ end
18
+
19
+ def post(contentType, body, req = nil)
20
+ req = Net::HTTP::Post.new(AtomURI.on_the_wire(@uri)) if req.nil?
21
+ req.set_content_type contentType
22
+ @headers.each { |k, v| req[k]= v }
23
+
24
+ begin
25
+ http = prepare_http
26
+
27
+ http.start do |connection|
28
+ @response = connection.request(req, body)
29
+
30
+ return post(contentType, body, req) if need_authentication?(req)
31
+ restart_authent_checker
32
+
33
+ if @response.code != '201'
34
+ @last_error = @response.message
35
+ return false
36
+ end
37
+
38
+ if (!((@response['Content-type'] =~ %r{^application/atom\+xml}) ||
39
+ (@response['Content-type'] =~ %r{^application/atom\+xml;type=entry})))
40
+ return true
41
+ end
42
+
43
+ begin
44
+ @entry = Entry.new(@response.body)
45
+ return true
46
+ rescue ArgumentError
47
+ @last_error = @entry.broken
48
+ return false
49
+ end
50
+ end
51
+ rescue Exception
52
+ @last_error = "Can't connect to #{@uri.host} on port #{@uri.port}: #{$!}"
53
+ return false
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,46 @@
1
+ # Copyright © 2006 Sun Microsystems, Inc. All rights reserved
2
+ # Use is subject to license terms - see file "LICENSE"
3
+ require 'net/http'
4
+
5
+ module Ape
6
+ class Putter < Invoker
7
+ attr_reader :headers
8
+
9
+ def initialize(uriString, authent)
10
+ super uriString, authent
11
+ @headers = {}
12
+ end
13
+
14
+ def set_header(name, val)
15
+ @headers[name] = val
16
+ end
17
+
18
+ def put(contentType, body, req = nil)
19
+ req = Net::HTTP::Put.new(AtomURI.on_the_wire(@uri)) unless req
20
+
21
+ req.set_content_type contentType
22
+ @headers.each { |k, v| req[k]= v }
23
+
24
+ begin
25
+ http = prepare_http
26
+
27
+ http.start do |connection|
28
+ @response = connection.request(req, body)
29
+
30
+ return put(contentType, body, req) if need_authentication?(req)
31
+ restart_authent_checker
32
+
33
+ unless @response.kind_of? Net::HTTPSuccess
34
+ @last_error = @response.message
35
+ return false
36
+ end
37
+
38
+ return true
39
+ end
40
+ rescue Exception
41
+ @last_error = "Can't connect to #{@uri.host} on port #{@uri.port}: #{$!}"
42
+ return nil
43
+ end
44
+ end
45
+ end
46
+ end