source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/net/ftp.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 21.3 KB
Line 
1#
2# = net/ftp.rb - FTP Client Library
3#
4# Written by Shugo Maeda <[email protected]>.
5#
6# Documentation by Gavin Sinclair, sourced from "Programming Ruby" (Hunt/Thomas)
7# and "Ruby In a Nutshell" (Matsumoto), used with permission.
8#
9# This library is distributed under the terms of the Ruby license.
10# You can freely distribute/modify this library.
11#
12# It is included in the Ruby standard library.
13#
14# See the Net::FTP class for an overview.
15#
16
17require "socket"
18require "monitor"
19
20module Net
21
22 # :stopdoc:
23 class FTPError < StandardError; end
24 class FTPReplyError < FTPError; end
25 class FTPTempError < FTPError; end
26 class FTPPermError < FTPError; end
27 class FTPProtoError < FTPError; end
28 # :startdoc:
29
30 #
31 # This class implements the File Transfer Protocol. If you have used a
32 # command-line FTP program, and are familiar with the commands, you will be
33 # able to use this class easily. Some extra features are included to take
34 # advantage of Ruby's style and strengths.
35 #
36 # == Example
37 #
38 # require 'net/ftp'
39 #
40 # === Example 1
41 #
42 # ftp = Net::FTP.new('ftp.netlab.co.jp')
43 # ftp.login
44 # files = ftp.chdir('pub/lang/ruby/contrib')
45 # files = ftp.list('n*')
46 # ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024)
47 # ftp.close
48 #
49 # === Example 2
50 #
51 # Net::FTP.open('ftp.netlab.co.jp') do |ftp|
52 # ftp.login
53 # files = ftp.chdir('pub/lang/ruby/contrib')
54 # files = ftp.list('n*')
55 # ftp.getbinaryfile('nif.rb-0.91.gz', 'nif.gz', 1024)
56 # end
57 #
58 # == Major Methods
59 #
60 # The following are the methods most likely to be useful to users:
61 # - FTP.open
62 # - #getbinaryfile
63 # - #gettextfile
64 # - #putbinaryfile
65 # - #puttextfile
66 # - #chdir
67 # - #nlst
68 # - #size
69 # - #rename
70 # - #delete
71 #
72 class FTP
73 include MonitorMixin
74
75 # :stopdoc:
76 FTP_PORT = 21
77 CRLF = "\r\n"
78 DEFAULT_BLOCKSIZE = 4096
79 # :startdoc:
80
81 # When +true+, transfers are performed in binary mode. Default: +true+.
82 attr_accessor :binary
83
84 # When +true+, the connection is in passive mode. Default: +false+.
85 attr_accessor :passive
86
87 # When +true+, all traffic to and from the server is written
88 # to +$stdout+. Default: +false+.
89 attr_accessor :debug_mode
90
91 # Sets or retrieves the +resume+ status, which decides whether incomplete
92 # transfers are resumed or restarted. Default: +false+.
93 attr_accessor :resume
94
95 # The server's welcome message.
96 attr_reader :welcome
97
98 # The server's last response code.
99 attr_reader :last_response_code
100 alias lastresp last_response_code
101
102 # The server's last response.
103 attr_reader :last_response
104
105 #
106 # A synonym for <tt>FTP.new</tt>, but with a mandatory host parameter.
107 #
108 # If a block is given, it is passed the +FTP+ object, which will be closed
109 # when the block finishes, or when an exception is raised.
110 #
111 def FTP.open(host, user = nil, passwd = nil, acct = nil)
112 if block_given?
113 ftp = new(host, user, passwd, acct)
114 begin
115 yield ftp
116 ensure
117 ftp.close
118 end
119 else
120 new(host, user, passwd, acct)
121 end
122 end
123
124 #
125 # Creates and returns a new +FTP+ object. If a +host+ is given, a connection
126 # is made. Additionally, if the +user+ is given, the given user name,
127 # password, and (optionally) account are used to log in. See #login.
128 #
129 def initialize(host = nil, user = nil, passwd = nil, acct = nil)
130 super()
131 @binary = true
132 @passive = false
133 @debug_mode = false
134 @resume = false
135 if host
136 connect(host)
137 if user
138 login(user, passwd, acct)
139 end
140 end
141 end
142
143 # Obsolete
144 def return_code
145 $stderr.puts("warning: Net::FTP#return_code is obsolete and do nothing")
146 return "\n"
147 end
148
149 # Obsolete
150 def return_code=(s)
151 $stderr.puts("warning: Net::FTP#return_code= is obsolete and do nothing")
152 end
153
154 def open_socket(host, port)
155 if defined? SOCKSsocket and ENV["SOCKS_SERVER"]
156 @passive = true
157 return SOCKSsocket.open(host, port)
158 else
159 return TCPSocket.open(host, port)
160 end
161 end
162 private :open_socket
163
164 #
165 # Establishes an FTP connection to host, optionally overriding the default
166 # port. If the environment variable +SOCKS_SERVER+ is set, sets up the
167 # connection through a SOCKS proxy. Raises an exception (typically
168 # <tt>Errno::ECONNREFUSED</tt>) if the connection cannot be established.
169 #
170 def connect(host, port = FTP_PORT)
171 if @debug_mode
172 print "connect: ", host, ", ", port, "\n"
173 end
174 synchronize do
175 @sock = open_socket(host, port)
176 voidresp
177 end
178 end
179
180 #
181 # WRITEME or make private
182 #
183 def set_socket(sock, get_greeting = true)
184 synchronize do
185 @sock = sock
186 if get_greeting
187 voidresp
188 end
189 end
190 end
191
192 def sanitize(s)
193 if s =~ /^PASS /i
194 return s[0, 5] + "*" * (s.length - 5)
195 else
196 return s
197 end
198 end
199 private :sanitize
200
201 def putline(line)
202 if @debug_mode
203 print "put: ", sanitize(line), "\n"
204 end
205 line = line + CRLF
206 @sock.write(line)
207 end
208 private :putline
209
210 def getline
211 line = @sock.readline # if get EOF, raise EOFError
212 line.sub!(/(\r\n|\n|\r)\z/n, "")
213 if @debug_mode
214 print "get: ", sanitize(line), "\n"
215 end
216 return line
217 end
218 private :getline
219
220 def getmultiline
221 line = getline
222 buff = line
223 if line[3] == ?-
224 code = line[0, 3]
225 begin
226 line = getline
227 buff << "\n" << line
228 end until line[0, 3] == code and line[3] != ?-
229 end
230 return buff << "\n"
231 end
232 private :getmultiline
233
234 def getresp
235 @last_response = getmultiline
236 @last_response_code = @last_response[0, 3]
237 case @last_response_code
238 when /\A[123]/
239 return @last_response
240 when /\A4/
241 raise FTPTempError, @last_response
242 when /\A5/
243 raise FTPPermError, @last_response
244 else
245 raise FTPProtoError, @last_response
246 end
247 end
248 private :getresp
249
250 def voidresp
251 resp = getresp
252 if resp[0] != ?2
253 raise FTPReplyError, resp
254 end
255 end
256 private :voidresp
257
258 #
259 # Sends a command and returns the response.
260 #
261 def sendcmd(cmd)
262 synchronize do
263 putline(cmd)
264 return getresp
265 end
266 end
267
268 #
269 # Sends a command and expect a response beginning with '2'.
270 #
271 def voidcmd(cmd)
272 synchronize do
273 putline(cmd)
274 voidresp
275 end
276 end
277
278 def sendport(host, port)
279 af = (@sock.peeraddr)[0]
280 if af == "AF_INET"
281 hbytes = host.split(".")
282 pbytes = [port / 256, port % 256]
283 bytes = hbytes + pbytes
284 cmd = "PORT " + bytes.join(",")
285 elsif af == "AF_INET6"
286 cmd = "EPRT |2|" + host + "|" + sprintf("%d", port) + "|"
287 else
288 raise FTPProtoError, host
289 end
290 voidcmd(cmd)
291 end
292 private :sendport
293
294 def makeport
295 sock = TCPServer.open(@sock.addr[3], 0)
296 port = sock.addr[1]
297 host = sock.addr[3]
298 resp = sendport(host, port)
299 return sock
300 end
301 private :makeport
302
303 def makepasv
304 if @sock.peeraddr[0] == "AF_INET"
305 host, port = parse227(sendcmd("PASV"))
306 else
307 host, port = parse229(sendcmd("EPSV"))
308 # host, port = parse228(sendcmd("LPSV"))
309 end
310 return host, port
311 end
312 private :makepasv
313
314 def transfercmd(cmd, rest_offset = nil)
315 if @passive
316 host, port = makepasv
317 conn = open_socket(host, port)
318 if @resume and rest_offset
319 resp = sendcmd("REST " + rest_offset.to_s)
320 if resp[0] != ?3
321 raise FTPReplyError, resp
322 end
323 end
324 resp = sendcmd(cmd)
325 if resp[0] != ?1
326 raise FTPReplyError, resp
327 end
328 else
329 sock = makeport
330 if @resume and rest_offset
331 resp = sendcmd("REST " + rest_offset.to_s)
332 if resp[0] != ?3
333 raise FTPReplyError, resp
334 end
335 end
336 resp = sendcmd(cmd)
337 if resp[0] != ?1
338 raise FTPReplyError, resp
339 end
340 conn = sock.accept
341 sock.close
342 end
343 return conn
344 end
345 private :transfercmd
346
347 def getaddress
348 thishost = Socket.gethostname
349 if not thishost.index(".")
350 thishost = Socket.gethostbyname(thishost)[0]
351 end
352 if ENV.has_key?("LOGNAME")
353 realuser = ENV["LOGNAME"]
354 elsif ENV.has_key?("USER")
355 realuser = ENV["USER"]
356 else
357 realuser = "anonymous"
358 end
359 return realuser + "@" + thishost
360 end
361 private :getaddress
362
363 #
364 # Logs in to the remote host. The session must have been previously
365 # connected. If +user+ is the string "anonymous" and the +password+ is
366 # +nil+, a password of <tt>user@host</tt> is synthesized. If the +acct+
367 # parameter is not +nil+, an FTP ACCT command is sent following the
368 # successful login. Raises an exception on error (typically
369 # <tt>Net::FTPPermError</tt>).
370 #
371 def login(user = "anonymous", passwd = nil, acct = nil)
372 if user == "anonymous" and passwd == nil
373 passwd = getaddress
374 end
375
376 resp = ""
377 synchronize do
378 resp = sendcmd('USER ' + user)
379 if resp[0] == ?3
380 resp = sendcmd('PASS ' + passwd)
381 end
382 if resp[0] == ?3
383 resp = sendcmd('ACCT ' + acct)
384 end
385 end
386 if resp[0] != ?2
387 raise FTPReplyError, resp
388 end
389 @welcome = resp
390 end
391
392 #
393 # Puts the connection into binary (image) mode, issues the given command,
394 # and fetches the data returned, passing it to the associated block in
395 # chunks of +blocksize+ characters. Note that +cmd+ is a server command
396 # (such as "RETR myfile").
397 #
398 def retrbinary(cmd, blocksize, rest_offset = nil) # :yield: data
399 synchronize do
400 voidcmd("TYPE I")
401 conn = transfercmd(cmd, rest_offset)
402 loop do
403 data = conn.read(blocksize)
404 break if data == nil
405 yield(data)
406 end
407 conn.close
408 voidresp
409 end
410 end
411
412 #
413 # Puts the connection into ASCII (text) mode, issues the given command, and
414 # passes the resulting data, one line at a time, to the associated block. If
415 # no block is given, prints the lines. Note that +cmd+ is a server command
416 # (such as "RETR myfile").
417 #
418 def retrlines(cmd) # :yield: line
419 synchronize do
420 voidcmd("TYPE A")
421 conn = transfercmd(cmd)
422 loop do
423 line = conn.gets
424 break if line == nil
425 if line[-2, 2] == CRLF
426 line = line[0 .. -3]
427 elsif line[-1] == ?\n
428 line = line[0 .. -2]
429 end
430 yield(line)
431 end
432 conn.close
433 voidresp
434 end
435 end
436
437 #
438 # Puts the connection into binary (image) mode, issues the given server-side
439 # command (such as "STOR myfile"), and sends the contents of the file named
440 # +file+ to the server. If the optional block is given, it also passes it
441 # the data, in chunks of +blocksize+ characters.
442 #
443 def storbinary(cmd, file, blocksize, rest_offset = nil, &block) # :yield: data
444 if rest_offset
445 file.seek(rest_offset, IO::SEEK_SET)
446 end
447 synchronize do
448 voidcmd("TYPE I")
449 conn = transfercmd(cmd, rest_offset)
450 loop do
451 buf = file.read(blocksize)
452 break if buf == nil
453 conn.write(buf)
454 yield(buf) if block
455 end
456 conn.close
457 voidresp
458 end
459 end
460
461 #
462 # Puts the connection into ASCII (text) mode, issues the given server-side
463 # command (such as "STOR myfile"), and sends the contents of the file
464 # named +file+ to the server, one line at a time. If the optional block is
465 # given, it also passes it the lines.
466 #
467 def storlines(cmd, file, &block) # :yield: line
468 synchronize do
469 voidcmd("TYPE A")
470 conn = transfercmd(cmd)
471 loop do
472 buf = file.gets
473 break if buf == nil
474 if buf[-2, 2] != CRLF
475 buf = buf.chomp + CRLF
476 end
477 conn.write(buf)
478 yield(buf) if block
479 end
480 conn.close
481 voidresp
482 end
483 end
484
485 #
486 # Retrieves +remotefile+ in binary mode, storing the result in +localfile+.
487 # If a block is supplied, it is passed the retrieved data in +blocksize+
488 # chunks.
489 #
490 def getbinaryfile(remotefile, localfile = File.basename(remotefile),
491 blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
492 if @resume
493 rest_offset = File.size?(localfile)
494 f = open(localfile, "a")
495 else
496 rest_offset = nil
497 f = open(localfile, "w")
498 end
499 begin
500 f.binmode
501 retrbinary("RETR " + remotefile, blocksize, rest_offset) do |data|
502 f.write(data)
503 yield(data) if block
504 end
505 ensure
506 f.close
507 end
508 end
509
510 #
511 # Retrieves +remotefile+ in ASCII (text) mode, storing the result in
512 # +localfile+. If a block is supplied, it is passed the retrieved data one
513 # line at a time.
514 #
515 def gettextfile(remotefile, localfile = File.basename(remotefile), &block) # :yield: line
516 f = open(localfile, "w")
517 begin
518 retrlines("RETR " + remotefile) do |line|
519 f.puts(line)
520 yield(line) if block
521 end
522 ensure
523 f.close
524 end
525 end
526
527 #
528 # Retrieves +remotefile+ in whatever mode the session is set (text or
529 # binary). See #gettextfile and #getbinaryfile.
530 #
531 def get(remotefile, localfile = File.basename(remotefile),
532 blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
533 unless @binary
534 gettextfile(remotefile, localfile, &block)
535 else
536 getbinaryfile(remotefile, localfile, blocksize, &block)
537 end
538 end
539
540 #
541 # Transfers +localfile+ to the server in binary mode, storing the result in
542 # +remotefile+. If a block is supplied, calls it, passing in the transmitted
543 # data in +blocksize+ chunks.
544 #
545 def putbinaryfile(localfile, remotefile = File.basename(localfile),
546 blocksize = DEFAULT_BLOCKSIZE, &block) # :yield: data
547 if @resume
548 begin
549 rest_offset = size(remotefile)
550 rescue Net::FTPPermError
551 rest_offset = nil
552 end
553 else
554 rest_offset = nil
555 end
556 f = open(localfile)
557 begin
558 f.binmode
559 storbinary("STOR " + remotefile, f, blocksize, rest_offset, &block)
560 ensure
561 f.close
562 end
563 end
564
565 #
566 # Transfers +localfile+ to the server in ASCII (text) mode, storing the result
567 # in +remotefile+. If callback or an associated block is supplied, calls it,
568 # passing in the transmitted data one line at a time.
569 #
570 def puttextfile(localfile, remotefile = File.basename(localfile), &block) # :yield: line
571 f = open(localfile)
572 begin
573 storlines("STOR " + remotefile, f, &block)
574 ensure
575 f.close
576 end
577 end
578
579 #
580 # Transfers +localfile+ to the server in whatever mode the session is set
581 # (text or binary). See #puttextfile and #putbinaryfile.
582 #
583 def put(localfile, remotefile = File.basename(localfile),
584 blocksize = DEFAULT_BLOCKSIZE, &block)
585 unless @binary
586 puttextfile(localfile, remotefile, &block)
587 else
588 putbinaryfile(localfile, remotefile, blocksize, &block)
589 end
590 end
591
592 #
593 # Sends the ACCT command. TODO: more info.
594 #
595 def acct(account)
596 cmd = "ACCT " + account
597 voidcmd(cmd)
598 end
599
600 #
601 # Returns an array of filenames in the remote directory.
602 #
603 def nlst(dir = nil)
604 cmd = "NLST"
605 if dir
606 cmd = cmd + " " + dir
607 end
608 files = []
609 retrlines(cmd) do |line|
610 files.push(line)
611 end
612 return files
613 end
614
615 #
616 # Returns an array of file information in the directory (the output is like
617 # `ls -l`). If a block is given, it iterates through the listing.
618 #
619 def list(*args, &block) # :yield: line
620 cmd = "LIST"
621 args.each do |arg|
622 cmd = cmd + " " + arg
623 end
624 if block
625 retrlines(cmd, &block)
626 else
627 lines = []
628 retrlines(cmd) do |line|
629 lines << line
630 end
631 return lines
632 end
633 end
634 alias ls list
635 alias dir list
636
637 #
638 # Renames a file on the server.
639 #
640 def rename(fromname, toname)
641 resp = sendcmd("RNFR " + fromname)
642 if resp[0] != ?3
643 raise FTPReplyError, resp
644 end
645 voidcmd("RNTO " + toname)
646 end
647
648 #
649 # Deletes a file on the server.
650 #
651 def delete(filename)
652 resp = sendcmd("DELE " + filename)
653 if resp[0, 3] == "250"
654 return
655 elsif resp[0] == ?5
656 raise FTPPermError, resp
657 else
658 raise FTPReplyError, resp
659 end
660 end
661
662 #
663 # Changes the (remote) directory.
664 #
665 def chdir(dirname)
666 if dirname == ".."
667 begin
668 voidcmd("CDUP")
669 return
670 rescue FTPPermError
671 if $![0, 3] != "500"
672 raise FTPPermError, $!
673 end
674 end
675 end
676 cmd = "CWD " + dirname
677 voidcmd(cmd)
678 end
679
680 #
681 # Returns the size of the given (remote) filename.
682 #
683 def size(filename)
684 voidcmd("TYPE I")
685 resp = sendcmd("SIZE " + filename)
686 if resp[0, 3] != "213"
687 raise FTPReplyError, resp
688 end
689 return resp[3..-1].strip.to_i
690 end
691
692 MDTM_REGEXP = /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/ # :nodoc:
693
694 #
695 # Returns the last modification time of the (remote) file. If +local+ is
696 # +true+, it is returned as a local time, otherwise it's a UTC time.
697 #
698 def mtime(filename, local = false)
699 str = mdtm(filename)
700 ary = str.scan(MDTM_REGEXP)[0].collect {|i| i.to_i}
701 return local ? Time.local(*ary) : Time.gm(*ary)
702 end
703
704 #
705 # Creates a remote directory.
706 #
707 def mkdir(dirname)
708 resp = sendcmd("MKD " + dirname)
709 return parse257(resp)
710 end
711
712 #
713 # Removes a remote directory.
714 #
715 def rmdir(dirname)
716 voidcmd("RMD " + dirname)
717 end
718
719 #
720 # Returns the current remote directory.
721 #
722 def pwd
723 resp = sendcmd("PWD")
724 return parse257(resp)
725 end
726 alias getdir pwd
727
728 #
729 # Returns system information.
730 #
731 def system
732 resp = sendcmd("SYST")
733 if resp[0, 3] != "215"
734 raise FTPReplyError, resp
735 end
736 return resp[4 .. -1]
737 end
738
739 #
740 # Aborts the previous command (ABOR command).
741 #
742 def abort
743 line = "ABOR" + CRLF
744 print "put: ABOR\n" if @debug_mode
745 @sock.send(line, Socket::MSG_OOB)
746 resp = getmultiline
747 unless ["426", "226", "225"].include?(resp[0, 3])
748 raise FTPProtoError, resp
749 end
750 return resp
751 end
752
753 #
754 # Returns the status (STAT command).
755 #
756 def status
757 line = "STAT" + CRLF
758 print "put: STAT\n" if @debug_mode
759 @sock.send(line, Socket::MSG_OOB)
760 return getresp
761 end
762
763 #
764 # Issues the MDTM command. TODO: more info.
765 #
766 def mdtm(filename)
767 resp = sendcmd("MDTM " + filename)
768 if resp[0, 3] == "213"
769 return resp[3 .. -1].strip
770 end
771 end
772
773 #
774 # Issues the HELP command.
775 #
776 def help(arg = nil)
777 cmd = "HELP"
778 if arg
779 cmd = cmd + " " + arg
780 end
781 sendcmd(cmd)
782 end
783
784 #
785 # Exits the FTP session.
786 #
787 def quit
788 voidcmd("QUIT")
789 end
790
791 #
792 # Issues a NOOP command.
793 #
794 def noop
795 voidcmd("NOOP")
796 end
797
798 #
799 # Issues a SITE command.
800 #
801 def site(arg)
802 cmd = "SITE " + arg
803 voidcmd(cmd)
804 end
805
806 #
807 # Closes the connection. Further operations are impossible until you open
808 # a new connection with #connect.
809 #
810 def close
811 @sock.close if @sock and not @sock.closed?
812 end
813
814 #
815 # Returns +true+ iff the connection is closed.
816 #
817 def closed?
818 @sock == nil or @sock.closed?
819 end
820
821 def parse227(resp)
822 if resp[0, 3] != "227"
823 raise FTPReplyError, resp
824 end
825 left = resp.index("(")
826 right = resp.index(")")
827 if left == nil or right == nil
828 raise FTPProtoError, resp
829 end
830 numbers = resp[left + 1 .. right - 1].split(",")
831 if numbers.length != 6
832 raise FTPProtoError, resp
833 end
834 host = numbers[0, 4].join(".")
835 port = (numbers[4].to_i << 8) + numbers[5].to_i
836 return host, port
837 end
838 private :parse227
839
840 def parse228(resp)
841 if resp[0, 3] != "228"
842 raise FTPReplyError, resp
843 end
844 left = resp.index("(")
845 right = resp.index(")")
846 if left == nil or right == nil
847 raise FTPProtoError, resp
848 end
849 numbers = resp[left + 1 .. right - 1].split(",")
850 if numbers[0] == "4"
851 if numbers.length != 9 || numbers[1] != "4" || numbers[2 + 4] != "2"
852 raise FTPProtoError, resp
853 end
854 host = numbers[2, 4].join(".")
855 port = (numbers[7].to_i << 8) + numbers[8].to_i
856 elsif numbers[0] == "6"
857 if numbers.length != 21 || numbers[1] != "16" || numbers[2 + 16] != "2"
858 raise FTPProtoError, resp
859 end
860 v6 = ["", "", "", "", "", "", "", ""]
861 for i in 0 .. 7
862 v6[i] = sprintf("%02x%02x", numbers[(i * 2) + 2].to_i,
863 numbers[(i * 2) + 3].to_i)
864 end
865 host = v6[0, 8].join(":")
866 port = (numbers[19].to_i << 8) + numbers[20].to_i
867 end
868 return host, port
869 end
870 private :parse228
871
872 def parse229(resp)
873 if resp[0, 3] != "229"
874 raise FTPReplyError, resp
875 end
876 left = resp.index("(")
877 right = resp.index(")")
878 if left == nil or right == nil
879 raise FTPProtoError, resp
880 end
881 numbers = resp[left + 1 .. right - 1].split(resp[left + 1, 1])
882 if numbers.length != 4
883 raise FTPProtoError, resp
884 end
885 port = numbers[3].to_i
886 host = (@sock.peeraddr())[3]
887 return host, port
888 end
889 private :parse229
890
891 def parse257(resp)
892 if resp[0, 3] != "257"
893 raise FTPReplyError, resp
894 end
895 if resp[3, 2] != ' "'
896 return ""
897 end
898 dirname = ""
899 i = 5
900 n = resp.length
901 while i < n
902 c = resp[i, 1]
903 i = i + 1
904 if c == '"'
905 if i > n or resp[i, 1] != '"'
906 break
907 end
908 i = i + 1
909 end
910 dirname = dirname + c
911 end
912 return dirname
913 end
914 private :parse257
915 end
916
917end
918
919
920# Documentation comments:
921# - sourced from pickaxe and nutshell, with improvements (hopefully)
922# - three methods should be private (search WRITEME)
923# - two methods need more information (search TODO)
Note: See TracBrowser for help on using the repository browser.