require 'cgi'
require 'html/htmltokenizer'
require 'base64'
require 'digest/sha1'
require 'hmac-sha1'

# Functions convenient for cryptography

module Crypto_math
	# This code is taken from this post[http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/19098]
	# by Eric Lee Green. x.mod_exp(n,q) returns x ** n % q
	def mod_exp(n,q)
		counter=0
		n_p=n  # N 
		y_p=1        # Y
		z_p=self  # Z
		while n_p != 0
      		if n_p[0]==1
				y_p=(y_p*z_p) % q
      		end
      		n_p = n_p >> 1  
      		z_p = (z_p * z_p) % q
      		counter += 1
    	end
    	return y_p
	end
end


class Fixnum
	include Crypto_math
	# Returns a number in big endian two's complement notation, stored
	# as a raw string.
	def to_btwoc
		bits = self.to_s(2)
		prepend = (8 - bits.length % 8) || (bits.index(/^1/) ? 8 : 0)
		bits = ('0' * prepend) + bits if prepend
		return [bits].pack('B*')
	end
end

class Bignum
	include Crypto_math
	# Returns a number in big endian two's complement notation, stored
	# as a raw string.
	def to_btwoc
		bits = self.to_s(2)
		prepend = (8 - bits.length % 8) || (bits.index(/^1/) ? 8 : 0)
		bits = ('0' * prepend) + bits if prepend
		return [bits].pack('B*')
	end
end

class DateTime
	# Converts to a UTC Time.
	def to_time()
		if offset == 0
			return Time.gm(year, month, day, hour, min, sec)
		else
			utc = new_offset
			return  Time.gm(utc.year, utc.month, utc.day, utc.hour, utc.min, utc.sec)
		end
	end
end #class DateTime

class String
	# Bitwise-XOR two equal length strings.
	# Raises an ArgumentError if strings are different length.
	def ^(other)
		raise ArgumentError, "Can't bitwise-XOR a String with a non-String" \
			unless other.kind_of? String
		raise ArgumentError, "Can't bitwise-XOR strings of different length" \
			unless self.length == other.length
		result = (0..self.length-1).collect { |i| self[i] ^ other[i] }
		result.pack("C*")\
	end
end

module OpenID	
	# Takes all entries in a hash and combines and escapes them (using CGI::escape) into a single string.
	def url_encode(query)
		output = ''
		query.each { |key, val|
			output += CGI::escape(key.to_s) + '=' + CGI::escape(val.to_s) + '&'
		}
		output.chop!
		return output
	end
	# Appends arguments in hash args to the existing url string
	def append_args(url, args)
		return url if args.empty?
		return url + (url.include? '?' and '&' or '?') + url_encode(args)
	end
	# Converts a raw string containing a big endian two's complement number into a Fixnum or Bignum
	def from_btwoc(str)
		# Maybe this should be part of a btwoc class or something
		str = "\000" * (4 - (str.length % 4)) + str
		num = 0
		str.unpack('N*').each { |x|
			num <<= 32
			num |= x
		}
		return num
	end
	# Parses an OpenID Key Value string(A new-line separated list containing key:value pairs).
	# Returns a hash.
	def parse_kv(d)
		d.strip!
		args = {}
		d.split("\n").each { |line|
			pair = line.split(':',2)
			if pair.length == 2
				k, v = pair
				args[k.strip] = v.strip
			end
		}
		return args
	end
	# Takes a string containing html, and yields the attributes of each link tags until a body tag is found.
	def parse_link_attrs(data)
		parser = HTMLTokenizer.new(data)
		while el = parser.getTag('link', 'body')
			if el.tag_name == 'link'
				yield el.attr_hash
			elsif el.tag_name == 'body'
				return
			end
		end
		
	end
	
	# Generates a signature for a set of fields. reply is a hash containing the 
	# data that was signed, key is the secret key, and signed_fields is an array 
	# containing the names (in order) of the fields to be signed.
	#	Returns the list of signed fields (comma separated) and the base64 encoded signature
	def sign_reply(reply, key, signed_fields)
		token = ''
		signed_fields.each { |x|
			token += x + ':' + reply['openid.' + x] + "\n"
		}
		d = Base64.encode64(HMAC::SHA1.digest(key,token)).delete("\n")
		return signed_fields.join(','), d
	end

end #module OpenID
