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