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