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