rye 0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/esc.rb ADDED
@@ -0,0 +1,301 @@
1
+ # escape.rb - escape/unescape library for several formats
2
+ #
3
+ # Copyright (C) 2006,2007 Tanaka Akira <akr@fsij.org>
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # 1. Redistributions of source code must retain the above copyright notice, this
9
+ # list of conditions and the following disclaimer.
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # 3. The name of the author may not be used to endorse or promote products
14
+ # derived from this software without specific prior written permission.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17
+ # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18
+ # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19
+ # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
21
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
24
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
25
+ # OF SUCH DAMAGE.
26
+
27
+ # Escape module provides several escape functions.
28
+ # * URI
29
+ # * HTML
30
+ # * shell command
31
+ module Escape # :nodoc:all
32
+ module_function
33
+
34
+ class StringWrapper # :nodoc:all
35
+ class << self
36
+ alias new_no_dup new
37
+ def new(str)
38
+ new_no_dup(str.dup)
39
+ end
40
+ end
41
+
42
+ def initialize(str)
43
+ @str = str
44
+ end
45
+
46
+ def to_s
47
+ @str.dup
48
+ end
49
+
50
+ def inspect
51
+ "\#<#{self.class}: #{@str}>"
52
+ end
53
+
54
+ def ==(other)
55
+ other.class == self.class && @str == other.instance_variable_get(:@str)
56
+ end
57
+ alias eql? ==
58
+
59
+ def hash
60
+ @str.hash
61
+ end
62
+ end
63
+
64
+ class ShellEscaped < StringWrapper #:nodoc:all
65
+ end
66
+
67
+ # Escape.shell_command composes
68
+ # a sequence of words to
69
+ # a single shell command line.
70
+ # All shell meta characters are quoted and
71
+ # the words are concatenated with interleaving space.
72
+ # It returns an instance of ShellEscaped.
73
+ #
74
+ # Escape.shell_command(["ls", "/"]) #=> #<Escape::ShellEscaped: ls />
75
+ # Escape.shell_command(["echo", "*"]) #=> #<Escape::ShellEscaped: echo '*'>
76
+ #
77
+ # Note that system(*command) and
78
+ # system(Escape.shell_command(command)) is roughly same.
79
+ # There are two exception as follows.
80
+ # * The first is that the later may invokes /bin/sh.
81
+ # * The second is an interpretation of an array with only one element:
82
+ # the element is parsed by the shell with the former but
83
+ # it is recognized as single word with the later.
84
+ # For example, system(*["echo foo"]) invokes echo command with an argument "foo".
85
+ # But system(Escape.shell_command(["echo foo"])) invokes "echo foo" command without arguments (and it probably fails).
86
+ def shell_command(*command)
87
+ command = [command].flatten.compact # Delano
88
+ s = command.map {|word| shell_single_word(word) }.join(' ')
89
+ ShellEscaped.new_no_dup(s)
90
+ end
91
+
92
+ # Escape.shell_single_word quotes shell meta characters.
93
+ # It returns an instance of ShellEscaped.
94
+ #
95
+ # The result string is always single shell word, even if
96
+ # the argument is "".
97
+ # Escape.shell_single_word("") returns #<Escape::ShellEscaped: ''>.
98
+ #
99
+ # Escape.shell_single_word("") #=> #<Escape::ShellEscaped: ''>
100
+ # Escape.shell_single_word("foo") #=> #<Escape::ShellEscaped: foo>
101
+ # Escape.shell_single_word("*") #=> #<Escape::ShellEscaped: '*'>
102
+ def shell_single_word(str)
103
+ return unless str
104
+ str &&= str.to_s # Delano fix
105
+ if str.empty?
106
+ ShellEscaped.new_no_dup("''")
107
+ elsif %r{\A[0-9A-Za-z+,./:=@_-]+\z} =~ str
108
+ ShellEscaped.new(str)
109
+ else
110
+ result = ''
111
+ str.scan(/('+)|[^']+/) {
112
+ if $1
113
+ result << %q{\'} * $1.length
114
+ else
115
+ result << "'#{$&}'"
116
+ end
117
+ }
118
+ ShellEscaped.new_no_dup(result)
119
+ end
120
+ end
121
+
122
+ class PercentEncoded < StringWrapper #:nodoc:all
123
+ end
124
+
125
+ # Escape.uri_segment escapes URI segment using percent-encoding.
126
+ # It returns an instance of PercentEncoded.
127
+ #
128
+ # Escape.uri_segment("a/b") #=> #<Escape::PercentEncoded: a%2Fb>
129
+ #
130
+ # The segment is "/"-splitted element after authority before query in URI, as follows.
131
+ #
132
+ # scheme://authority/segment1/segment2/.../segmentN?query#fragment
133
+ #
134
+ # See RFC 3986 for details of URI.
135
+ def uri_segment(str)
136
+ # pchar - pct-encoded = unreserved / sub-delims / ":" / "@"
137
+ # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
138
+ # sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
139
+ s = str.gsub(%r{[^A-Za-z0-9\-._~!$&'()*+,;=:@]}n) {
140
+ '%' + $&.unpack("H2")[0].upcase
141
+ }
142
+ PercentEncoded.new_no_dup(s)
143
+ end
144
+
145
+ # Escape.uri_path escapes URI path using percent-encoding.
146
+ # The given path should be a sequence of (non-escaped) segments separated by "/".
147
+ # The segments cannot contains "/".
148
+ # It returns an instance of PercentEncoded.
149
+ #
150
+ # Escape.uri_path("a/b/c") #=> #<Escape::PercentEncoded: a/b/c>
151
+ # Escape.uri_path("a?b/c?d/e?f") #=> #<Escape::PercentEncoded: a%3Fb/c%3Fd/e%3Ff>
152
+ #
153
+ # The path is the part after authority before query in URI, as follows.
154
+ #
155
+ # scheme://authority/path#fragment
156
+ #
157
+ # See RFC 3986 for details of URI.
158
+ #
159
+ # Note that this function is not appropriate to convert OS path to URI.
160
+ def uri_path(str)
161
+ s = str.gsub(%r{[^/]+}n) { uri_segment($&) }
162
+ PercentEncoded.new_no_dup(s)
163
+ end
164
+
165
+ def html_form_fast(pairs, sep='&')
166
+ s = pairs.map {|k, v|
167
+ # query-chars - pct-encoded - x-www-form-urlencoded-delimiters =
168
+ # unreserved / "!" / "$" / "'" / "(" / ")" / "*" / "," / ":" / "@" / "/" / "?"
169
+ # query-char - pct-encoded = unreserved / sub-delims / ":" / "@" / "/" / "?"
170
+ # query-char = pchar / "/" / "?" = unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?"
171
+ # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
172
+ # sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
173
+ # x-www-form-urlencoded-delimiters = "&" / "+" / ";" / "="
174
+ k = k.gsub(%r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n) {
175
+ '%' + $&.unpack("H2")[0].upcase
176
+ }
177
+ v = v.gsub(%r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n) {
178
+ '%' + $&.unpack("H2")[0].upcase
179
+ }
180
+ "#{k}=#{v}"
181
+ }.join(sep)
182
+ PercentEncoded.new_no_dup(s)
183
+ end
184
+
185
+ # Escape.html_form composes HTML form key-value pairs as a x-www-form-urlencoded encoded string.
186
+ # It returns an instance of PercentEncoded.
187
+ #
188
+ # Escape.html_form takes an array of pair of strings or
189
+ # an hash from string to string.
190
+ #
191
+ # Escape.html_form([["a","b"], ["c","d"]]) #=> #<Escape::PercentEncoded: a=b&c=d>
192
+ # Escape.html_form({"a"=>"b", "c"=>"d"}) #=> #<Escape::PercentEncoded: a=b&c=d>
193
+ #
194
+ # In the array form, it is possible to use same key more than once.
195
+ # (It is required for a HTML form which contains
196
+ # checkboxes and select element with multiple attribute.)
197
+ #
198
+ # Escape.html_form([["k","1"], ["k","2"]]) #=> #<Escape::PercentEncoded: k=1&k=2>
199
+ #
200
+ # If the strings contains characters which must be escaped in x-www-form-urlencoded,
201
+ # they are escaped using %-encoding.
202
+ #
203
+ # Escape.html_form([["k=","&;="]]) #=> #<Escape::PercentEncoded: k%3D=%26%3B%3D>
204
+ #
205
+ # The separator can be specified by the optional second argument.
206
+ #
207
+ # Escape.html_form([["a","b"], ["c","d"]], ";") #=> #<Escape::PercentEncoded: a=b;c=d>
208
+ #
209
+ # See HTML 4.01 for details.
210
+ def html_form(pairs, sep='&')
211
+ r = ''
212
+ first = true
213
+ pairs.each {|k, v|
214
+ # query-chars - pct-encoded - x-www-form-urlencoded-delimiters =
215
+ # unreserved / "!" / "$" / "'" / "(" / ")" / "*" / "," / ":" / "@" / "/" / "?"
216
+ # query-char - pct-encoded = unreserved / sub-delims / ":" / "@" / "/" / "?"
217
+ # query-char = pchar / "/" / "?" = unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?"
218
+ # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
219
+ # sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
220
+ # x-www-form-urlencoded-delimiters = "&" / "+" / ";" / "="
221
+ r << sep if !first
222
+ first = false
223
+ k.each_byte {|byte|
224
+ ch = byte.chr
225
+ if %r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n =~ ch
226
+ r << "%" << ch.unpack("H2")[0].upcase
227
+ else
228
+ r << ch
229
+ end
230
+ }
231
+ r << '='
232
+ v.each_byte {|byte|
233
+ ch = byte.chr
234
+ if %r{[^0-9A-Za-z\-\._~:/?@!\$'()*,]}n =~ ch
235
+ r << "%" << ch.unpack("H2")[0].upcase
236
+ else
237
+ r << ch
238
+ end
239
+ }
240
+ }
241
+ PercentEncoded.new_no_dup(r)
242
+ end
243
+
244
+ class HTMLEscaped < StringWrapper #:nodoc:all
245
+ end
246
+
247
+ HTML_TEXT_ESCAPE_HASH = {
248
+ '&' => '&amp;',
249
+ '<' => '&lt;',
250
+ '>' => '&gt;',
251
+ }
252
+
253
+
254
+ # Escape.html_text escapes a string appropriate for HTML text using character references.
255
+ # It returns an instance of HTMLEscaped.
256
+ #
257
+ # It escapes 3 characters:
258
+ # * '&' to '&amp;'
259
+ # * '<' to '&lt;'
260
+ # * '>' to '&gt;'
261
+ #
262
+ # Escape.html_text("abc") #=> #<Escape::HTMLEscaped: abc>
263
+ # Escape.html_text("a & b < c > d") #=> #<Escape::HTMLEscaped: a &amp; b &lt; c &gt; d>
264
+ #
265
+ # This function is not appropriate for escaping HTML element attribute
266
+ # because quotes are not escaped.
267
+ def html_text(str) #:nodoc:all
268
+ s = str.gsub(/[&<>]/) {|ch| HTML_TEXT_ESCAPE_HASH[ch] }
269
+ HTMLEscaped.new_no_dup(s)
270
+ end
271
+
272
+ HTML_ATTR_ESCAPE_HASH = { #:nodoc:all
273
+ '&' => '&amp;',
274
+ '<' => '&lt;',
275
+ '>' => '&gt;',
276
+ '"' => '&quot;',
277
+ }
278
+
279
+
280
+ class HTMLAttrValue < StringWrapper #:nodoc:all
281
+ end
282
+
283
+ # Escape.html_attr_value encodes a string as a double-quoted HTML attribute using character references.
284
+ # It returns an instance of HTMLAttrValue.
285
+ #
286
+ # Escape.html_attr_value("abc") #=> #<Escape::HTMLAttrValue: "abc">
287
+ # Escape.html_attr_value("a&b") #=> #<Escape::HTMLAttrValue: "a&amp;b">
288
+ # Escape.html_attr_value("ab&<>\"c") #=> #<Escape::HTMLAttrValue: "ab&amp;&lt;&gt;&quot;c">
289
+ # Escape.html_attr_value("a'c") #=> #<Escape::HTMLAttrValue: "a'c">
290
+ #
291
+ # It escapes 4 characters:
292
+ # * '&' to '&amp;'
293
+ # * '<' to '&lt;'
294
+ # * '>' to '&gt;'
295
+ # * '"' to '&quot;'
296
+ #
297
+ def html_attr_value(str) #:nodoc:all
298
+ s = '"' + str.gsub(/[&<>"]/) {|ch| HTML_ATTR_ESCAPE_HASH[ch] } + '"'
299
+ HTMLAttrValue.new_no_dup(s)
300
+ end
301
+ end
data/lib/rye.rb ADDED
@@ -0,0 +1,155 @@
1
+
2
+ require 'rubygems' unless defined? Gem
3
+
4
+ require 'net/ssh'
5
+ require 'thread'
6
+ require 'highline'
7
+ require 'esc'
8
+ require 'sys'
9
+
10
+ # = Rye
11
+ #
12
+ # Run system commands via SSH locally and remotely in a Ruby way.
13
+ #
14
+ # Rye is similar to Rush[http://rush.heroku.com] but everything
15
+ # happens over SSH (no HTTP daemon) and the default settings are
16
+ # less powerful (for safety). For example, file globs are disabled
17
+ # so unless otherwise specified, you can't do this:
18
+ # <tt>rbox.rm('/etc/**/*')</tt>.
19
+ #
20
+ # * See +bin/try+ for a bunch of working examples.
21
+ # * See Rye::Box#initialize for info about disabling safe-mode.
22
+ #
23
+ module Rye
24
+ extend self
25
+
26
+ unless defined?(SYSINFO)
27
+ VERSION = 0.3.freeze
28
+ SYSINFO = SystemInfo.new.freeze
29
+ end
30
+
31
+ @@agent_env = Hash.new # holds ssh-agent env vars
32
+ @@mutex = Mutex.new # for synchronizing threads
33
+
34
+ # Accessor for an instance of SystemInfo
35
+ def Rye.sysinfo; SYSINFO; end
36
+
37
+ # Accessor for an instance of SystemInfo
38
+ def sysinfo; SYSINFO; end
39
+
40
+ class CommandNotFound < RuntimeError; end
41
+ class NoBoxes < RuntimeError; end
42
+ class NoHost < RuntimeError; end
43
+ class NotConnected < RuntimeError; end
44
+
45
+ # Reload Rye dynamically. Useful with irb.
46
+ # NOTE: does not reload rye.rb.
47
+ def reload
48
+ pat = File.join(File.dirname(__FILE__), 'rye')
49
+ %w{rap cmd box set}.each {|lib| load File.join(pat, "#{lib}.rb") }
50
+ end
51
+
52
+ def mutex
53
+ @@mutex
54
+ end
55
+
56
+
57
+ # Add one or more private keys to the SSH Agent.
58
+ # * +keys+ one or more file paths to private keys used for passwordless logins.
59
+ def add_keys(*keys)
60
+ keys = [keys].flatten.compact || []
61
+ return if keys.empty?
62
+ Rye::Box.shell("ssh-add", keys) if keys
63
+ Rye::Box.shell("ssh-add") # Add the user's default keys
64
+ keys
65
+ end
66
+
67
+ # Returns an Array of info about the currently available
68
+ # SSH keys, as provided by the SSH Agent. See
69
+ # Rye.start_sshagent_environment
70
+ #
71
+ # Returns: [[bits, finger-print, file-path], ...]
72
+ #
73
+ def keys
74
+ # 2048 76:cb:d7:82:90:92:ad:75:3d:68:6c:a9:21:ca:7b:7f /Users/rye/.ssh/id_rsa (RSA)
75
+ # 2048 7b:a6:ba:55:b1:10:1d:91:9f:73:3a:aa:0c:d4:88:0e /Users/rye/.ssh/id_dsa (DSA)
76
+ keystr = Rye::Box.shell("ssh-add", '-l')
77
+ return nil unless keystr
78
+ keystr.split($/).collect do |key|
79
+ key.split(/\s+/)
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ # Start the SSH Agent locally. This is important
86
+ # primarily because Rye relies on it for SSH key
87
+ # management. If the agent doesn't start then
88
+ # passwordless logins won't work.
89
+ #
90
+ # This method starts an instances of ssh-agent
91
+ # and sets the appropriate environment so all
92
+ # local commands run by Rye will have access be aware
93
+ # of this instance of the agent too.
94
+ #
95
+ # The equivalent commands on the shell are:
96
+ #
97
+ # $ ssh-agent -s
98
+ # SSH_AUTH_SOCK=/tmp/ssh-tGvaOXIXSr/agent.12951; export SSH_AUTH_SOCK;
99
+ # SSH_AGENT_PID=12952; export SSH_AGENT_PID;
100
+ # $ SSH_AUTH_SOCK=/tmp/ssh-tGvaOXIXSr/agent.12951; export SSH_AUTH_SOCK;
101
+ # $ SSH_AGENT_PID=12952; export SSH_AGENT_PID;
102
+ #
103
+ # NOTE: The OpenSSL library (The C one, not the Ruby one)
104
+ # must be installed for this to work.
105
+ #
106
+ def start_sshagent_environment
107
+ return if @@agent_env["SSH_AGENT_PID"]
108
+
109
+ lines = Rye::Box.shell("ssh-agent", '-s') || ''
110
+ lines.split($/).each do |line|
111
+ next unless line.index("echo").nil?
112
+ line = line.slice(0..(line.index(';')-1))
113
+ key, value = line.chomp.split( /=/ )
114
+ @@agent_env[key] = value
115
+ end
116
+ ENV["SSH_AUTH_SOCK"] = @@agent_env["SSH_AUTH_SOCK"]
117
+ ENV["SSH_AGENT_PID"] = @@agent_env["SSH_AGENT_PID"]
118
+ nil
119
+ end
120
+
121
+ # Kill the local instance of the SSH Agent we started.
122
+ #
123
+ # Calls this command via the local shell:
124
+ #
125
+ # $ ssh-agent -k
126
+ #
127
+ # which uses the SSH_AUTH_SOCK and SSH_AGENT_PID environment variables
128
+ # to determine which ssh-agent to kill.
129
+ #
130
+ def end_sshagent_environment
131
+ pid = @@agent_env["SSH_AGENT_PID"]
132
+ Rye::Box.shell("ssh-agent", '-k') || ''
133
+ #Rye::Box.shell("kill", ['-9', pid]) if pid
134
+ @@agent_env.clear
135
+ nil
136
+ end
137
+
138
+ Rye.reload
139
+
140
+ begin
141
+ @@mutex.synchronize { # One thread only
142
+ start_sshagent_environment # Run this now
143
+ at_exit { end_sshagent_environment } # Run this before Ruby exits
144
+ }
145
+ rescue => ex
146
+ STDERR.puts "Error initializing the SSH Agent (is OpenSSL installed?):"
147
+ STDERR.puts ex.message
148
+ exit 1
149
+ end
150
+
151
+ end
152
+
153
+
154
+
155
+