ftp_client_misc.c
Go to the documentation of this file.
1 /**
2  * @file ftp_client_misc.c
3  * @brief Helper functions for FTP client
4  *
5  * @section License
6  *
7  * Copyright (C) 2010-2018 Oryx Embedded SARL. All rights reserved.
8  *
9  * This file is part of CycloneTCP Open.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24  *
25  * @author Oryx Embedded SARL (www.oryx-embedded.com)
26  * @version 1.9.0
27  **/
28 
29 //Switch to the appropriate trace level
30 #define TRACE_LEVEL FTP_TRACE_LEVEL
31 
32 //Dependencies
33 #include <stdlib.h>
34 #include <ctype.h>
35 #include "ftp/ftp_client.h"
37 #include "ftp/ftp_client_misc.h"
38 #include "str.h"
39 #include "error.h"
40 #include "debug.h"
41 
42 //Check TCP/IP stack configuration
43 #if (FTP_CLIENT_SUPPORT == ENABLED)
44 
45 
46 /**
47  * @brief Update FTP client state
48  * @param[in] context Pointer to the FTP client context
49  * @param[in] newState New state to switch to
50  **/
51 
53 {
54  //Save current time
55  context->timestamp = osGetSystemTime();
56 
57  //Switch to the new state
58  context->state = newState;
59 }
60 
61 
62 /**
63  * @brief Send FTP command and wait for a reply
64  * @param[in] context Pointer to the FTP client context
65  * @return Error code
66  **/
67 
69 {
70  error_t error;
71  size_t n;
72  bool_t more;
73  char_t *reply;
74 
75  //Initialize status code
76  error = NO_ERROR;
77 
78  //Point to the server's response
79  reply = context->buffer;
80 
81  //Send FTP command and wait for the FTP reply to be received
82  while(!error)
83  {
84  //Send FTP command
85  if(context->bufferPos < context->commandLen)
86  {
87  //Send more data
88  error = ftpClientSendData(&context->controlConnection,
89  context->buffer + context->bufferPos,
90  context->commandLen - context->bufferPos, &n, 0);
91 
92  //Check status code
93  if(!error)
94  {
95  //Advance data pointer
96  context->bufferPos += n;
97  }
98  }
99  else
100  {
101  //Determine whether more data should be collected
102  if(context->replyLen != 0 && reply[context->replyLen - 1] == '\n')
103  more = FALSE;
104  else if(context->replyLen == (FTP_CLIENT_BUFFER_SIZE - 1))
105  more = FALSE;
106  else
107  more = TRUE;
108 
109  //Receive FTP response
110  if(more)
111  {
112  //Receive more data
113  error = ftpClientReceiveData(&context->controlConnection,
114  context->buffer + context->replyLen,
115  FTP_CLIENT_BUFFER_SIZE - 1 - context->replyLen,
117 
118  //Check status code
119  if(!error)
120  {
121  //Advance data pointer
122  context->replyLen += n;
123  }
124  }
125  else
126  {
127  //Properly terminate the response with a NULL character
128  reply[context->replyLen] = '\0';
129 
130  //Remove trailing whitespace from the response
131  strRemoveTrailingSpace(reply);
132 
133  //All replies begin with a three digit numeric code
134  if(isdigit((uint8_t) reply[0]) &&
135  isdigit((uint8_t) reply[1]) &&
136  isdigit((uint8_t) reply[2]))
137  {
138  //A space character follows the response code for the last line
139  if(reply[3] == ' ' || reply[3] == '\0')
140  {
141  //Debug message
142  TRACE_DEBUG("FTP server: %s\r\n", reply);
143 
144  //Retrieve FTP reply code
145  context->replyCode = strtoul(reply, NULL, 10);
146 
147  //A valid FTP response has been received
148  break;
149  }
150  else
151  {
152  //Ignore all intermediary lines
153  context->replyLen = 0;
154  }
155  }
156  else
157  {
158  //Ignore all intermediary lines
159  context->replyLen = 0;
160  }
161  }
162  }
163  }
164 
165  //Return status code
166  return error;
167 }
168 
169 
170 /**
171  * @brief Format FTP command
172  * @param[in] context Pointer to the FTP client context
173  * @param[in] command NULL-terminated string containing the FTP command
174  * @param[in] argument NULL-terminated string containing the argument
175  * @return Error code
176  **/
177 
179  const char_t *command, const char_t *argument)
180 {
181  //The argument is optional
182  if(argument != NULL)
183  {
184  //Format FTP command
185  sprintf(context->buffer, "%s %s\r\n", command, argument);
186  }
187  else
188  {
189  //Format FTP command
190  sprintf(context->buffer, "%s\r\n", command);
191  }
192 
193  //Calculate the length of the FTP command
194  context->commandLen = strlen(context->buffer);
195 
196  //Debug message
197  TRACE_DEBUG("FTP client: %s", context->buffer);
198 
199  //Flush receive buffer
200  context->bufferPos = 0;
201  context->replyLen = 0;
202 
203  //Successful processing
204  return NO_ERROR;
205 }
206 
207 
208 
209 /**
210  * @brief Format PORT or EPRT command
211  * @param[in] context Pointer to the FTP client context
212  * @param[in] ipAddr Host IP address
213  * @param[in] port TCP port number
214  * @return Error code
215  **/
216 
218  const IpAddr *ipAddr, uint16_t port)
219 {
220  error_t error;
221  char_t *p;
222 
223  //Initialize status code
224  error = NO_ERROR;
225 
226 #if (IPV4_SUPPORT == ENABLED)
227  //IPv4 address?
228  if(ipAddr->length == sizeof(Ipv4Addr))
229  {
230  //Format the PORT command
231  strcpy(context->buffer, "PORT ");
232 
233  //Append host address
234  ipv4AddrToString(ipAddr->ipv4Addr, context->buffer + 5);
235  //Change dots to commas
236  strReplaceChar(context->buffer, '.', ',');
237 
238  //Point to the end of the resulting string
239  p = context->buffer + strlen(context->buffer);
240  //Append port number
241  sprintf(p, ",%" PRIu8 ",%" PRIu8 "\r\n", MSB(port), LSB(port));
242  }
243  else
244 #endif
245 #if (IPV6_SUPPORT == ENABLED)
246  //IPv6 address?
247  if(ipAddr->length == sizeof(Ipv6Addr))
248  {
249  //Format the EPRT command
250  strcpy(context->buffer, "EPRT |2|");
251 
252  //Append host address
253  ipv6AddrToString(&ipAddr->ipv6Addr, context->buffer + 8);
254 
255  //Point to the end of the resulting string
256  p = context->buffer + strlen(context->buffer);
257  //Append port number
258  sprintf(p, "|%" PRIu16 "|\r\n", port);
259  }
260  else
261 #endif
262  //Invalid IP address?
263  {
264  //Report an error
265  error = ERROR_INVALID_ADDRESS;
266  }
267 
268  //Check status code
269  if(!error)
270  {
271  //Calculate the length of the FTP command
272  context->commandLen = strlen(context->buffer);
273 
274  //Debug message
275  TRACE_DEBUG("FTP client: %s", context->buffer);
276 
277  //Flush receive buffer
278  context->bufferPos = 0;
279  context->replyLen = 0;
280  }
281 
282  //Return status code
283  return error;
284 }
285 
286 
287 /**
288  * @brief Format PASV or EPSV command
289  * @param[in] context Pointer to the FTP client context
290  * @return Error code
291  **/
292 
294 {
295  error_t error;
296 
297  //Initialize status code
298  error = NO_ERROR;
299 
300 #if (IPV4_SUPPORT == ENABLED)
301  //IPv4 address?
302  if(context->serverIpAddr.length == sizeof(Ipv4Addr))
303  {
304  //Format PASV command
305  strcpy(context->buffer, "PASV\r\n");
306  }
307  else
308 #endif
309 #if (IPV6_SUPPORT == ENABLED)
310  //IPv6 address?
311  if(context->serverIpAddr.length == sizeof(Ipv6Addr))
312  {
313  //Format EPSV command
314  strcpy(context->buffer, "EPSV\r\n");
315  }
316  else
317 #endif
318  //Invalid IP address?
319  {
320  //Report an error
321  error = ERROR_INVALID_ADDRESS;
322  }
323 
324  //Check status code
325  if(!error)
326  {
327  //Calculate the length of the FTP command
328  context->commandLen = strlen(context->buffer);
329 
330  //Debug message
331  TRACE_DEBUG("FTP client: %s", context->buffer);
332 
333  //Flush receive buffer
334  context->bufferPos = 0;
335  context->replyLen = 0;
336  }
337 
338  //Return status code
339  return error;
340 }
341 
342 
343 /**
344  * @brief Parse PASV or EPSV response
345  * @param[in] context Pointer to the FTP client context
346  * @param[out] port The TCP port number the server is listening on
347  * @return Error code
348  **/
349 
351 {
352  char_t *p;
353  char_t delimiter;
354 
355 #if (IPV4_SUPPORT == ENABLED)
356  //IPv4 address?
357  if(context->serverIpAddr.length == sizeof(Ipv4Addr))
358  {
359  //Delimiter character
360  delimiter = ',';
361 
362  //Retrieve the low byte of the port number
363  p = strrchr(context->buffer, delimiter);
364  //Failed to parse the response?
365  if(p == NULL)
366  return ERROR_INVALID_SYNTAX;
367 
368  //Convert the resulting string
369  *port = (uint16_t) strtoul(p + 1, NULL, 10);
370  //Split the string
371  *p = '\0';
372 
373  //Retrieve the high byte of the port number
374  p = strrchr(context->buffer, delimiter);
375  //Failed to parse the response?
376  if(p == NULL)
377  return ERROR_INVALID_SYNTAX;
378 
379  //Convert the resulting string
380  *port |= (uint16_t) strtoul(p + 1, NULL, 10) << 8;
381  }
382  else
383 #endif
384 #if (IPV6_SUPPORT == ENABLED)
385  //IPv6 address?
386  if(context->serverIpAddr.length == sizeof(Ipv6Addr))
387  {
388  //Search for the opening parenthesis
389  p = strrchr(context->buffer, '(');
390  //Failed to parse the response?
391  if(p == NULL || p[1] == '\0')
392  return ERROR_INVALID_SYNTAX;
393 
394  //Retrieve the delimiter character
395  delimiter = p[1];
396 
397  //Search for the last delimiter character
398  p = strrchr(context->buffer, delimiter);
399  //Failed to parse the response?
400  if(p == NULL)
401  return ERROR_INVALID_SYNTAX;
402 
403  //Split the string
404  *p = '\0';
405 
406  //Search for the last but one delimiter character
407  p = strrchr(context->buffer, delimiter);
408  //Failed to parse the response?
409  if(p == NULL)
410  return ERROR_INVALID_SYNTAX;
411 
412  //Convert the resulting string
413  *port = (uint16_t) strtoul(p + 1, NULL, 10);
414  }
415  else
416 #endif
417  //Invalid IP address?
418  {
419  //Report an error
420  return ERROR_INVALID_ADDRESS;
421  }
422 
423  //Successful processing
424  return NO_ERROR;
425 }
426 
427 
428 /**
429  * @brief Parse PWD response
430  * @param[in] context Pointer to the FTP client context
431  * @param[out] path Output buffer where to store the current directory
432  * @param[in] maxLen Maximum number of characters the buffer can hold
433  * @return Error code
434  **/
435 
437  size_t maxLen)
438 {
439  size_t length;
440  char_t *p;
441 
442  //Search for the last double quote
443  p = strrchr(context->buffer, '\"');
444  //Failed to parse the response?
445  if(p == NULL)
446  return ERROR_INVALID_SYNTAX;
447 
448  //Split the string
449  *p = '\0';
450 
451  //Search for the first double quote
452  p = strchr(context->buffer, '\"');
453  //Failed to parse the response?
454  if(p == NULL)
455  return ERROR_INVALID_SYNTAX;
456 
457  //Retrieve the length of the working directory
458  length = strlen(p + 1);
459  //Limit the number of characters to copy
460  length = MIN(length, maxLen);
461 
462  //Copy the string
463  strncpy(path, p + 1, length);
464  //Properly terminate the string with a NULL character
465  path[length] = '\0';
466 
467  //Successful processing
468  return NO_ERROR;
469 }
470 
471 
472 /**
473  * @brief Parse directory entry
474  * @param[in] line NULL-terminated string
475  * @param[out] dirEntry Pointer to a directory entry
476  * @return Error code
477  **/
478 
480 {
481  uint_t i;
482  size_t n;
483  char_t *p;
484  char_t *token;
485 
486  //Abbreviated months
487  static const char months[13][4] =
488  {
489  " ",
490  "Jan",
491  "Feb",
492  "Mar",
493  "Apr",
494  "May",
495  "Jun",
496  "Jul",
497  "Aug",
498  "Sep",
499  "Oct",
500  "Nov",
501  "Dec"
502  };
503 
504  //Read first field
505  token = strtok_r(line, " \t", &p);
506  //Invalid directory entry?
507  if(token == NULL)
508  return ERROR_INVALID_SYNTAX;
509 
510  //MS-DOS listing format?
511  if(isdigit((uint8_t) token[0]))
512  {
513  //Check modification date format
514  if(strlen(token) == 8 && token[2] == '-' && token[5] == '-')
515  {
516  //The format of the date is mm-dd-yy
517  dirEntry->modified.month = (uint8_t) strtoul(token, NULL, 10);
518  dirEntry->modified.day = (uint8_t) strtoul(token + 3, NULL, 10);
519  dirEntry->modified.year = (uint16_t) strtoul(token + 6, NULL, 10) + 2000;
520  }
521  else if(strlen(token) == 10 && token[2] == '/' && token[5] == '/')
522  {
523  //The format of the date is mm/dd/yyyy
524  dirEntry->modified.month = (uint8_t) strtoul(token, NULL, 10);
525  dirEntry->modified.day = (uint8_t) strtoul(token + 3, NULL, 10);
526  dirEntry->modified.year = (uint16_t) strtoul(token + 6, NULL, 10);
527  }
528  else
529  {
530  //Invalid time format
531  return ERROR_INVALID_SYNTAX;
532  }
533 
534  //Read modification time
535  token = strtok_r(NULL, " ", &p);
536  //Invalid directory entry?
537  if(token == NULL)
538  return ERROR_INVALID_SYNTAX;
539 
540  //Check modification time format
541  if(strlen(token) >= 5 && token[2] == ':')
542  {
543  //The format of the time hh:mm
544  dirEntry->modified.hours = (uint8_t) strtoul(token, NULL, 10);
545  dirEntry->modified.minutes = (uint8_t) strtoul(token + 3, NULL, 10);
546 
547  //The PM period covers the 12 hours from noon to midnight
548  if(strstr(token, "PM") != NULL)
549  dirEntry->modified.hours += 12;
550  }
551  else
552  {
553  //Invalid time format
554  return ERROR_INVALID_SYNTAX;
555  }
556 
557  //Read next field
558  token = strtok_r(NULL, " ", &p);
559  //Invalid directory entry?
560  if(token == NULL)
561  return ERROR_INVALID_SYNTAX;
562 
563  //Check whether the current entry is a directory
564  if(!strcmp(token, "<DIR>"))
565  {
566  //Update attributes
567  dirEntry->attributes |= FTP_FILE_ATTR_DIRECTORY;
568  }
569  else
570  {
571  //Save the size of the file
572  dirEntry->size = strtoul(token, NULL, 10);
573  }
574 
575  //Read filename field
576  token = strtok_r(NULL, " \r\n", &p);
577  //Invalid directory entry?
578  if(token == NULL)
579  return ERROR_INVALID_SYNTAX;
580 
581  //Retrieve the length of the filename
582  n = strlen(token);
583  //Limit the number of characters to copy
585 
586  //Copy the filename
587  strncpy(dirEntry->name, token, n);
588  //Properly terminate the string with a NULL character
589  dirEntry->name[n] = '\0';
590  }
591  //Unix listing format?
592  else
593  {
594  //Check file permissions
595  if(strchr(token, 'd') != NULL)
596  dirEntry->attributes |= FTP_FILE_ATTR_DIRECTORY;
597  if(strchr(token, 'w') == NULL)
598  dirEntry->attributes |= FTP_FILE_ATTR_READ_ONLY;
599 
600  //Read next field
601  token = strtok_r(NULL, " ", &p);
602  //Invalid directory entry?
603  if(token == NULL)
604  return ERROR_INVALID_SYNTAX;
605 
606  //Discard owner field
607  token = strtok_r(NULL, " ", &p);
608  //Invalid directory entry?
609  if(token == NULL)
610  return ERROR_INVALID_SYNTAX;
611 
612  //Discard group field
613  token = strtok_r(NULL, " ", &p);
614  //Invalid directory entry?
615  if(token == NULL)
616  return ERROR_INVALID_SYNTAX;
617 
618  //Read size field
619  token = strtok_r(NULL, " ", &p);
620  //Invalid directory entry?
621  if(token == NULL)
622  return ERROR_INVALID_SYNTAX;
623 
624  //Save the size of the file
625  dirEntry->size = strtoul(token, NULL, 10);
626 
627  //Read modification time (month)
628  token = strtok_r(NULL, " ", &p);
629  //Invalid directory entry?
630  if(token == NULL)
631  return ERROR_INVALID_SYNTAX;
632 
633  //Decode the 3-letter month name
634  for(i = 1; i <= 12; i++)
635  {
636  //Compare month name
637  if(!strcmp(token, months[i]))
638  {
639  //Save month number
640  dirEntry->modified.month = i;
641  break;
642  }
643  }
644 
645  //Read modification time (day)
646  token = strtok_r(NULL, " ", &p);
647  //Invalid directory entry?
648  if(token == NULL)
649  return ERROR_INVALID_SYNTAX;
650 
651  //Save day number
652  dirEntry->modified.day = (uint8_t) strtoul(token, NULL, 10);
653 
654  //Read next field
655  token = strtok_r(NULL, " ", &p);
656  //Invalid directory entry?
657  if(token == NULL)
658  return ERROR_INVALID_SYNTAX;
659 
660  //Check modification time format
661  if(strlen(token) == 4)
662  {
663  //The format of the year is yyyy
664  dirEntry->modified.year = (uint16_t) strtoul(token, NULL, 10);
665 
666  }
667  else if(strlen(token) == 5)
668  {
669  //The format of the time hh:mm
670  token[2] = '\0';
671  dirEntry->modified.hours = (uint8_t) strtoul(token, NULL, 10);
672  dirEntry->modified.minutes = (uint8_t) strtoul(token + 3, NULL, 10);
673  }
674  else
675  {
676  //Invalid time format
677  return ERROR_INVALID_SYNTAX;
678  }
679 
680  //Read filename field
681  token = strtok_r(NULL, " \r\n", &p);
682  //Invalid directory entry?
683  if(token == NULL)
684  return ERROR_INVALID_SYNTAX;
685 
686  //Retrieve the length of the filename
687  n = strlen(token);
688  //Limit the number of characters to copy
690 
691  //Copy the filename
692  strncpy(dirEntry->name, token, n);
693  //Properly terminate the string with a NULL character
694  dirEntry->name[n] = '\0';
695  }
696 
697  //The directory entry is valid
698  return NO_ERROR;
699 }
700 
701 
702 /**
703  * @brief Initiate data transfer
704  * @param[in] context Pointer to the FTP client context
705  * @param[in] direction Data transfer direction
706  * @return Error code
707  **/
708 
710 {
711  error_t error;
712  IpAddr ipAddr;
713  uint16_t port;
714  Socket *socket;
715 
716  //Initialize status code
717  error = NO_ERROR;
718 
719  //Check current state
720  if(context->state == FTP_CLIENT_STATE_SUB_COMMAND_2)
721  {
722 #if (FTP_CLIENT_TLS_SUPPORT == ENABLED)
723  //TLS-secured connection?
724  if(context->controlConnection.tlsContext != NULL)
725  {
726  //A PBSZ command must be issued, but must have a parameter
727  //of '0' to indicate that no buffering is taking place and
728  //the data connection should not be encapsulated
729  error = ftpClientFormatCommand(context, "PBSZ", "0");
730 
731  //Check status code
732  if(!error)
733  {
734  //Send PBSZ command and wait for the server's response
736  }
737  }
738  else
739 #endif
740  {
741  //Update FTP client state
743  }
744  }
745  else if(context->state == FTP_CLIENT_STATE_SUB_COMMAND_3)
746  {
747  //Send PBSZ command and wait for the server's response
748  error = ftpClientSendCommand(context);
749 
750  //Check status code
751  if(!error)
752  {
753  //Check FTP response code
754  if(FTP_REPLY_CODE_2YZ(context->replyCode))
755  {
756  //If the data connection security level is 'Private', then a TLS
757  //negotiation must take place on the data connection
758  error = ftpClientFormatCommand(context, "PROT", "P");
759 
760  //Check status code
761  if(!error)
762  {
763  //Send PROT command and wait for the server's response
765  }
766  }
767  else
768  {
769  //Report an error
771  }
772  }
773  }
774  else if(context->state == FTP_CLIENT_STATE_SUB_COMMAND_4)
775  {
776  //Send PROT command and wait for the server's response
777  error = ftpClientSendCommand(context);
778 
779  //Check status code
780  if(!error)
781  {
782  //Check FTP response code
783  if(FTP_REPLY_CODE_2YZ(context->replyCode))
784  {
785  //Update FTP client state
787  }
788  else
789  {
790  //Report an error
792  }
793  }
794  }
795  else if(context->state == FTP_CLIENT_STATE_SUB_COMMAND_5)
796  {
797  //Check data transfer direction
798  if(direction)
799  {
800  //Open data socket
801  error = ftpClientOpenConnection(context, &context->dataConnection,
803  }
804  else
805  {
806  //Open data socket
807  error = ftpClientOpenConnection(context, &context->dataConnection,
809  }
810 
811  //Check status code
812  if(!error)
813  {
814  //Check transfer mode
815  if(!context->passiveMode)
816  {
817  //Place the data socket in the listening state
818  error = socketListen(context->dataConnection.socket, 1);
819 
820  //Check status code
821  if(!error)
822  {
823  //Retrieve local IP address
824  error = socketGetLocalAddr(context->controlConnection.socket,
825  &ipAddr, NULL);
826  }
827 
828  //Check status code
829  if(!error)
830  {
831  //Retrieve local port number
832  error = socketGetLocalAddr(context->dataConnection.socket,
833  NULL, &port);
834  }
835 
836  //Check status code
837  if(!error)
838  {
839  //Set the port to be used in data connection
840  error = ftpClientFormatPortCommand(context, &ipAddr, port);
841  }
842 
843  //Check status code
844  if(!error)
845  {
846  //Send PORT command and wait for the server's response
848  }
849  }
850  else
851  {
852  //Enter passive mode
853  error = ftpClientFormatPasvCommand(context);
854 
855  //Check status code
856  if(!error)
857  {
858  //Send PASV command and wait for the server's response
860  }
861  }
862  }
863  }
864  else if(context->state == FTP_CLIENT_STATE_SUB_COMMAND_6)
865  {
866  //Send PORT/PASV command and wait for the server's response
867  error = ftpClientSendCommand(context);
868 
869  //Check status code
870  if(!error)
871  {
872  //Check FTP response code
873  if(FTP_REPLY_CODE_2YZ(context->replyCode))
874  {
875  //Check transfer mode
876  if(!context->passiveMode)
877  {
878  //Update FTP client state
880  }
881  else
882  {
883  //Parse server's response
884  error = ftpClientParsePasvReply(context, &context->serverPort);
885 
886  //Check status code
887  if(!error)
888  {
889  //Establish data connection
891  }
892  }
893  }
894  else
895  {
896  //Report an error
898  }
899  }
900  }
901  else if(context->state == FTP_CLIENT_STATE_CONNECTING_TCP)
902  {
903  //Establish data connection
904  error = socketConnect(context->dataConnection.socket,
905  &context->serverIpAddr, context->serverPort);
906 
907  //Check status code
908  if(!error)
909  {
910  //Update FTP client state
912  }
913  }
914  else if(context->state == FTP_CLIENT_STATE_SUB_COMMAND_8)
915  {
916  //Send STOR/APPE/RETR/LIST command and wait for the server's response
917  error = ftpClientSendCommand(context);
918 
919  //Check status code
920  if(!error)
921  {
922  //Check FTP response code
923  if(FTP_REPLY_CODE_1YZ(context->replyCode))
924  {
925  //Check transfer mode
926  if(!context->passiveMode)
927  {
928  //Wait for the server to connect back to the client's data port
930  }
931  else
932  {
933 #if (FTP_CLIENT_TLS_SUPPORT == ENABLED)
934  //TLS-secured connection?
935  if(context->controlConnection.tlsContext != NULL)
936  {
937  //Check data transfer direction
938  if(direction)
939  {
940  //TLS initialization
941  error = ftpClientOpenSecureConnection(context,
942  &context->dataConnection, FTP_CLIENT_TLS_TX_BUFFER_SIZE,
944  }
945  else
946  {
947  //TLS initialization
948  error = ftpClientOpenSecureConnection(context,
949  &context->dataConnection, FTP_CLIENT_TLS_TX_BUFFER_SIZE,
951  }
952 
953  //Check status code
954  if(!error)
955  {
956  //Perform TLS handshake
958  }
959  }
960  else
961 #endif
962  {
963  //Update FTP client state
965  }
966  }
967  }
968  else
969  {
970  //Report an error
972  }
973  }
974  }
975  else if(context->state == FTP_CLIENT_STATE_ACCEPTING_TCP)
976  {
977  //Wait for the server to connect back to the client's data port
978  socket = socketAccept(context->dataConnection.socket, NULL, NULL);
979 
980  //Valid socket handle?
981  if(socket != NULL)
982  {
983  //Close the listening socket
984  socketClose(context->dataConnection.socket);
985  //Save socket handle
986  context->dataConnection.socket = socket;
987 
988  //Set timeout
989  error = socketSetTimeout(context->dataConnection.socket,
990  context->timeout);
991 
992 #if (FTP_CLIENT_TLS_SUPPORT == ENABLED)
993  //TLS-secured connection?
994  if(context->controlConnection.tlsContext != NULL)
995  {
996  //Check status code
997  if(!error)
998  {
999  //Check data transfer direction
1000  if(direction)
1001  {
1002  //TLS initialization
1003  error = ftpClientOpenSecureConnection(context,
1004  &context->dataConnection, FTP_CLIENT_TLS_TX_BUFFER_SIZE,
1006  }
1007  else
1008  {
1009  //TLS initialization
1010  error = ftpClientOpenSecureConnection(context,
1011  &context->dataConnection, FTP_CLIENT_TLS_TX_BUFFER_SIZE,
1013  }
1014  }
1015 
1016  //Check status code
1017  if(!error)
1018  {
1019  //Perform TLS handshake
1021  }
1022  }
1023  else
1024 #endif
1025  {
1026  //Update FTP client state
1028  }
1029  }
1030  else
1031  {
1032  //Report an error
1033  error = ERROR_WOULD_BLOCK;
1034  }
1035  }
1036  else if(context->state == FTP_CLIENT_STATE_CONNECTING_TLS)
1037  {
1038  //Perform TLS handshake
1039  error = ftpClientEstablishSecureConnection(&context->dataConnection);
1040 
1041  //Check status code
1042  if(!error)
1043  {
1044  //The content of the file can be transferred via the data connection
1046  }
1047  }
1048  else
1049  {
1050  //Invalid state
1051  error = ERROR_WRONG_STATE;
1052  }
1053 
1054  //Return status code
1055  return error;
1056 }
1057 
1058 
1059 /**
1060  * @brief Terminate data transfer
1061  * @param[in] context Pointer to the FTP client context
1062  * @return Error code
1063  **/
1064 
1066 {
1067  error_t error;
1068 
1069  //Initialize status code
1070  error = NO_ERROR;
1071 
1072  //Execute FTP command sequence
1073  while(!error)
1074  {
1075  //Check current state
1076  if(context->state == FTP_CLIENT_STATE_WRITING_DATA ||
1077  context->state == FTP_CLIENT_STATE_READING_DATA)
1078  {
1079  //Update FTP client state
1081  }
1082  else if(context->state == FTP_CLIENT_STATE_DISCONNECTING_1)
1083  {
1084  //Shutdown data connection
1085  error = ftpClientShutdownConnection(&context->dataConnection);
1086 
1087  //Check status code
1088  if(!error)
1089  {
1090  //Close data connection
1091  ftpClientCloseConnection(&context->dataConnection);
1092 
1093  //Flush buffer
1094  context->bufferPos = 0;
1095  context->commandLen = 0;
1096  context->replyLen = 0;
1097 
1098  //Wait for the transfer status
1100  }
1101  }
1102  else if(context->state == FTP_CLIENT_STATE_SUB_COMMAND_1)
1103  {
1104  //Wait for the transfer status
1105  error = ftpClientSendCommand(context);
1106 
1107  //Check status code
1108  if(!error)
1109  {
1110  //Check FTP response code
1111  if(FTP_REPLY_CODE_2YZ(context->replyCode))
1112  {
1113  //Update FTP client state
1115  }
1116  else
1117  {
1118  //Report an error
1119  error = ERROR_UNEXPECTED_RESPONSE;
1120  }
1121  }
1122  }
1123  else if(context->state == FTP_CLIENT_STATE_CONNECTED)
1124  {
1125  //We are done
1126  break;
1127  }
1128  else
1129  {
1130  //Invalid state
1131  error = ERROR_WRONG_STATE;
1132  }
1133  }
1134 
1135  //Check status code
1136  if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
1137  {
1138  //Check whether the timeout has elapsed
1139  error = ftpClientCheckTimeout(context);
1140  }
1141 
1142  //Failed to close file?
1143  if(error != NO_ERROR && error != ERROR_WOULD_BLOCK)
1144  {
1145  //Close data connection
1146  ftpClientCloseConnection(&context->dataConnection);
1147  //Update FTP client state
1149  }
1150 
1151  //Return status code
1152  return error;
1153 }
1154 
1155 
1156 /**
1157  * @brief Determine whether a timeout error has occurred
1158  * @param[in] context Pointer to the FTP client context
1159  * @return Error code
1160  **/
1161 
1163 {
1164 #if (NET_RTOS_SUPPORT == DISABLED)
1165  error_t error;
1166  systime_t time;
1167 
1168  //Get current time
1169  time = osGetSystemTime();
1170 
1171  //Check whether the timeout has elapsed
1172  if(timeCompare(time, context->timestamp + context->timeout) >= 0)
1173  {
1174  //Report a timeout error
1175  error = ERROR_TIMEOUT;
1176  }
1177  else
1178  {
1179  //The operation would block
1180  error = ERROR_WOULD_BLOCK;
1181  }
1182 
1183  //Return status code
1184  return error;
1185 #else
1186  //Report a timeout error
1187  return ERROR_TIMEOUT;
1188 #endif
1189 }
1190 
1191 #endif
uint32_t Ipv4Addr
IPv4 network address.
Definition: ipv4.h:232
uint32_t systime_t
Definition: compiler_port.h:44
#define timeCompare(t1, t2)
Definition: os_port.h:40
char char_t
Definition: compiler_port.h:41
error_t ftpClientSendCommand(FtpClientContext *context)
Send FTP command and wait for a reply.
char_t * ipv4AddrToString(Ipv4Addr ipAddr, char_t *str)
Convert a binary IPv4 address to dot-decimal notation.
Definition: ipv4.c:1785
uint16_t year
Definition: date_time.h:46
#define FTP_CLIENT_MAX_TCP_BUFFER_SIZE
Definition: ftp_client.h:73
systime_t osGetSystemTime(void)
Retrieve system time.
#define MSB(x)
Definition: os_port.h:56
uint32_t time
Debugging facilities.
uint32_t attributes
Definition: ftp_client.h:269
uint8_t p
Definition: ndp.h:295
#define FTP_REPLY_CODE_2YZ(code)
Definition: ftp_client.h:114
void ftpClientCloseConnection(FtpClientSocket *connection)
Close network connection.
#define FTP_CLIENT_MAX_FILENAME_LEN
Definition: ftp_client.h:101
Socket * socketAccept(Socket *socket, IpAddr *clientIpAddr, uint16_t *clientPort)
Permit an incoming connection attempt on a socket.
Definition: socket.c:457
#define FtpClientContext
Definition: ftp_client.h:121
error_t ftpClientParsePasvReply(FtpClientContext *context, uint16_t *port)
Parse PASV or EPSV response.
error_t socketListen(Socket *socket, uint_t backlog)
Place a socket in the listening state.
Definition: socket.c:420
IP network address.
Definition: ip.h:57
uint8_t minutes
Definition: date_time.h:51
error_t ftpClientTerminateDataTransfer(FtpClientContext *context)
Terminate data transfer.
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:797
__start_packed struct @183 Ipv6Addr
IPv6 network address.
char_t name[FTP_CLIENT_MAX_FILENAME_LEN+1]
Definition: ftp_client.h:268
#define FTP_CLIENT_BUFFER_SIZE
Definition: ftp_client.h:59
String manipulation helper functions.
#define FTP_CLIENT_MIN_TCP_BUFFER_SIZE
Definition: ftp_client.h:66
error_t ftpClientShutdownConnection(FtpClientSocket *connection)
Shutdown network connection.
error_t ftpClientParsePwdReply(FtpClientContext *context, char_t *path, size_t maxLen)
Parse PWD response.
#define strtok_r(str, delim, p)
error_t ftpClientOpenConnection(FtpClientContext *context, FtpClientSocket *connection, size_t txBufferSize, size_t rxBufferSize)
Open network connection.
#define LSB(x)
Definition: os_port.h:52
error_t ftpClientInitDataTransfer(FtpClientContext *context, bool_t direction)
Initiate data transfer.
error_t ftpClientFormatPortCommand(FtpClientContext *context, const IpAddr *ipAddr, uint16_t port)
Format PORT or EPRT command.
FtpClientState
FTP client states.
Definition: ftp_client.h:186
#define TRUE
Definition: os_port.h:48
int_t socket(int_t family, int_t type, int_t protocol)
Create a socket that is bound to a specific transport service provider.
Definition: bsd_socket.c:106
uint8_t ipAddr[4]
Definition: mib_common.h:185
uint8_t day
Definition: date_time.h:48
Error codes description.
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:216
#define Socket
Definition: socket.h:34
Transport protocol abstraction layer.
FTP client (File Transfer Protocol)
void ftpClientChangeState(FtpClientContext *context, FtpClientState newState)
Update FTP client state.
Helper functions for FTP client.
uint8_t month
Definition: date_time.h:47
void strReplaceChar(char_t *s, char_t oldChar, char_t newChar)
Replace all occurrences of the specified character.
Definition: str.c:134
uint32_t size
Definition: ftp_client.h:270
error_t ftpClientFormatCommand(FtpClientContext *context, const char_t *command, const char_t *argument)
Format FTP command.
#define FTP_CLIENT_MAX_TLS_RX_BUFFER_SIZE
Definition: ftp_client.h:94
error_t ftpClientCheckTimeout(FtpClientContext *context)
Determine whether a timeout error has occurred.
error_t ftpClientFormatPasvCommand(FtpClientContext *context)
Format PASV or EPSV command.
error_t ftpClientReceiveData(FtpClientSocket *connection, void *data, size_t size, size_t *received, uint_t flags)
Receive data using the relevant transport protocol.
#define MIN(a, b)
Definition: os_port.h:60
#define FTP_CLIENT_MIN_TLS_RX_BUFFER_SIZE
Definition: ftp_client.h:87
Success.
Definition: error.h:42
error_t socketConnect(Socket *socket, const IpAddr *remoteIpAddr, uint16_t remotePort)
Establish a connection to a specified socket.
Definition: socket.c:357
error_t
Error codes.
Definition: error.h:40
uint8_t hours
Definition: date_time.h:50
unsigned int uint_t
Definition: compiler_port.h:43
Directory entry.
Definition: ftp_client.h:266
uint8_t token[]
Definition: coap_common.h:181
error_t ftpClientSendData(FtpClientSocket *connection, const void *data, size_t length, size_t *written, uint_t flags)
Send data using the relevant transport protocol.
error_t ftpClientParseDirEntry(char_t *line, FtpDirEntry *dirEntry)
Parse directory entry.
error_t socketGetLocalAddr(Socket *socket, IpAddr *localIpAddr, uint16_t *localPort)
Retrieve the local address for a given socket.
Definition: socket.c:703
#define FTP_REPLY_CODE_1YZ(code)
Definition: ftp_client.h:113
uint16_t port
Definition: dns_common.h:221
#define FTP_CLIENT_TLS_TX_BUFFER_SIZE
Definition: ftp_client.h:80
char_t * ipv6AddrToString(const Ipv6Addr *ipAddr, char_t *str)
Convert a binary IPv6 address to a string representation.
Definition: ipv6.c:2298
error_t ftpClientOpenSecureConnection(FtpClientContext *context, FtpClientSocket *connection, size_t txBufferSize, size_t rxBufferSize)
Open secure connection.
DateTime modified
Definition: ftp_client.h:271
uint8_t length
Definition: dtls_misc.h:140
uint8_t n
#define FALSE
Definition: os_port.h:44
int bool_t
Definition: compiler_port.h:47
void strRemoveTrailingSpace(char_t *s)
Removes all trailing whitespace from a string.
Definition: str.c:107
error_t ftpClientEstablishSecureConnection(FtpClientSocket *connection)
Establish secure connection.
#define TRACE_DEBUG(...)
Definition: debug.h:98