ev/net.rb in rwdmovies-0.6 vs ev/net.rb in rwdmovies-0.7

- old
+ new

@@ -1,750 +1,750 @@ -require "ev/ruby" -require "ev/ftools" -require "net/http" -require "socket" -require "uri" -require "cgi" -require "md5" -require "thread" - -$proxy = ENV["PROXY"] if $proxy.nil? - -file = "#{home}/.evnet" -if File.file?(file) - Hash.file(file).each do |k, v| - eval "$#{k} = '#{v}'" unless k=~ /^\#/ - #$proxy_auth = [$proxy_auth].pack("m").chomp if k == "proxy_auth" - end -end - -def uri2txt(s) - i = s.index(/%[[:digit:]]{2}/) - while not i.nil? - s = s[0..(i-1)] + s[(i+1)..(i+2)].unpack('H2').shift.to_i.chr + s[(i+3)..-1] - i = s.index(/%[[:digit:]]{2}/) - end - s -end - -class TCPServer - def self.freeport(from, to, remote=false) - if windows? or cygwin? - TCPServer.freeport_windows(from, to, remote) - else - TCPServer.freeport_linux(from, to, remote) - end - end - - def self.freeport_linux(from, to, remote) - ports = (from..to).to_a - port = nil - res = nil - - while res.nil? and not ports.empty? - begin - port = ports[0] - ports.delete(port) - - io = TCPServer.new(remote ? "0.0.0.0" : "localhost", port) - - res = [port, io] - rescue - end - end - - res = [nil, nil] if res.nil? - - port, io = res - - return port, io - end - - def self.freeport_windows(from, to, remote) - ports = (from..to).to_a - port = nil - res = nil - - while res.nil? and not ports.empty? - begin - port = ports.any - ports.delete(port) - - io = TCPSocket.new("localhost", port) - io.close - rescue - res = port - end - end - - port, io = res - - return port, io - end - - def self.freeport_windows2(from, to, remote) - res = nil - port = from - - while res.nil? and port <= to - begin - io = TCPSocket.new("localhost", port) - io.close - - port += 1 - rescue - res = port - end - end - - return res - end - - def self.usedports(from, to) - threads = [] - res = [] - - from.upto(to) do |port| - threads << Thread.new do - begin - io = TCPSocket.new("localhost", port) - io.close - - port - rescue - nil - end - end - end - - threads.each do |thread| - port = thread.value - res << port unless port.nil? - end - - return res - end -end - -class EVURI - attr_reader :protocol - attr_writer :protocol - attr_reader :userpass - attr_writer :userpass - attr_reader :host - attr_writer :host - attr_reader :port - attr_writer :port - attr_reader :path - attr_writer :path - attr_reader :vars - attr_writer :vars - attr_reader :anchor - attr_writer :anchor - - def initialize(url) - begin - @protocol, @userpass, @host, @port, d1, @path, d2, @vars, @anchor = URI.split(url.to_s) - rescue - end - - @protocol = "" if @protocol.nil? - @userpass = "" if @userpass.nil? - @host = "" if @host.nil? - @port = 0 if @port.nil? - @path = "" if @path.nil? - @vars = "" if @vars.nil? - @anchor = "" if @anchor.nil? - - res = {} - @varsvolgorde = [] - @vars.split(/&/).each{|var| k, v = var.split(/=/) ; res[k] = v ; @varsvolgorde << k} - @vars = res - - @port = @port.to_i - end - - def + (url2) - url1 = self.to_s - url2 = url2.to_s if url2.kind_of?(self.class) - - return EVURI.new((URI::Generic.new(*URI.split(url1)) + URI::Generic.new(*URI.split(url2))).to_s) rescue nil - end - - def to_s - protocol = @protocol - userpass = @userpass - host = @host - port = @port.to_s - path = @path - vars = varstring - anchor = @anchor - - protocol = nil if @protocol.empty? - userpass = nil if @userpass.empty? - host = nil if @host.empty? - port = nil if @port.zero? - path = nil if @path.empty? - vars = nil if @vars.empty? - anchor = nil if @anchor.empty? - - res = URI::HTTP.new(@protocol, @userpass, @host, port, nil, @path, nil, vars, @anchor).to_s.from_html - - res.gsub!(/@/, "") if (@userpass.nil? or @userpass.empty?) - - res.gsub!(/\#$/, "") - - return res - end - - def varstring - res = [] - vars = @vars.dup - - @varsvolgorde.each do |k| - if vars.include?(k) - v = vars[k] - vars.delete(k) - - res << (v.nil? ? k : "#{k}=#{v}") - end - end - - res.concat(vars.collect{|k, v| v.nil? ? k : "#{k}=#{v}"}) - - return res.join("&") - end -end - -class HTTPClient - @@versie = 1 - @@mutex = Mutex.new - @@hosts = {} - - class Header - attr_reader :header - attr_reader :protocol - attr_reader :code - attr_reader :text - - def initialize(header) - @header = {} - - if not header.nil? - firstline, rest = header.split(/\r*\n/, 2) - - @protocol, @code, @text = firstline.split(/ */, 3) - - @code = @code.to_i - - if not rest.nil? - rest.split(/\r*\n/).each do |line| - key, value = line.split(/ /, 2) - @header[key.sub(/:$/, "").downcase] = value - end - end - end - end - - def to_s - res = "" - - res << "%s %s %s\n" % [@protocol, @code, @text] - - @header.each do |k, v| - res << "%s=%s\n" % [k, v] - end - - return res - end - end - - class Chunk - def initialize(data) - @data = "" - line, data = data.split(/\r*\n/, 2) - size, ext = line.split(/;/, 2) - size = size.hex - while not size.zero? and not data.nil? - @data += data[0..(size-1)] - data = data[size..-1] - if not data.nil? - data.gsub!(/^\r*\n/, "") - line, data = data.split(/\r*\n/, 2) - size, ext = line.split(/;/, 2) - size = size.hex - end - end - end - - def to_s - @data - end - end - - def self.head(uri, form={}, recursive=true) - header = Header.new(nil) - - begin - while not uri.nil? - if $proxy.nil? or $proxy.empty? - uri = EVURI.new(uri) if uri.kind_of? String - host = uri.host - port = uri.port - io = nil - - @@mutex.synchronize do - @@hosts[host] = IPSocket.getaddress(host) if not @@hosts.include?(host) - io = TCPSocket.new(@@hosts[host], port.zero? ? 80 : port) - end - - io.write("HEAD #{uri.path or '/'}#{uri.varstring.empty? ? '' : '?' + uri.varstring} HTTP/1.0\r\nHost: #{host}\r\n\r\n") - else - proxy = EVURI.new($proxy) - host = proxy.host - port = proxy.port - - io = TCPSocket.new(host, port.zero? ? 8080 : port) - - io.write("HEAD #{uri} HTTP/1.0\r\n#{"Proxy-Authorization: Basic "+$proxy_auth+"\r\n" if not $proxy_auth.nil?}\r\n\r\n") - end - - io.close_write - - res = io.read - header, data = nil, nil - header, data = res.split(/\r*\n\r*\n/, 2) if not res.nil? - header = Header.new(header) - - if recursive and header.header["location"] != uri.to_s - uri = EVURI.new(uri) + header.header["location"] - else - uri = nil - end - end - rescue - header = Header.new(nil) - end - - return header - end - - def self.get(uri, form={}) - post = Array.new - form.each_pair do |var, value| - post << "#{var.to_html}=#{value.to_html}" - end - post = post.join("?") - - data = nil - - begin - while not uri.nil? - if $proxy.nil? or $proxy.empty? - uri = EVURI.new(uri) if uri.kind_of? String - host = uri.host - port = uri.port - - io = nil - @@mutex.synchronize do - @@hosts[host] = IPSocket.getaddress(host) if not @@hosts.include?(host) - io = TCPSocket.new(@@hosts[host], port.zero? ? 80 : port) - end - - if post.empty? - io.write "GET %s%s HTTP/1.0\r\n" % [(uri.path or '/'), (uri.varstring.empty? ? '' : '?' + uri.varstring)] - else - io.write "POST %s%s HTTP/1.0\r\n" % [(uri.path or '/'), (uri.varstring.empty? ? '' : '?' + uri.varstring)] - end - else - proxy = EVURI.new($proxy) - host = proxy.host - port = proxy.port - - io = TCPSocket.new(host, port.zero? ? 8080 : port) - - if post.empty? - io.write "GET %s HTTP/1.0\r\n" % uri - else - io.write "POST %s HTTP/1.0\r\n" % uri - end - end - - io.write "Host: %s\r\n" % host - io.write "User-Agent: evwget\r\n" - io.write "Proxy-Authorization: Basic %s\r\n" % $proxy_auth unless $proxy_auth.nil? - #io.write "Accept-Encoding: deflate\r\n" - #io.write "Connection: close\r\n" - io.write "Content-Type: application/x-www-form-urlencoded\r\n" unless post.empty? - io.write "Content-Length: %s\r\n" % post.length unless post.empty? - io.write "\r\n" - io.write post unless post.empty? - - io.close_write - - res = io.read - header, data = nil, nil - header, data = res.split(/\r*\n\r*\n/, 2) if not res.nil? - - header = Header.new(header) - - if header.header["location"] != uri.to_s - uri = EVURI.new(uri) + header.header["location"] - else - uri = nil - end - - if header.header["transfer-encoding"] == "chunked" - data = Chunk.new(data).to_s if not data.nil? - end - - data = nil unless header.code == 200 - end - rescue - data = nil - end - - return data - end - - def self.head_from_cache(uri, form={}) - from_cache("head", uri, form) - end - - def self.get_from_cache(uri, form={}) - from_cache("get", uri, form) - end - - def self.from_cache(action, uri, form) - loc = uri.to_s + form.sort.inspect - hash = MD5.new("#{@@versie} #{loc}") - - dir = "#{temp}/evcache.#{user}/httpclient.#{action}" - file = "#{dir}/#{hash}" - data = nil - - Dir.mkdirrec(dir) - - expire = 356*24*60*60 - - if File.file?(file) and (Time.new.to_f - File.stat(file).mtime.to_f < expire) - @@mutex.synchronize do - File.open(file, "rb") {|f| data = f.read} - end - else - data = method(action).call(uri, form) - - if not data.nil? - @@mutex.synchronize do - File.open(file, "wb") {|f| f.write data} - end - end - end - - return data - end -end - -class RequestGet < Hash - def initialize(data) - CGI.parse(data).each do |k, v| - self[k] = v.join(" ") - end - end -end - -class RequestPost < Hash - def initialize(data) - CGI.parse(data).each do |k, v| - self[k] = v.join(" ") - end - end -end - -class RequestRequest - attr_reader :method - attr_reader :uri - attr_reader :path - attr_reader :data - attr_reader :protocol - - def initialize(firstline) - @method, @uri, @protocol = firstline.split(/ /) - @path, @data = @uri.split(/\?/) - @data = "" if @data.nil? # TODO - -# i = @path.index(/%[[:digit:]]{2}/) -# while not i.nil? -# @path = @path[0..(i-1)] + @path[(i+1)..(i+2)].unpack('H2').shift.to_i.chr + @path[(i+3)..-1] -# i = @path.index(/%[[:digit:]]{2}/) -# end - end - - def to_s - "#{@method} #{@uri} #{@protocol}\r\n" - end - - def inspect - "(RequestRequest: %s)" % [@method, @path, @data, @protocol].join(", ") - end -end - -class Request < Hash - attr_reader :peeraddr - attr_reader :request - attr_reader :cookies - attr_reader :vars - - def initialize(io) - @io = io - - firstline = @io.gets - - return if firstline.nil? - - @request = RequestRequest.new(firstline.strip) - - line = @io.gets - line = line.strip unless line.nil? - while not line.nil? and not line.empty? - key, value = line.split(" ", 2) - self[key.sub(/:$/, "").downcase] = value - - line = @io.gets - line = line.strip unless line.nil? - end - - cookie = self["cookie"] - cookie = "" if cookie.nil? - @cookies = {} - cookie.split(/;/).each do |s| - k, v = s.strip.split(/=/, 2) - @cookies[k] = v - end - - if not @request.method.nil? - case @request.method.upcase - when "HEAD" - when "GET" - @vars = RequestGet.new(@request.data.nil? ? "" : @request.data) - when "POST" - data = (@io.read(self["content-length"].to_i) or "") - @vars = RequestPost.new((self["content-type"] == "application/x-www-form-urlencoded") ? data : "") - else - $stderr.puts "Unknown request ('#{firstline}')." - end - end - - @peeraddr = @io.peeraddr - - @pda = false - @pda = true if (self.include?("user-agent") and self["user-agent"].downcase.include?("windows ce")) - @pda = true if (self.include?("user-agent") and self["user-agent"].downcase.include?("handhttp")) - - @io.close_read - end - - def pda? - @pda - end - - def to_s - res = @request.to_s - self.each do |k, v| - res << "#{k}: #{v}\r\n" - end - res - end - - def inspect - "(Request: %s)" % [@peeraddr, @request.inspect, @vars.inspect, @cookies.inspect, super].join(", ") - end -end - -class Response < Hash - attr_writer :response - attr_reader :cookies - attr_reader :stop - attr_reader :at_stop - - def initialize(io) - @io = io - @response = "HTTP/1.0 200 OK" - @cookies = {} - @data = "" - @syncd = false - @stop = false - @at_stop = lambda{} - end - - def flush - sync - @io.close - end - - def to_s - res = "#{@response}\r\n" - self.each do |k, v| - res << "#{k}: #{v}\r\n" - end - - @cookies.each do |k, v| - res << "Set-Cookie: %s=%s;\r\n" % [k, v] - end - - res - end - - def sync - @io.write("#{to_s}\r\n") unless @syncd - @io.write(@data) - @data = "" - @syncd = true - end - - def << (s) - @data << s - end - - def inspect - "(Response: %s)" % [@response, @data].join(", ") - end - - def stop(&block) - @stop = true - @at_stop = block unless block.nil? - end - - def stop? - @stop - end -end - -class HTTPServerException < Exception -end - -class HTTPServer - def self.serve(portio=80, remote=false, auth=nil, realm="ev/net") - port, server = portio - - begin - server = TCPServer.new(remote ? "0.0.0.0" : "localhost", port) if server.nil? - - $stderr.puts "Just point your browser to http://localhost:#{port}/ ..." - rescue - server = nil - - $stderr.puts "Port #{port} is in use." - end - - if not server.nil? - count = 0 - - at_exit do - $stderr.puts "Received #{count} requests" - end - - serverthread = - Thread.new do - mutex = Mutex.new - - Thread.current["threads"] = [] - - every(1, Thread.current) do |thread| - mutex.synchronize do - thread["threads"].delete_if{|t| (not t.alive?)} - end - end - - loop do - io = server.accept - count += 1 - - thread = - Thread.new(Thread.current, count) do |parentthread, count2| - stop = false - - begin - begin - req = Request.new(io) - resp = Response.new(io) - rescue - raise HTTPServerException - end - - begin - ip = req.peeraddr[3] - rescue NameError - raise HTTPServerException - end - - if (not remote) or (remote and (auth.nil? or auth.empty? or authenticate(auth, realm, req, resp))) - $stderr.puts "#{count2}, #{Time.new.strftime("%Y-%m-%d.%H:%M:%S")}, #{ip}, #{req.request.to_s.strip}" - - begin - yield(req, resp) - rescue Exception => e - mutex.synchronize do - $stderr.puts e.class.to_s + ": " + e.message - $stderr.puts e.backtrace.collect{|s| "\t"+s}.join("\n") - end - end - - stop = true if resp.stop? - end - - begin - resp.flush - rescue - raise HTTPServerException - end - rescue HTTPServerException - end - - parentthread["stop"] = resp if stop - end - - mutex.synchronize do - Thread.current["threads"] << thread - end - end - end - - sleep 0.1 while not serverthread["stop"] - - serverthread["threads"].each {|t| t.join} - - serverthread["stop"].at_stop.call - - serverthread.kill - end - end - - def self.authenticate(auth, realm, req, resp) - if auth.kind_of? String - file = "#{home}/#{auth}" - auths = {} - auths = Hash.file(file) if File.file?(file) - else - auths = auth - end - - authuserpassword = req["authorization"] - if not authuserpassword.nil? - authtype, userpassword = authuserpassword.split(/ /) - if authtype == "Basic" and not userpassword.nil? - u, p = userpassword.unpack("m").shift.split(/:/) - end - end - - ok = (auths.include?(u) and auths[u] == p) - - if ok - - else - resp["WWW-Authenticate"] = "Basic realm=\"#{realm}\"" - resp.response = "HTTP/1.0 401 Unauthorized" - end - - return ok - end -end +require "ev/ruby" +require "ev/ftools" +require "net/http" +require "socket" +require "uri" +require "cgi" +require "md5" +require "thread" + +$proxy = ENV["PROXY"] if $proxy.nil? + +file = "#{home}/.evnet" +if File.file?(file) + Hash.file(file).each do |k, v| + eval "$#{k} = '#{v}'" unless k=~ /^\#/ + #$proxy_auth = [$proxy_auth].pack("m").chomp if k == "proxy_auth" + end +end + +def uri2txt(s) + i = s.index(/%[[:digit:]]{2}/) + while not i.nil? + s = s[0..(i-1)] + s[(i+1)..(i+2)].unpack('H2').shift.to_i.chr + s[(i+3)..-1] + i = s.index(/%[[:digit:]]{2}/) + end + s +end + +class TCPServer + def self.freeport(from, to, remote=false) + if windows? or cygwin? + TCPServer.freeport_windows(from, to, remote) + else + TCPServer.freeport_linux(from, to, remote) + end + end + + def self.freeport_linux(from, to, remote) + ports = (from..to).to_a + port = nil + res = nil + + while res.nil? and not ports.empty? + begin + port = ports[0] + ports.delete(port) + + io = TCPServer.new(remote ? "0.0.0.0" : "localhost", port) + + res = [port, io] + rescue + end + end + + res = [nil, nil] if res.nil? + + port, io = res + + return port, io + end + + def self.freeport_windows(from, to, remote) + ports = (from..to).to_a + port = nil + res = nil + + while res.nil? and not ports.empty? + begin + port = ports.any + ports.delete(port) + + io = TCPSocket.new("localhost", port) + io.close + rescue + res = port + end + end + + port, io = res + + return port, io + end + + def self.freeport_windows2(from, to, remote) + res = nil + port = from + + while res.nil? and port <= to + begin + io = TCPSocket.new("localhost", port) + io.close + + port += 1 + rescue + res = port + end + end + + return res + end + + def self.usedports(from, to) + threads = [] + res = [] + + from.upto(to) do |port| + threads << Thread.new do + begin + io = TCPSocket.new("localhost", port) + io.close + + port + rescue + nil + end + end + end + + threads.each do |thread| + port = thread.value + res << port unless port.nil? + end + + return res + end +end + +class EVURI + attr_reader :protocol + attr_writer :protocol + attr_reader :userpass + attr_writer :userpass + attr_reader :host + attr_writer :host + attr_reader :port + attr_writer :port + attr_reader :path + attr_writer :path + attr_reader :vars + attr_writer :vars + attr_reader :anchor + attr_writer :anchor + + def initialize(url) + begin + @protocol, @userpass, @host, @port, d1, @path, d2, @vars, @anchor = URI.split(url.to_s) + rescue + end + + @protocol = "" if @protocol.nil? + @userpass = "" if @userpass.nil? + @host = "" if @host.nil? + @port = 0 if @port.nil? + @path = "" if @path.nil? + @vars = "" if @vars.nil? + @anchor = "" if @anchor.nil? + + res = {} + @varsvolgorde = [] + @vars.split(/&/).each{|var| k, v = var.split(/=/) ; res[k] = v ; @varsvolgorde << k} + @vars = res + + @port = @port.to_i + end + + def + (url2) + url1 = self.to_s + url2 = url2.to_s if url2.kind_of?(self.class) + + return EVURI.new((URI::Generic.new(*URI.split(url1)) + URI::Generic.new(*URI.split(url2))).to_s) rescue nil + end + + def to_s + protocol = @protocol + userpass = @userpass + host = @host + port = @port.to_s + path = @path + vars = varstring + anchor = @anchor + + protocol = nil if @protocol.empty? + userpass = nil if @userpass.empty? + host = nil if @host.empty? + port = nil if @port.zero? + path = nil if @path.empty? + vars = nil if @vars.empty? + anchor = nil if @anchor.empty? + + res = URI::HTTP.new(@protocol, @userpass, @host, port, nil, @path, nil, vars, @anchor).to_s.from_html + + res.gsub!(/@/, "") if (@userpass.nil? or @userpass.empty?) + + res.gsub!(/\#$/, "") + + return res + end + + def varstring + res = [] + vars = @vars.dup + + @varsvolgorde.each do |k| + if vars.include?(k) + v = vars[k] + vars.delete(k) + + res << (v.nil? ? k : "#{k}=#{v}") + end + end + + res.concat(vars.collect{|k, v| v.nil? ? k : "#{k}=#{v}"}) + + return res.join("&") + end +end + +class HTTPClient + @@versie = 1 + @@mutex = Mutex.new + @@hosts = {} + + class Header + attr_reader :header + attr_reader :protocol + attr_reader :code + attr_reader :text + + def initialize(header) + @header = {} + + if not header.nil? + firstline, rest = header.split(/\r*\n/, 2) + + @protocol, @code, @text = firstline.split(/ */, 3) + + @code = @code.to_i + + if not rest.nil? + rest.split(/\r*\n/).each do |line| + key, value = line.split(/ /, 2) + @header[key.sub(/:$/, "").downcase] = value + end + end + end + end + + def to_s + res = "" + + res << "%s %s %s\n" % [@protocol, @code, @text] + + @header.each do |k, v| + res << "%s=%s\n" % [k, v] + end + + return res + end + end + + class Chunk + def initialize(data) + @data = "" + line, data = data.split(/\r*\n/, 2) + size, ext = line.split(/;/, 2) + size = size.hex + while not size.zero? and not data.nil? + @data += data[0..(size-1)] + data = data[size..-1] + if not data.nil? + data.gsub!(/^\r*\n/, "") + line, data = data.split(/\r*\n/, 2) + size, ext = line.split(/;/, 2) + size = size.hex + end + end + end + + def to_s + @data + end + end + + def self.head(uri, form={}, recursive=true) + header = Header.new(nil) + + begin + while not uri.nil? + if $proxy.nil? or $proxy.empty? + uri = EVURI.new(uri) if uri.kind_of? String + host = uri.host + port = uri.port + io = nil + + @@mutex.synchronize do + @@hosts[host] = IPSocket.getaddress(host) if not @@hosts.include?(host) + io = TCPSocket.new(@@hosts[host], port.zero? ? 80 : port) + end + + io.write("HEAD #{uri.path or '/'}#{uri.varstring.empty? ? '' : '?' + uri.varstring} HTTP/1.0\r\nHost: #{host}\r\n\r\n") + else + proxy = EVURI.new($proxy) + host = proxy.host + port = proxy.port + + io = TCPSocket.new(host, port.zero? ? 8080 : port) + + io.write("HEAD #{uri} HTTP/1.0\r\n#{"Proxy-Authorization: Basic "+$proxy_auth+"\r\n" if not $proxy_auth.nil?}\r\n\r\n") + end + + io.close_write + + res = io.read + header, data = nil, nil + header, data = res.split(/\r*\n\r*\n/, 2) if not res.nil? + header = Header.new(header) + + if recursive and header.header["location"] != uri.to_s + uri = EVURI.new(uri) + header.header["location"] + else + uri = nil + end + end + rescue + header = Header.new(nil) + end + + return header + end + + def self.get(uri, form={}) + post = Array.new + form.each_pair do |var, value| + post << "#{var.to_html}=#{value.to_html}" + end + post = post.join("?") + + data = nil + + begin + while not uri.nil? + if $proxy.nil? or $proxy.empty? + uri = EVURI.new(uri) if uri.kind_of? String + host = uri.host + port = uri.port + + io = nil + @@mutex.synchronize do + @@hosts[host] = IPSocket.getaddress(host) if not @@hosts.include?(host) + io = TCPSocket.new(@@hosts[host], port.zero? ? 80 : port) + end + + if post.empty? + io.write "GET %s%s HTTP/1.0\r\n" % [(uri.path or '/'), (uri.varstring.empty? ? '' : '?' + uri.varstring)] + else + io.write "POST %s%s HTTP/1.0\r\n" % [(uri.path or '/'), (uri.varstring.empty? ? '' : '?' + uri.varstring)] + end + else + proxy = EVURI.new($proxy) + host = proxy.host + port = proxy.port + + io = TCPSocket.new(host, port.zero? ? 8080 : port) + + if post.empty? + io.write "GET %s HTTP/1.0\r\n" % uri + else + io.write "POST %s HTTP/1.0\r\n" % uri + end + end + + io.write "Host: %s\r\n" % host + io.write "User-Agent: evwget\r\n" + io.write "Proxy-Authorization: Basic %s\r\n" % $proxy_auth unless $proxy_auth.nil? + #io.write "Accept-Encoding: deflate\r\n" + #io.write "Connection: close\r\n" + io.write "Content-Type: application/x-www-form-urlencoded\r\n" unless post.empty? + io.write "Content-Length: %s\r\n" % post.length unless post.empty? + io.write "\r\n" + io.write post unless post.empty? + + io.close_write + + res = io.read + header, data = nil, nil + header, data = res.split(/\r*\n\r*\n/, 2) if not res.nil? + + header = Header.new(header) + + if header.header["location"] != uri.to_s + uri = EVURI.new(uri) + header.header["location"] + else + uri = nil + end + + if header.header["transfer-encoding"] == "chunked" + data = Chunk.new(data).to_s if not data.nil? + end + + data = nil unless header.code == 200 + end + rescue + data = nil + end + + return data + end + + def self.head_from_cache(uri, form={}) + from_cache("head", uri, form) + end + + def self.get_from_cache(uri, form={}) + from_cache("get", uri, form) + end + + def self.from_cache(action, uri, form) + loc = uri.to_s + form.sort.inspect + hash = MD5.new("#{@@versie} #{loc}") + + dir = "#{temp}/evcache.#{user}/httpclient.#{action}" + file = "#{dir}/#{hash}" + data = nil + + Dir.mkdirrec(dir) + + expire = 356*24*60*60 + + if File.file?(file) and (Time.new.to_f - File.stat(file).mtime.to_f < expire) + @@mutex.synchronize do + File.open(file, "rb") {|f| data = f.read} + end + else + data = method(action).call(uri, form) + + if not data.nil? + @@mutex.synchronize do + File.open(file, "wb") {|f| f.write data} + end + end + end + + return data + end +end + +class RequestGet < Hash + def initialize(data) + CGI.parse(data).each do |k, v| + self[k] = v.join(" ") + end + end +end + +class RequestPost < Hash + def initialize(data) + CGI.parse(data).each do |k, v| + self[k] = v.join(" ") + end + end +end + +class RequestRequest + attr_reader :method + attr_reader :uri + attr_reader :path + attr_reader :data + attr_reader :protocol + + def initialize(firstline) + @method, @uri, @protocol = firstline.split(/ /) + @path, @data = @uri.split(/\?/) + @data = "" if @data.nil? # TODO + +# i = @path.index(/%[[:digit:]]{2}/) +# while not i.nil? +# @path = @path[0..(i-1)] + @path[(i+1)..(i+2)].unpack('H2').shift.to_i.chr + @path[(i+3)..-1] +# i = @path.index(/%[[:digit:]]{2}/) +# end + end + + def to_s + "#{@method} #{@uri} #{@protocol}\r\n" + end + + def inspect + "(RequestRequest: %s)" % [@method, @path, @data, @protocol].join(", ") + end +end + +class Request < Hash + attr_reader :peeraddr + attr_reader :request + attr_reader :cookies + attr_reader :vars + + def initialize(io) + @io = io + + firstline = @io.gets + + return if firstline.nil? + + @request = RequestRequest.new(firstline.strip) + + line = @io.gets + line = line.strip unless line.nil? + while not line.nil? and not line.empty? + key, value = line.split(" ", 2) + self[key.sub(/:$/, "").downcase] = value + + line = @io.gets + line = line.strip unless line.nil? + end + + cookie = self["cookie"] + cookie = "" if cookie.nil? + @cookies = {} + cookie.split(/;/).each do |s| + k, v = s.strip.split(/=/, 2) + @cookies[k] = v + end + + if not @request.method.nil? + case @request.method.upcase + when "HEAD" + when "GET" + @vars = RequestGet.new(@request.data.nil? ? "" : @request.data) + when "POST" + data = (@io.read(self["content-length"].to_i) or "") + @vars = RequestPost.new((self["content-type"] == "application/x-www-form-urlencoded") ? data : "") + else + $stderr.puts "Unknown request ('#{firstline}')." + end + end + + @peeraddr = @io.peeraddr + + @pda = false + @pda = true if (self.include?("user-agent") and self["user-agent"].downcase.include?("windows ce")) + @pda = true if (self.include?("user-agent") and self["user-agent"].downcase.include?("handhttp")) + + @io.close_read + end + + def pda? + @pda + end + + def to_s + res = @request.to_s + self.each do |k, v| + res << "#{k}: #{v}\r\n" + end + res + end + + def inspect + "(Request: %s)" % [@peeraddr, @request.inspect, @vars.inspect, @cookies.inspect, super].join(", ") + end +end + +class Response < Hash + attr_writer :response + attr_reader :cookies + attr_reader :stop + attr_reader :at_stop + + def initialize(io) + @io = io + @response = "HTTP/1.0 200 OK" + @cookies = {} + @data = "" + @syncd = false + @stop = false + @at_stop = lambda{} + end + + def flush + sync + @io.close + end + + def to_s + res = "#{@response}\r\n" + self.each do |k, v| + res << "#{k}: #{v}\r\n" + end + + @cookies.each do |k, v| + res << "Set-Cookie: %s=%s;\r\n" % [k, v] + end + + res + end + + def sync + @io.write("#{to_s}\r\n") unless @syncd + @io.write(@data) + @data = "" + @syncd = true + end + + def << (s) + @data << s + end + + def inspect + "(Response: %s)" % [@response, @data].join(", ") + end + + def stop(&block) + @stop = true + @at_stop = block unless block.nil? + end + + def stop? + @stop + end +end + +class HTTPServerException < Exception +end + +class HTTPServer + def self.serve(portio=80, remote=false, auth=nil, realm="ev/net") + port, server = portio + + begin + server = TCPServer.new(remote ? "0.0.0.0" : "localhost", port) if server.nil? + + $stderr.puts "Just point your browser to http://localhost:#{port}/ ..." + rescue + server = nil + + $stderr.puts "Port #{port} is in use." + end + + if not server.nil? + count = 0 + + at_exit do + $stderr.puts "Received #{count} requests" + end + + serverthread = + Thread.new do + mutex = Mutex.new + + Thread.current["threads"] = [] + + every(1, Thread.current) do |thread| + mutex.synchronize do + thread["threads"].delete_if{|t| (not t.alive?)} + end + end + + loop do + io = server.accept + count += 1 + + thread = + Thread.new(Thread.current, count) do |parentthread, count2| + stop = false + + begin + begin + req = Request.new(io) + resp = Response.new(io) + rescue + raise HTTPServerException + end + + begin + ip = req.peeraddr[3] + rescue NameError + raise HTTPServerException + end + + if (not remote) or (remote and (auth.nil? or auth.empty? or authenticate(auth, realm, req, resp))) + $stderr.puts "#{count2}, #{Time.new.strftime("%Y-%m-%d.%H:%M:%S")}, #{ip}, #{req.request.to_s.strip}" + + begin + yield(req, resp) + rescue Exception => e + mutex.synchronize do + $stderr.puts e.class.to_s + ": " + e.message + $stderr.puts e.backtrace.collect{|s| "\t"+s}.join("\n") + end + end + + stop = true if resp.stop? + end + + begin + resp.flush + rescue + raise HTTPServerException + end + rescue HTTPServerException + end + + parentthread["stop"] = resp if stop + end + + mutex.synchronize do + Thread.current["threads"] << thread + end + end + end + + sleep 0.1 while not serverthread["stop"] + + serverthread["threads"].each {|t| t.join} + + serverthread["stop"].at_stop.call + + serverthread.kill + end + end + + def self.authenticate(auth, realm, req, resp) + if auth.kind_of? String + file = "#{home}/#{auth}" + auths = {} + auths = Hash.file(file) if File.file?(file) + else + auths = auth + end + + authuserpassword = req["authorization"] + if not authuserpassword.nil? + authtype, userpassword = authuserpassword.split(/ /) + if authtype == "Basic" and not userpassword.nil? + u, p = userpassword.unpack("m").shift.split(/:/) + end + end + + ok = (auths.include?(u) and auths[u] == p) + + if ok + + else + resp["WWW-Authenticate"] = "Basic realm=\"#{realm}\"" + resp.response = "HTTP/1.0 401 Unauthorized" + end + + return ok + end +end