FTP for all PB OS's

Share your advanced PureBasic knowledge/code with the community.
zapman*
Enthusiast
Enthusiast
Posts: 115
Joined: Wed Jun 02, 2004 10:17 pm
Location: New Caledonia (South Pacific)
Contact:

Post by zapman* »

I tested the two libraries (from Num3 and from DarkDragon) and got problems with the both, but the DarkDragon one was more reliable.

So I made a mix with the two ones and this one seems to work better. It is fully compatible with the "FTP_Library_Include_7b.pb" version and can still be use with the "FTP_Library_Example_7b.pb" demo program proposed by Terry at http://elfecc.no-ip.info/purebasic#FTP_Library .

Code: Select all

; FTP_Library_Include
; Original code by Num3
; Modified by TerryHough - Oct 20, 2004, May 05, 2005 Vs.0.7a
;                        - tested with zFTPServer
;                        -   DIR responds with "public" style listing 
;                        - May 16, 2005 Vs.0.7b
;                        - tested with the Broker server 
;                        -   DIR responds with "ftp ftp" style listing 
;                        - modified FTP_DirDisplay to handle both styles
; Modified by Zapman     - June 10, 2005 Vs Z1
;
#LFCR = Chr(13) + Chr(10)
#CRLF = Chr(10) + Chr(13)
;
#FTP_OK      =  1
#FTP_ERROR   =  0
#FTP_TimeOut = -1
;
;
Global FTP_Last_Message.s
Global PortID.l
Global Server$
;
;
Global ConnectionID.l
Global In.s
Global TotalBytesSent.l
Global TotalBytesRecd.l
;
Global CLog.s
;
#LongTimeOut  = 15000
#SmallTimeOut = 10000
;
#Block_size = 8192   ; 4096
;
;
Structure FTPFileInfo
  Name$
  Hour$
  Day.l
  Month$
  FSize.l
EndStructure
;
Procedure Minimum(a,b)
  If a<b
    ProcedureReturn a
  Else
    ProcedureReturn b
  EndIf
