;;; -*- Mode:Lisp; Readtable:CL; Package:USER; Base:10; Patch-File:T -*-
;;; Patch file for System version 123.77
;;; Reason:
;;;  Miscellaneous User and Server FTP cleanups
;;;    - Random errors return :report-string in the FTP reply
;;;    - User FTP was not properly skipping continuations
;;;    - FTP Server Help now puts continuations on all line
;;; Written 27-Oct-87 22:44:29 by pld at site LMI Cambridge
;;; while running on Jack Flanders from band 2
;;; with Experimental System 123.76, Experimental Local-File 73.0, Experimental FILE-Server 22.0, Experimental Unix-Interface 11.0, Experimental Tape 18.0, Experimental KERMIT 34.0, Experimental ZMail 71.0, Experimental Lambda-Diag 15.0, microcode 1754, SDU Boot Tape 3.12, SDU ROM 8.



; From modified file DJ: L.NETWORK.IP-TCP.SERVER; FTP.LISP#68 at 27-Oct-87 22:51:26
#10R FTP#: 
(COMPILER-LET ((*PACKAGE* (GLOBAL:PKG-FIND-PACKAGE "FTP")))
  (COMPILER::PATCH-SOURCE-FILE "SYS: NETWORK; IP-TCP; SERVER; FTP  "


(defun ftp-retrieve-1 (state pn directory-p &aux os is)
  (setq is (cond (directory-p
		  (or (send pn :name) (setq pn (send pn :new-name :wild)))
		  (setq pn (fs:merge-pathname-components
			     pn
			     (ftpstate-pn-defaults state)
			     :default-type :wild
			     :default-version :wild))
		  (cond ((member directory-p '(:name-list :directory-list) :test #'eq)
			 (fs:directory-list-stream pn))
			(t (zwei:directory-input-stream pn :deleted))))
		 (t (open pn
			  :characters (eq (ftpstate-transfer-type state) :ascii)
			  :byte-size (ftpstate-byte-size state)))))
  (setq os (get-ftp-data-connection state
				    (send is :send-if-handles :truename)
				    (send is :send-if-handles :length)
				    :output))
  (if (streamp os)
      (ftp-send-data state is os directory-p)
    (ftp-reply state 425 "Can't open data connection")))


))

; From modified file DJ: L.NETWORK.IP-TCP.SERVER; FTP.LISP#68 at 27-Oct-87 22:51:46
#10R FTP#: 
(COMPILER-LET ((*PACKAGE* (GLOBAL:PKG-FIND-PACKAGE "FTP")))
  (COMPILER::PATCH-SOURCE-FILE "SYS: NETWORK; IP-TCP; SERVER; FTP  "


(defun ftp-store-1 (state pn mode &aux os is)
  "Write a file to the local TCP host (modulo chaosnet file servers)
    Mode can be any keyword acceptable for the :IF-EXISTS option to OPEN."
  (setq os (open pn
		 :characters (eq (ftpstate-transfer-type state) :ascii)
		 :byte-size (ftpstate-byte-size state)
		 :if-exists mode
		 
;;; not used, so commented out for release 3.0, should implement this
;;; in our local file system then use it.
;;;	:ESTIMATED-LENGTH (PROG1 (FTPSTATE-ESTIMATED-LENGTH STATE)  
;;;			      (SETF (FTPSTATE-ESTIMATED-LENGTH STATE) NIL))
		 
		 :direction :output))
  (setq is (get-ftp-data-connection state
				    (send os :send-if-handles :truename)
				    nil
				    :input))
  (if (streamp is) 
      (ftp-receive-data state is os)
    (ftp-reply state 425 "Can't open data connection")))



))

