require 'cgi' require 'net/http' require 'uri' require 'rexml/document' require 'w3c_validators/exceptions' require 'w3c_validators/constants' require 'w3c_validators/results' require 'w3c_validators/message' module W3CValidators # Base class for MarkupValidator and FeedValidator. class Validator VERSION = '1.0.2' USER_AGENT = "Ruby W3C Validators/#{Validator::VERSION} (http://code.dunae.ca/w3c_validators/)" HEAD_STATUS_HEADER = 'X-W3C-Validator-Status' HEAD_ERROR_COUNT_HEADER = 'X-W3C-Validator-Errors' SOAP_OUTPUT_PARAM = 'soap12' attr_reader :results, :validator_uri # Create a new instance of the Validator. # # +options+ Hash can optionally include # - +proxy_host+ # - +proxy_port+ # - +proxy_user+ # - +proxy_pass+ def initialize(options = {}) @options = {:proxy_host => nil, :proxy_port => nil, :proxy_user => nil, :proxy_pass => nil}.merge(options) end protected # Perform a validation request. # # +request_mode+ must be either :get, :head or :post. # # Returns Net::HTTPResponse. def send_request(options, request_mode = :get) response = nil results = nil r = Net::HTTP::Proxy(@options[:proxy_host], @options[:proxy_port], @options[:proxy_user], @options[:proxy_pass]).start(@validator_uri.host, @validator_uri.port) do |http| case request_mode when :head # perform a HEAD request raise ArgumentError, "a URI must be provided for HEAD requests." unless options[:uri] query = create_query_string_data(options) response = http.request_head(@validator_uri.path + '?' + query) when :get # send a GET request query = create_query_string_data(options) response = http.get(@validator_uri.path + '?' + query) when :post # send a multipart form request query, boundary = create_multipart_data(options) response = http.post2(@validator_uri.path, query, "Content-type" => "multipart/form-data; boundary=" + boundary) else raise ArgumentError, "request_mode must be either :get, :head or :post" end end response.value return response rescue Exception => e handle_exception e end def create_multipart_data(options) # :nodoc: boundary = '349832898984244898448024464570528145' # added 2008-03-12: HTML5 validator expects 'file' and 'content' to be the last fields so # we process those params separately last_params = [] # added 2008-03-12: HTML5 validator expects 'file' instead of 'uploaded_file' if options[:file] and !options[:uploaded_file] options[:uploaded_file] = options[:file] end if options[:uploaded_file] filename = options[:file_path] ||= 'temp.html' content = options[:uploaded_file] last_params << "Content-Disposition: form-data; name=\"uploaded_file\"; filename=\"#{filename}\"\r\n" + "Content-Type: text/html\r\n" + "\r\n" + "#{content}\r\n" options.delete(:uploaded_file) options.delete(:file_path) end if options[:content] last_params << "Content-Disposition: form-data; name=\"#{CGI::escape('content')}\"\r\n" + "\r\n" + "#{options[:content]}\r\n" end misc_params = [] options.each do |key, value| if value misc_params << "Content-Disposition: form-data; name=\"#{CGI::escape(key.to_s)}\"\r\n" + "\r\n" + "#{value}\r\n" end end params = misc_params + last_params multipart_query = params.collect {|p| '--' + boundary + "\r\n" + p}.join('') + "--" + boundary + "--\r\n" [multipart_query, boundary] end def create_query_string_data(options) # :nodoc: qs = '' options.each do |key, value| if value qs += "#{key}=" + CGI::escape(value.to_s) + "&" end end qs end def read_local_file(file_path) # :nodoc: IO.read(file_path) end private #-- # Big thanks to ara.t.howard and Joel VanderWerf on Ruby-Talk for the exception handling help. #++ def handle_exception(e, msg = '') # :nodoc: case e when Net::HTTPServerException, SocketError msg = "unable to connect to the validator at #{@validator_uri} (response was #{e.message})." raise ValidatorUnavailable, msg, caller when REXML::ParseException msg = "unable to parse the response from the validator." raise ParsingError, msg, caller else raise e end if e.respond_to?(:error_handler_before) fcall(e, :error_handler_before, self) end if e.respond_to?(:error_handler_instead) fcall(e, :error_handler_instead, self) else if e.respond_to? :status exit_status(( e.status )) end if SystemExit === e stderr.puts e.message unless(SystemExit === e and e.message.to_s == 'exit') ### avoids double message for abort('message') end end if e.respond_to?(:error_handler_after) fcall(e, :error_handler_after, self) end exit_status(( exit_failure )) if exit_status == exit_success exit_status(( Integer(exit_status) rescue(exit_status ? 0 : 1) )) exit exit_status end end end