# frozen_string_literal: true
# backtick_javascript: true
# Copied from https://raw.githubusercontent.com/ruby/ruby/373babeaac8c3e663e1ded74a9f06ac94a671ed9/lib/open-uri.rb
require 'stringio'
require 'corelib/array/pack'
module Kernel
private
alias open_uri_original_open open # :nodoc:
class << self
alias open_uri_original_open open # :nodoc:
end
# Allows the opening of various resources including URIs.
#
# If the first argument responds to the 'open' method, 'open' is called on
# it with the rest of the arguments.
#
# If the first argument is a string that begins with xxx://, it is parsed by
# URI.parse. If the parsed object responds to the 'open' method,
# 'open' is called on it with the rest of the arguments.
#
# Otherwise, the original Kernel#open is called.
#
# OpenURI::OpenRead#open provides URI::HTTP#open, URI::HTTPS#open and
# URI::FTP#open, Kernel#open.
#
# We can accept URIs and strings that begin with http://, https:// and
# ftp://. In these cases, the opened file object is extended by OpenURI::Meta.
def open(name, *rest, &block) # :doc:
if name.respond_to?(:to_str) && %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name
OpenURI.open_uri(name, *rest, &block)
else
open_uri_original_open(name, *rest, &block)
end
end
module_function :open
end
# OpenURI is an easy-to-use wrapper for Net::HTTP, Net::HTTPS and Net::FTP.
#
# == Example
#
# It is possible to open an http, https or ftp URL as though it were a file:
#
# open("http://www.ruby-lang.org/") {|f|
# f.each_line {|line| p line}
# }
#
# The opened file has several getter methods for its meta-information, as
# follows, since it is extended by OpenURI::Meta.
#
# open("http://www.ruby-lang.org/en") {|f|
# f.each_line {|line| p line}
# p f.base_uri #
# p f.content_type # "text/html"
# p f.charset # "iso-8859-1"
# p f.content_encoding # []
# p f.last_modified # Thu Dec 05 02:45:02 UTC 2002
# }
#
# Additional header fields can be specified by an optional hash argument.
#
# open("http://www.ruby-lang.org/en/",
# "User-Agent" => "Ruby/#{RUBY_VERSION}",
# "From" => "foo@bar.invalid",
# "Referer" => "http://www.ruby-lang.org/") {|f|
# # ...
# }
#
# The environment variables such as http_proxy, https_proxy and ftp_proxy
# are in effect by default. Here we disable proxy:
#
# open("http://www.ruby-lang.org/en/", :proxy => nil) {|f|
# # ...
# }
#
# See OpenURI::OpenRead.open and Kernel#open for more on available options.
#
# URI objects can be opened in a similar way.
#
# uri = URI.parse("http://www.ruby-lang.org/en/")
# uri.open {|f|
# # ...
# }
#
# URI objects can be read directly. The returned string is also extended by
# OpenURI::Meta.
#
# str = uri.read
# p str.base_uri
#
# Author:: Tanaka Akira
module OpenURI
def self.open_uri(name, *rest) # :nodoc:
io = open_loop(name, {})
io.rewind
if block_given?
begin
yield io
ensure
close_io(io)
end
else
io
end
end
def self.close_io(io)
if io.respond_to? :close!
io.close! # Tempfile
else
io.close unless io.closed?
end
end
def self.open_loop(uri, options) # :nodoc:
req = request(uri)
data = `req.responseText`
status = `req.status`
status_text = `req.statusText && req.statusText.errno ? req.statusText.errno : req.statusText`
if status == 200 || (status == 0 && data)
build_response(req, status, status_text)
else
raise OpenURI::HTTPError.new("#{status} #{status_text}", '')
end
end
def self.build_response(req, status, status_text)
buf = Buffer.new
buf << data(req).pack('c*')
io = buf.io
#io.base_uri = uri # TODO: Generate a URI object from the uri String
io.status = "#{status} #{status_text}"
io.meta_add_field('content-type', `req.getResponseHeader("Content-Type") || ''`)
last_modified = `req.getResponseHeader("Last-Modified")`
io.meta_add_field('last-modified', last_modified) if last_modified
io
end
def self.data(req)
%x{
var binStr = req.responseText;
var byteArray = [];
for (var i = 0, len = binStr.length; i < len; ++i) {
var c = binStr.charCodeAt(i);
var byteCode = c & 0xff; // byte at offset i
byteArray.push(byteCode);
}
return byteArray;
}
end
def self.request(uri)
%x{
try {
var xhr = new XMLHttpRequest();
xhr.open('GET', uri, false);
// We cannot use xhr.responseType = "arraybuffer" because XMLHttpRequest is used in synchronous mode.
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType#Synchronous_XHR_restrictions
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.send();
return xhr;
} catch (error) {
#{raise OpenURI::HTTPError.new(`error.message`, '')}
}
}
end
class HTTPError < StandardError
def initialize(message, io)
super(message, io)
@io = io
end
attr_reader :io
end
class Buffer # :nodoc: all
def initialize
@io = StringIO.new
@size = 0
end
attr_reader :size
def <<(str)
@io << str
@size += str.length
end
def io
Meta.init @io unless Meta === @io
@io
end
end
# Mixin for holding meta-information.
module Meta
def Meta.init(obj, src=nil) # :nodoc:
obj.extend Meta
obj.instance_eval {
@base_uri = nil
@meta = {} # name to string. legacy.
@metas = {} # name to array of strings.
}
if src
obj.status = src.status
obj.base_uri = src.base_uri
src.metas.each {|name, values|
obj.meta_add_field2(name, values)
}
end
end
# returns an Array that consists of status code and message.
attr_accessor :status
# returns a URI that is the base of relative URIs in the data.
# It may differ from the URI supplied by a user due to redirection.
attr_accessor :base_uri
# returns a Hash that represents header fields.
# The Hash keys are downcased for canonicalization.
# The Hash values are a field body.
# If there are multiple field with same field name,
# the field values are concatenated with a comma.
attr_reader :meta
# returns a Hash that represents header fields.
# The Hash keys are downcased for canonicalization.
# The Hash value are an array of field values.
attr_reader :metas
def meta_setup_encoding # :nodoc:
charset = self.charset
enc = find_encoding(charset)
set_encoding(enc)
end
def set_encoding(enc)
if self.respond_to? :force_encoding
self.force_encoding(enc)
elsif self.respond_to? :string
self.string.force_encoding(enc)
else # Tempfile
self.set_encoding enc
end
end
def find_encoding(charset)
enc = nil
if charset
begin
enc = Encoding.find(charset)
rescue ArgumentError
end
end
enc = Encoding::ASCII_8BIT unless enc
enc
end
def meta_add_field2(name, values) # :nodoc:
name = name.downcase
@metas[name] = values
@meta[name] = values.join(', ')
meta_setup_encoding if name == 'content-type'
end
def meta_add_field(name, value) # :nodoc:
meta_add_field2(name, [value])
end
def last_modified
if (vs = @metas['last-modified'])
Time.at(`Date.parse(#{vs.join(', ')}) / 1000`).utc
else
nil
end
end
def content_type_parse # :nodoc:
content_type = @metas['content-type']
# FIXME Extract type, subtype and parameters
content_type.join(', ')
end
# returns a charset parameter in Content-Type field.
# It is downcased for canonicalization.
#
# If charset parameter is not given but a block is given,
# the block is called and its result is returned.
# It can be used to guess charset.
#
# If charset parameter and block is not given,
# nil is returned except text type in HTTP.
# In that case, "iso-8859-1" is returned as defined by RFC2616 3.7.1.
def charset
type = content_type_parse
if type && %r{\Atext/} =~ type && @base_uri && /\Ahttp\z/i =~ @base_uri.scheme
'iso-8859-1' # RFC2616 3.7.1
else
nil
end
end
# returns "type/subtype" which is MIME Content-Type.
# It is downcased for canonicalization.
# Content-Type parameters are stripped.
def content_type
type = content_type_parse
type || 'application/octet-stream'
end
end
# Mixin for HTTP and FTP URIs.
module OpenRead
# OpenURI::OpenRead#open provides `open' for URI::HTTP and URI::FTP.
#
# OpenURI::OpenRead#open takes optional 3 arguments as:
#
# OpenURI::OpenRead#open([mode [, perm]] [, options]) [{|io| ... }]
#
# OpenURI::OpenRead#open returns an IO-like object if block is not given.
# Otherwise it yields the IO object and return the value of the block.
# The IO object is extended with OpenURI::Meta.
#
# +mode+ and +perm+ are the same as Kernel#open.
#
# However, +mode+ must be read mode because OpenURI::OpenRead#open doesn't
# support write mode (yet).
# Also +perm+ is ignored because it is meaningful only for file creation.
#
def open(*rest, &block)
OpenURI.open_uri(self, *rest, &block)
end
# OpenURI::OpenRead#read([options]) reads a content referenced by self and
# returns the content as string.
# The string is extended with OpenURI::Meta.
# The argument +options+ is same as OpenURI::OpenRead#open.
def read(options={})
self.open(options) {|f|
str = f.read
Meta.init str, f
str
}
end
end
end