@@ -659,7 +659,254 @@ public struct FTPRequest {
659659 _response.code = code;
660660 return _response;
661661 }
662+ FTPResponse list (string uri = null ) {
663+ enforce( uri || _uri.host, " FTP URL undefined" );
664+ string response;
665+ ushort code;
666+
667+ _response = new FTPResponse;
668+ _contentReceived = 0 ;
669+ _method = " GET" ;
670+
671+ _response._startedAt = Clock .currTime;
672+ scope (exit) {
673+ _response._finishedAt = Clock .currTime;
674+ }
675+
676+ if ( uri ) {
677+ handleChangeURI(uri);
678+ }
679+
680+ _response.uri = _uri;
681+ _response.finalURI = _uri;
682+
683+ _controlChannel = _cm.get (_uri.scheme, _uri.host, _uri.port);
684+
685+ if ( ! _controlChannel ) {
686+ _controlChannel = new TCPStream();
687+ _controlChannel.bind(_bind);
688+ _controlChannel.connect(_uri.host, _uri.port, _timeout);
689+ if ( auto purged_connection = _cm.put(_uri.scheme, _uri.host, _uri.port, _controlChannel) )
690+ {
691+ debug (requests) tracef(" closing purged connection %s" , purged_connection);
692+ purged_connection.close();
693+ }
694+ _response._connectedAt = Clock .currTime;
695+ response = serverResponse(_controlChannel);
696+ _responseHistory ~= response;
662697
698+ code = responseToCode(response);
699+ debug (requests) tracef(" Server initial response: %s" , response);
700+ if ( code/ 100 > 2 ) {
701+ _response.code = code;
702+ return _response;
703+ }
704+ // Log in
705+ string user, pass;
706+ if ( _authenticator ) {
707+ user = _authenticator.userName();
708+ pass = _authenticator.password();
709+ }
710+ else {
711+ user = _uri.username.length ? _uri.username : " anonymous" ;
712+ pass = _uri.password.length ? _uri.password : " requests@" ;
713+ }
714+ debug (requests) tracef(" Use %s:%s%s as username:password" , user, pass[0 ], replicate(" -" , pass.length- 1 ));
715+
716+ code = sendCmdGetResponse(" USER " ~ user ~ " \r\n " , _controlChannel);
717+ if ( code/ 100 > 3 ) {
718+ _response.code = code;
719+ return _response;
720+ } else if ( code/ 100 == 3 ) {
721+ code = sendCmdGetResponse(" PASS " ~ pass ~ " \r\n " , _controlChannel);
722+ if ( code/ 100 > 2 ) {
723+ _response.code = code;
724+ return _response;
725+ }
726+ }
727+ }
728+ else {
729+ _response._connectedAt = Clock .currTime;
730+ }
731+
732+ code = sendCmdGetResponse(" PWD\r\n " , _controlChannel);
733+ string pwd;
734+ if ( code/ 100 == 2 ) {
735+ // like '257 "/home/testuser"'
736+ auto a = _responseHistory[$- 1 ].split();
737+ if ( a.length > 1 ) {
738+ pwd = a[1 ].chompPrefix(` "` ).chomp(` "` );
739+ }
740+ }
741+ scope (exit) {
742+ if ( pwd && _controlChannel && ! _useStreaming ) {
743+ sendCmdGetResponse(" CWD " ~ pwd ~ " \r\n " , _controlChannel);
744+ }
745+ }
746+
747+ auto path = dirName(_uri.path);
748+ if ( path != " /" ) {
749+ path = path.chompPrefix(" /" );
750+ }
751+ code = sendCmdGetResponse(" CWD " ~ path ~ " \r\n " , _controlChannel);
752+ if ( code/ 100 > 2 ) {
753+ _response.code = code;
754+ return _response;
755+ }
756+
757+ code = sendCmdGetResponse(" TYPE I\r\n " , _controlChannel);
758+ if ( code/ 100 > 2 ) {
759+ _response.code = code;
760+ return _response;
761+ }
762+
763+ code = sendCmdGetResponse(" SIZE " ~ baseName(_uri.path) ~ " \r\n " , _controlChannel);
764+ if ( code/ 100 == 2 ) {
765+ // something like
766+ // 213 229355520
767+ auto s = _responseHistory[$- 1 ].findSplitAfter(" " );
768+ if ( s.length ) {
769+ try {
770+ _contentLength = to! long (s[1 ]);
771+ } catch (ConvException) {
772+ debug (requests) trace(" Failed to convert string %s to file size" .format(s[1 ]));
773+ }
774+ }
775+ }
776+
777+ if ( _maxContentLength > 0 && _contentLength > _maxContentLength ) {
778+ throw new RequestException(" maxContentLength exceeded for ftp data" );
779+ }
780+
781+ code = sendCmdGetResponse(" PASV\r\n " , _controlChannel);
782+ if ( code/ 100 > 2 ) {
783+ _response.code = code;
784+ return _response;
785+ }
786+ // something like "227 Entering Passive Mode (132,180,15,2,210,187)" expected
787+ // in last response.
788+ // Cut anything between ( and )
789+ auto v = _responseHistory[$- 1 ].findSplitBefore(" )" )[0 ].findSplitAfter(" (" )[1 ];
790+
791+ TCPStream dataStream;
792+ try {
793+ dataStream = connectData(v);
794+ } catch (FormatException e) {
795+ error(" Failed to parse " , v);
796+ _response.code = 500 ;
797+ return _response;
798+ }
799+ scope (exit ) {
800+ if ( dataStream ! is null && ! _response._receiveAsRange.activated ) {
801+ dataStream.close();
802+ }
803+ }
804+
805+ _response._requestSentAt = Clock .currTime;
806+
807+ code = sendCmdGetResponse(" LIST " ~ baseName(_uri.path) ~ " \r\n " , _controlChannel);
808+ if ( code/ 100 > 1 && code/ 100 < 5 ) {
809+ _response.code = code;
810+ return _response;
811+ }
812+ if ( code/ 100 == 5 ) {
813+ dataStream.close();
814+ code = sendCmdGetResponse(" PASV\r\n " , _controlChannel);
815+ if ( code/ 100 > 2 ) {
816+ _response.code = code;
817+ return _response;
818+ }
819+ v = _responseHistory[$- 1 ].findSplitBefore(" )" )[0 ].findSplitAfter(" (" )[1 ];
820+ dataStream = connectData(v);
821+ code = sendCmdGetResponse(" NLST " ~ _uri.path ~ " \r\n " , _controlChannel);
822+ if ( code/ 100 > 1 ) {
823+ _response.code = code;
824+ return _response;
825+ }
826+ }
827+
828+ dataStream.readTimeout = _timeout;
829+ while ( true ) {
830+ auto b = new ubyte [_bufferSize];
831+ auto rc = dataStream.receive(b);
832+ if ( rc <= 0 ) {
833+ debug (requests) trace(" done" );
834+ break ;
835+ }
836+ debug (requests) tracef(" got %d bytes from data channel" , rc);
837+
838+ _contentReceived += rc;
839+ _response._responseBody.putNoCopy(b[0 .. rc]);
840+
841+ if ( _maxContentLength && _response._responseBody.length >= _maxContentLength ) {
842+ throw new RequestException(" maxContentLength exceeded for ftp data" );
843+ }
844+ if ( _useStreaming ) {
845+ debug (requests) trace(" ftp uses streaming" );
846+
847+ auto __maxContentLength = _maxContentLength;
848+ auto __contentLength = _contentLength;
849+ auto __contentReceived = _contentReceived;
850+ auto __bufferSize = _bufferSize;
851+ auto __dataStream = dataStream;
852+ auto __controlChannel = _controlChannel;
853+
854+ _response._contentLength = _contentLength;
855+ _response.receiveAsRange.activated = true ;
856+ _response.receiveAsRange.data.length = 0 ;
857+ _response.receiveAsRange.data = _response._responseBody.data;
858+ _response.receiveAsRange.read = delegate ubyte [] () {
859+ Buffer ! ubyte result;
860+ while (true ) {
861+ // check if we received everything we need
862+ if ( __maxContentLength > 0 && __contentReceived >= __maxContentLength )
863+ {
864+ throw new RequestException(" ContentLength > maxContentLength (%d>%d)" .
865+ format(__contentLength, __maxContentLength));
866+ }
867+ // have to continue
868+ auto b = new ubyte [__bufferSize];
869+ ptrdiff_t read;
870+ try {
871+ read = __dataStream.receive(b);
872+ }
873+ catch (Exception e) {
874+ throw new RequestException(" streaming_in error reading from socket" , __FILE__ , __LINE__ , e);
875+ }
876+
877+ if ( read > 0 ) {
878+ _response._contentReceived += read;
879+ __contentReceived += read;
880+ result.putNoCopy(b[0 .. read]);
881+ return result.data;
882+ }
883+ if ( read == 0 ) {
884+ debug (requests) tracef(" streaming_in: server closed connection" );
885+ __dataStream.close();
886+ code = responseToCode(serverResponse(__controlChannel));
887+ if ( code/ 100 == 2 ) {
888+ debug (requests) tracef(" Successfully received %d bytes" , _response._responseBody.length);
889+ }
890+ _response.code = code;
891+ sendCmdGetResponse(" CWD " ~ pwd ~ " \r\n " , __controlChannel);
892+ break ;
893+ }
894+ }
895+ return result.data;
896+ };
897+ debug (requests) tracef(" leave streaming get" );
898+ return _response;
899+ }
900+ }
901+ dataStream.close();
902+ response = serverResponse(_controlChannel);
903+ code = responseToCode(response);
904+ if ( code/ 100 == 2 ) {
905+ debug (requests) tracef(" Successfully received %d bytes" , _response._responseBody.length);
906+ }
907+ _response.code = code;
908+ return _response;
909+ }
663910 FTPResponse execute (Request r)
664911 {
665912 string method = r.method;
@@ -683,6 +930,9 @@ public struct FTPRequest {
683930 {
684931 return post ();
685932 }
933+ if ( method == " LIST" ) {
934+ return list ();
935+ }
686936 assert (0 , " Can't handle method %s for ftp request" .format(method));
687937 }
688938}
@@ -729,4 +979,4 @@ public struct FTPRequest {
729979// assert(unreliable_network || rq.format("%m|%h|%p|%P|%q|%U") == "GET|ftp.iij.ad.jp|21|/pub/FreeBSD/README.TXT||ftp://ftp.iij.ad.jp/pub/FreeBSD/README.TXT");
730980// assert(unreliable_network || rs.format("%h|%p|%P|%q|%U") == "ftp.iij.ad.jp|21|/pub/FreeBSD/README.TXT||ftp://ftp.iij.ad.jp/pub/FreeBSD/README.TXT");
731981// info("testing ftp - done.");
732- // }
982+ // }
0 commit comments