Atom Protocol Exerciser
11 | 12 |(Only the URI argument is required)
13 | 35 |#{message}.
14 | EndOfText 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/ape/print_writer.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved 2 | # See the included LICENSE[link:/files/LICENSE.html] file for details. 3 | module Ape 4 | # this is a wrapper for the weird derived-from-PrintWriter class that comes 5 | # out of HttpResponse.getWriter 6 | class Printwriter 7 | def initialize(java_writer) 8 | @w = java_writer 9 | end 10 | 11 | def puts(s) 12 | @w.println s 13 | end 14 | 15 | def << (s) 16 | @w.print s 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /scripts/go.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.dirname(__FILE__) + '/../lib' 2 | require 'cgi' 3 | require 'html' 4 | require 'ape' 5 | 6 | debug = ENV['APE_DEBUG'] || false 7 | 8 | cgi = debug ? CGI.new('html4') : CGI.new 9 | 10 | if !cgi['uri'] || (cgi['uri'] == '') 11 | HTML.error "URI argument is required" 12 | end 13 | 14 | uri = cgi['uri'] 15 | user = cgi['username'] 16 | pass = cgi['password'] 17 | 18 | ape = Ape::Ape.new({:crumbs => true, :output => 'html', :debug => debug}) 19 | 20 | if user == '' 21 | ape.check(uri) 22 | else 23 | ape.check(uri, user, pass) 24 | end 25 | ape.report 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /lib/ape/samples/basic_entry.eruby: -------------------------------------------------------------------------------- 1 | 2 |A test post from the <APE> at #{updated}
11 |If you see this in an entry, it's probably a left-over from an 12 | unsuccessful Ape run; feel free to delete it.
13 |hey
10 | 11 | 12 |Hey
13 |
9 | (Only the URI argument is required)
13 | 35 |The Ape source code may be found at 43 | ape.rubyforge.org. 44 | 45 |
Ape Logo by Greg 47 | Borenstein.
48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/ape/authent.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved 2 | # Use is subject to license terms - see file "LICENSE" 3 | 4 | module Ape 5 | class AuthenticationError < StandardError ; end 6 | 7 | class Authent 8 | def initialize(username, password, scheme=nil) 9 | @username = username 10 | @password = password 11 | @auth_plugin = nil 12 | end 13 | 14 | def add_to(req, authentication = nil) 15 | return unless @username && @password 16 | if (authentication) 17 | if authentication.strip.downcase.include? 'basic' 18 | req.basic_auth @username, @password 19 | else 20 | @auth_plugin = resolve_plugin(authentication) unless @auth_plugin 21 | @auth_plugin.add_credentials(req, authentication, @username, @password) 22 | end 23 | else 24 | req.basic_auth @username, @password 25 | end 26 | end 27 | 28 | def resolve_plugin(authentication) 29 | Dir.glob(File.join(File.dirname(__FILE__), 'auth/*.rb')).each do |file| 30 | plugin_name = file.gsub(/(.+\/auth\/)(.+)(_credentials.rb)/, '\2').gsub(/_/, '') 31 | plugin_class = file.gsub(/(.+\/auth\/)(.+)(.rb)/, '\2').gsub(/(^|_)(.)/) { $2.upcase } 32 | 33 | if (authentication.strip.downcase.include?(plugin_name)) 34 | return eval("#{plugin_class}.new", binding, __FILE__, __LINE__) 35 | end 36 | end 37 | raise AuthenticationError, "Unknown authentication method: #{authentication}" 38 | end 39 | end 40 | end 41 | 42 | Dir[File.dirname(__FILE__) + '/auth/*.rb'].each { |l| require l } 43 | -------------------------------------------------------------------------------- /lib/ape/crumbs.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved 2 | # Use is subject to license terms - see file "LICENSE" 3 | 4 | module Ape 5 | 6 | # All descendants of Invoker use Net::HTTP::set_debug_output 7 | # to capture the dialogue. This class is what gets passed to 8 | # set_debug_output; it exists to define #<< to save only the interesting bits 9 | # of the dialog. 10 | class Crumbs 11 | 12 | def initialize #:nodoc: 13 | @crumbs = [] 14 | @keep_next = false 15 | end 16 | 17 | # call-seq: 18 | # crumbs.grep(pattern) => array 19 | # 20 | # Returns an array of crumbs for whichpattern === crumb
21 | #
22 | # ==== Options
23 | # * pattern - A string or Regexp literal, as with Enumerable#grep.
24 | def grep(pattern)
25 | @crumbs.grep(pattern)
26 | end
27 |
28 | # Appends +data+ to the report dialog
29 | #
30 | # ==== Options
31 | # * data - The message, as passed by an Invoker descendant. Required.
32 | def <<(data)
33 | if @keep_next
34 | @crumbs << "> #{data}"
35 | @keep_next = false
36 | elsif data =~ /^->/
37 | @crumbs << "< #{data.gsub(/^.../, '')}"
38 | elsif data =~ /^<-/
39 | @keep_next = true
40 | end
41 | end
42 |
43 | # Yields each crumb sequentially to the supplied block.
44 | def each #:yields: crumb
45 | @crumbs.each { |c| yield c }
46 | end
47 |
48 | # call-seq:
49 | # crumbs.to_s => string
50 | #
51 | # Returns a string containing all crumbs, seperated by newlines.
52 | def to_s
53 | " " + @crumbs.join("...\n")
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/ape/samples/categories_schema.txt:
--------------------------------------------------------------------------------
1 | # -*- rnc -*-
2 | # RELAX NG Compact Syntax Grammar for the Atom Protocol
3 |
4 | namespace app = "http://www.w3.org/2007/app"
5 | namespace atom = "http://www.w3.org/2005/Atom"
6 | namespace xsd = "http://www.w3.org/2001/XMLSchema"
7 | namespace local = ""
8 |
9 | start = appCategories
10 |
11 | atomCommonAttributes =
12 | attribute xml:base { atomURI }?,
13 | attribute xml:lang { atomLanguageTag }?,
14 | undefinedAttribute*
15 |
16 | undefinedAttribute =
17 | attribute * - (xml:base | xml:lang | local:*) { text }
18 |
19 | atomURI = text
20 |
21 | atomLanguageTag = xsd:string {
22 | pattern = "[A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*"
23 | }
24 |
25 |
26 | atomCategory =
27 | element atom:category {
28 | atomCommonAttributes,
29 | attribute term { text },
30 | attribute scheme { atomURI }?,
31 | attribute label { text }?,
32 | undefinedContent
33 | }
34 |
35 | appInlineCategories =
36 | element app:categories {
37 | attribute fixed { "yes" | "no" }?,
38 | attribute scheme { atomURI }?,
39 | (atomCategory*)
40 | }
41 |
42 | appOutOfLineCategories =
43 | element app:categories {
44 | attribute href { atomURI },
45 | (empty)
46 | }
47 |
48 | appCategories = appInlineCategories | appOutOfLineCategories
49 |
50 |
51 | # Extensibility
52 |
53 | undefinedContent = (text|anyForeignElement)*
54 |
55 | anyElement =
56 | element * {
57 | (attribute * { text }
58 | | text
59 | | anyElement)*
60 | }
61 |
62 | anyForeignElement =
63 | element * - atom:* {
64 | (attribute * { text }
65 | | text
66 | | anyElement)*
67 | }
68 |
69 | # EOF
--------------------------------------------------------------------------------
/lib/ape/invoker.rb:
--------------------------------------------------------------------------------
1 | # Copyright (c) 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 }
55 |
--------------------------------------------------------------------------------
/lib/ape/invokers/poster.rb:
--------------------------------------------------------------------------------
1 | # Copyright (c) 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
58 |
--------------------------------------------------------------------------------
/lib/ape/validator.rb:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved
2 | # Use is subject to license terms - see file "LICENSE"
3 |
4 | if RUBY_PLATFORM =~ /java/
5 | require 'java'
6 | CompactSchemaReader = com.thaiopensource.validate.rng.CompactSchemaReader
7 | ValidationDriver = com.thaiopensource.validate.ValidationDriver
8 | StringReader = java.io.StringReader
9 | StringWriter = java.io.StringWriter
10 | InputSource = org.xml.sax.InputSource
11 | ErrorHandlerImpl = com.thaiopensource.xml.sax.ErrorHandlerImpl
12 | PropertyMapBuilder = com.thaiopensource.util.PropertyMapBuilder
13 | ValidateProperty = com.thaiopensource.validate.ValidateProperty
14 | end
15 |
16 | module Ape
17 | class Validator
18 |
19 | attr_reader :error
20 |
21 | def Validator.validate(schema, text, name, ape)
22 | # Can do this in JRuby, not native Ruby (sigh)
23 | if RUBY_PLATFORM =~ /java/
24 | rnc_validate(schema, text, name, ape)
25 | else
26 | true
27 | end
28 | end
29 |
30 | def Validator.rnc_validate(schema, text, name, ape)
31 | schemaError = StringWriter.new
32 | schemaEH = ErrorHandlerImpl.new(schemaError)
33 | properties = PropertyMapBuilder.new
34 | properties.put(ValidateProperty::ERROR_HANDLER, schemaEH)
35 | error = nil
36 | driver = ValidationDriver.new(properties.toPropertyMap, CompactSchemaReader.getInstance)
37 | if driver.loadSchema(InputSource.new(StringReader.new(schema)))
38 | begin
39 | if !driver.validate(InputSource.new(StringReader.new(text)))
40 | error = schemaError.toString
41 | end
42 | rescue org.xml.sax.SAXParseException
43 | error = $!.to_s.sub(/\n.*$/, '')
44 | end
45 | else
46 | error = schemaError.toString
47 | end
48 |
49 | if !error
50 | ape.good "#{name} passed schema validation."
51 | true
52 | else
53 | # this kind of sucks, but I spent a looong time lost in a maze of twisty
54 | # little passages without being able to figure out how to
55 | # tell jing what name I'd like to call the InputSource
56 | ape.error "#{name} failed schema validation:\n" + error.gsub('(unknown file):', 'Line ')
57 | false
58 | end
59 |
60 | end
61 |
62 | end
63 | end
64 |
65 |
66 |
--------------------------------------------------------------------------------
/lib/ape/invokers/getter.rb:
--------------------------------------------------------------------------------
1 | # Copyright (c) 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 |
81 |
--------------------------------------------------------------------------------
/lib/ape/categories.rb:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved
2 | # Copyright © 2006 Sun Microsystems, Inc. All rights reserved
3 | # Use is subject to license terms - see file "LICENSE"
4 |
5 | require 'rexml/document'
6 | require 'rexml/xpath'
7 |
8 | module Ape
9 | class Categories
10 | attr_reader :fixed
11 |
12 | def Categories.from_collection(collection, authent, ape=nil)
13 |
14 | # "catses" because if cats is short for categories, then catses
15 | # suggests multiple