shopify-cli 1.3.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +2 -2
- data/.github/CONTRIBUTING.md +9 -1
- data/.github/PULL_REQUEST_TEMPLATE.md +10 -1
- data/.github/workflows/release.yml +61 -0
- data/.github/workflows/triage.yml +22 -0
- data/.gitignore +0 -1
- data/.rubocop.yml +61 -8
- data/.rubocop_todo.yml +11 -0
- data/.travis.yml +1 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +39 -37
- data/README.md +39 -7
- data/RELEASING.md +19 -29
- data/Rakefile +2 -0
- data/dev.yml +2 -2
- data/docs/_config.yml +1 -18
- data/docs/app/node/commands/index.md +2 -80
- data/docs/app/node/index.md +2 -33
- data/docs/app/rails/commands/index.md +2 -78
- data/docs/app/rails/index.md +2 -34
- data/docs/core/index.md +2 -84
- data/docs/getting-started/index.md +2 -25
- data/docs/getting-started/install/index.md +1 -118
- data/docs/getting-started/migrate/index.md +2 -94
- data/docs/getting-started/uninstall/index.md +2 -35
- data/docs/getting-started/upgrade/index.md +2 -39
- data/docs/help/start-app/index.md +2 -4
- data/docs/index.md +2 -24
- data/install.sh +1 -1
- data/lib/project_types/extension/cli.rb +21 -11
- data/lib/project_types/extension/commands/extension_command.rb +2 -2
- data/lib/project_types/extension/features/argo.rb +117 -0
- data/lib/project_types/extension/forms/create.rb +2 -2
- data/lib/project_types/extension/models/specification.rb +35 -0
- data/lib/project_types/extension/models/specification_handlers/checkout_post_purchase.rb +19 -0
- data/lib/project_types/extension/models/specification_handlers/default.rb +67 -0
- data/lib/project_types/extension/models/specifications.rb +77 -0
- data/lib/project_types/extension/tasks/configure_features.rb +52 -0
- data/lib/project_types/extension/tasks/fetch_specifications.rb +38 -0
- data/lib/project_types/node/cli.rb +4 -1
- data/lib/project_types/node/commands/connect.rb +15 -0
- data/lib/project_types/node/commands/create.rb +10 -4
- data/lib/project_types/node/commands/generate.rb +2 -11
- data/lib/project_types/node/messages/messages.rb +16 -50
- data/lib/project_types/rails/cli.rb +4 -1
- data/lib/project_types/rails/commands/connect.rb +15 -0
- data/lib/project_types/rails/commands/create.rb +15 -12
- data/lib/project_types/rails/forms/create.rb +1 -1
- data/lib/project_types/rails/gem.rb +1 -1
- data/lib/project_types/rails/messages/messages.rb +8 -5
- data/lib/project_types/script/cli.rb +9 -5
- data/lib/project_types/script/commands/create.rb +6 -4
- data/lib/project_types/script/commands/enable.rb +12 -4
- data/lib/project_types/script/commands/push.rb +5 -13
- data/lib/project_types/script/config/extension_points.yml +17 -12
- data/lib/project_types/script/errors.rb +21 -0
- data/lib/project_types/script/forms/create.rb +26 -2
- data/lib/project_types/script/graphql/app_script_update_or_create.graphql +10 -1
- data/lib/project_types/script/layers/application/build_script.rb +18 -17
- data/lib/project_types/script/layers/application/create_script.rb +12 -10
- data/lib/project_types/script/layers/application/extension_points.rb +24 -0
- data/lib/project_types/script/layers/application/push_script.rb +18 -16
- data/lib/project_types/script/layers/domain/errors.rb +7 -0
- data/lib/project_types/script/layers/domain/extension_point.rb +62 -7
- data/lib/project_types/script/layers/domain/metadata.rb +55 -0
- data/lib/project_types/script/layers/domain/push_package.rb +25 -6
- data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +17 -52
- data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +42 -11
- data/lib/project_types/script/layers/infrastructure/errors.rb +16 -0
- data/lib/project_types/script/layers/infrastructure/extension_point_repository.rb +10 -4
- data/lib/project_types/script/layers/infrastructure/project_creator.rb +2 -1
- data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +25 -13
- data/lib/project_types/script/layers/infrastructure/rust_project_creator.rb +72 -0
- data/lib/project_types/script/layers/infrastructure/rust_task_runner.rb +59 -0
- data/lib/project_types/script/layers/infrastructure/script_service.rb +9 -1
- data/lib/project_types/script/layers/infrastructure/task_runner.rb +4 -3
- data/lib/project_types/script/messages/messages.rb +55 -4
- data/lib/project_types/script/script_project.rb +25 -16
- data/lib/project_types/script/ui/error_handler.rb +59 -1
- data/lib/project_types/theme/cli.rb +40 -0
- data/lib/project_types/theme/commands/connect.rb +54 -0
- data/lib/project_types/theme/commands/create.rb +48 -0
- data/lib/project_types/theme/commands/deploy.rb +38 -0
- data/lib/project_types/theme/commands/generate.rb +20 -0
- data/lib/project_types/theme/commands/generate/env.rb +79 -0
- data/lib/project_types/theme/commands/push.rb +55 -0
- data/lib/project_types/theme/commands/serve.rb +31 -0
- data/lib/project_types/theme/forms/connect.rb +34 -0
- data/lib/project_types/theme/forms/create.rb +22 -0
- data/lib/project_types/theme/messages/messages.rb +147 -0
- data/lib/project_types/theme/tasks/ensure_themekit_installed.rb +78 -0
- data/lib/project_types/theme/themekit.rb +113 -0
- data/lib/shopify-cli/admin_api.rb +42 -2
- data/lib/shopify-cli/api.rb +34 -33
- data/lib/shopify-cli/commands/config.rb +24 -0
- data/lib/shopify-cli/commands/connect.rb +32 -15
- data/lib/shopify-cli/commands/system.rb +10 -1
- data/lib/shopify-cli/context.rb +23 -2
- data/lib/shopify-cli/core/entry_point.rb +1 -1
- data/lib/shopify-cli/core/monorail.rb +6 -4
- data/lib/shopify-cli/feature.rb +0 -2
- data/lib/shopify-cli/http_request.rb +27 -0
- data/lib/shopify-cli/js_deps.rb +1 -1
- data/lib/shopify-cli/messages/messages.rb +31 -7
- data/lib/shopify-cli/method_object.rb +104 -0
- data/lib/shopify-cli/partners_api.rb +25 -3
- data/lib/shopify-cli/process_supervision.rb +1 -1
- data/lib/shopify-cli/project.rb +12 -8
- data/lib/shopify-cli/project_type.rb +18 -2
- data/lib/shopify-cli/resolve_constant.rb +25 -0
- data/lib/shopify-cli/result.rb +432 -0
- data/lib/shopify-cli/shopifolk.rb +87 -0
- data/lib/shopify-cli/task.rb +8 -0
- data/lib/shopify-cli/tasks/create_api_client.rb +13 -2
- data/lib/shopify-cli/tasks/ensure_env.rb +3 -0
- data/lib/shopify-cli/tasks/select_org_and_shop.rb +10 -5
- data/lib/shopify-cli/tunnel.rb +8 -2
- data/lib/shopify-cli/version.rb +1 -1
- data/lib/shopify_cli.rb +5 -1
- data/shopify.fish +1 -1
- data/shopify.sh +1 -1
- data/vendor/deps/cli-kit/REVISION +1 -1
- data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +2 -2
- data/vendor/deps/cli-kit/lib/cli/kit/system.rb +3 -3
- data/vendor/deps/cli-ui/REVISION +1 -1
- data/vendor/deps/cli-ui/lib/cli/ui.rb +26 -22
- data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +4 -6
- data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +3 -3
- data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +8 -9
- data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +1 -1
- data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +1 -0
- data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +15 -3
- data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +4 -11
- data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +3 -5
- data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +10 -10
- data/vendor/deps/cli-ui/lib/cli/ui/version.rb +1 -1
- data/vendor/deps/cli-ui/lib/cli/ui/wrap.rb +56 -0
- data/vendor/deps/webrick/.gitignore +9 -0
- data/vendor/deps/webrick/Gemfile +3 -0
- data/vendor/deps/webrick/LICENSE.txt +22 -0
- data/vendor/deps/webrick/README.md +61 -0
- data/vendor/deps/webrick/Rakefile +10 -0
- data/vendor/deps/webrick/lib/webrick.rb +232 -0
- data/vendor/deps/webrick/lib/webrick/accesslog.rb +157 -0
- data/vendor/deps/webrick/lib/webrick/cgi.rb +313 -0
- data/vendor/deps/webrick/lib/webrick/compat.rb +36 -0
- data/vendor/deps/webrick/lib/webrick/config.rb +158 -0
- data/vendor/deps/webrick/lib/webrick/cookie.rb +172 -0
- data/vendor/deps/webrick/lib/webrick/htmlutils.rb +30 -0
- data/vendor/deps/webrick/lib/webrick/httpauth.rb +96 -0
- data/vendor/deps/webrick/lib/webrick/httpauth/authenticator.rb +117 -0
- data/vendor/deps/webrick/lib/webrick/httpauth/basicauth.rb +116 -0
- data/vendor/deps/webrick/lib/webrick/httpauth/digestauth.rb +395 -0
- data/vendor/deps/webrick/lib/webrick/httpauth/htdigest.rb +132 -0
- data/vendor/deps/webrick/lib/webrick/httpauth/htgroup.rb +97 -0
- data/vendor/deps/webrick/lib/webrick/httpauth/htpasswd.rb +158 -0
- data/vendor/deps/webrick/lib/webrick/httpauth/userdb.rb +53 -0
- data/vendor/deps/webrick/lib/webrick/httpproxy.rb +354 -0
- data/vendor/deps/webrick/lib/webrick/httprequest.rb +636 -0
- data/vendor/deps/webrick/lib/webrick/httpresponse.rb +564 -0
- data/vendor/deps/webrick/lib/webrick/https.rb +152 -0
- data/vendor/deps/webrick/lib/webrick/httpserver.rb +294 -0
- data/vendor/deps/webrick/lib/webrick/httpservlet.rb +23 -0
- data/vendor/deps/webrick/lib/webrick/httpservlet/abstract.rb +152 -0
- data/vendor/deps/webrick/lib/webrick/httpservlet/cgi_runner.rb +47 -0
- data/vendor/deps/webrick/lib/webrick/httpservlet/cgihandler.rb +126 -0
- data/vendor/deps/webrick/lib/webrick/httpservlet/erbhandler.rb +88 -0
- data/vendor/deps/webrick/lib/webrick/httpservlet/filehandler.rb +552 -0
- data/vendor/deps/webrick/lib/webrick/httpservlet/prochandler.rb +47 -0
- data/vendor/deps/webrick/lib/webrick/httpstatus.rb +194 -0
- data/vendor/deps/webrick/lib/webrick/httputils.rb +512 -0
- data/vendor/deps/webrick/lib/webrick/httpversion.rb +76 -0
- data/vendor/deps/webrick/lib/webrick/log.rb +156 -0
- data/vendor/deps/webrick/lib/webrick/server.rb +381 -0
- data/vendor/deps/webrick/lib/webrick/ssl.rb +215 -0
- data/vendor/deps/webrick/lib/webrick/utils.rb +265 -0
- data/vendor/deps/webrick/lib/webrick/version.rb +18 -0
- data/vendor/deps/webrick/webrick.gemspec +74 -0
- metadata +77 -27
- data/docs/Gemfile +0 -5
- data/docs/Gemfile.lock +0 -258
- data/docs/_data/nav.yml +0 -35
- data/docs/_includes/footer.html +0 -15
- data/docs/_includes/head.html +0 -19
- data/docs/_includes/sidebar_nav.html +0 -22
- data/docs/_includes/toc.html +0 -112
- data/docs/_layouts/default.html +0 -79
- data/docs/css/docs.css +0 -157
- data/docs/images/header.png +0 -0
- data/docs/installing-ruby.md +0 -28
- data/lib/project_types/extension/features/argo/admin.rb +0 -20
- data/lib/project_types/extension/features/argo/base.rb +0 -129
- data/lib/project_types/extension/features/argo/checkout.rb +0 -20
- data/lib/project_types/extension/models/type.rb +0 -81
- data/lib/project_types/extension/models/types/checkout_post_purchase.rb +0 -23
- data/lib/project_types/extension/models/types/product_subscription.rb +0 -24
- data/lib/project_types/node/commands/generate/billing.rb +0 -39
- data/lib/project_types/node/commands/generate/page.rb +0 -59
- data/lib/project_types/node/commands/generate/webhook.rb +0 -37
- data/lib/project_types/script/layers/domain/script.rb +0 -18
- data/lib/project_types/script/layers/infrastructure/assemblyscript_tsconfig.rb +0 -38
- data/lib/project_types/script/layers/infrastructure/script_repository.rb +0 -59
- data/lib/project_types/script/templates/ts/as-pect.config.js +0 -27
- data/lib/project_types/script/templates/ts/as-pect.d.ts +0 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# httpservlet.rb -- HTTPServlet Module
|
|
4
|
+
#
|
|
5
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
|
6
|
+
# Copyright (c) 2000 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
|
7
|
+
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
|
8
|
+
# reserved.
|
|
9
|
+
#
|
|
10
|
+
# $IPR: abstract.rb,v 1.24 2003/07/11 11:16:46 gotoyuzo Exp $
|
|
11
|
+
|
|
12
|
+
require_relative '../htmlutils'
|
|
13
|
+
require_relative '../httputils'
|
|
14
|
+
require_relative '../httpstatus'
|
|
15
|
+
|
|
16
|
+
module WEBrick
|
|
17
|
+
module HTTPServlet
|
|
18
|
+
class HTTPServletError < StandardError; end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# AbstractServlet allows HTTP server modules to be reused across multiple
|
|
22
|
+
# servers and allows encapsulation of functionality.
|
|
23
|
+
#
|
|
24
|
+
# By default a servlet will respond to GET, HEAD (through an alias to GET)
|
|
25
|
+
# and OPTIONS requests.
|
|
26
|
+
#
|
|
27
|
+
# By default a new servlet is initialized for every request. A servlet
|
|
28
|
+
# instance can be reused by overriding ::get_instance in the
|
|
29
|
+
# AbstractServlet subclass.
|
|
30
|
+
#
|
|
31
|
+
# == A Simple Servlet
|
|
32
|
+
#
|
|
33
|
+
# class Simple < WEBrick::HTTPServlet::AbstractServlet
|
|
34
|
+
# def do_GET request, response
|
|
35
|
+
# status, content_type, body = do_stuff_with request
|
|
36
|
+
#
|
|
37
|
+
# response.status = status
|
|
38
|
+
# response['Content-Type'] = content_type
|
|
39
|
+
# response.body = body
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# def do_stuff_with request
|
|
43
|
+
# return 200, 'text/plain', 'you got a page'
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# This servlet can be mounted on a server at a given path:
|
|
48
|
+
#
|
|
49
|
+
# server.mount '/simple', Simple
|
|
50
|
+
#
|
|
51
|
+
# == Servlet Configuration
|
|
52
|
+
#
|
|
53
|
+
# Servlets can be configured via initialize. The first argument is the
|
|
54
|
+
# HTTP server the servlet is being initialized for.
|
|
55
|
+
#
|
|
56
|
+
# class Configurable < Simple
|
|
57
|
+
# def initialize server, color, size
|
|
58
|
+
# super server
|
|
59
|
+
# @color = color
|
|
60
|
+
# @size = size
|
|
61
|
+
# end
|
|
62
|
+
#
|
|
63
|
+
# def do_stuff_with request
|
|
64
|
+
# content = "<p " \
|
|
65
|
+
# %q{style="color: #{@color}; font-size: #{@size}"} \
|
|
66
|
+
# ">Hello, World!"
|
|
67
|
+
#
|
|
68
|
+
# return 200, "text/html", content
|
|
69
|
+
# end
|
|
70
|
+
# end
|
|
71
|
+
#
|
|
72
|
+
# This servlet must be provided two arguments at mount time:
|
|
73
|
+
#
|
|
74
|
+
# server.mount '/configurable', Configurable, 'red', '2em'
|
|
75
|
+
|
|
76
|
+
class AbstractServlet
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# Factory for servlet instances that will handle a request from +server+
|
|
80
|
+
# using +options+ from the mount point. By default a new servlet
|
|
81
|
+
# instance is created for every call.
|
|
82
|
+
|
|
83
|
+
def self.get_instance(server, *options)
|
|
84
|
+
self.new(server, *options)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# Initializes a new servlet for +server+ using +options+ which are
|
|
89
|
+
# stored as-is in +@options+. +@logger+ is also provided.
|
|
90
|
+
|
|
91
|
+
def initialize(server, *options)
|
|
92
|
+
@server = @config = server
|
|
93
|
+
@logger = @server[:Logger]
|
|
94
|
+
@options = options
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
##
|
|
98
|
+
# Dispatches to a +do_+ method based on +req+ if such a method is
|
|
99
|
+
# available. (+do_GET+ for a GET request). Raises a MethodNotAllowed
|
|
100
|
+
# exception if the method is not implemented.
|
|
101
|
+
|
|
102
|
+
def service(req, res)
|
|
103
|
+
method_name = "do_" + req.request_method.gsub(/-/, "_")
|
|
104
|
+
if respond_to?(method_name)
|
|
105
|
+
__send__(method_name, req, res)
|
|
106
|
+
else
|
|
107
|
+
raise HTTPStatus::MethodNotAllowed,
|
|
108
|
+
"unsupported method `#{req.request_method}'."
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
##
|
|
113
|
+
# Raises a NotFound exception
|
|
114
|
+
|
|
115
|
+
def do_GET(req, res)
|
|
116
|
+
raise HTTPStatus::NotFound, "not found."
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
##
|
|
120
|
+
# Dispatches to do_GET
|
|
121
|
+
|
|
122
|
+
def do_HEAD(req, res)
|
|
123
|
+
do_GET(req, res)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
##
|
|
127
|
+
# Returns the allowed HTTP request methods
|
|
128
|
+
|
|
129
|
+
def do_OPTIONS(req, res)
|
|
130
|
+
m = self.methods.grep(/\Ado_([A-Z]+)\z/) {$1}
|
|
131
|
+
m.sort!
|
|
132
|
+
res["allow"] = m.join(",")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
##
|
|
138
|
+
# Redirects to a path ending in /
|
|
139
|
+
|
|
140
|
+
def redirect_to_directory_uri(req, res)
|
|
141
|
+
if req.path[-1] != ?/
|
|
142
|
+
location = WEBrick::HTTPUtils.escape_path(req.path + "/")
|
|
143
|
+
if req.query_string && req.query_string.bytesize > 0
|
|
144
|
+
location << "?" << req.query_string
|
|
145
|
+
end
|
|
146
|
+
res.set_redirect(HTTPStatus::MovedPermanently, location)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# cgi_runner.rb -- CGI launcher.
|
|
4
|
+
#
|
|
5
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
|
6
|
+
# Copyright (c) 2000 TAKAHASHI Masayoshi, GOTOU YUUZOU
|
|
7
|
+
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
|
8
|
+
# reserved.
|
|
9
|
+
#
|
|
10
|
+
# $IPR: cgi_runner.rb,v 1.9 2002/09/25 11:33:15 gotoyuzo Exp $
|
|
11
|
+
|
|
12
|
+
def sysread(io, size)
|
|
13
|
+
buf = +""
|
|
14
|
+
while size > 0
|
|
15
|
+
tmp = io.sysread(size)
|
|
16
|
+
buf << tmp
|
|
17
|
+
size -= tmp.bytesize
|
|
18
|
+
end
|
|
19
|
+
return buf
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
STDIN.binmode
|
|
23
|
+
|
|
24
|
+
len = sysread(STDIN, 8).to_i
|
|
25
|
+
out = sysread(STDIN, len)
|
|
26
|
+
STDOUT.reopen(File.open(out, "w"))
|
|
27
|
+
|
|
28
|
+
len = sysread(STDIN, 8).to_i
|
|
29
|
+
err = sysread(STDIN, len)
|
|
30
|
+
STDERR.reopen(File.open(err, "w"))
|
|
31
|
+
|
|
32
|
+
len = sysread(STDIN, 8).to_i
|
|
33
|
+
dump = sysread(STDIN, len)
|
|
34
|
+
hash = Marshal.restore(dump)
|
|
35
|
+
ENV.keys.each{|name| ENV.delete(name) }
|
|
36
|
+
hash.each{|k, v| ENV[k] = v if v }
|
|
37
|
+
|
|
38
|
+
dir = File::dirname(ENV["SCRIPT_FILENAME"])
|
|
39
|
+
Dir::chdir dir
|
|
40
|
+
|
|
41
|
+
if ARGV[0]
|
|
42
|
+
argv = ARGV.dup
|
|
43
|
+
argv << ENV["SCRIPT_FILENAME"]
|
|
44
|
+
exec(*argv)
|
|
45
|
+
# NOTREACHED
|
|
46
|
+
end
|
|
47
|
+
exec ENV["SCRIPT_FILENAME"]
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# cgihandler.rb -- CGIHandler Class
|
|
4
|
+
#
|
|
5
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
|
6
|
+
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
|
7
|
+
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
|
8
|
+
# reserved.
|
|
9
|
+
#
|
|
10
|
+
# $IPR: cgihandler.rb,v 1.27 2003/03/21 19:56:01 gotoyuzo Exp $
|
|
11
|
+
|
|
12
|
+
require 'rbconfig'
|
|
13
|
+
require 'tempfile'
|
|
14
|
+
require_relative '../config'
|
|
15
|
+
require_relative 'abstract'
|
|
16
|
+
|
|
17
|
+
module WEBrick
|
|
18
|
+
module HTTPServlet
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Servlet for handling CGI scripts
|
|
22
|
+
#
|
|
23
|
+
# Example:
|
|
24
|
+
#
|
|
25
|
+
# server.mount('/cgi/my_script', WEBrick::HTTPServlet::CGIHandler,
|
|
26
|
+
# '/path/to/my_script')
|
|
27
|
+
|
|
28
|
+
class CGIHandler < AbstractServlet
|
|
29
|
+
Ruby = RbConfig.ruby # :nodoc:
|
|
30
|
+
CGIRunner = "\"#{Ruby}\" \"#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb\"" # :nodoc:
|
|
31
|
+
CGIRunnerArray = [Ruby, "#{WEBrick::Config::LIBDIR}/httpservlet/cgi_runner.rb".freeze].freeze # :nodoc:
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# Creates a new CGI script servlet for the script at +name+
|
|
35
|
+
|
|
36
|
+
def initialize(server, name)
|
|
37
|
+
super(server, name)
|
|
38
|
+
@script_filename = name
|
|
39
|
+
@tempdir = server[:TempDir]
|
|
40
|
+
interpreter = server[:CGIInterpreter]
|
|
41
|
+
if interpreter.is_a?(Array)
|
|
42
|
+
@cgicmd = CGIRunnerArray + interpreter
|
|
43
|
+
else
|
|
44
|
+
@cgicmd = "#{CGIRunner} #{interpreter}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# :stopdoc:
|
|
49
|
+
|
|
50
|
+
def do_GET(req, res)
|
|
51
|
+
cgi_in = IO::popen(@cgicmd, "wb")
|
|
52
|
+
cgi_out = Tempfile.new("webrick.cgiout.", @tempdir, mode: IO::BINARY)
|
|
53
|
+
cgi_out.set_encoding("ASCII-8BIT")
|
|
54
|
+
cgi_err = Tempfile.new("webrick.cgierr.", @tempdir, mode: IO::BINARY)
|
|
55
|
+
cgi_err.set_encoding("ASCII-8BIT")
|
|
56
|
+
begin
|
|
57
|
+
cgi_in.sync = true
|
|
58
|
+
meta = req.meta_vars
|
|
59
|
+
meta["SCRIPT_FILENAME"] = @script_filename
|
|
60
|
+
meta["PATH"] = @config[:CGIPathEnv]
|
|
61
|
+
meta.delete("HTTP_PROXY")
|
|
62
|
+
if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
|
|
63
|
+
meta["SystemRoot"] = ENV["SystemRoot"]
|
|
64
|
+
end
|
|
65
|
+
dump = Marshal.dump(meta)
|
|
66
|
+
|
|
67
|
+
cgi_in.write("%8d" % cgi_out.path.bytesize)
|
|
68
|
+
cgi_in.write(cgi_out.path)
|
|
69
|
+
cgi_in.write("%8d" % cgi_err.path.bytesize)
|
|
70
|
+
cgi_in.write(cgi_err.path)
|
|
71
|
+
cgi_in.write("%8d" % dump.bytesize)
|
|
72
|
+
cgi_in.write(dump)
|
|
73
|
+
|
|
74
|
+
req.body { |chunk| cgi_in.write(chunk) }
|
|
75
|
+
ensure
|
|
76
|
+
cgi_in.close
|
|
77
|
+
status = $?.exitstatus
|
|
78
|
+
sleep 0.1 if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
|
|
79
|
+
data = cgi_out.read
|
|
80
|
+
cgi_out.close(true)
|
|
81
|
+
if errmsg = cgi_err.read
|
|
82
|
+
if errmsg.bytesize > 0
|
|
83
|
+
@logger.error("CGIHandler: #{@script_filename}:\n" + errmsg)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
cgi_err.close(true)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if status != 0
|
|
90
|
+
@logger.error("CGIHandler: #{@script_filename} exit with #{status}")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
data = "" unless data
|
|
94
|
+
raw_header, body = data.split(/^[\xd\xa]+/, 2)
|
|
95
|
+
raise HTTPStatus::InternalServerError,
|
|
96
|
+
"Premature end of script headers: #{@script_filename}" if body.nil?
|
|
97
|
+
|
|
98
|
+
begin
|
|
99
|
+
header = HTTPUtils::parse_header(raw_header)
|
|
100
|
+
if /^(\d+)/ =~ header['status'][0]
|
|
101
|
+
res.status = $1.to_i
|
|
102
|
+
header.delete('status')
|
|
103
|
+
end
|
|
104
|
+
if header.has_key?('location')
|
|
105
|
+
# RFC 3875 6.2.3, 6.2.4
|
|
106
|
+
res.status = 302 unless (300...400) === res.status
|
|
107
|
+
end
|
|
108
|
+
if header.has_key?('set-cookie')
|
|
109
|
+
header['set-cookie'].each{|k|
|
|
110
|
+
res.cookies << Cookie.parse_set_cookie(k)
|
|
111
|
+
}
|
|
112
|
+
header.delete('set-cookie')
|
|
113
|
+
end
|
|
114
|
+
header.each{|key, val| res[key] = val.join(", ") }
|
|
115
|
+
rescue => ex
|
|
116
|
+
raise HTTPStatus::InternalServerError, ex.message
|
|
117
|
+
end
|
|
118
|
+
res.body = body
|
|
119
|
+
end
|
|
120
|
+
alias do_POST do_GET
|
|
121
|
+
|
|
122
|
+
# :startdoc:
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# erbhandler.rb -- ERBHandler Class
|
|
4
|
+
#
|
|
5
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
|
6
|
+
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
|
7
|
+
# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
|
|
8
|
+
# reserved.
|
|
9
|
+
#
|
|
10
|
+
# $IPR: erbhandler.rb,v 1.25 2003/02/24 19:25:31 gotoyuzo Exp $
|
|
11
|
+
|
|
12
|
+
require_relative 'abstract'
|
|
13
|
+
|
|
14
|
+
require 'erb'
|
|
15
|
+
|
|
16
|
+
module WEBrick
|
|
17
|
+
module HTTPServlet
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# ERBHandler evaluates an ERB file and returns the result. This handler
|
|
21
|
+
# is automatically used if there are .rhtml files in a directory served by
|
|
22
|
+
# the FileHandler.
|
|
23
|
+
#
|
|
24
|
+
# ERBHandler supports GET and POST methods.
|
|
25
|
+
#
|
|
26
|
+
# The ERB file is evaluated with the local variables +servlet_request+ and
|
|
27
|
+
# +servlet_response+ which are a WEBrick::HTTPRequest and
|
|
28
|
+
# WEBrick::HTTPResponse respectively.
|
|
29
|
+
#
|
|
30
|
+
# Example .rhtml file:
|
|
31
|
+
#
|
|
32
|
+
# Request to <%= servlet_request.request_uri %>
|
|
33
|
+
#
|
|
34
|
+
# Query params <%= servlet_request.query.inspect %>
|
|
35
|
+
|
|
36
|
+
class ERBHandler < AbstractServlet
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# Creates a new ERBHandler on +server+ that will evaluate and serve the
|
|
40
|
+
# ERB file +name+
|
|
41
|
+
|
|
42
|
+
def initialize(server, name)
|
|
43
|
+
super(server, name)
|
|
44
|
+
@script_filename = name
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Handles GET requests
|
|
49
|
+
|
|
50
|
+
def do_GET(req, res)
|
|
51
|
+
unless defined?(ERB)
|
|
52
|
+
@logger.warn "#{self.class}: ERB not defined."
|
|
53
|
+
raise HTTPStatus::Forbidden, "ERBHandler cannot work."
|
|
54
|
+
end
|
|
55
|
+
begin
|
|
56
|
+
data = File.open(@script_filename, &:read)
|
|
57
|
+
res.body = evaluate(ERB.new(data), req, res)
|
|
58
|
+
res['content-type'] ||=
|
|
59
|
+
HTTPUtils::mime_type(@script_filename, @config[:MimeTypes])
|
|
60
|
+
rescue StandardError
|
|
61
|
+
raise
|
|
62
|
+
rescue Exception => ex
|
|
63
|
+
@logger.error(ex)
|
|
64
|
+
raise HTTPStatus::InternalServerError, ex.message
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
##
|
|
69
|
+
# Handles POST requests
|
|
70
|
+
|
|
71
|
+
alias do_POST do_GET
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
##
|
|
76
|
+
# Evaluates +erb+ providing +servlet_request+ and +servlet_response+ as
|
|
77
|
+
# local variables.
|
|
78
|
+
|
|
79
|
+
def evaluate(erb, servlet_request, servlet_response)
|
|
80
|
+
Module.new.module_eval{
|
|
81
|
+
servlet_request.meta_vars
|
|
82
|
+
servlet_request.query
|
|
83
|
+
erb.result(binding)
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# filehandler.rb -- FileHandler Module
|
|
4
|
+
#
|
|
5
|
+
# Author: IPR -- Internet Programming with Ruby -- writers
|
|
6
|
+
# Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
|
|
7
|
+
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
|
|
8
|
+
# reserved.
|
|
9
|
+
#
|
|
10
|
+
# $IPR: filehandler.rb,v 1.44 2003/06/07 01:34:51 gotoyuzo Exp $
|
|
11
|
+
|
|
12
|
+
require 'time'
|
|
13
|
+
|
|
14
|
+
require_relative '../htmlutils'
|
|
15
|
+
require_relative '../httputils'
|
|
16
|
+
require_relative '../httpstatus'
|
|
17
|
+
|
|
18
|
+
module WEBrick
|
|
19
|
+
module HTTPServlet
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Servlet for serving a single file. You probably want to use the
|
|
23
|
+
# FileHandler servlet instead as it handles directories and fancy indexes.
|
|
24
|
+
#
|
|
25
|
+
# Example:
|
|
26
|
+
#
|
|
27
|
+
# server.mount('/my_page.txt', WEBrick::HTTPServlet::DefaultFileHandler,
|
|
28
|
+
# '/path/to/my_page.txt')
|
|
29
|
+
#
|
|
30
|
+
# This servlet handles If-Modified-Since and Range requests.
|
|
31
|
+
|
|
32
|
+
class DefaultFileHandler < AbstractServlet
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Creates a DefaultFileHandler instance for the file at +local_path+.
|
|
36
|
+
|
|
37
|
+
def initialize(server, local_path)
|
|
38
|
+
super(server, local_path)
|
|
39
|
+
@local_path = local_path
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# :stopdoc:
|
|
43
|
+
|
|
44
|
+
def do_GET(req, res)
|
|
45
|
+
st = File::stat(@local_path)
|
|
46
|
+
mtime = st.mtime
|
|
47
|
+
res['etag'] = sprintf("%x-%x-%x", st.ino, st.size, st.mtime.to_i)
|
|
48
|
+
|
|
49
|
+
if not_modified?(req, res, mtime, res['etag'])
|
|
50
|
+
res.body = ''
|
|
51
|
+
raise HTTPStatus::NotModified
|
|
52
|
+
elsif req['range']
|
|
53
|
+
make_partial_content(req, res, @local_path, st.size)
|
|
54
|
+
raise HTTPStatus::PartialContent
|
|
55
|
+
else
|
|
56
|
+
mtype = HTTPUtils::mime_type(@local_path, @config[:MimeTypes])
|
|
57
|
+
res['content-type'] = mtype
|
|
58
|
+
res['content-length'] = st.size.to_s
|
|
59
|
+
res['last-modified'] = mtime.httpdate
|
|
60
|
+
res.body = File.open(@local_path, "rb")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def not_modified?(req, res, mtime, etag)
|
|
65
|
+
if ir = req['if-range']
|
|
66
|
+
begin
|
|
67
|
+
if Time.httpdate(ir) >= mtime
|
|
68
|
+
return true
|
|
69
|
+
end
|
|
70
|
+
rescue
|
|
71
|
+
if HTTPUtils::split_header_value(ir).member?(res['etag'])
|
|
72
|
+
return true
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if (ims = req['if-modified-since']) && Time.parse(ims) >= mtime
|
|
78
|
+
return true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if (inm = req['if-none-match']) &&
|
|
82
|
+
HTTPUtils::split_header_value(inm).member?(res['etag'])
|
|
83
|
+
return true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
return false
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# returns a lambda for webrick/httpresponse.rb send_body_proc
|
|
90
|
+
def multipart_body(body, parts, boundary, mtype, filesize)
|
|
91
|
+
lambda do |socket|
|
|
92
|
+
begin
|
|
93
|
+
begin
|
|
94
|
+
first = parts.shift
|
|
95
|
+
last = parts.shift
|
|
96
|
+
socket.write(
|
|
97
|
+
"--#{boundary}#{CRLF}" \
|
|
98
|
+
"Content-Type: #{mtype}#{CRLF}" \
|
|
99
|
+
"Content-Range: bytes #{first}-#{last}/#{filesize}#{CRLF}" \
|
|
100
|
+
"#{CRLF}"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
begin
|
|
104
|
+
IO.copy_stream(body, socket, last - first + 1, first)
|
|
105
|
+
rescue NotImplementedError
|
|
106
|
+
body.seek(first, IO::SEEK_SET)
|
|
107
|
+
IO.copy_stream(body, socket, last - first + 1)
|
|
108
|
+
end
|
|
109
|
+
socket.write(CRLF)
|
|
110
|
+
end while parts[0]
|
|
111
|
+
socket.write("--#{boundary}--#{CRLF}")
|
|
112
|
+
ensure
|
|
113
|
+
body.close
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def make_partial_content(req, res, filename, filesize)
|
|
119
|
+
mtype = HTTPUtils::mime_type(filename, @config[:MimeTypes])
|
|
120
|
+
unless ranges = HTTPUtils::parse_range_header(req['range'])
|
|
121
|
+
raise HTTPStatus::BadRequest,
|
|
122
|
+
"Unrecognized range-spec: \"#{req['range']}\""
|
|
123
|
+
end
|
|
124
|
+
File.open(filename, "rb"){|io|
|
|
125
|
+
if ranges.size > 1
|
|
126
|
+
time = Time.now
|
|
127
|
+
boundary = "#{time.sec}_#{time.usec}_#{Process::pid}"
|
|
128
|
+
parts = []
|
|
129
|
+
ranges.each {|range|
|
|
130
|
+
prange = prepare_range(range, filesize)
|
|
131
|
+
next if prange[0] < 0
|
|
132
|
+
parts.concat(prange)
|
|
133
|
+
}
|
|
134
|
+
raise HTTPStatus::RequestRangeNotSatisfiable if parts.empty?
|
|
135
|
+
res["content-type"] = "multipart/byteranges; boundary=#{boundary}"
|
|
136
|
+
if req.http_version < '1.1'
|
|
137
|
+
res['connection'] = 'close'
|
|
138
|
+
else
|
|
139
|
+
res.chunked = true
|
|
140
|
+
end
|
|
141
|
+
res.body = multipart_body(io.dup, parts, boundary, mtype, filesize)
|
|
142
|
+
elsif range = ranges[0]
|
|
143
|
+
first, last = prepare_range(range, filesize)
|
|
144
|
+
raise HTTPStatus::RequestRangeNotSatisfiable if first < 0
|
|
145
|
+
res['content-type'] = mtype
|
|
146
|
+
res['content-range'] = "bytes #{first}-#{last}/#{filesize}"
|
|
147
|
+
res['content-length'] = (last - first + 1).to_s
|
|
148
|
+
res.body = io.dup
|
|
149
|
+
else
|
|
150
|
+
raise HTTPStatus::BadRequest
|
|
151
|
+
end
|
|
152
|
+
}
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def prepare_range(range, filesize)
|
|
156
|
+
first = range.first < 0 ? filesize + range.first : range.first
|
|
157
|
+
return -1, -1 if first < 0 || first >= filesize
|
|
158
|
+
last = range.last < 0 ? filesize + range.last : range.last
|
|
159
|
+
last = filesize - 1 if last >= filesize
|
|
160
|
+
return first, last
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# :startdoc:
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
##
|
|
167
|
+
# Serves a directory including fancy indexing and a variety of other
|
|
168
|
+
# options.
|
|
169
|
+
#
|
|
170
|
+
# Example:
|
|
171
|
+
#
|
|
172
|
+
# server.mount('/assets', WEBrick::HTTPServlet::FileHandler,
|
|
173
|
+
# '/path/to/assets')
|
|
174
|
+
|
|
175
|
+
class FileHandler < AbstractServlet
|
|
176
|
+
HandlerTable = Hash.new # :nodoc:
|
|
177
|
+
|
|
178
|
+
##
|
|
179
|
+
# Allow custom handling of requests for files with +suffix+ by class
|
|
180
|
+
# +handler+
|
|
181
|
+
|
|
182
|
+
def self.add_handler(suffix, handler)
|
|
183
|
+
HandlerTable[suffix] = handler
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
##
|
|
187
|
+
# Remove custom handling of requests for files with +suffix+
|
|
188
|
+
|
|
189
|
+
def self.remove_handler(suffix)
|
|
190
|
+
HandlerTable.delete(suffix)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
##
|
|
194
|
+
# Creates a FileHandler servlet on +server+ that serves files starting
|
|
195
|
+
# at directory +root+
|
|
196
|
+
#
|
|
197
|
+
# +options+ may be a Hash containing keys from
|
|
198
|
+
# WEBrick::Config::FileHandler or +true+ or +false+.
|
|
199
|
+
#
|
|
200
|
+
# If +options+ is true or false then +:FancyIndexing+ is enabled or
|
|
201
|
+
# disabled respectively.
|
|
202
|
+
|
|
203
|
+
def initialize(server, root, options={}, default=Config::FileHandler)
|
|
204
|
+
@config = server.config
|
|
205
|
+
@logger = @config[:Logger]
|
|
206
|
+
@root = File.expand_path(root)
|
|
207
|
+
if options == true || options == false
|
|
208
|
+
options = { :FancyIndexing => options }
|
|
209
|
+
end
|
|
210
|
+
@options = default.dup.update(options)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# :stopdoc:
|
|
214
|
+
|
|
215
|
+
def set_filesystem_encoding(str)
|
|
216
|
+
enc = Encoding.find('filesystem')
|
|
217
|
+
if enc == Encoding::US_ASCII
|
|
218
|
+
str.b
|
|
219
|
+
else
|
|
220
|
+
str.dup.force_encoding(enc)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def service(req, res)
|
|
225
|
+
# if this class is mounted on "/" and /~username is requested.
|
|
226
|
+
# we're going to override path information before invoking service.
|
|
227
|
+
if defined?(Etc) && @options[:UserDir] && req.script_name.empty?
|
|
228
|
+
if %r|^(/~([^/]+))| =~ req.path_info
|
|
229
|
+
script_name, user = $1, $2
|
|
230
|
+
path_info = $'
|
|
231
|
+
begin
|
|
232
|
+
passwd = Etc::getpwnam(user)
|
|
233
|
+
@root = File::join(passwd.dir, @options[:UserDir])
|
|
234
|
+
req.script_name = script_name
|
|
235
|
+
req.path_info = path_info
|
|
236
|
+
rescue
|
|
237
|
+
@logger.debug "#{self.class}#do_GET: getpwnam(#{user}) failed"
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
prevent_directory_traversal(req, res)
|
|
242
|
+
super(req, res)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def do_GET(req, res)
|
|
246
|
+
unless exec_handler(req, res)
|
|
247
|
+
set_dir_list(req, res)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def do_POST(req, res)
|
|
252
|
+
unless exec_handler(req, res)
|
|
253
|
+
raise HTTPStatus::NotFound, "`#{req.path}' not found."
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def do_OPTIONS(req, res)
|
|
258
|
+
unless exec_handler(req, res)
|
|
259
|
+
super(req, res)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# ToDo
|
|
264
|
+
# RFC2518: HTTP Extensions for Distributed Authoring -- WEBDAV
|
|
265
|
+
#
|
|
266
|
+
# PROPFIND PROPPATCH MKCOL DELETE PUT COPY MOVE
|
|
267
|
+
# LOCK UNLOCK
|
|
268
|
+
|
|
269
|
+
# RFC3253: Versioning Extensions to WebDAV
|
|
270
|
+
# (Web Distributed Authoring and Versioning)
|
|
271
|
+
#
|
|
272
|
+
# VERSION-CONTROL REPORT CHECKOUT CHECK_IN UNCHECKOUT
|
|
273
|
+
# MKWORKSPACE UPDATE LABEL MERGE ACTIVITY
|
|
274
|
+
|
|
275
|
+
private
|
|
276
|
+
|
|
277
|
+
def trailing_pathsep?(path)
|
|
278
|
+
# check for trailing path separator:
|
|
279
|
+
# File.dirname("/aaaa/bbbb/") #=> "/aaaa")
|
|
280
|
+
# File.dirname("/aaaa/bbbb/x") #=> "/aaaa/bbbb")
|
|
281
|
+
# File.dirname("/aaaa/bbbb") #=> "/aaaa")
|
|
282
|
+
# File.dirname("/aaaa/bbbbx") #=> "/aaaa")
|
|
283
|
+
return File.dirname(path) != File.dirname(path+"x")
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def prevent_directory_traversal(req, res)
|
|
287
|
+
# Preventing directory traversal on Windows platforms;
|
|
288
|
+
# Backslashes (0x5c) in path_info are not interpreted as special
|
|
289
|
+
# character in URI notation. So the value of path_info should be
|
|
290
|
+
# normalize before accessing to the filesystem.
|
|
291
|
+
|
|
292
|
+
# dirty hack for filesystem encoding; in nature, File.expand_path
|
|
293
|
+
# should not be used for path normalization. [Bug #3345]
|
|
294
|
+
path = req.path_info.dup.force_encoding(Encoding.find("filesystem"))
|
|
295
|
+
if trailing_pathsep?(req.path_info)
|
|
296
|
+
# File.expand_path removes the trailing path separator.
|
|
297
|
+
# Adding a character is a workaround to save it.
|
|
298
|
+
# File.expand_path("/aaa/") #=> "/aaa"
|
|
299
|
+
# File.expand_path("/aaa/" + "x") #=> "/aaa/x"
|
|
300
|
+
expanded = File.expand_path(path + "x")
|
|
301
|
+
expanded.chop! # remove trailing "x"
|
|
302
|
+
else
|
|
303
|
+
expanded = File.expand_path(path)
|
|
304
|
+
end
|
|
305
|
+
expanded.force_encoding(req.path_info.encoding)
|
|
306
|
+
req.path_info = expanded
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def exec_handler(req, res)
|
|
310
|
+
raise HTTPStatus::NotFound, "`#{req.path}' not found." unless @root
|
|
311
|
+
if set_filename(req, res)
|
|
312
|
+
handler = get_handler(req, res)
|
|
313
|
+
call_callback(:HandlerCallback, req, res)
|
|
314
|
+
h = handler.get_instance(@config, res.filename)
|
|
315
|
+
h.service(req, res)
|
|
316
|
+
return true
|
|
317
|
+
end
|
|
318
|
+
call_callback(:HandlerCallback, req, res)
|
|
319
|
+
return false
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def get_handler(req, res)
|
|
323
|
+
suffix1 = (/\.(\w+)\z/ =~ res.filename) && $1.downcase
|
|
324
|
+
if /\.(\w+)\.([\w\-]+)\z/ =~ res.filename
|
|
325
|
+
if @options[:AcceptableLanguages].include?($2.downcase)
|
|
326
|
+
suffix2 = $1.downcase
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
handler_table = @options[:HandlerTable]
|
|
330
|
+
return handler_table[suffix1] || handler_table[suffix2] ||
|
|
331
|
+
HandlerTable[suffix1] || HandlerTable[suffix2] ||
|
|
332
|
+
DefaultFileHandler
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def set_filename(req, res)
|
|
336
|
+
res.filename = @root
|
|
337
|
+
path_info = req.path_info.scan(%r|/[^/]*|)
|
|
338
|
+
|
|
339
|
+
path_info.unshift("") # dummy for checking @root dir
|
|
340
|
+
while base = path_info.first
|
|
341
|
+
base = set_filesystem_encoding(base)
|
|
342
|
+
break if base == "/"
|
|
343
|
+
break unless File.directory?(File.expand_path(res.filename + base))
|
|
344
|
+
shift_path_info(req, res, path_info)
|
|
345
|
+
call_callback(:DirectoryCallback, req, res)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
if base = path_info.first
|
|
349
|
+
base = set_filesystem_encoding(base)
|
|
350
|
+
if base == "/"
|
|
351
|
+
if file = search_index_file(req, res)
|
|
352
|
+
shift_path_info(req, res, path_info, file)
|
|
353
|
+
call_callback(:FileCallback, req, res)
|
|
354
|
+
return true
|
|
355
|
+
end
|
|
356
|
+
shift_path_info(req, res, path_info)
|
|
357
|
+
elsif file = search_file(req, res, base)
|
|
358
|
+
shift_path_info(req, res, path_info, file)
|
|
359
|
+
call_callback(:FileCallback, req, res)
|
|
360
|
+
return true
|
|
361
|
+
else
|
|
362
|
+
raise HTTPStatus::NotFound, "`#{req.path}' not found."
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
return false
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def check_filename(req, res, name)
|
|
370
|
+
if nondisclosure_name?(name) || windows_ambiguous_name?(name)
|
|
371
|
+
@logger.warn("the request refers nondisclosure name `#{name}'.")
|
|
372
|
+
raise HTTPStatus::NotFound, "`#{req.path}' not found."
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def shift_path_info(req, res, path_info, base=nil)
|
|
377
|
+
tmp = path_info.shift
|
|
378
|
+
base = base || set_filesystem_encoding(tmp)
|
|
379
|
+
req.path_info = path_info.join
|
|
380
|
+
req.script_name << base
|
|
381
|
+
res.filename = File.expand_path(res.filename + base)
|
|
382
|
+
check_filename(req, res, File.basename(res.filename))
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def search_index_file(req, res)
|
|
386
|
+
@config[:DirectoryIndex].each{|index|
|
|
387
|
+
if file = search_file(req, res, "/"+index)
|
|
388
|
+
return file
|
|
389
|
+
end
|
|
390
|
+
}
|
|
391
|
+
return nil
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def search_file(req, res, basename)
|
|
395
|
+
langs = @options[:AcceptableLanguages]
|
|
396
|
+
path = res.filename + basename
|
|
397
|
+
if File.file?(path)
|
|
398
|
+
return basename
|
|
399
|
+
elsif langs.size > 0
|
|
400
|
+
req.accept_language.each{|lang|
|
|
401
|
+
path_with_lang = path + ".#{lang}"
|
|
402
|
+
if langs.member?(lang) && File.file?(path_with_lang)
|
|
403
|
+
return basename + ".#{lang}"
|
|
404
|
+
end
|
|
405
|
+
}
|
|
406
|
+
(langs - req.accept_language).each{|lang|
|
|
407
|
+
path_with_lang = path + ".#{lang}"
|
|
408
|
+
if File.file?(path_with_lang)
|
|
409
|
+
return basename + ".#{lang}"
|
|
410
|
+
end
|
|
411
|
+
}
|
|
412
|
+
end
|
|
413
|
+
return nil
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def call_callback(callback_name, req, res)
|
|
417
|
+
if cb = @options[callback_name]
|
|
418
|
+
cb.call(req, res)
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def windows_ambiguous_name?(name)
|
|
423
|
+
return true if /[. ]+\z/ =~ name
|
|
424
|
+
return true if /::\$DATA\z/ =~ name
|
|
425
|
+
return false
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def nondisclosure_name?(name)
|
|
429
|
+
@options[:NondisclosureName].each{|pattern|
|
|
430
|
+
if File.fnmatch(pattern, name, File::FNM_CASEFOLD)
|
|
431
|
+
return true
|
|
432
|
+
end
|
|
433
|
+
}
|
|
434
|
+
return false
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def set_dir_list(req, res)
|
|
438
|
+
redirect_to_directory_uri(req, res)
|
|
439
|
+
unless @options[:FancyIndexing]
|
|
440
|
+
raise HTTPStatus::Forbidden, "no access permission to `#{req.path}'"
|
|
441
|
+
end
|
|
442
|
+
local_path = res.filename
|
|
443
|
+
list = Dir::entries(local_path).collect{|name|
|
|
444
|
+
next if name == "." || name == ".."
|
|
445
|
+
next if nondisclosure_name?(name)
|
|
446
|
+
next if windows_ambiguous_name?(name)
|
|
447
|
+
st = (File::stat(File.join(local_path, name)) rescue nil)
|
|
448
|
+
if st.nil?
|
|
449
|
+
[ name, nil, -1 ]
|
|
450
|
+
elsif st.directory?
|
|
451
|
+
[ name + "/", st.mtime, -1 ]
|
|
452
|
+
else
|
|
453
|
+
[ name, st.mtime, st.size ]
|
|
454
|
+
end
|
|
455
|
+
}
|
|
456
|
+
list.compact!
|
|
457
|
+
|
|
458
|
+
query = req.query
|
|
459
|
+
|
|
460
|
+
d0 = nil
|
|
461
|
+
idx = nil
|
|
462
|
+
%w[N M S].each_with_index do |q, i|
|
|
463
|
+
if d = query.delete(q)
|
|
464
|
+
idx ||= i
|
|
465
|
+
d0 ||= d
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
d0 ||= "A"
|
|
469
|
+
idx ||= 0
|
|
470
|
+
d1 = (d0 == "A") ? "D" : "A"
|
|
471
|
+
|
|
472
|
+
if d0 == "A"
|
|
473
|
+
list.sort!{|a,b| a[idx] <=> b[idx] }
|
|
474
|
+
else
|
|
475
|
+
list.sort!{|a,b| b[idx] <=> a[idx] }
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
namewidth = query["NameWidth"]
|
|
479
|
+
if namewidth == "*"
|
|
480
|
+
namewidth = nil
|
|
481
|
+
elsif !namewidth or (namewidth = namewidth.to_i) < 2
|
|
482
|
+
namewidth = 25
|
|
483
|
+
end
|
|
484
|
+
query = query.inject('') {|s, (k, v)| s << '&' << HTMLUtils::escape("#{k}=#{v}")}.dup
|
|
485
|
+
|
|
486
|
+
type = +"text/html"
|
|
487
|
+
case enc = Encoding.find('filesystem')
|
|
488
|
+
when Encoding::US_ASCII, Encoding::ASCII_8BIT
|
|
489
|
+
else
|
|
490
|
+
type << "; charset=\"#{enc.name}\""
|
|
491
|
+
end
|
|
492
|
+
res['content-type'] = type
|
|
493
|
+
|
|
494
|
+
title = "Index of #{HTMLUtils::escape(req.path)}"
|
|
495
|
+
res.body = +<<-_end_of_html_
|
|
496
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
|
497
|
+
<HTML>
|
|
498
|
+
<HEAD>
|
|
499
|
+
<TITLE>#{title}</TITLE>
|
|
500
|
+
<style type="text/css">
|
|
501
|
+
<!--
|
|
502
|
+
.name, .mtime { text-align: left; }
|
|
503
|
+
.size { text-align: right; }
|
|
504
|
+
td { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }
|
|
505
|
+
table { border-collapse: collapse; }
|
|
506
|
+
tr th { border-bottom: 2px groove; }
|
|
507
|
+
//-->
|
|
508
|
+
</style>
|
|
509
|
+
</HEAD>
|
|
510
|
+
<BODY>
|
|
511
|
+
<H1>#{title}</H1>
|
|
512
|
+
_end_of_html_
|
|
513
|
+
|
|
514
|
+
res.body << "<TABLE width=\"100%\"><THEAD><TR>\n"
|
|
515
|
+
res.body << "<TH class=\"name\"><A HREF=\"?N=#{d1}#{query}\">Name</A></TH>"
|
|
516
|
+
res.body << "<TH class=\"mtime\"><A HREF=\"?M=#{d1}#{query}\">Last modified</A></TH>"
|
|
517
|
+
res.body << "<TH class=\"size\"><A HREF=\"?S=#{d1}#{query}\">Size</A></TH>\n"
|
|
518
|
+
res.body << "</TR></THEAD>\n"
|
|
519
|
+
res.body << "<TBODY>\n"
|
|
520
|
+
|
|
521
|
+
query.sub!(/\A&/, '?')
|
|
522
|
+
list.unshift [ "..", File::mtime(local_path+"/.."), -1 ]
|
|
523
|
+
list.each{ |name, time, size|
|
|
524
|
+
if name == ".."
|
|
525
|
+
dname = "Parent Directory"
|
|
526
|
+
elsif namewidth and name.size > namewidth
|
|
527
|
+
dname = name[0...(namewidth - 2)] << '..'
|
|
528
|
+
else
|
|
529
|
+
dname = name
|
|
530
|
+
end
|
|
531
|
+
s = +"<TR><TD class=\"name\"><A HREF=\"#{HTTPUtils::escape(name)}#{query if name.end_with?('/')}\">#{HTMLUtils::escape(dname)}</A></TD>"
|
|
532
|
+
s << "<TD class=\"mtime\">" << (time ? time.strftime("%Y/%m/%d %H:%M") : "") << "</TD>"
|
|
533
|
+
s << "<TD class=\"size\">" << (size >= 0 ? size.to_s : "-") << "</TD></TR>\n"
|
|
534
|
+
res.body << s
|
|
535
|
+
}
|
|
536
|
+
res.body << "</TBODY></TABLE>"
|
|
537
|
+
res.body << "<HR>"
|
|
538
|
+
|
|
539
|
+
res.body << <<-_end_of_html_
|
|
540
|
+
<ADDRESS>
|
|
541
|
+
#{HTMLUtils::escape(@config[:ServerSoftware])}<BR>
|
|
542
|
+
at #{req.host}:#{req.port}
|
|
543
|
+
</ADDRESS>
|
|
544
|
+
</BODY>
|
|
545
|
+
</HTML>
|
|
546
|
+
_end_of_html_
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# :startdoc:
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
end
|