; From modified file DJ: L.NETWORK.IP-TCP.SERVER; FTP.LISP#68 at 27-Oct-87 22:52:46
#10R FTP#: 
(COMPILER-LET ((*PACKAGE* (GLOBAL:PKG-FIND-PACKAGE "FTP")))
  (COMPILER::PATCH-SOURCE-FILE "SYS: NETWORK; IP-TCP; SERVER; FTP  "


(defun get-ftp-data-connection (state name size mode)
  ;; Establish connection if necessary
  (when (eq (ftpstate-data-his-address state) :default)
    (setf (ftpstate-data-his-address state) (send (ftpstate-ctrl-stream state) :remote-address))
    (setf (ftpstate-data-his-port state) (send (ftpstate-ctrl-stream state) :remote-port)))

  (let ((success (or (eq (ftpstate-data-connection-method state) :passive)
		     (make-ftp-data-connection state name size mode))))

    ;; reset defaults for next data transfer
    (setf (ftpstate-data-connection-method state) :active) 
    (setf (ftpstate-data-his-address state) :default)

    (and success (ftpstate-data-stream state))))

))

; From modified file DJ: L.NETWORK.IP-TCP.SERVER; FTP.LISP#68 at 27-Oct-87 22:54:06
#10R FTP#: 
(COMPILER-LET ((*PACKAGE* (GLOBAL:PKG-FIND-PACKAGE "FTP")))
  (COMPILER::PATCH-SOURCE-FILE "SYS: NETWORK; IP-TCP; SERVER; FTP  "

(defun ftp-receive-data-function (state instream outstream &aux success)
  "Transfer the contents of net instream to local outstream"
  (global:condition-case-if ftp-catch-errors (err)
      (case (ftpstate-transfer-type state)
	(:ascii
	 (let ((ftp:*hash* nil)) 
	   (declare (special ftp:*hash*))
	   (with-open-stream (is (ftp:make-ascii-translating-input-stream instream))
	     (global:stream-copy-until-eof is outstream)))
	 (setq success t))
	((:image :logical-byte-size)
	 (case (ftpstate-byte-size state)
	   (8
	    (global:stream-copy-until-eof instream outstream))
	   (16
	    (let ((trans (make-8b-to-16b-translating-output-stream outstream)))
	      (global:stream-copy-until-eof instream trans)
	      (send trans :force-output))))
	 (setq success t))
	(otherwise (error "Bad transfer type in FTPSTATE.")))
    (fs:file-error (ftp-file-error-reply state err) nil)
    (error (ftp-reply state 451 "Local error in processing: ~A." (send err :report-string)) nil))
  (close outstream)
  (ftp-cleanup-data-connection state nil)
  (setf (ftpstate-data-transfer-in-progress state) nil)
  (if success (ftp-reply state 226 "Transfer complete.")))

))

; From modified file DJ: L.NETWORK.IP-TCP.SERVER; FTP.LISP#68 at 27-Oct-87 22:57:59
#10R FTP#: 
(COMPILER-LET ((*PACKAGE* (GLOBAL:PKG-FIND-PACKAGE "FTP")))
  (COMPILER::PATCH-SOURCE-FILE "SYS: NETWORK; IP-TCP; SERVER; FTP  "

(defun ftp-send-data-function (state instream outstream directory-p)
  (let ((translating-outstream
	  (case (ftpstate-transfer-type state)
	    (:ascii
	     (ftp:make-ascii-translating-output-stream outstream nil))
	    ((:image :logical-byte-size)
	     (case (ftpstate-byte-size state)
	       (8 outstream)
	       (16 (make-16b-to-8b-translating-output-stream outstream))))
	    (t
	     nil)))
	(completedp nil))
    (unwind-protect
	(if (not translating-outstream)
	    (ftp-reply state 504 "Unimplemented type ~A." (ftpstate-transfer-type state))
	  (global:condition-case-if ftp-catch-errors (err)
	      (progn (cond ((not directory-p)
			    (global:stream-copy-until-eof instream translating-outstream)
			    (or (eq translating-outstream outstream)
				(send translating-outstream :force-output)))
			   ((eq directory-p t)
			    (do ((entry))
				((null (setq entry (send instream :line-in))))
			      (send translating-outstream :string-out entry)
			      (terpri translating-outstream)))
			   ((eq directory-p :directory-list)
			    (do ((entry))
				((null (setq entry (send instream :entry))))
			      (prin1 entry translating-outstream)
			      (terpri translating-outstream)))
			   ((eq directory-p :name-list)
			    (send instream :entry) ;; GET RID OF DISK-SPACE-DESCRIPTION
			    (do ((entry))
				((null (setq entry (send instream :entry))))
			      (send translating-outstream :string-out
				    (send (car entry) :string-for-printing))
			      (terpri translating-outstream))))
		     (setq completedp t))
	    (fs:file-error (ftp-file-error-reply state err))
	    (error (ftp-reply state 451 "Local error in processing: ~A." (send err :report-string)) nil)))
      (close instream)
      (if completedp (send outstream :force-output))
      (ftp-cleanup-data-connection state nil)
      (setf (ftpstate-data-transfer-in-progress state) nil)
      (if completedp (ftp-reply state 226 "Transfer complete")))))

))

