Skip to content

Commit eba6ec7

Browse files
committed
added ftp LIST method
1 parent dc81b60 commit eba6ec7

File tree

1 file changed

+251
-1
lines changed

1 file changed

+251
-1
lines changed

source/requests/ftp.d

Lines changed: 251 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)