shopify-cli 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. checksums.yaml +4 -4
  2. data/.github/PULL_REQUEST_TEMPLATE.md +1 -0
  3. data/.travis.yml +1 -0
  4. data/CHANGELOG.md +9 -0
  5. data/README.md +39 -7
  6. data/Rakefile +2 -0
  7. data/dev.yml +2 -2
  8. data/docs/_config.yml +1 -18
  9. data/docs/app/node/commands/index.md +2 -80
  10. data/docs/app/node/index.md +2 -33
  11. data/docs/app/rails/commands/index.md +2 -78
  12. data/docs/app/rails/index.md +2 -34
  13. data/docs/core/index.md +2 -84
  14. data/docs/getting-started/index.md +2 -25
  15. data/docs/getting-started/install/index.md +1 -118
  16. data/docs/getting-started/migrate/index.md +2 -94
  17. data/docs/getting-started/uninstall/index.md +2 -35
  18. data/docs/getting-started/upgrade/index.md +2 -39
  19. data/docs/help/start-app/index.md +2 -4
  20. data/docs/index.md +2 -24
  21. data/install.sh +1 -1
  22. data/lib/project_types/extension/cli.rb +19 -10
  23. data/lib/project_types/extension/commands/extension_command.rb +2 -2
  24. data/lib/project_types/extension/features/argo.rb +117 -0
  25. data/lib/project_types/extension/forms/create.rb +2 -2
  26. data/lib/project_types/extension/models/specification.rb +35 -0
  27. data/lib/project_types/extension/models/specification_handlers/checkout_post_purchase.rb +19 -0
  28. data/lib/project_types/extension/models/specification_handlers/default.rb +67 -0
  29. data/lib/project_types/extension/models/specifications.rb +77 -0
  30. data/lib/project_types/extension/tasks/configure_features.rb +52 -0
  31. data/lib/project_types/extension/tasks/fetch_specifications.rb +38 -0
  32. data/lib/project_types/node/commands/create.rb +3 -1
  33. data/lib/project_types/node/commands/generate.rb +2 -11
  34. data/lib/project_types/node/messages/messages.rb +9 -44
  35. data/lib/project_types/rails/commands/create.rb +8 -9
  36. data/lib/project_types/rails/forms/create.rb +1 -1
  37. data/lib/project_types/rails/gem.rb +1 -1
  38. data/lib/project_types/rails/messages/messages.rb +1 -1
  39. data/lib/project_types/script/cli.rb +7 -4
  40. data/lib/project_types/script/commands/create.rb +6 -4
  41. data/lib/project_types/script/commands/push.rb +5 -13
  42. data/lib/project_types/script/config/extension_points.yml +9 -5
  43. data/lib/project_types/script/errors.rb +17 -0
  44. data/lib/project_types/script/forms/create.rb +26 -2
  45. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +10 -1
  46. data/lib/project_types/script/layers/application/build_script.rb +9 -4
  47. data/lib/project_types/script/layers/application/create_script.rb +12 -10
  48. data/lib/project_types/script/layers/application/extension_points.rb +24 -0
  49. data/lib/project_types/script/layers/application/push_script.rb +18 -16
  50. data/lib/project_types/script/layers/domain/errors.rb +4 -0
  51. data/lib/project_types/script/layers/domain/extension_point.rb +62 -6
  52. data/lib/project_types/script/layers/domain/metadata.rb +55 -0
  53. data/lib/project_types/script/layers/domain/push_package.rb +25 -6
  54. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +6 -6
  55. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +16 -6
  56. data/lib/project_types/script/layers/infrastructure/extension_point_repository.rb +10 -4
  57. data/lib/project_types/script/layers/infrastructure/project_creator.rb +2 -1
  58. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +25 -13
  59. data/lib/project_types/script/layers/infrastructure/rust_project_creator.rb +72 -0
  60. data/lib/project_types/script/layers/infrastructure/rust_task_runner.rb +59 -0
  61. data/lib/project_types/script/layers/infrastructure/script_service.rb +7 -1
  62. data/lib/project_types/script/layers/infrastructure/task_runner.rb +4 -3
  63. data/lib/project_types/script/messages/messages.rb +39 -8
  64. data/lib/project_types/script/script_project.rb +25 -16
  65. data/lib/project_types/script/ui/error_handler.rb +34 -1
  66. data/lib/project_types/theme/cli.rb +40 -0
  67. data/lib/project_types/theme/commands/connect.rb +54 -0
  68. data/lib/project_types/theme/commands/create.rb +48 -0
  69. data/lib/project_types/theme/commands/deploy.rb +38 -0
  70. data/lib/project_types/theme/commands/generate.rb +20 -0
  71. data/lib/project_types/theme/commands/generate/env.rb +79 -0
  72. data/lib/project_types/theme/commands/push.rb +55 -0
  73. data/lib/project_types/theme/commands/serve.rb +31 -0
  74. data/lib/project_types/theme/forms/connect.rb +34 -0
  75. data/lib/project_types/theme/forms/create.rb +22 -0
  76. data/lib/project_types/theme/messages/messages.rb +147 -0
  77. data/lib/project_types/theme/tasks/ensure_themekit_installed.rb +78 -0
  78. data/lib/project_types/theme/themekit.rb +113 -0
  79. data/lib/shopify-cli/admin_api.rb +42 -2
  80. data/lib/shopify-cli/api.rb +27 -24
  81. data/lib/shopify-cli/commands/system.rb +1 -1
  82. data/lib/shopify-cli/context.rb +23 -2
  83. data/lib/shopify-cli/feature.rb +0 -2
  84. data/lib/shopify-cli/http_request.rb +20 -8
  85. data/lib/shopify-cli/messages/messages.rb +6 -3
  86. data/lib/shopify-cli/method_object.rb +104 -0
  87. data/lib/shopify-cli/partners_api.rb +8 -2
  88. data/lib/shopify-cli/project_type.rb +1 -1
  89. data/lib/shopify-cli/resolve_constant.rb +25 -0
  90. data/lib/shopify-cli/result.rb +432 -0
  91. data/lib/shopify-cli/shopifolk.rb +3 -2
  92. data/lib/shopify-cli/tasks/select_org_and_shop.rb +6 -5
  93. data/lib/shopify-cli/tunnel.rb +7 -1
  94. data/lib/shopify-cli/version.rb +1 -1
  95. data/lib/shopify_cli.rb +4 -1
  96. data/shopify.fish +1 -1
  97. data/shopify.sh +1 -1
  98. data/vendor/deps/cli-kit/REVISION +1 -1
  99. data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +2 -2
  100. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +3 -3
  101. data/vendor/deps/cli-ui/REVISION +1 -1
  102. data/vendor/deps/cli-ui/lib/cli/ui.rb +26 -22
  103. data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +4 -6
  104. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +3 -3
  105. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +8 -9
  106. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +1 -1
  107. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +1 -0
  108. data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +15 -3
  109. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +4 -11
  110. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +3 -5
  111. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +10 -10
  112. data/vendor/deps/cli-ui/lib/cli/ui/version.rb +1 -1
  113. data/vendor/deps/cli-ui/lib/cli/ui/wrap.rb +56 -0
  114. data/vendor/deps/webrick/.gitignore +9 -0
  115. data/vendor/deps/webrick/Gemfile +3 -0
  116. data/vendor/deps/webrick/LICENSE.txt +22 -0
  117. data/vendor/deps/webrick/README.md +61 -0
  118. data/vendor/deps/webrick/Rakefile +10 -0
  119. data/vendor/deps/webrick/lib/webrick.rb +232 -0
  120. data/vendor/deps/webrick/lib/webrick/accesslog.rb +157 -0
  121. data/vendor/deps/webrick/lib/webrick/cgi.rb +313 -0
  122. data/vendor/deps/webrick/lib/webrick/compat.rb +36 -0
  123. data/vendor/deps/webrick/lib/webrick/config.rb +158 -0
  124. data/vendor/deps/webrick/lib/webrick/cookie.rb +172 -0
  125. data/vendor/deps/webrick/lib/webrick/htmlutils.rb +30 -0
  126. data/vendor/deps/webrick/lib/webrick/httpauth.rb +96 -0
  127. data/vendor/deps/webrick/lib/webrick/httpauth/authenticator.rb +117 -0
  128. data/vendor/deps/webrick/lib/webrick/httpauth/basicauth.rb +116 -0
  129. data/vendor/deps/webrick/lib/webrick/httpauth/digestauth.rb +395 -0
  130. data/vendor/deps/webrick/lib/webrick/httpauth/htdigest.rb +132 -0
  131. data/vendor/deps/webrick/lib/webrick/httpauth/htgroup.rb +97 -0
  132. data/vendor/deps/webrick/lib/webrick/httpauth/htpasswd.rb +158 -0
  133. data/vendor/deps/webrick/lib/webrick/httpauth/userdb.rb +53 -0
  134. data/vendor/deps/webrick/lib/webrick/httpproxy.rb +354 -0
  135. data/vendor/deps/webrick/lib/webrick/httprequest.rb +636 -0
  136. data/vendor/deps/webrick/lib/webrick/httpresponse.rb +564 -0
  137. data/vendor/deps/webrick/lib/webrick/https.rb +152 -0
  138. data/vendor/deps/webrick/lib/webrick/httpserver.rb +294 -0
  139. data/vendor/deps/webrick/lib/webrick/httpservlet.rb +23 -0
  140. data/vendor/deps/webrick/lib/webrick/httpservlet/abstract.rb +152 -0
  141. data/vendor/deps/webrick/lib/webrick/httpservlet/cgi_runner.rb +47 -0
  142. data/vendor/deps/webrick/lib/webrick/httpservlet/cgihandler.rb +126 -0
  143. data/vendor/deps/webrick/lib/webrick/httpservlet/erbhandler.rb +88 -0
  144. data/vendor/deps/webrick/lib/webrick/httpservlet/filehandler.rb +552 -0
  145. data/vendor/deps/webrick/lib/webrick/httpservlet/prochandler.rb +47 -0
  146. data/vendor/deps/webrick/lib/webrick/httpstatus.rb +194 -0
  147. data/vendor/deps/webrick/lib/webrick/httputils.rb +512 -0
  148. data/vendor/deps/webrick/lib/webrick/httpversion.rb +76 -0
  149. data/vendor/deps/webrick/lib/webrick/log.rb +156 -0
  150. data/vendor/deps/webrick/lib/webrick/server.rb +381 -0
  151. data/vendor/deps/webrick/lib/webrick/ssl.rb +215 -0
  152. data/vendor/deps/webrick/lib/webrick/utils.rb +265 -0
  153. data/vendor/deps/webrick/lib/webrick/version.rb +18 -0
  154. data/vendor/deps/webrick/webrick.gemspec +74 -0
  155. metadata +70 -26
  156. data/docs/Gemfile +0 -5
  157. data/docs/Gemfile.lock +0 -258
  158. data/docs/_data/nav.yml +0 -35
  159. data/docs/_includes/footer.html +0 -15
  160. data/docs/_includes/head.html +0 -19
  161. data/docs/_includes/sidebar_nav.html +0 -22
  162. data/docs/_includes/toc.html +0 -112
  163. data/docs/_layouts/default.html +0 -79
  164. data/docs/css/docs.css +0 -157
  165. data/docs/images/header.png +0 -0
  166. data/docs/installing-ruby.md +0 -28
  167. data/lib/project_types/extension/features/argo/admin.rb +0 -20
  168. data/lib/project_types/extension/features/argo/base.rb +0 -129
  169. data/lib/project_types/extension/features/argo/checkout.rb +0 -20
  170. data/lib/project_types/extension/models/type.rb +0 -81
  171. data/lib/project_types/extension/models/types/checkout_post_purchase.rb +0 -23
  172. data/lib/project_types/extension/models/types/product_subscription.rb +0 -24
  173. data/lib/project_types/node/commands/generate/billing.rb +0 -39
  174. data/lib/project_types/node/commands/generate/page.rb +0 -59
  175. data/lib/project_types/node/commands/generate/webhook.rb +0 -37
  176. data/lib/project_types/script/layers/domain/script.rb +0 -18
  177. data/lib/project_types/script/layers/infrastructure/script_repository.rb +0 -47
  178. data/lib/project_types/script/templates/ts/as-pect.config.js +0 -27
  179. data/lib/project_types/script/templates/ts/as-pect.d.ts +0 -1
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # httpauth/htgroup.rb -- Apache compatible htgroup file
4
+ #
5
+ # Author: IPR -- Internet Programming with Ruby -- writers
6
+ # Copyright (c) 2003 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: htgroup.rb,v 1.1 2003/02/16 22:22:56 gotoyuzo Exp $
10
+
11
+ require 'tempfile'
12
+
13
+ module WEBrick
14
+ module HTTPAuth
15
+
16
+ ##
17
+ # Htgroup accesses apache-compatible group files. Htgroup can be used to
18
+ # provide group-based authentication for users. Currently Htgroup is not
19
+ # directly integrated with any authenticators in WEBrick. For security,
20
+ # the path for a digest password database should be stored outside of the
21
+ # paths available to the HTTP server.
22
+ #
23
+ # Example:
24
+ #
25
+ # htgroup = WEBrick::HTTPAuth::Htgroup.new 'my_group_file'
26
+ # htgroup.add 'superheroes', %w[spiderman batman]
27
+ #
28
+ # htgroup.members('superheroes').include? 'magneto' # => false
29
+
30
+ class Htgroup
31
+
32
+ ##
33
+ # Open a group database at +path+
34
+
35
+ def initialize(path)
36
+ @path = path
37
+ @mtime = Time.at(0)
38
+ @group = Hash.new
39
+ File.open(@path,"a").close unless File.exist?(@path)
40
+ reload
41
+ end
42
+
43
+ ##
44
+ # Reload groups from the database
45
+
46
+ def reload
47
+ if (mtime = File::mtime(@path)) > @mtime
48
+ @group.clear
49
+ File.open(@path){|io|
50
+ while line = io.gets
51
+ line.chomp!
52
+ group, members = line.split(/:\s*/)
53
+ @group[group] = members.split(/\s+/)
54
+ end
55
+ }
56
+ @mtime = mtime
57
+ end
58
+ end
59
+
60
+ ##
61
+ # Flush the group database. If +output+ is given the database will be
62
+ # written there instead of to the original path.
63
+
64
+ def flush(output=nil)
65
+ output ||= @path
66
+ tmp = Tempfile.create("htgroup", File::dirname(output))
67
+ begin
68
+ @group.keys.sort.each{|group|
69
+ tmp.puts(format("%s: %s", group, self.members(group).join(" ")))
70
+ }
71
+ ensure
72
+ tmp.close
73
+ if $!
74
+ File.unlink(tmp.path)
75
+ else
76
+ return File.rename(tmp.path, output)
77
+ end
78
+ end
79
+ end
80
+
81
+ ##
82
+ # Retrieve the list of members from +group+
83
+
84
+ def members(group)
85
+ reload
86
+ @group[group] || []
87
+ end
88
+
89
+ ##
90
+ # Add an Array of +members+ to +group+
91
+
92
+ def add(group, members)
93
+ @group[group] = members(group) | members
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # httpauth/htpasswd -- Apache compatible htpasswd file
4
+ #
5
+ # Author: IPR -- Internet Programming with Ruby -- writers
6
+ # Copyright (c) 2003 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: htpasswd.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $
10
+
11
+ require_relative 'userdb'
12
+ require_relative 'basicauth'
13
+ require 'tempfile'
14
+
15
+ module WEBrick
16
+ module HTTPAuth
17
+
18
+ ##
19
+ # Htpasswd accesses apache-compatible password files. Passwords are
20
+ # matched to a realm where they are valid. For security, the path for a
21
+ # password database should be stored outside of the paths available to the
22
+ # HTTP server.
23
+ #
24
+ # Htpasswd is intended for use with WEBrick::HTTPAuth::BasicAuth.
25
+ #
26
+ # To create an Htpasswd database with a single user:
27
+ #
28
+ # htpasswd = WEBrick::HTTPAuth::Htpasswd.new 'my_password_file'
29
+ # htpasswd.set_passwd 'my realm', 'username', 'password'
30
+ # htpasswd.flush
31
+
32
+ class Htpasswd
33
+ include UserDB
34
+
35
+ ##
36
+ # Open a password database at +path+
37
+
38
+ def initialize(path, password_hash: nil)
39
+ @path = path
40
+ @mtime = Time.at(0)
41
+ @passwd = Hash.new
42
+ @auth_type = BasicAuth
43
+ @password_hash = password_hash
44
+
45
+ case @password_hash
46
+ when nil
47
+ # begin
48
+ # require "string/crypt"
49
+ # rescue LoadError
50
+ # warn("Unable to load string/crypt, proceeding with deprecated use of String#crypt, consider using password_hash: :bcrypt")
51
+ # end
52
+ @password_hash = :crypt
53
+ when :crypt
54
+ # require "string/crypt"
55
+ when :bcrypt
56
+ require "bcrypt"
57
+ else
58
+ raise ArgumentError, "only :crypt and :bcrypt are supported for password_hash keyword argument"
59
+ end
60
+
61
+ File.open(@path,"a").close unless File.exist?(@path)
62
+ reload
63
+ end
64
+
65
+ ##
66
+ # Reload passwords from the database
67
+
68
+ def reload
69
+ mtime = File::mtime(@path)
70
+ if mtime > @mtime
71
+ @passwd.clear
72
+ File.open(@path){|io|
73
+ while line = io.gets
74
+ line.chomp!
75
+ case line
76
+ when %r!\A[^:]+:[a-zA-Z0-9./]{13}\z!
77
+ if @password_hash == :bcrypt
78
+ raise StandardError, ".htpasswd file contains crypt password, only bcrypt passwords supported"
79
+ end
80
+ user, pass = line.split(":")
81
+ when %r!\A[^:]+:\$2[aby]\$\d{2}\$.{53}\z!
82
+ if @password_hash == :crypt
83
+ raise StandardError, ".htpasswd file contains bcrypt password, only crypt passwords supported"
84
+ end
85
+ user, pass = line.split(":")
86
+ when /:\$/, /:{SHA}/
87
+ raise NotImplementedError,
88
+ 'MD5, SHA1 .htpasswd file not supported'
89
+ else
90
+ raise StandardError, 'bad .htpasswd file'
91
+ end
92
+ @passwd[user] = pass
93
+ end
94
+ }
95
+ @mtime = mtime
96
+ end
97
+ end
98
+
99
+ ##
100
+ # Flush the password database. If +output+ is given the database will
101
+ # be written there instead of to the original path.
102
+
103
+ def flush(output=nil)
104
+ output ||= @path
105
+ tmp = Tempfile.create("htpasswd", File::dirname(output))
106
+ renamed = false
107
+ begin
108
+ each{|item| tmp.puts(item.join(":")) }
109
+ tmp.close
110
+ File::rename(tmp.path, output)
111
+ renamed = true
112
+ ensure
113
+ tmp.close
114
+ File.unlink(tmp.path) if !renamed
115
+ end
116
+ end
117
+
118
+ ##
119
+ # Retrieves a password from the database for +user+ in +realm+. If
120
+ # +reload_db+ is true the database will be reloaded first.
121
+
122
+ def get_passwd(realm, user, reload_db)
123
+ reload() if reload_db
124
+ @passwd[user]
125
+ end
126
+
127
+ ##
128
+ # Sets a password in the database for +user+ in +realm+ to +pass+.
129
+
130
+ def set_passwd(realm, user, pass)
131
+ if @password_hash == :bcrypt
132
+ # Cost of 5 to match Apache default, and because the
133
+ # bcrypt default of 10 will introduce significant delays
134
+ # for every request.
135
+ @passwd[user] = BCrypt::Password.create(pass, :cost=>5)
136
+ else
137
+ @passwd[user] = make_passwd(realm, user, pass)
138
+ end
139
+ end
140
+
141
+ ##
142
+ # Removes a password from the database for +user+ in +realm+.
143
+
144
+ def delete_passwd(realm, user)
145
+ @passwd.delete(user)
146
+ end
147
+
148
+ ##
149
+ # Iterate passwords in the database.
150
+
151
+ def each # :yields: [user, password]
152
+ @passwd.keys.sort.each{|user|
153
+ yield([user, @passwd[user]])
154
+ }
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ #--
3
+ # httpauth/userdb.rb -- UserDB mix-in module.
4
+ #
5
+ # Author: IPR -- Internet Programming with Ruby -- writers
6
+ # Copyright (c) 2003 Internet Programming with Ruby writers. All rights
7
+ # reserved.
8
+ #
9
+ # $IPR: userdb.rb,v 1.2 2003/02/20 07:15:48 gotoyuzo Exp $
10
+
11
+ module WEBrick
12
+ module HTTPAuth
13
+
14
+ ##
15
+ # User database mixin for HTTPAuth. This mixin dispatches user record
16
+ # access to the underlying auth_type for this database.
17
+
18
+ module UserDB
19
+
20
+ ##
21
+ # The authentication type.
22
+ #
23
+ # WEBrick::HTTPAuth::BasicAuth or WEBrick::HTTPAuth::DigestAuth are
24
+ # built-in.
25
+
26
+ attr_accessor :auth_type
27
+
28
+ ##
29
+ # Creates an obscured password in +realm+ with +user+ and +password+
30
+ # using the auth_type of this database.
31
+
32
+ def make_passwd(realm, user, pass)
33
+ @auth_type::make_passwd(realm, user, pass)
34
+ end
35
+
36
+ ##
37
+ # Sets a password in +realm+ with +user+ and +password+ for the
38
+ # auth_type of this database.
39
+
40
+ def set_passwd(realm, user, pass)
41
+ self[user] = pass
42
+ end
43
+
44
+ ##
45
+ # Retrieves a password in +realm+ for +user+ for the auth_type of this
46
+ # database. +reload_db+ is a dummy value.
47
+
48
+ def get_passwd(realm, user, reload_db=false)
49
+ make_passwd(realm, user, self[user])
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,354 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # httpproxy.rb -- HTTPProxy Class
4
+ #
5
+ # Author: IPR -- Internet Programming with Ruby -- writers
6
+ # Copyright (c) 2002 GOTO Kentaro
7
+ # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
8
+ # reserved.
9
+ #
10
+ # $IPR: httpproxy.rb,v 1.18 2003/03/08 18:58:10 gotoyuzo Exp $
11
+ # $kNotwork: straw.rb,v 1.3 2002/02/12 15:13:07 gotoken Exp $
12
+
13
+ require_relative "httpserver"
14
+ require "net/http"
15
+
16
+ module WEBrick
17
+
18
+ NullReader = Object.new # :nodoc:
19
+ class << NullReader # :nodoc:
20
+ def read(*args)
21
+ nil
22
+ end
23
+ alias gets read
24
+ end
25
+
26
+ FakeProxyURI = Object.new # :nodoc:
27
+ class << FakeProxyURI # :nodoc:
28
+ def method_missing(meth, *args)
29
+ if %w(scheme host port path query userinfo).member?(meth.to_s)
30
+ return nil
31
+ end
32
+ super
33
+ end
34
+ end
35
+
36
+ # :startdoc:
37
+
38
+ ##
39
+ # An HTTP Proxy server which proxies GET, HEAD and POST requests.
40
+ #
41
+ # To create a simple proxy server:
42
+ #
43
+ # require 'webrick'
44
+ # require 'webrick/httpproxy'
45
+ #
46
+ # proxy = WEBrick::HTTPProxyServer.new Port: 8000
47
+ #
48
+ # trap 'INT' do proxy.shutdown end
49
+ # trap 'TERM' do proxy.shutdown end
50
+ #
51
+ # proxy.start
52
+ #
53
+ # See ::new for proxy-specific configuration items.
54
+ #
55
+ # == Modifying proxied responses
56
+ #
57
+ # To modify content the proxy server returns use the +:ProxyContentHandler+
58
+ # option:
59
+ #
60
+ # handler = proc do |req, res|
61
+ # if res['content-type'] == 'text/plain' then
62
+ # res.body << "\nThis content was proxied!\n"
63
+ # end
64
+ # end
65
+ #
66
+ # proxy =
67
+ # WEBrick::HTTPProxyServer.new Port: 8000, ProxyContentHandler: handler
68
+
69
+ class HTTPProxyServer < HTTPServer
70
+
71
+ ##
72
+ # Proxy server configurations. The proxy server handles the following
73
+ # configuration items in addition to those supported by HTTPServer:
74
+ #
75
+ # :ProxyAuthProc:: Called with a request and response to authorize a
76
+ # request
77
+ # :ProxyVia:: Appended to the via header
78
+ # :ProxyURI:: The proxy server's URI
79
+ # :ProxyContentHandler:: Called with a request and response and allows
80
+ # modification of the response
81
+ # :ProxyTimeout:: Sets the proxy timeouts to 30 seconds for open and 60
82
+ # seconds for read operations
83
+
84
+ def initialize(config={}, default=Config::HTTP)
85
+ super(config, default)
86
+ c = @config
87
+ @via = "#{c[:HTTPVersion]} #{c[:ServerName]}:#{c[:Port]}"
88
+ end
89
+
90
+ # :stopdoc:
91
+ def service(req, res)
92
+ if req.request_method == "CONNECT"
93
+ do_CONNECT(req, res)
94
+ elsif req.unparsed_uri =~ %r!^http://!
95
+ proxy_service(req, res)
96
+ else
97
+ super(req, res)
98
+ end
99
+ end
100
+
101
+ def proxy_auth(req, res)
102
+ if proc = @config[:ProxyAuthProc]
103
+ proc.call(req, res)
104
+ end
105
+ req.header.delete("proxy-authorization")
106
+ end
107
+
108
+ def proxy_uri(req, res)
109
+ # should return upstream proxy server's URI
110
+ return @config[:ProxyURI]
111
+ end
112
+
113
+ def proxy_service(req, res)
114
+ # Proxy Authentication
115
+ proxy_auth(req, res)
116
+
117
+ begin
118
+ public_send("do_#{req.request_method}", req, res)
119
+ rescue NoMethodError
120
+ raise HTTPStatus::MethodNotAllowed,
121
+ "unsupported method `#{req.request_method}'."
122
+ rescue => err
123
+ logger.debug("#{err.class}: #{err.message}")
124
+ raise HTTPStatus::ServiceUnavailable, err.message
125
+ end
126
+
127
+ # Process contents
128
+ if handler = @config[:ProxyContentHandler]
129
+ handler.call(req, res)
130
+ end
131
+ end
132
+
133
+ def do_CONNECT(req, res)
134
+ # Proxy Authentication
135
+ proxy_auth(req, res)
136
+
137
+ ua = Thread.current[:WEBrickSocket] # User-Agent
138
+ raise HTTPStatus::InternalServerError,
139
+ "[BUG] cannot get socket" unless ua
140
+
141
+ host, port = req.unparsed_uri.split(":", 2)
142
+ # Proxy authentication for upstream proxy server
143
+ if proxy = proxy_uri(req, res)
144
+ proxy_request_line = "CONNECT #{host}:#{port} HTTP/1.0"
145
+ if proxy.userinfo
146
+ credentials = "Basic " + [proxy.userinfo].pack("m0")
147
+ end
148
+ host, port = proxy.host, proxy.port
149
+ end
150
+
151
+ begin
152
+ @logger.debug("CONNECT: upstream proxy is `#{host}:#{port}'.")
153
+ os = TCPSocket.new(host, port) # origin server
154
+
155
+ if proxy
156
+ @logger.debug("CONNECT: sending a Request-Line")
157
+ os << proxy_request_line << CRLF
158
+ @logger.debug("CONNECT: > #{proxy_request_line}")
159
+ if credentials
160
+ @logger.debug("CONNECT: sending credentials")
161
+ os << "Proxy-Authorization: " << credentials << CRLF
162
+ end
163
+ os << CRLF
164
+ proxy_status_line = os.gets(LF)
165
+ @logger.debug("CONNECT: read Status-Line from the upstream server")
166
+ @logger.debug("CONNECT: < #{proxy_status_line}")
167
+ if %r{^HTTP/\d+\.\d+\s+200\s*} =~ proxy_status_line
168
+ while line = os.gets(LF)
169
+ break if /\A(#{CRLF}|#{LF})\z/om =~ line
170
+ end
171
+ else
172
+ raise HTTPStatus::BadGateway
173
+ end
174
+ end
175
+ @logger.debug("CONNECT #{host}:#{port}: succeeded")
176
+ res.status = HTTPStatus::RC_OK
177
+ rescue => ex
178
+ @logger.debug("CONNECT #{host}:#{port}: failed `#{ex.message}'")
179
+ res.set_error(ex)
180
+ raise HTTPStatus::EOFError
181
+ ensure
182
+ if handler = @config[:ProxyContentHandler]
183
+ handler.call(req, res)
184
+ end
185
+ res.send_response(ua)
186
+ access_log(@config, req, res)
187
+
188
+ # Should clear request-line not to send the response twice.
189
+ # see: HTTPServer#run
190
+ req.parse(NullReader) rescue nil
191
+ end
192
+
193
+ begin
194
+ while fds = IO::select([ua, os])
195
+ if fds[0].member?(ua)
196
+ buf = ua.readpartial(1024);
197
+ @logger.debug("CONNECT: #{buf.bytesize} byte from User-Agent")
198
+ os.write(buf)
199
+ elsif fds[0].member?(os)
200
+ buf = os.readpartial(1024);
201
+ @logger.debug("CONNECT: #{buf.bytesize} byte from #{host}:#{port}")
202
+ ua.write(buf)
203
+ end
204
+ end
205
+ rescue
206
+ os.close
207
+ @logger.debug("CONNECT #{host}:#{port}: closed")
208
+ end
209
+
210
+ raise HTTPStatus::EOFError
211
+ end
212
+
213
+ def do_GET(req, res)
214
+ perform_proxy_request(req, res, Net::HTTP::Get)
215
+ end
216
+
217
+ def do_HEAD(req, res)
218
+ perform_proxy_request(req, res, Net::HTTP::Head)
219
+ end
220
+
221
+ def do_POST(req, res)
222
+ perform_proxy_request(req, res, Net::HTTP::Post, req.body_reader)
223
+ end
224
+
225
+ def do_OPTIONS(req, res)
226
+ res['allow'] = "GET,HEAD,POST,OPTIONS,CONNECT"
227
+ end
228
+
229
+ private
230
+
231
+ # Some header fields should not be transferred.
232
+ HopByHop = %w( connection keep-alive proxy-authenticate upgrade
233
+ proxy-authorization te trailers transfer-encoding )
234
+ ShouldNotTransfer = %w( set-cookie proxy-connection )
235
+ def split_field(f) f ? f.split(/,\s+/).collect{|i| i.downcase } : [] end
236
+
237
+ def choose_header(src, dst)
238
+ connections = split_field(src['connection'])
239
+ src.each{|key, value|
240
+ key = key.downcase
241
+ if HopByHop.member?(key) || # RFC2616: 13.5.1
242
+ connections.member?(key) || # RFC2616: 14.10
243
+ ShouldNotTransfer.member?(key) # pragmatics
244
+ @logger.debug("choose_header: `#{key}: #{value}'")
245
+ next
246
+ end
247
+ dst[key] = value
248
+ }
249
+ end
250
+
251
+ # Net::HTTP is stupid about the multiple header fields.
252
+ # Here is workaround:
253
+ def set_cookie(src, dst)
254
+ if str = src['set-cookie']
255
+ cookies = []
256
+ str.split(/,\s*/).each{|token|
257
+ if /^[^=]+;/o =~ token
258
+ cookies[-1] << ", " << token
259
+ elsif /=/o =~ token
260
+ cookies << token
261
+ else
262
+ cookies[-1] << ", " << token
263
+ end
264
+ }
265
+ dst.cookies.replace(cookies)
266
+ end
267
+ end
268
+
269
+ def set_via(h)
270
+ if @config[:ProxyVia]
271
+ if h['via']
272
+ h['via'] << ", " << @via
273
+ else
274
+ h['via'] = @via
275
+ end
276
+ end
277
+ end
278
+
279
+ def setup_proxy_header(req, res)
280
+ # Choose header fields to transfer
281
+ header = Hash.new
282
+ choose_header(req, header)
283
+ set_via(header)
284
+ return header
285
+ end
286
+
287
+ def setup_upstream_proxy_authentication(req, res, header)
288
+ if upstream = proxy_uri(req, res)
289
+ if upstream.userinfo
290
+ header['proxy-authorization'] =
291
+ "Basic " + [upstream.userinfo].pack("m0")
292
+ end
293
+ return upstream
294
+ end
295
+ return FakeProxyURI
296
+ end
297
+
298
+ def create_net_http(uri, upstream)
299
+ Net::HTTP.new(uri.host, uri.port, upstream.host, upstream.port)
300
+ end
301
+
302
+ def perform_proxy_request(req, res, req_class, body_stream = nil)
303
+ uri = req.request_uri
304
+ path = uri.path.dup
305
+ path << "?" << uri.query if uri.query
306
+ header = setup_proxy_header(req, res)
307
+ upstream = setup_upstream_proxy_authentication(req, res, header)
308
+
309
+ body_tmp = []
310
+ http = create_net_http(uri, upstream)
311
+ req_fib = Fiber.new do
312
+ http.start do
313
+ if @config[:ProxyTimeout]
314
+ ################################## these issues are
315
+ http.open_timeout = 30 # secs # necessary (maybe because
316
+ http.read_timeout = 60 # secs # Ruby's bug, but why?)
317
+ ##################################
318
+ end
319
+ if body_stream && req['transfer-encoding'] =~ /\bchunked\b/i
320
+ header['Transfer-Encoding'] = 'chunked'
321
+ end
322
+ http_req = req_class.new(path, header)
323
+ http_req.body_stream = body_stream if body_stream
324
+ http.request(http_req) do |response|
325
+ # Persistent connection requirements are mysterious for me.
326
+ # So I will close the connection in every response.
327
+ res['proxy-connection'] = "close"
328
+ res['connection'] = "close"
329
+
330
+ # stream Net::HTTP::HTTPResponse to WEBrick::HTTPResponse
331
+ res.status = response.code.to_i
332
+ res.chunked = response.chunked?
333
+ choose_header(response, res)
334
+ set_cookie(response, res)
335
+ set_via(res)
336
+ response.read_body do |buf|
337
+ body_tmp << buf
338
+ Fiber.yield # wait for res.body Proc#call
339
+ end
340
+ end # http.request
341
+ end
342
+ end
343
+ req_fib.resume # read HTTP response headers and first chunk of the body
344
+ res.body = ->(socket) do
345
+ while buf = body_tmp.shift
346
+ socket.write(buf)
347
+ buf.clear
348
+ req_fib.resume # continue response.read_body
349
+ end
350
+ end
351
+ end
352
+ # :stopdoc:
353
+ end
354
+ end