EndProcedure
;
Procedure SendNetworkString2(CID, message$)
  If UCase(Left(message$,4))="PASS"
    CLog + "<---> PASS ****" + #LFCR
  Else
    CLog + "<---> "+RemoveString(message$, #LFCR) + #LFCR
  EndIf
  Debug "<---> "+RemoveString(message$, #LFCR) + #LFCR
  ProcedureReturn SendNetworkString(CID, message$)
EndProcedure 
;
;
Procedure.s Wait(Connection, Timeout) 
  Delay(10)
  BufferLenght = 32000
  *Buffer = AllocateMemory(BufferLenght)
  If *Buffer > 0
    Text.s = ""
    Repeat
      t = ElapsedMilliseconds()
      Size = -1
      Repeat
        Result = NetworkClientEvent(Connection)
        If result <> 2 : Delay(5) : EndIf 
      Until Result = 2 Or ElapsedMilliseconds()-t > Timeout
      If Result = 2
        Size = ReceiveNetworkData(Connection, *Buffer, BufferLenght)
        If Size > 0
          Text.s + PeekS(*Buffer,Size)
        EndIf
      EndIf
      If size > 150
        Timeout = 1000
      Else
        Timeout = 50
      EndIf
    Until Size < 1
    If Text
      While Right(Text,1)=Chr(10) Or Right(Text,1)=Chr(13) Or Right(Text,1)=" "
        Text = Left(Text,Len(Text)-1)
      Wend 
      FreeMemory(*Buffer)
      CLog + ">---< "+Text + #LFCR
      Debug ">---< "+Text + #LFCR
      ProcedureReturn Text
    Else
      CLog + "Time Out" + #LFCR
      Debug "!!!  Time Out  !!!"
      FreeMemory(*Buffer)
      ProcedureReturn "TimeOut"
    EndIf
  EndIf
EndProcedure
;
Procedure.s PassiveIP(Text.s) 
  s = FindString(Text, "(", 1)+1 
  l = FindString(Text, ")", s)-s 
  Host.s = Mid(Text, s, l) 
  IP.s = StringField(Host, 1, ",")+"."+StringField(Host, 2, ",")+"."+StringField(Host, 3, ",")+"."+StringField(Host, 4, ",") 
  ProcedureReturn IP.s 
EndProcedure 
;
Procedure.l PassivePort(Text.s) 
  s = FindString(Text, "(", 1)+1 
  l = FindString(Text, ")", s)-s 
  Host.s = Mid(Text, s, l) 
  Port = Val(StringField(Host, 5, ","))*256+Val(StringField(Host, 6, ",")) 
  ProcedureReturn Port 
EndProcedure 
;
Procedure Int_FTP_PASV(Ftp, Log_Gadget)
  If Log_Gadget
    AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "PASV")
  EndIf
  If ConnectionID = 0
    ;
    SendNetworkString2(Ftp, "PASV" + #LFCR)
    ;
    In = Wait(Ftp, #SmallTimeOut)
    If In = "TimeOut"
      FTP_Last_Message = "Timed out"
      ProcedureReturn #FTP_TimeOut
    ElseIf In
      ; -- Analyze data --
      Select Left(In,3)

      Case "530" ; -- Error Parsing
        ProcedureReturn #FTP_ERROR
        
      Case "227" ; -- OK Parsing
        ; -- Get the PASV port assignment
        FTP_Last_Message + " [Port " + Str(PassivePort(In)) + "]"
        If Log_Gadget
          AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "---->" + Chr(10) + FTP_Last_Message)
        EndIf
        
        ConnectionID = OpenNetworkConnection(PassiveIP(In),PassivePort(In))

        If ConnectionID
          ProcedureReturn #FTP_OK
        Else
          ;MessageRequester("Debug","Failed to connect on PASV ClientPort: "+Str(ClientPort)+Chr(10)+Server$ + Chr(10) + PassiveIP(In),0)
          FTP_Last_Message + Chr(10) + "Unable to establish PASV connection"
          ProcedureReturn #FTP_ERROR
        EndIf
      EndSelect
    EndIf
  Else
    ProcedureReturn #FTP_OK
  EndIf 
EndProcedure

Procedure Int_FTP_PASV_CLOSE()
  CloseNetworkConnection(ConnectionID)
  ConnectionID = 0
EndProcedure

Procedure.s FTP_Last_Message()
  ProcedureReturn FTP_Last_Message
EndProcedure

Procedure FTP_PutFile(ProgBarGadgetID.l,mem,file_size)
  If ConnectionID
    TotalBytesSent = 0
    Repeat
      toSend.l = Minimum(file_size, #Block_size)
      ReadData(mem,toSend)
      Repeat
        result = SendNetworkData(ConnectionID, mem, toSend)
      Until result = toSend
      If result <> toSend
        FTP_Last_Message = "Data send failure"
        ProcedureReturn #FTP_ERROR
      EndIf
      ; Compute progress ----------------------------------
      TotalBytesSent + result
      If ProgBarGadgetID
        ; Display progress
        Progress.f = TotalBytesSent / file_size * 100
        SetGadgetState(ProgBarGadgetID,Progress)
        While WindowEvent() : Wend
      EndIf
      ; ---------------------------------------------------
      file_size - result ; Decrement by bytes just sent
    Until file_size = 0
    ProcedureReturn #FTP_OK
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure

Procedure FTP_GetFile(ProgBarGadgetID.l,mem,file_size)
  If ConnectionID
    TotalBytesRecd = 0
    Repeat
      event = NetworkClientEvent(ConnectionID)
      Select event
      Case 2
        toRecv.l = Minimum(file_size, #Block_size)
        result = ReceiveNetworkData(ConnectionID, mem, toRecv)
        WriteData(mem,result)
        ; Compute progress ----------------------------------
        TotalBytesRecd + result
        If ProgBarGadgetID
          ; Display progress
          Progress.f = TotalBytesRecd / file_size * 100
          SetGadgetState(ProgBarGadgetID,Progress)
          While WindowEvent() : Wend
        EndIf
        ; ---------------------------------------------------
        file_size - result  ; Decrement by bytes just received
      Case 0
        ; Nothing received from server yet
      Case 3
        ; A file was received - shouldn't have happened
        FTP_Last_Message = "Error - A file waiting message received"
        ProcedureReturn #FTP_ERROR
      EndSelect
    Until file_size = 0
    ProcedureReturn #FTP_OK
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure

Procedure FTP_DirList(Ftp)
  FTP_Last_Message = ""
  If ConnectionID
    For vr = 1 To 10
      In = Wait(ConnectionID, 1000)
      If In = "TimeOut"
        R226.s = Wait(Ftp, 1000) ; look for "226 - transfert completed"
        If Left(R226,3)="226" ; end of transfert, we won't wait more
          In = Wait(ConnectionID, 1000) ; we try just one more time to be sure to miss no data
          vr = 10 ;                          and get out the loop
          If In = "TimeOut"
            In = "" ; The directory is empty. We'll return #FTP_OK
          EndIf
        EndIf
      Else 
        vr = 10 ; to get out the loop
      EndIf
    Next
    If Left(R226,3)<>"226"
      Wait(Ftp, #LongTimeOut)
    EndIf
    If In = "TimeOut"
      FTP_Last_Message = "Timed out while reading catalog"
      ProcedureReturn #FTP_TimeOut
    Else
      FTP_Last_Message + Trim(In.s)
      ProcedureReturn #FTP_OK
    EndIf
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure
;
Procedure.s ExtractFileInfo(DirEntry$,FTPInfo)
  *FTPInfoT.FTPFileInfo = FTPInfo
  DirEntry$ = ReplaceString(DirEntry$, "public", "ftp ftp", 1) ; modify for zFTP
  DirEntry$ = ReplaceString(DirEntry$, "  ", " ", 0)
  DirEntry$ = Trim(ReplaceString(DirEntry$, "  ", " ", 0))
  Temp = FindString(DirEntry$, Chr(10), 1)
  If Temp = 0 : Temp = Len(DirEntry$)+1 : EndIf
  EndLine = Temp
  While Asc(Mid(DirEntry$,Temp,1))=32 Or Asc(Mid(DirEntry$,Temp,1))=10 Or Asc(Mid(DirEntry$,Temp,1))=13 : Temp - 1 : Wend
  Line$ = Trim(Left(DirEntry$,Temp))
  DirEntry$ = Right(DirEntry$, Len(DirEntry$) - EndLine)

  Temp = Len(Line$)
  For ct = 5 To 1 Step - 1 ; Examine data from end to start
    posf = Temp
    While Temp>0 And Asc(Mid(Line$,Temp,1))<>32 : Temp - 1 : Wend ; Look for space (separator)
    posd = Temp + 1
    Select ct
      Case 5
        *FTPInfoT\Name$ = Mid(Line$,posd,posf-posd + 1)
      Case 4
        *FTPInfoT\Hour$ = Mid(Line$,posd,posf-posd + 1)
      Case 3
        *FTPInfoT\Day   = Val(Mid(Line$,posd,posf-posd + 1))
      Case 2
        *FTPInfoT\Month$= Mid(Line$,posd,posf-posd + 1)
      Case 1
        *FTPInfoT\FSize = Val(Mid(Line$,posd,posf-posd + 1))
    EndSelect
    Temp - 1
    posf = Temp
  Next
  ProcedureReturn DirEntry$
EndProcedure 
;
Procedure.s FTP_DirDisplay(gadget)
  DirEntry$ = Trim(In)
  While DirEntry$
    DirEntry$ = ExtractFileInfo(DirEntry$,FTPInfo.FTPFileInfo)

    FileName$ = FTPInfo\Name$
    file_size = FTPInfo\FSize
    If FTPInfo\Name$<>"." And FTPInfo\Name$<>".."
      dir.s + FTPInfo\Name$ + Chr(13)
    EndIf 
    If file_size And FileName$ <> "" And gadget
      FileType$ = LCase(StringField(FileName$,2,"."))
      FileName$ = RemoveString(FileName$,"." + FileType$,1)
      AddGadgetItem(gadget, -1, Chr(10) + FileName$ + Chr(10) + FileType$ + Chr(10) + Str(file_size))
      SendMessage_(GadgetID(gadget), #LVM_ENSUREVISIBLE,  CountGadgetItems(gadget) - 1, 0) ; Center justify column
      While WindowEvent() : Wend
      FileType$ = ""
      FileName$ = ""
    EndIf
    ;
  Wend
  ProcedureReturn dir
EndProcedure

Procedure FTP_Init()
  If InitNetwork()
    FTP_Last_Message = "Successfully started the TCP/IP stack..."
    ProcedureReturn #FTP_OK
  Else
    FTP_Last_Message = "Unable to start TCP/IP stack..."
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure
;
Procedure FTP_Connect(Server.s, PortNo.l) ; // Returns FTPconnection
  PortID.l = OpenNetworkConnection(Server,PortNo)
  ConnectionID = 0
  CLog = ""
  If PortID
    In = Wait(PortID, #SmallTimeOut)
    If In = "TimeOut"
      FTP_Last_Message = "Timed out"
      ProcedureReturn #FTP_TimeOut
    ElseIf In
      FTP_Last_Message = ReplaceString(FTP_Last_Message(),"***",Server,1)
      ; -- Analyze Data --
      Select Left(In,3)
        ; -- OK Parsing
      Case "220"
        If Log_Gadget
          AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "---->" + Chr(10) + In)
        EndIf
        ProcedureReturn PortID
        ; -- Error Parsing
      Case "120"
      Case "421"
      EndSelect
    EndIf 
  Else
    FTP_Last_Message = "Unable to connect to specified server"
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure

Procedure FTP_Login(Ftp.l, User.s, Pass.s, Log_Gadget)
  If Ftp
    ; Online with the server
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "USER " + User.s)
    EndIf
    ;
    SendNetworkString2(Ftp,"USER " + User + #LFCR)
    ;
    Time.l = Date()
    Repeat 
      In = Wait(Ftp, #SmallTimeOut)
      If In = "TimeOut"
        FTP_Last_Message = "Timed out"
        ProcedureReturn #FTP_TimeOut
      ElseIf In
        FTP_Last_Message = In
        ; -- Analyze Data --
        Select Left(In,3)

          ; -- OK Parsing
        Case "200" ; TYPE A ACCEPTED
          If Log_Gadget
            AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "---->" + Chr(10) + In)
          EndIf
          In.s = ""
          ProcedureReturn #FTP_OK
        Case "230" ; LOGIN ACCEPTED
          If Log_Gadget
            AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "---->" + Chr(10) + In)
          EndIf
          SendNetworkString2(Ftp, "TYPE A" + #LFCR)
        Case "331"; Server requests a password
          If Log_Gadget
            AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "PASS ********")
          EndIf
          SendNetworkString2(Ftp,"PASS " + Pass + #LFCR)
        Case "530"; -- Error Parsing
          ProcedureReturn #FTP_ERROR
        Default
          Delay(10)
          If (Date()-Time)>#LongTimeOut
            FTP_Last_Message = "Timed out"
            ProcedureReturn #FTP_ERROR
          EndIf
        EndSelect
      EndIf 
    Until In = "" 
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure

Procedure FTP_LogOut(Ftp.l, Log_Gadget)
  If ConnectionID
    Int_FTP_PASV_CLOSE()
  EndIf
  ;
  If Ftp
    ; Online with the server
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "QUIT")
    EndIf
    ;
    SendNetworkString2(Ftp, "QUIT" + #LFCR)
    ;
    ProcedureReturn #FTP_OK
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure
;
Procedure FTP_Close(Ftp.l)
  If Ftp
    ; Online with the server
    If CloseNetworkConnection(Ftp)
      ;FTP_Last_Message="Successfully closed the specified ftp connection"
      ProcedureReturn #FTP_OK
    Else
      ;FTP_Last_Message="Connection previously closed or unable to close specified ftp connection"
      ProcedureReturn #FTP_ERROR
    EndIf
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure
;
Procedure Parse226 () ; Decode this message to extract the number of files from it.
  NbrOfFiles = 1 ; if the number of files is not found, we'll return 1 anyway
  found = 0
  If Left(In,3) = "226"
    While In
      pos = 5
      While Val(Mid(In,pos,1)) Or Mid(In,pos,1)="0" Or pos>Len(In)
        pos + 1
      Wend
      If pos > 5
        NbrOfFiles = Val(Mid(In,5,pos-5))
        FTP_Last_Message = Str(NbrOfFiles)+" files were found"
        In = ""
      Else
        pos = FindString(In,#LFCR,0)
        If pos
          In = Right(In, Len(In)-pos-Len(#LFCR)+1)
        Else
          In = ""
        EndIf 
      EndIf
    Wend
  EndIf
  ProcedureReturn NbrOfFiles
EndProcedure
;
Procedure FTP_Help(Ftp.l, ListArg$, Log_Gadget)
  If Ftp
    ; Attempt to create a PASV connection
    If Int_FTP_PASV(Ftp, Log_Gadget) <> #FTP_OK
      ProcedureReturn Result
    EndIf
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "LIST"  + " " + ListArg$)
    EndIf
    ;
    SendNetworkString2(Ftp, "HELP " + ListArg$ + #LFCR)
    ;
    Time.l = Date()
    Repeat
      In = Wait(Ftp, #SmallTimeOut)
      If In = "TimeOut"
        FTP_Last_Message = "Timed out"
        ProcedureReturn #FTP_TimeOut
      ElseIf In
        ; -- Analyze data --
        If Left(In,3) = "125" Or Left(In,3) = "150"; Opened the data connection for the directory list (125 = zFTPServer, 150 = Broker)
          In = ""
          Result = FTP_DirList(Ftp)
          Int_FTP_PASV_CLOSE()
          ProcedureReturn Result 
          ;
        ElseIf Left(In,3) = "451" Or Left(In,3) = "530"; -- Error Parsing
          Int_FTP_PASV_CLOSE()
          ProcedureReturn #FTP_ERROR
        Else
          If (Date()-Time)>#LongTimeOut
            Int_FTP_PASV_CLOSE()
            FTP_Last_Message = "Timed out"
            ProcedureReturn #FTP_ERROR
          EndIf
        EndIf
      EndIf
    Until In = ""
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure
;
Procedure FTP_List(Ftp.l, ListArg$, Log_Gadget)
  If Ftp
    ; Attempt to create a PASV connection
    If Int_FTP_PASV(Ftp, Log_Gadget) <> #FTP_OK
      ProcedureReturn Result
    EndIf
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "LIST"  + " " + ListArg$)
    EndIf
    ;
    SendNetworkString2(Ftp, "LIST"  + " " + ListArg$ + #LFCR)
    ;
    Time.l = Date()
    Repeat
      In = Wait(Ftp, #SmallTimeOut)
      If In = "TimeOut"
        FTP_Last_Message = "Timed out"
        ProcedureReturn #FTP_TimeOut
      ElseIf In
        ; -- Analyze data --
        If Left(In,3) = "125" Or Left(In,3) = "150"; Opened the data connection for the directory list (125 = zFTPServer, 150 = Broker)
          In = ""
          Result = FTP_DirList(Ftp)
          Int_FTP_PASV_CLOSE()
          ProcedureReturn Result 
          ;
        ElseIf Left(In,3) = "451" Or Left(In,3) = "530"; -- Error Parsing
          Int_FTP_PASV_CLOSE()
          ProcedureReturn #FTP_ERROR
        Else
          If (Date()-Time)>#LongTimeOut
            Int_FTP_PASV_CLOSE()
            FTP_Last_Message = "Timed out"
            ProcedureReturn #FTP_ERROR
          EndIf
        EndIf
      EndIf
    Until In = ""
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure

Procedure FTP_Retrieve(Ftp.l,filename.s,Destination.s,ProgBarGadgetID.l, Log_Gadget)
  If Ftp
    ; Online with the server
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "SIZE"  + " " + ListArg$)
    EndIf
    SendNetworkString2(Ftp,"SIZE " + filename + #LFCR) ; get the size of the file to download
    In = Wait(Ftp, #SmallTimeOut)
    If In = "TimeOut"
      FTP_Last_Message = "Timed out"
      ProcedureReturn #FTP_TimeOut
    Else
      If Left(In,3)="213"
        file_size.l = Val(Right(In, Len(In)-4))
      Else
        FTP_Last_Message = "File size has not been sent"
        ProcedureReturn #FTP_ERROR
      EndIf
    EndIf
    If file_size = 0
      FTP_Last_Message = "File size is null!"
      ProcedureReturn #FTP_ERROR
    EndIf
    ;
    mem = AllocateMemory(Minimum(file_size, #Block_size))
    If mem>0
      If CreateFile(1,Destination + "\" + filename) = 0
        FTP_Last_Message = "Unable to create file"
        FreeMemory(mem)
        ProcedureReturn #FTP_ERROR
      EndIf
    Else     
      ProcedureReturn #FTP_ERROR
    EndIf
    
    If ConnectionID = 0
      ; Attempt to create a PASV connection
      If Int_FTP_PASV(Ftp, Log_Gadget) <> #FTP_OK
        CloseFile(1)
        FreeMemory(mem)
        DeleteFile(Destination + "\" + filename)
        ProcedureReturn #FTP_ERROR
      EndIf
    EndIf
    
    starttime.l = Date()
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "RETR " + filename)
    EndIf
    ;
    SendNetworkString2(Ftp,"RETR " + filename + #LFCR)
    ;
    Time.l = Date()
    Repeat
      In = Wait(Ftp, #LongTimeOut)
      If In = "TimeOut"
        FTP_Last_Message = "Timed out"
        CloseFile(1)
        FreeMemory(mem)
        Int_FTP_PASV_CLOSE()
        ProcedureReturn #FTP_TimeOut
      ElseIf In
        FTP_Last_Message = In
        
        ; -- Analyze data --
        If Left(In,3) = "125" Or Left(In,3) = "150" Or Left(In,3) = "226"
          Result = FTP_GetFile(ProgBarGadgetID,mem,file_size)
          CloseFile(1)
          FreeMemory(mem)
          Int_FTP_PASV_CLOSE()
          If Result <>  #FTP_OK
            ProcedureReturn #FTP_ERROR
          EndIf
          If Left(In,3) <> "226" ; now, some server will send the #226 message and some will not
            In = Wait(Ftp, 1000) ; we put a small timeout to avoid to loose to much time
            If Left(In,3) = "226"
              now.l = Date()
              speed.f = 0
              If (now - starttime) > 0
                speed = (TotalBytesRecd / 1024) / (now - starttime)
              Else
                speed = TotalBytesRecd / 1024
              EndIf
              FTP_Last_Message + " -- " + Str(TotalBytesRecd) + " bytes (" + StrF(speed,2) + " Kb/sec)"
            EndIf
          EndIf
          ProcedureReturn #FTP_OK

          ; -- Error Parsing
        ElseIf Left(In,3) = "425" Or Left(In,3) = "426" Or Left(In,3) = "501" Or Left(In,3) = "550" ; Unable to open the connection
          ; "426" means : "Data connection closed abnormally"
          CloseFile(1)
          FreeMemory(mem)
          Int_FTP_PASV_CLOSE()
          FTP_Last_Message = "Data connection closed abnormally"
          ProcedureReturn #FTP_ERROR
        Else
          Delay(10)
          If (Date()-Time)>#LongTimeOut
            CloseFile(0)
            FreeMemory(mem)
            Int_FTP_PASV_CLOSE()
            FTP_Last_Message = "Timed out"
            ProcedureReturn #FTP_ERROR
          EndIf
        EndIf
      EndIf 
    Until In = ""
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure

Procedure FTP_CurrentDir(Ftp.l, Log_Gadget)
  If Ftp
    ; Online with server
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "PWD")
    EndIf
    ;
    SendNetworkString2(Ftp, "PWD" + #LFCR)
    ;
    In = Wait(Ftp, #SmallTimeOut)
    If In = "TimeOut"
      FTP_Last_Message = "Timed out"
      ProcedureReturn #FTP_TimeOut
    ElseIf In
  
      FTP_Last_Message = In
      
      ; -- Analyze data --
      Select Left(In,3)
        ; -- OK Parsing
      Case "257"
        ProcedureReturn #FTP_OK
        ; -- Error Parsing
      Case "530"
        ProcedureReturn #FTP_ERROR
      EndSelect
    EndIf 
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure

Procedure FTP_ChangeDir(Ftp.l, Dirname.s, Log_Gadget)
  If Ftp
    ; Online with the server
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "CWD " + Dirname)
    EndIf
    ;
    SendNetworkString2(Ftp, "CWD " + Dirname + #LFCR)
    ;
    In = Wait(Ftp, #SmallTimeOut)
    If In = "TimeOut"
      FTP_Last_Message = "Timed out"
      ProcedureReturn #FTP_TimeOut
    ElseIf In
      FTP_Last_Message = In
      
      ; -- Analyze data --
      Select Left(In,3)
        
      Case "250" ; -- OK Parsing
        ProcedureReturn #FTP_OK
        
      Case "550" ; -- Error Parsing
        ProcedureReturn #FTP_ERROR
      EndSelect
    EndIf 
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure

Procedure FTP_Store(Ftp.l, filename.s, ProgBarGadgetID.l, Log_Gadget)
  ConnectionID = 0
  If Ftp
    ; Online with the server
    
    file_size.l = FileSize(filename)
    ;
    If OpenFile(0,filename) = 0
      FTP_Last_Message = "Unable to open file"
      ProcedureReturn #FTP_ERROR
    EndIf
    ;
    mem = AllocateMemory(Minimum(file_size, #Block_size))
    If mem<1
      FTP_Last_Message = "Unable to allocate memory"
      CloseFile(0)
      ProcedureReturn #FTP_ERROR
    EndIf
    
    If ConnectionID = 0
      ; Attempt to create PASV connection
      If Int_FTP_PASV(Ftp, Log_Gadget) = 0
        CloseFile(0)
        FreeMemory(mem)
        ProcedureReturn #FTP_ERROR
      EndIf
    EndIf
    
    starttime.l = Date()
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "STOR " + GetFilePart(filename))
    EndIf
    ;
    SendNetworkString2(FTP, "STOR " + GetFilePart(filename) + #LFCR)
    ;
    Time.l = Date()
    Repeat 
      In = Wait(Ftp, #SmallTimeOut)
      If In = "TimeOut"
        FTP_Last_Message = "Timed out"
        CloseFile(0)
        FreeMemory(mem)
        Int_FTP_PASV_CLOSE()
        ProcedureReturn #FTP_TimeOut
      ElseIf In
    
        FTP_Last_Message = In

        ; -- Analyze data --
        If Left(In,3) = "125" Or Left(In,3) = "150" Or Left(In,3) = "226"

          Result = FTP_PutFile(ProgBarGadgetID,mem,file_size)
          CloseFile(0)
          FreeMemory(mem)
          Int_FTP_PASV_CLOSE()
          If Result <> #FTP_OK
            ProcedureReturn #FTP_ERROR
          EndIf
          If Left(In,3) <> "226"
            In = Wait(Ftp, #SmallTimeOut)
            If Left(In,3) = "226"
              now.l = Date()
              speed.f = 0
              If (now - starttime) > 0
                speed = (TotalBytesSent / 1024) / (now - starttime)
              Else
                speed = TotalBytesSent / 1024
              EndIf
              FTP_Last_Message + " -- " + Str(TotalBytesSent) + " bytes (" + StrF(speed,2) + " Kb/sec)"
            EndIf
          EndIf
          ProcedureReturn #FTP_OK
          ; -- Error Parsing
        ElseIf Left(In,3) = "501" Or Left(In,3) = "550"
          CloseFile(0)
          FreeMemory(mem)
          Int_FTP_PASV_CLOSE()
          ProcedureReturn #FTP_ERROR
        Else
          Delay(10)
          If (Date()-Time)>#LongTimeOut
            CloseFile(0)
            FreeMemory(mem)
            Int_FTP_PASV_CLOSE()
            FTP_Last_Message = "Timed out"
            ProcedureReturn #FTP_ERROR
          EndIf
        EndIf
      EndIf 
    Until In = ""
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure

Procedure FTP_MakeDir(Ftp.l, Dirname.s, Log_Gadget)
  If Ftp
    ; Online with the server
    If Dirname = ""
      ProcedureReturn #FTP_ERROR
    EndIf
    ;
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "MKD " + Dirname)
    EndIf
    ;
    SendNetworkString2(Ftp, "MKD " + Dirname + #LFCR)
    ;
    In = Wait(Ftp, #SmallTimeOut)
    If In = "TimeOut"
      FTP_Last_Message = "Timed out"
      ProcedureReturn #FTP_TimeOut
    ElseIf In
      FTP_Last_Message = In
      
      ; -- Analyze data --
      Select Left(In,3)
        
      Case "257" ; -- OK Parsing
        In.s = ""
        ProcedureReturn #FTP_OK
      Case "500" ; Access denied, already exists
        In.s = ""
        ProcedureReturn #FTP_OK
      Case "550"
        In.s = ""
        ProcedureReturn #FTP_OK
        ; -- Error Parsing
      Case "530"
        ProcedureReturn #FTP_ERROR
      EndSelect
    EndIf 
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure
;
Procedure FTP_RemoveDir(Ftp.l, Dirname.s, Log_Gadget)
  If Ftp
    ; Online with the server
    If Dirname = ""
      ProcedureReturn #FTP_ERROR
    EndIf
    ;
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "RMD " + Dirname)
    EndIf
    ;
    SendNetworkString2(Ftp, "RMD " + Dirname + #LFCR)
    ;
    In = Wait(Ftp, #SmallTimeOut)
    If In = "TimeOut"
      FTP_Last_Message = "Timed out"
      ProcedureReturn #FTP_TimeOut
    ElseIf In
      FTP_Last_Message = In
      
      ; -- Analyze data --
      Select Mid(In,1,3)
        
      Case "250" ; -- OK Parsing
        ProcedureReturn #FTP_OK
      Case "500"  ; Access denied, directory not empty
        ProcedureReturn #FTP_OK
        ; -- Error Parsing
      Case "550"
        ProcedureReturn #FTP_ERROR
      EndSelect
    EndIf 
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure

Procedure FTP_Delete(Ftp.l, filename.s, Log_Gadget)
  If Ftp
    ; Online with the server
    If filename = ""
      ProcedureReturn #FTP_ERROR
    EndIf
    ;
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "DELE " + filename)
    EndIf
    ;
    SendNetworkString2(Ftp, "DELE " + filename + #LFCR)
    ;
    In = Wait(Ftp, #SmallTimeOut)
    If In = "TimeOut"
      FTP_Last_Message = "Timed out"
      ProcedureReturn #FTP_TimeOut
    ElseIf In
      FTP_Last_Message = In
      
      ;{ -- Analyze data --
      Select Left(In,3)

      Case "250" ; -- OK Parsing
        ; Note: Server responds 250 if the file deleted successful or doesn't exist
        ProcedureReturn #FTP_OK
        ; -- Error Parsing
      Case "550"
        ; Note: zFTPServer responds 500 if the file doesn't exist, still OK
        ProcedureReturn #FTP_OK
      EndSelect
    EndIf 
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure

Procedure FTP_Rename(Ftp.l, filename.s, newname.s, Log_Gadget)
  If Ftp
    ; Online with the server
    If filename = ""
      ProcedureReturn #FTP_ERROR
    EndIf

    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + "Rename " + filename + "to " + newname)
    EndIf
    ;
    SendNetworkString2(Ftp, "RNFR " + filename + #LFCR)
    ;
    In = Wait(Ftp, #SmallTimeOut)
    If In = "TimeOut"
      FTP_Last_Message = "Timed out"
      ProcedureReturn #FTP_TimeOut
    ElseIf In
      FTP_Last_Message = In
      
      ;{ -- Analyze data --
      Select Left(In,3)
        ; -- OK Parsing
      Case "350"
        ; Server responds 350 if the file to be renamed exists
        ; and requests the new pathname
        SendNetworkString2(Ftp, "RNTO " + newname + #LFCR)
      Case "250"
        ; Server responds 250 if the file is successfully renamed
        ProcedureReturn #FTP_OK
        ; -- Error Parsing
      Case "500"  ; Access denied, file already exist
        ProcedureReturn #FTP_ERROR
      Case "550"
        ProcedureReturn #FTP_ERROR
      EndSelect
    EndIf 
  Else
    ProcedureReturn #FTP_ERROR
  EndIf
EndProcedure
Last edited by zapman* on Tue Jun 14, 2005 10:12 am, edited 2 times in total.
Don't try - DO it !
Num3
PureBasic Expert
PureBasic Expert
Posts: 2812
Joined: Fri Apr 25, 2003 4:51 pm
Location: Portugal, Lisbon
Contact:

Post by Num3 »

Pretty cool guys !
zapman*
Enthusiast
Enthusiast
Posts: 115
Joined: Wed Jun 02, 2004 10:17 pm
Location: New Caledonia (South Pacific)
Contact:

Post by zapman* »

The code above has been updated.
To try to know the size of a file by parsing the answer to "LIST" is a pretty bad idea because there is a lot of different possible forms for this answer (it's not normalized at all).
This new version use the "SIZE" command which is reliable.
Don't try - DO it !
TerryHough
Enthusiast
Enthusiast
Posts: 781
Joined: Fri Apr 25, 2003 6:51 pm
Location: NC, USA
Contact:

Post by TerryHough »

@zapman

Glad to see someone making use of the FTP for all OS's examples.

You said that you had problems using the FTP_Libary_Include_7b.pb. I
would appreciate knowing the exact problems you had, if you can recall.

There has been only one reported problem, that I still haven't solved,
where the expected 227 response to the Init_FTP_PASV is issued by the
server but is never received by the client while using the internet. The
problem does not occur when used across an intranet though. I am
baffled, so far.

You should notice that FTP_Library_Include_7b does not have a single
Delay() command or any other type of wait other than "time out" code for
each necessary command. The "client to server" FTP conversation
should not ever need one when implemented with the PB Network
commands. This was accomplished using more code lines than would
otherwise have been necessary.

DarkDragon's code makes extensive use of delays and a custom wait
procedure. His excellent adaptation of Num3's original code certainly
works but does create a slower FTP conversation.

BTW, I ran my FTP_Example_7b using your updated code. It runs, but
several of the Server's responses are not returned. That could potentially
create a misdirection for the client.
parsing the answer to "LIST" is a pretty bad idea....
This new version use the "SIZE" command which is reliable.
I agree... but, would you believe it, several FTP servers I have tested do
not recognize the SIZE command. Even the Windows FTP command line
program does not include it (but it can be issued using the QUOTE
command). So, I left it out of my examples although I do use it when I
know the server supports it.

Keep up the good work. I hope we can generate a complete FTP client
handling with this thread.

Terry
zapman*
Enthusiast
Enthusiast
Posts: 115
Joined: Wed Jun 02, 2004 10:17 pm
Location: New Caledonia (South Pacific)
Contact:

Post by zapman* »

I'm happy to get an answer from you, Terry. Here is a list of some problems encountered with your 7b version :
- In FTP_List, your version did wait for the 226 message before switch into passive mode an read the data. Unfortunately, some servers don't send the 226 message if the passive mode is not initiated. Then nothing happens !
- I did test your version with 5 servers. The format of the LIST answer was not compatible with your way of finding the filesize in all the cases. Neither it was with the DarkDargon way of doing it. With my servers, the "ExtractFileInfo" makes that stuff correctly but I don't know if it's the case for you (I would be interrested to know that!). If you got problems with the SIZE command, the good way is perhaps to test it at first and then, if it is not supported, to try to extract info from the LIST answer.
- The answer for LIST is sometimes sent into several pieces. In that case, your version only gets the first one
- In FTP_Retrieve and FTP-Store functions, your version doesn't free memory and doesn't close the opened file if an error occure (bad boy !!)
- You "Repeat/Forever" system, used in several functions, doesn't examine all possibilities and freeze in several cases of errors.
- In many cases, you test the result of function's calls as "If FTP_Function(...)". If the function's call returns a #FTP_TimeOut error, it will be interpreted in the same way as if it returns #FTP_OK. I replaced that type of test by "If FTP_Function(...) = #FTP_OK" to be sure of what happens.
- Your code has a lot of similar sequences. The "wait" idea from DarkDragon allows to short down and to simplify it. There is probably still a lot to do to simplify it and make it more readable.
- in the INT_FTP_PASV function you get the ConnectionID by doing

Code: Select all

ConnectionID = OpenNetworkConnection(Server$,ClientID).
That's bad and does'nt work with some servers. You should not use the server name but the server IP. You must do (as DarkDragon)

Code: Select all

ConnectionID = OpenNetworkConnection(PassiveIP(In),PassivePort(In))
- I've probably forgot many other corrections

To conclude, I had to spend a lot of time to make your code OK for my servers but I'm not sure, at the moment, that it is totally perfect.

The DarkDragon is easy to read and to use but it does'nt verify a lot af situations and does'nt return informations when there is a problem. It's quite good for a personnal need. It's not for a professionnal one.

Regarding the Delay() used in my Wait function, I don't agree that it can slow down the communication in any maner. But you're code can slow down the machine because there is not any delay in many loops and it will absorb all the CPU power

Regarding the Server's responses not returned, I thinck that, one more time, your way of managing it was'nt the good one. The good way would be to put only one AddGadgetItem into the "SendNetworkString2" function and just another one into the "wait" function and all communications would be perfectly reported. I did'nt correct that because I don't use the (very interresting) gadget managment abilities.

Anyway, thank you very much for sharing your code.

Sorry for my very poor english, I'm a froggy.
Don't try - DO it !
TerryHough
Enthusiast
Enthusiast
Posts: 781
Joined: Fri Apr 25, 2003 6:51 pm
Location: NC, USA
Contact:

Post by TerryHough »

Thanks for reply zapman*
zapman* wrote:- In FTP_List, your version did wait for the 226 message before switch into passive mode an read the data. Unfortunately, some servers don't send the 226 message if the passive mode is not initiated. Then nothing happens !
Well not quite... but first, remember that the FTP_Library_Include does
REQUIRE a server compatible with PASV port assignments. The sparse
documentaion embedded in the code may not say that, but Num3 and I
both have mentioned that fact in this thread. It does not work except in
PASV mode.

FTP_List requests the server to enter PASV mode and return the assigned
data port (the expected 227 message). Then it sends the LIST command
on the normal port and waits for the expected 125 or 150 command that
indicates the server is opening the PASV port to send the directory listing
data on. Then it calls another procedure to receive the actual data via
the assigned PASV port. When that process is completed, the server
sends the expected 226 reply on the normal port.
- I did test your version with 5 servers.
1) The format of the LIST answer was not compatible with your way of finding the filesize in all the cases. Neither it was with the DarkDargon way of doing it.
2) With my servers, the "ExtractFileInfo" makes that stuff correctly but I don't know if it's the case for you (I would be interrested to know that!).
3) If you got problems with the SIZE command, the good way is perhaps to test it at first and then, if it is not supported, to try to extract info from the LIST answer.
4)The answer for LIST is sometimes sent into several pieces. In that case, your version only gets the first one
Thanks for your testing time and this notice of the problems you hit.
1) Agreed. Seems every server does something slightly different. Since
I intended my version of FTP_Library_Include to be a bit of a tutorial, I
let the code show some of the differences.

2) Nice! And I modified your idea to create a generic handling in
FTP_DirDisplay() that works with the 8 servers I am testing with. Thanks.

3) I have added my FTP_Size() function to the FTP_Library_Include in
Vs. 0.7c. Most servers seem to support the SIZE command now. And it
certainly saves time. However, I need the information returned by the
LIST command for other purposes, so I must be able to retrieve and
interpret it.

4) That could happen depending on the size of the directory listing and the
capacity of the string defined to receive it. So far in my testing, the code
receives and handles well over 100 directory entries. I haven't tried
testing extremely large listings.
- In FTP_Retrieve and FTP-Store functions, your version doesn't free memory and doesn't close the opened file if an error occure (bad boy !!)
Mea culpa! Fifty lashes with a wet noodle. I will fix that.
- You "Repeat/Forever" system, used in several functions, doesn't examine all possibilities and freeze in several cases of errors.
Agreed. I find new responses to FTP commands with each new server
I test with. For example: If you atempt DELE a file that doesn't exist
on the server, some will respond with a 500 message, others with a 550.
So, as I test with more servers, I will be adding more response handling.
The server authors don't seem to comply/agree with the RFC's suggested
response codes in many case. Right not, if a test fails, the first place I
look is for an unexpected server response.
- In many cases, you test the result of function's calls as "If FTP_Function(...)". If the function's call returns a #FTP_TimeOut error, it will be interpreted in the same way as if it returns #FTP_OK. I replaced that type of test by "If FTP_Function(...) = #FTP_OK" to be sure of what happens.
Agreed. :D The FTP_Library_Example was intended as a guide, and
the potential bugs are completely free.
- Your code has a lot of similar sequences. The "wait" idea from DarkDragon allows to short down and to simplify it. There is probably still a lot to do to simplify it and make it more readable.
That was by design. I wanted the code to exhibit the steps necessary
in each command so that it could be a bit of a tutorial. There is quite a
lot of similar sequences that could be combined into one procedure call,
etc.
- in the INT_FTP_PASV function you get the ConnectionID by doing

Code: Select all

ConnectionID = OpenNetworkConnection(Server$,ClientID).
That's bad and does'nt work with some servers. You should not use the server name but the server IP. You must do (as DarkDragon)

Code: Select all

ConnectionID = OpenNetworkConnection(PassiveIP(In),PassivePort(In))
Correct. However, I was working with a server that is behind a
router/firewall and it returns only the internal IP address (A server
problem) and I forgot to change the code back before posting it. Actually,
the code works this way unless the PASV port is at some location other
than that which is resolved from the "server name" URL.
Regarding the Delay() used in my Wait function, I don't agree that it can slow down the communication in any maner. But you're code can slow down the machine because there is not any delay in many loops and it will absorb all the CPU power
I see that you are using much shorter delays than DarkDragon and they
will have negligible affect on the commuication process.

And you are correct that my method will consume the system while it
is operating.

My point was that it is not necessary to have the delays embedded in
the functions themselves. The PB Network commands work quite well
for me without them. However, to provide some available cycles to
other processes on the system, I do normally use some delays in the
calling program (not done in the FTP_Library_Example program).

Version 0.7c of the discussed code (current version) is available at
http://elfecc.no-ip.info/purebasic#FTP_Library
last updated June 15, 2005

Here are some of the changes:
- June 15, 2005 Vs. 0.7c
- added external log file handling
- prepare for future PORT command (not working yet),
- to make PASV optional instead of required
- made FTP_DirDisplay more generic (credit to zapman*)
- modify FTP_DirDisplay to provide two display methods
- added FTP_Size command (credit to zapman*)
- captured more server (unique) responses
- expanded failure message explanations

Terry

Continue in this thread for info about the latest version.
Last edited by TerryHough on Wed Jun 29, 2005 10:27 pm, edited 1 time in total.
zapman*
Enthusiast
Enthusiast
Posts: 115
Joined: Wed Jun 02, 2004 10:17 pm
Location: New Caledonia (South Pacific)
Contact:

Post by zapman* »

Some more remarks :
Because some servers don't really follow standards, I had to change Int_FTP_PASV as this:

Code: Select all

Procedure FTP_PASV(Ftp, Command.s, Log_Gadget)
  If Log_Gadget
    AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + Command)
  EndIf
  If ConnectionID = 0
    ;
    SendNetworkString2(Ftp, Command + #LFCR)
    ;
    In = Wait(Ftp, #SmallTimeOut)
    If In = "TimeOut"
      FTP_Last_Message = "Timed out"
      ProcedureReturn #FTP_TimeOut
    ElseIf In
      ; -- Analyze data --
      Select Left(In,3)

      Case "530" ; -- Error Parsing
        ProcedureReturn #FTP_ERROR
        
      Case "227" ; -- OK Parsing
        ; -- Get the PASV port assignment
        FTP_Last_Message + " [Port " + Str(PassivePort(In)) + "]"
        If Log_Gadget
          AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "---->" + Chr(10) + FTP_Last_Message)
        EndIf
        
        ConnectionID = OpenNetworkConnection(PassiveIP(In),PassivePort(In))

        If ConnectionID
          ProcedureReturn #FTP_OK
        Else
          ;MessageRequester("Debug","Failed to connect on PASV ClientPort: "+Str(ClientPort)+Chr(10)+Server$ + Chr(10) + PassiveIP(In),0)
          FTP_Last_Message + Chr(10) + "Unable to establish PASV connection"
          ProcedureReturn #FTP_ERROR
        EndIf
      EndSelect
    EndIf
  Else
    ProcedureReturn #FTP_OK
  EndIf 
EndProcedure
;
;
;
;
Procedure Int_FTP_PASV(Ftp, Log_Gadget)
  Result = FTP_PASV(Ftp, "PASV", Log_Gadget)
  If Result <> #FTP_OK
    Result = FTP_PASV(Ftp, "passive", Log_Gadget)
  EndIf
  ProcedureReturn Result
EndProcedure
I also need the CHMOD command to change read/write authorizations and made that:

Code: Select all

Procedure FTP_CHMOD(Ftp.l, Command.s, FileOrFolder.s, mod.s, Log_Gadget)
  If Ftp
    ; Online with the server
    If Log_Gadget
      AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "<----" + Chr(10) + Command + " " + FileOrFolder)
    EndIf
    ;
    SendNetworkString2(Ftp,Command+" " + mod + " " + FileOrFolder + #LFCR)
    In = Wait(Ftp, #SmallTimeOut)
    If In = "TimeOut"
      FTP_Last_Message = "Timed out"
      ProcedureReturn #FTP_TimeOut
    ElseIf In
      FTP_Last_Message = In
      If Left(In,3) = "200" ; CHMOD ACCEPTED
        If Log_Gadget
          AddGadgetItem(Log_Gadget,-1,FormatDate("%hh:%ii:%ss", Date()) + Chr(10) + "---->" + Chr(10) + In)
        EndIf
        In.s = ""
        ProcedureReturn #FTP_OK
      Else
        ProcedureReturn #FTP_ERROR
      EndIf
    Else
      ProcedureReturn #FTP_ERROR
    EndIf
  EndIf
EndProcedure
;
Procedure Int_FTP_CHMOD(Ftp.l, FileOrFolder.s, mod.s, Log_Gadget)
  Result = FTP_CHMOD(Ftp.l, "CHMOD", FileOrFolder.s, mod.s, Log_Gadget)
  If Result <> #FTP_OK
    Result = FTP_CHMOD(Ftp.l, "SITE CHMOD", FileOrFolder.s, mod.s, Log_Gadget)
  EndIf
  ProcedureReturn Result
EndProcedure
Terry wrote:Remember that the FTP_Library_Include does
REQUIRE a server compatible with PASV port assignments. The sparse
documentaion embedded in the code may not say that, but Num3 and I
both have mentioned that fact in this thread. It does not work except in
PASV mode.

FTP_List requests the server to enter PASV mode and return the assigned
data port (the expected 227 message). Then it sends the LIST command
on the normal port and waits for the expected 125 or 150 command that
indicates the server is opening the PASV port to send the directory listing
data on. Then it calls another procedure to receive the actual data via
the assigned PASV port. When that process is completed, the server
sends the expected 226 reply on the normal port.
The fact is that your version did not work in some case even if the server support the PASV mode, because (with the 7b) you dit not follow what you say above and wait for 226 reply BEFORE call the procedure that read the data via the PASV port.
Terry wrote:4) That could happen depending on the size of the directory listing and the capacity of the string defined to receive it. So far in my testing, the code receives and handles well over 100 directory entries. I haven't tried testing extremely large listings.
The problem is not really about the size of the directory but about the server/line availability. I got parsed answers with quite small directories. I thinck that you MUST include that eventuality into the Wait() procedure (or your equivalent).
That was by design. I wanted the code to exhibit the steps necessary in each command so that it could be a bit of a tutorial.
I agree, but the result is so big that it becomes a bit frightening. And that's not a good point for a tutorial (that is just an opinion).
I was working with a server that is behind arouter/firewall and it returns only the internal IP address (A server problem) and I forgot to change the code back before posting it.
Even in a firewall-server case, using the server name is not the standard rule. I didn't yet examine precisely the process to follow but it seems that there is also a port (or socket?) assignment to do by the client.

I continue to work on my version. My target is not to write a sort of tuto but to obtain a code which works well with the wider possible range of servers and communication conditions.
To add the active transmission mode is also one of my project (don't know when...)
Thanks again for your help.
Don't try - DO it !
TerryHough
Enthusiast
Enthusiast
Posts: 781
Joined: Fri Apr 25, 2003 6:51 pm
Location: NC, USA
Contact:

Post by TerryHough »

zapman* wrote:Because some servers don't really follow standards...
Right! And that is why my code expands/changes with each new server
I test against. I suspect that will continue a while longer.

Just glad to see you (and others) interested in FTP communications and
that I could contribute in some way.

Keep me posted on your progress. Feel free to PM me if you wish.

Terry
Dare2
Moderator
Moderator
Posts: 3321
Joined: Sat Dec 27, 2003 3:55 am
Location: Great Southern Land

Post by Dare2 »

I think this thread is pretty cool. We're seeing something pretty darn nifty evolving here.

I wish there were more "ducks guts" threads like this!

Thanks guys, please keep pushing the limits!
@}--`--,-- A rose by any other name ..
zapman*
Enthusiast
Enthusiast
Posts: 115
Joined: Wed Jun 02, 2004 10:17 pm
Location: New Caledonia (South Pacific)
Contact:

Post by zapman* »

Hard job!
I found some more bugs in my version. I'll post a new version when they will be fixed.
Don't try - DO it !
dell_jockey
Enthusiast
Enthusiast
Posts: 767
Joined: Sat Jan 24, 2004 6:56 pm

Post by dell_jockey »

Dare2 wrote:I think this thread is pretty cool. We're seeing something pretty darn nifty evolving here.

I wish there were more "ducks guts" threads like this!

Thanks guys, please keep pushing the limits!
I agree wholeheartedly! THANKS and keep up the good work!
cheers,
dell_jockey
________
http://blog.forex-trading-ideas.com
TerryHough
Enthusiast
Enthusiast
Posts: 781
Joined: Fri Apr 25, 2003 6:51 pm
Location: NC, USA
Contact:

Post by TerryHough »

zapman* wrote:Hard job!
I found some more bugs in my version. I'll post a new version when they will be fixed.
ROFLMAO :D Me too!
Dare2
Moderator
Moderator
Posts: 3321
Joined: Sat Dec 27, 2003 3:55 am
Location: Great Southern Land

Post by Dare2 »

Hi,

How are you guys handling servers that respond with more than one "positive completion" message?

I encountered a server that responded to login sequence as follows:

Code: Select all

>> Connecting to (Client's Server Name) on 21
<< 220 Microsoft FTP Service
>> USER (User Name) {CRLF} 
<< 331 Password required for (User Name).
>> PASS (password) {CRLF}
<< 230-Welcome to FTP server.
   ; ^^ This should mean completion (ok) and end of server response!?
>> TYPE A {CRLF} 
<< 230 User (name) logged in.
   ; ^^ but here it is again, different text
   ;     so another TYPE A sent by my slightly confused prog
>> TYPE A {CRLF} 
<< 200 Type set to A.
  ; Looked like it worked ok  .. but ..
>> PWD {CRLF} 
<< 200 Type set to A.
   ; ^^ but it seems I am now out of synch!
   ;     this apparently in response to previous command
   ;     so buffer not flushed.?
   ;     Anyhow, my completely confused prog quits
>> QUIT {CRLF}
I tried looping through NetworkClientEvent/ReceiveNetworkData hoping to flush all of these until getting an empty string but I get timeouts instead.

Tried a REST 0 as well, after first 230, but that just added another out of synch command. (The response 350 restarting at zero happening after a TYPE A)

How do you handle this?

Thanks!
@}--`--,-- A rose by any other name ..
TerryHough
Enthusiast
Enthusiast
Posts: 781
Joined: Fri Apr 25, 2003 6:51 pm
Location: NC, USA
Contact:

Post by TerryHough »

Dare2 wrote:How are you guys handling servers that respond with more than one "positive completion" message?
Hi Dare2,

I, too, have recently run into a server that responds with multiple lines
for certain responses. Oh for the good ole days when FTP servers
stuck to the original RFCs... But not to worry.

Notice in the responses that the response code is followed by a "-"
(hyphen) instead of a space. That indicates there will be a following line
that continues with the same response code.

So, you can check the first 4 characters of the response and just display
the ones with a - in the 4th character (or trash it) and only respond on
lines with a valid response code with a space in the 4th position.

I ended up splitting the TYPE command from LOGIN to alleviate this
problem which usually occurs only during the login conversation. Then I
found a server which fails to put a LFCR at the end of the last 230- line
and immediately follows it with a 200 response. That means the 200
response was embedded in a 230- response. Go figure...

Then I hit a server that responds with multiple lines on the 226 responses.

FYI...
I will soon be posting FTP_Library_Include_7d (my Version 0.7d). It
benefits from lots more debugging, is more stable with PASV compatible
servers, adds a couple more commands, and implements PORT
handling. If you would like to help me with testing 7d, shoot me your
email address and I will send it to you before then.

Good luck,
Terry
Dare2
Moderator
Moderator
Posts: 3321
Joined: Sat Dec 27, 2003 3:55 am
Location: Great Southern Land

Post by Dare2 »

Hi Terry,

Now that you point out the hyphen, there it is! :) At last, something to get a handle on! Thanks for that!

And yes, it is mainly (only, thus far) in the login that this is happening. I think i will also take out the TYPE command and handle it elsewhere, just to clarify/simplify an increasingly complex and cluttered operation.


lol, standards are meant to be broken, it seems. A short while ago I was parsing RTF files. And creating them with many different editors. None of the RTF writers actually broke the rules, but interpretation of these was pretty broad and flexible.


And so now, it seems, with FTP.


Thanks again.
@}--`--,-- A rose by any other name ..
Post Reply