; From modified file DJ: L.NETWORK.IP-TCP.USER; FTP.LISP#32 at 28-Oct-87 00:01:49
#10R FTP#: 
(COMPILER-LET ((*PACKAGE* (GLOBAL:PKG-FIND-PACKAGE "FTP")))
  (COMPILER::PATCH-SOURCE-FILE "SYS: NETWORK; IP-TCP; USER; FTP  "

(defun getreply-line (expecteof)
  (labels ((peekc ()
		  (or (and *control* (send *control* :tyipeek))
		      (if expecteof nil (lostpeer))))
	   (getc ()
		 (let ((c (and *control* (send *control* :tyi))))
		   (or expecteof c (lostpeer))
		   (or (not c) (= c (sym lf))
		       (history-record-char (if (= c (sym cr)) #\return c)))
		   (cond ((or (not c) (not *verbose*)))
			 ((= c (sym lf)))
			 ((= c (sym cr))
			  (terpri))
			 ('else
			  (write-char c)))
		   c)))
    (prog (code j c weight continuationp)
	  (setq code 0 j 0)
       get-code
	  (cond ((not (= j 3)))
		((eq #\- (int-char (peekc)))
		 (setq continuationp t)
		 (getc)
		 (go get-crlf))
		('else
		 (go get-crlf)))
	  (setq c (getc))
	  (cond ((null c)
		 (return (values -1 nil)))
		((null (setq weight (digit-char-p c)))
		 (setq code nil)
		 (go get-crlf))
		('else
		 (setq code (+ (* code 10) weight))
		 (incf j)
		 (go get-code)))
       get-crlf
	  (setq c (getc))
	  (cond ((null c)
		 (return (values code continuationp)))
		((= c (sym cr))
		 (setq c (getc))
		 (or (eq c (sym lf))
		     (not *debug*)
		     (error "expecting LF after CR but got: ~@C" c))
		 (return (values code continuationp)))
		('else
		 (go get-crlf))))))

))

; From modified file DJ: L.NETWORK.IP-TCP.SERVER; FTP.LISP#68 at 28-Oct-87 00:08:24
#10R FTP#: 
(COMPILER-LET ((*PACKAGE* (GLOBAL:PKG-FIND-PACKAGE "FTP")))
  (COMPILER::PATCH-SOURCE-FILE "SYS: NETWORK; IP-TCP; SERVER; FTP  "

(defun ftp-help (state cmdstring &aux cmd)
  (cond (cmdstring
	 (setq cmd (ftp-cmd-from-string cmdstring))
	 (if (symbolp cmd)
	     (if (member cmd ftp-unimplemented-cmdlist :test #'eq)
		 (ftp-reply state 214 "~A unimplemented." cmd)
	       (ftp-reply state 214 "Syntax: ~A~A." cmd (get cmd 'ftp-help)))
	   (ftp-reply state 504 "Unknown command ~A." cmd)))
	(t
	 (ftp-reply state nil 
		    "214-The following commands are recognized (* =>'s unimplemented).")
	 
	 (zl:loop for l on ftp-cmdlist by #'(lambda (l) (nthcdr 6 l))
		  while l
		  do (ftp-reply state nil
				(apply #'string-append
				       "214-"
				       (zl:loop for x in l
						for count from 1 to 6
						collect (format nil "~A~4A" 
								(if (member x ftp-unimplemented-cmdlist
									      :test #'eq)
								    " *"
								  "  ")
								x))))
		  finally (ftp-reply state 214 "Direct comments to ~A."
				     (or (global:get-site-option :ftp-server-administrator)
					 *ftp-server-administrator*))))))

))
