3 * Copyright (c) 1996-2001 Mike Gleason, NCEMRSoft.
11 FTPChdir(const FTPCIPtr cip
, const char *const cdCwd
)
16 return (kErrBadParameter
);
17 if (strcmp(cip
->magic
, kLibraryMagic
))
18 return (kErrBadMagic
);
21 result
= kErrInvalidDirParam
;
22 cip
->errNo
= kErrInvalidDirParam
;
24 if (cdCwd
[0] == '\0') /* But allow FTPChdir(cip, ".") to go through. */
26 else if (strcmp(cdCwd
, "..") == 0)
27 result
= FTPCmd(cip
, "CDUP");
29 result
= FTPCmd(cip
, "CWD %s", cdCwd
);
34 result
= kErrCWDFailed
;
35 cip
->errNo
= kErrCWDFailed
;
46 FTPChmod(const FTPCIPtr cip
, const char *const pattern
, const char *const mode
, const int doGlob
)
51 int onceResult
, batchResult
;
54 return (kErrBadParameter
);
55 if (strcmp(cip
->magic
, kLibraryMagic
))
56 return (kErrBadMagic
);
58 batchResult
= FTPRemoteGlob(cip
, &fileList
, pattern
, doGlob
);
59 if (batchResult
!= kNoErr
)
62 for (batchResult
= kNoErr
, filePtr
= fileList
.first
;
64 filePtr
= filePtr
->next
)
68 batchResult
= kErrBadLineList
;
69 cip
->errNo
= kErrBadLineList
;
72 onceResult
= FTPCmd(cip
, "SITE CHMOD %s %s", mode
, file
);
74 batchResult
= onceResult
;
77 if (onceResult
!= 2) {
78 batchResult
= kErrChmodFailed
;
79 cip
->errNo
= kErrChmodFailed
;
82 DisposeLineListContents(&fileList
);
90 FTPRmdirRecursiveL2(const FTPCIPtr cip
)
97 result
= FTPRemoteGlob(cip
, &fileList
, "**", kGlobYes
);
98 if (result
!= kNoErr
) {
102 for (filePtr
= fileList
.first
;
104 filePtr
= filePtr
->next
)
106 file
= filePtr
->line
;
108 cip
->errNo
= kErrBadLineList
;
112 if ((file
[0] == '.') && ((file
[1] == '\0') || ((file
[1] == '.') && (file
[2] == '\0'))))
113 continue; /* Skip . and .. */
115 if (FTPChdir(cip
, file
) == kNoErr
) {
116 /* It was a directory.
119 result
= FTPRmdirRecursiveL2(cip
);
121 if (FTPChdir(cip
, "..") != kNoErr
) {
122 /* Panic -- we can no longer
123 * cd back to the directory
126 result
= kErrCannotGoToPrevDir
;
127 cip
->errNo
= kErrCannotGoToPrevDir
;
131 if ((result
< 0) && (result
!= kErrGlobNoMatch
))
134 result
= FTPRmdir(cip
, file
, kRecursiveNo
, kGlobNo
);
135 if (result
!= kNoErr
) {
136 /* Well, we couldn't remove the empty
137 * directory. Perhaps we screwed up
138 * and the directory wasn't empty.
143 /* Assume it was a file -- remove it. */
144 result
= FTPDelete(cip
, file
, kRecursiveNo
, kGlobNo
);
145 /* Try continuing to remove the rest,
146 * even if this failed.
150 DisposeLineListContents(&fileList
);
153 } /* FTPRmdirRecursiveL2 */
158 FTPRmdirRecursive(const FTPCIPtr cip
, const char *const dir
)
162 /* Preserve old working directory. */
163 (void) FTPGetCWD(cip
, cip
->buf
, cip
->bufSize
);
165 result
= FTPChdir(cip
, dir
);
166 if (result
!= kNoErr
) {
170 result
= FTPRmdirRecursiveL2(cip
);
172 if (FTPChdir(cip
, cip
->buf
) != kNoErr
) {
173 /* Could not cd back to the original user directory -- bad. */
174 if (result
!= kNoErr
) {
175 result
= kErrCannotGoToPrevDir
;
176 cip
->errNo
= kErrCannotGoToPrevDir
;
181 /* Now rmdir the last node, the root of the tree
182 * we just went through.
184 result2
= FTPRmdir(cip
, dir
, kRecursiveNo
, kGlobNo
);
185 if ((result2
!= kNoErr
) && (result
== kNoErr
))
189 } /* FTPRmdirRecursive */
195 FTPDelete(const FTPCIPtr cip
, const char *const pattern
, const int recurse
, const int doGlob
)
200 int onceResult
, batchResult
;
203 return (kErrBadParameter
);
204 if (strcmp(cip
->magic
, kLibraryMagic
))
205 return (kErrBadMagic
);
207 batchResult
= FTPRemoteGlob(cip
, &fileList
, pattern
, doGlob
);
208 if (batchResult
!= kNoErr
)
209 return (batchResult
);
211 for (batchResult
= kNoErr
, filePtr
= fileList
.first
;
213 filePtr
= filePtr
->next
)
215 file
= filePtr
->line
;
217 batchResult
= kErrBadLineList
;
218 cip
->errNo
= kErrBadLineList
;
221 onceResult
= FTPCmd(cip
, "DELE %s", file
);
222 if (onceResult
< 0) {
223 batchResult
= onceResult
;
226 if (onceResult
!= 2) {
227 if (recurse
!= kRecursiveYes
) {
228 batchResult
= kErrDELEFailed
;
229 cip
->errNo
= kErrDELEFailed
;
231 onceResult
= FTPCmd(cip
, "RMD %s", file
);
232 if (onceResult
< 0) {
233 batchResult
= onceResult
;
236 if (onceResult
!= 2) {
237 onceResult
= FTPRmdirRecursive(cip
, file
);
238 if (onceResult
< 0) {
239 batchResult
= kErrRMDFailed
;
240 cip
->errNo
= kErrRMDFailed
;
246 DisposeLineListContents(&fileList
);
247 return (batchResult
);
254 FTPGetCWD(const FTPCIPtr cip
, char *const newCwd
, const size_t newCwdSize
)
261 return (kErrBadParameter
);
262 if (strcmp(cip
->magic
, kLibraryMagic
))
263 return (kErrBadMagic
);
265 if ((newCwd
== NULL
) || (newCwdSize
== 0)) {
266 result
= kErrInvalidDirParam
;
267 cip
->errNo
= kErrInvalidDirParam
;
271 result
= kErrMallocFailed
;
272 cip
->errNo
= kErrMallocFailed
;
273 Error(cip
, kDontPerror
, "Malloc failed.\n");
275 result
= RCmd(cip
, rp
, "PWD");
277 if ((r
= strrchr(rp
->msg
.first
->line
, '"')) != NULL
) {
278 /* "xxxx" is current directory.
279 * Strip out just the xxxx to copy into the remote cwd.
281 l
= strchr(rp
->msg
.first
->line
, '"');
282 if ((l
!= NULL
) && (l
!= r
)) {
285 (void) Strncpy(newCwd
, l
, newCwdSize
);
286 *r
= '"'; /* Restore, so response prints correctly. */
289 /* xxxx is current directory.
292 if ((r
= strchr(rp
->msg
.first
->line
, ' ')) != NULL
) {
294 (void) Strncpy(newCwd
, (rp
->msg
.first
->line
), newCwdSize
);
295 *r
= ' '; /* Restore, so response prints correctly. */
299 } else if (result
> 0) {
300 result
= kErrPWDFailed
;
301 cip
->errNo
= kErrPWDFailed
;
303 DoneWithResponse(cip
, rp
);
313 FTPChdirAndGetCWD(const FTPCIPtr cip
, const char *const cdCwd
, char *const newCwd
, const size_t newCwdSize
)
320 return (kErrBadParameter
);
321 if (strcmp(cip
->magic
, kLibraryMagic
))
322 return (kErrBadMagic
);
324 if ((newCwd
== NULL
) || (cdCwd
== NULL
)) {
325 result
= kErrInvalidDirParam
;
326 cip
->errNo
= kErrInvalidDirParam
;
328 if (cdCwd
[0] == '\0') { /* But allow FTPChdir(cip, ".") to go through. */
329 result
= FTPGetCWD(cip
, newCwd
, newCwdSize
);
334 result
= kErrMallocFailed
;
335 cip
->errNo
= kErrMallocFailed
;
336 Error(cip
, kDontPerror
, "Malloc failed.\n");
338 if (strcmp(cdCwd
, "..") == 0)
339 result
= RCmd(cip
, rp
, "CDUP");
341 result
= RCmd(cip
, rp
, "CWD %s", cdCwd
);
343 l
= strchr(rp
->msg
.first
->line
, '"');
344 if ((l
== rp
->msg
.first
->line
) && ((r
= strrchr(rp
->msg
.first
->line
, '"')) != NULL
) && (l
!= r
)) {
345 /* "xxxx" is current directory.
346 * Strip out just the xxxx to copy into the remote cwd.
348 * This is nice because we didn't have to do a PWD.
352 (void) Strncpy(newCwd
, l
, newCwdSize
);
353 *r
= '"'; /* Restore, so response prints correctly. */
354 DoneWithResponse(cip
, rp
);
357 DoneWithResponse(cip
, rp
);
358 result
= FTPGetCWD(cip
, newCwd
, newCwdSize
);
360 } else if (result
> 0) {
361 result
= kErrCWDFailed
;
362 cip
->errNo
= kErrCWDFailed
;
363 DoneWithResponse(cip
, rp
);
365 DoneWithResponse(cip
, rp
);
370 } /* FTPChdirAndGetCWD */
376 FTPChdir3(FTPCIPtr cip
, const char *const cdCwd
, char *const newCwd
, const size_t newCwdSize
, int flags
)
384 return (kErrBadParameter
);
385 if (strcmp(cip
->magic
, kLibraryMagic
))
386 return (kErrBadMagic
);
389 result
= kErrInvalidDirParam
;
390 cip
->errNo
= kErrInvalidDirParam
;
394 if (flags
== kChdirOnly
)
395 return (FTPChdir(cip
, cdCwd
));
396 if (flags
== kChdirAndGetCWD
) {
397 return (FTPChdirAndGetCWD(cip
, cdCwd
, newCwd
, newCwdSize
));
398 } else if (flags
== kChdirAndMkdir
) {
399 result
= FTPMkdir(cip
, cdCwd
, kRecursiveYes
);
400 if (result
== kNoErr
)
401 result
= FTPChdir(cip
, cdCwd
);
403 } else if (flags
== (kChdirAndMkdir
|kChdirAndGetCWD
)) {
404 result
= FTPMkdir(cip
, cdCwd
, kRecursiveYes
);
405 if (result
== kNoErr
)
406 result
= FTPChdirAndGetCWD(cip
, cdCwd
, newCwd
, newCwdSize
);
410 /* else: (flags | kChdirOneSubdirAtATime) == true */
413 cp
[cip
->bufSize
- 1] = '\0';
414 (void) Strncpy(cip
->buf
, cdCwd
, cip
->bufSize
);
415 if (cp
[cip
->bufSize
- 1] != '\0')
416 return (kErrBadParameter
);
418 mkd
= (flags
& kChdirAndMkdir
);
419 pwd
= (flags
& kChdirAndGetCWD
);
421 if ((cdCwd
[0] == '\0') || (strcmp(cdCwd
, ".") == 0)) {
423 if (flags
== kChdirAndGetCWD
)
424 result
= FTPGetCWD(cip
, newCwd
, newCwdSize
);
431 cp
= StrFindLocalPathDelim(cp
);
433 /* If this is the first slash in an absolute
434 * path, then startcp will be empty. We will
435 * use this below to treat this as the root
442 if (strcmp(startcp
, ".") == 0) {
444 if ((lastSubDir
!= 0) && (pwd
!= 0))
445 result
= FTPGetCWD(cip
, newCwd
, newCwdSize
);
446 } else if ((lastSubDir
!= 0) && (pwd
!= 0)) {
447 result
= FTPChdirAndGetCWD(cip
, (*startcp
!= '\0') ? startcp
: "/", newCwd
, newCwdSize
);
449 result
= FTPChdir(cip
, (*startcp
!= '\0') ? startcp
: "/");
452 if ((mkd
!= 0) && (*startcp
!= '\0')) {
453 if (FTPCmd(cip
, "MKD %s", startcp
) == 2) {
454 result
= FTPChdir(cip
, startcp
);
456 /* couldn't change nor create */
463 } while ((!lastSubDir
) && (result
== 0));
472 FTPMkdir2(const FTPCIPtr cip
, const char *const newDir
, const int recurse
, const char *const curDir
)
475 char *cp
, *newTreeStart
, *cp2
;
481 return (kErrBadParameter
);
482 if (strcmp(cip
->magic
, kLibraryMagic
))
483 return (kErrBadMagic
);
485 if ((newDir
== NULL
) || (newDir
[0] == '\0')) {
486 cip
->errNo
= kErrInvalidDirParam
;
487 return (kErrInvalidDirParam
);
490 /* Preserve old working directory. */
491 if ((curDir
== NULL
) || (curDir
[0] == '\0')) {
492 /* This hack is nice so you can eliminate an
493 * unnecessary "PWD" command on the server,
494 * since if you already knew what directory
495 * you're in. We want to minimize the number
496 * of client-server exchanges when feasible.
498 (void) FTPGetCWD(cip
, cip
->buf
, cip
->bufSize
);
501 result
= FTPChdir(cip
, newDir
);
502 if (result
== kNoErr
) {
503 /* Directory already exists -- but we
504 * must now change back to where we were.
506 result2
= FTPChdir(cip
, ((curDir
== NULL
) || (curDir
[0] == '\0')) ? cip
->buf
: curDir
);
508 result
= kErrCannotGoToPrevDir
;
509 cip
->errNo
= kErrCannotGoToPrevDir
;
513 /* Don't need to create it. */
517 if (recurse
== kRecursiveNo
) {
518 result
= FTPCmd(cip
, "MKD %s", newDir
);
521 Error(cip
, kDontPerror
, "MKD %s failed; [%s]\n", newDir
, cip
->lastFTPCmdResultStr
);
522 result
= kErrMKDFailed
;
523 cip
->errNo
= kErrMKDFailed
;
530 (void) STRNCPY(dir
, newDir
);
532 /* Strip trailing slashes. */
533 cp
= dir
+ strlen(dir
) - 1;
536 if ((newDir
== NULL
) || (newDir
[0] == '\0')) {
537 cip
->errNo
= kErrInvalidDirParam
;
538 result
= kErrInvalidDirParam
;
542 if ((*cp
!= '/') && (*cp
!= '\\')) {
548 (void) STRNCPY(dir2
, dir
);
550 if ((strrchr(dir
, '/') == dir
) || (strrchr(dir
, '\\') == dir
)) {
551 /* Special case "mkdir /subdir" */
552 result
= FTPCmd(cip
, "MKD %s", dir
);
557 Error(cip
, kDontPerror
, "MKD %s failed; [%s]\n", dir
, cip
->lastFTPCmdResultStr
);
558 result
= kErrMKDFailed
;
559 cip
->errNo
= kErrMKDFailed
;
562 /* Haven't chdir'ed, don't need to goto goback. */
567 cp
= strrchr(dir
, '/');
569 cp
= strrchr(dir
, '\\');
571 cp
= dir
+ strlen(dir
) - 1;
572 if (dir
[0] == '\0') {
573 result
= kErrMKDFailed
;
574 cip
->errNo
= kErrMKDFailed
;
577 /* Note: below we will refer to cp + 1
578 * which is why we set cp to point to
579 * the byte before the array begins!
585 result
= kErrMKDFailed
;
586 cip
->errNo
= kErrMKDFailed
;
590 result
= FTPChdir(cip
, dir
);
592 break; /* Found a valid parent dir. */
593 /* from this point, we need to preserve old dir. */
597 newTreeStart
= dir2
+ ((cp
+ 1) - dir
);
598 for (cp
= newTreeStart
; ; ) {
600 cp
= strchr(cp2
, '/');
603 cp
= strchr(cp2
, '\\');
608 /* Done, if they did "mkdir /tmp/dir/" */
612 result
= FTPCmd(cip
, "MKD %s", newTreeStart
);
617 Error(cip
, kDontPerror
, "Cwd=%s; MKD %s failed; [%s]\n", cip
->buf
, newTreeStart
, cip
->lastFTPCmdResultStr
);
618 result
= kErrMKDFailed
;
619 cip
->errNo
= kErrMKDFailed
;
623 break; /* No more to make, done. */
629 result2
= FTPChdir(cip
, ((curDir
== NULL
) || (curDir
[0] == '\0')) ? cip
->buf
: curDir
);
630 if ((result
== 0) && (result2
< 0)) {
631 result
= kErrCannotGoToPrevDir
;
632 cip
->errNo
= kErrCannotGoToPrevDir
;
641 FTPMkdir(const FTPCIPtr cip
, const char *const newDir
, const int recurse
)
643 return (FTPMkdir2(cip
, newDir
, recurse
, NULL
));
649 FTPFileModificationTime(const FTPCIPtr cip
, const char *const file
, time_t *const mdtm
)
655 return (kErrBadParameter
);
656 if (strcmp(cip
->magic
, kLibraryMagic
))
657 return (kErrBadMagic
);
659 if ((mdtm
== NULL
) || (file
== NULL
))
660 return (kErrBadParameter
);
661 *mdtm
= kModTimeUnknown
;
663 if (cip
->hasMDTM
== kCommandNotAvailable
) {
664 cip
->errNo
= kErrMDTMNotAvailable
;
665 result
= kErrMDTMNotAvailable
;
669 result
= kErrMallocFailed
;
670 cip
->errNo
= kErrMallocFailed
;
671 Error(cip
, kDontPerror
, "Malloc failed.\n");
673 result
= RCmd(cip
, rp
, "MDTM %s", file
);
675 DoneWithResponse(cip
, rp
);
677 } else if (strncmp(rp
->msg
.first
->line
, "19100", 5) == 0) {
678 Error(cip
, kDontPerror
, "Warning: Server has Y2K Bug in \"MDTM\" command.\n");
679 cip
->errNo
= kErrMDTMFailed
;
680 result
= kErrMDTMFailed
;
681 } else if (result
== 2) {
682 *mdtm
= UnMDTMDate(rp
->msg
.first
->line
);
683 cip
->hasMDTM
= kCommandAvailable
;
685 } else if (UNIMPLEMENTED_CMD(rp
->code
)) {
686 cip
->hasMDTM
= kCommandNotAvailable
;
687 cip
->errNo
= kErrMDTMNotAvailable
;
688 result
= kErrMDTMNotAvailable
;
690 cip
->errNo
= kErrMDTMFailed
;
691 result
= kErrMDTMFailed
;
693 DoneWithResponse(cip
, rp
);
697 } /* FTPFileModificationTime */
703 FTPRename(const FTPCIPtr cip
, const char *const oldname
, const char *const newname
)
708 return (kErrBadParameter
);
709 if (strcmp(cip
->magic
, kLibraryMagic
))
710 return (kErrBadMagic
);
711 if ((oldname
== NULL
) || (oldname
[0] == '\0'))
712 return (kErrBadParameter
);
713 if ((newname
== NULL
) || (oldname
[0] == '\0'))
714 return (kErrBadParameter
);
717 result
= FTPCmd(cip
, "RNFR %s", oldname
);
721 cip
->errNo
= kErrRenameFailed
;
725 result
= FTPCmd(cip
, "RNTO %s", newname
);
729 cip
->errNo
= kErrRenameFailed
;
739 FTPRemoteHelp(const FTPCIPtr cip
, const char *const pattern
, const LineListPtr llp
)
744 if ((cip
== NULL
) || (llp
== NULL
))
745 return (kErrBadParameter
);
746 if (strcmp(cip
->magic
, kLibraryMagic
))
747 return (kErrBadMagic
);
752 result
= kErrMallocFailed
;
753 cip
->errNo
= kErrMallocFailed
;
754 Error(cip
, kDontPerror
, "Malloc failed.\n");
756 if ((pattern
== NULL
) || (*pattern
== '\0'))
757 result
= RCmd(cip
, rp
, "HELP");
759 result
= RCmd(cip
, rp
, "HELP %s", pattern
);
761 DoneWithResponse(cip
, rp
);
763 } else if (result
== 2) {
764 if (CopyLineList(llp
, &rp
->msg
) < 0) {
765 result
= kErrMallocFailed
;
766 cip
->errNo
= kErrMallocFailed
;
767 Error(cip
, kDontPerror
, "Malloc failed.\n");
772 cip
->errNo
= kErrHELPFailed
;
773 result
= kErrHELPFailed
;
775 DoneWithResponse(cip
, rp
);
778 } /* FTPRemoteHelp */
784 FTPRmdir(const FTPCIPtr cip
, const char *const pattern
, const int recurse
, const int doGlob
)
789 int onceResult
, batchResult
;
792 return (kErrBadParameter
);
793 if (strcmp(cip
->magic
, kLibraryMagic
))
794 return (kErrBadMagic
);
796 batchResult
= FTPRemoteGlob(cip
, &fileList
, pattern
, doGlob
);
797 if (batchResult
!= kNoErr
)
798 return (batchResult
);
800 for (batchResult
= kNoErr
, filePtr
= fileList
.first
;
802 filePtr
= filePtr
->next
)
804 file
= filePtr
->line
;
806 batchResult
= kErrBadLineList
;
807 cip
->errNo
= kErrBadLineList
;
810 onceResult
= FTPCmd(cip
, "RMD %s", file
);
811 if (onceResult
< 0) {
812 batchResult
= onceResult
;
815 if (onceResult
!= 2) {
816 if (recurse
== kRecursiveYes
) {
817 onceResult
= FTPRmdirRecursive(cip
, file
);
818 if (onceResult
< 0) {
819 batchResult
= kErrRMDFailed
;
820 cip
->errNo
= kErrRMDFailed
;
823 batchResult
= kErrRMDFailed
;
824 cip
->errNo
= kErrRMDFailed
;
828 DisposeLineListContents(&fileList
);
829 return (batchResult
);
836 FTPSetTransferType(const FTPCIPtr cip
, int type
)
841 return (kErrBadParameter
);
842 if (strcmp(cip
->magic
, kLibraryMagic
))
843 return (kErrBadMagic
);
845 if (cip
->curTransferType
!= type
) {
863 /* Yeah, we don't support Tenex. Who cares? */
864 Error(cip
, kDontPerror
, "Bad transfer type [%c].\n", type
);
865 cip
->errNo
= kErrBadTransferType
;
866 return (kErrBadTransferType
);
868 result
= FTPCmd(cip
, "TYPE %c", type
);
870 result
= kErrTYPEFailed
;
871 cip
->errNo
= kErrTYPEFailed
;
874 cip
->curTransferType
= type
;
877 } /* FTPSetTransferType */
882 /* If the remote host supports the SIZE command, we can find out the exact
883 * size of a remote file, depending on the transfer type in use. SIZE
884 * returns different values for ascii and binary modes!
887 FTPFileSize(const FTPCIPtr cip
, const char *const file
, longest_int
*const size
, const int type
)
893 return (kErrBadParameter
);
894 if (strcmp(cip
->magic
, kLibraryMagic
))
895 return (kErrBadMagic
);
897 if ((size
== NULL
) || (file
== NULL
))
898 return (kErrBadParameter
);
899 *size
= kSizeUnknown
;
901 result
= FTPSetTransferType(cip
, type
);
905 if (cip
->hasSIZE
== kCommandNotAvailable
) {
906 cip
->errNo
= kErrSIZENotAvailable
;
907 result
= kErrSIZENotAvailable
;
911 result
= kErrMallocFailed
;
912 cip
->errNo
= kErrMallocFailed
;
913 Error(cip
, kDontPerror
, "Malloc failed.\n");
915 result
= RCmd(cip
, rp
, "SIZE %s", file
);
917 DoneWithResponse(cip
, rp
);
919 } else if (result
== 2) {
920 #if defined(HAVE_LONG_LONG) && defined(SCANF_LONG_LONG)
921 (void) sscanf(rp
->msg
.first
->line
, SCANF_LONG_LONG
, size
);
922 #elif defined(HAVE_LONG_LONG) && defined(HAVE_STRTOQ)
923 *size
= (longest_int
) strtoq(rp
->msg
.first
->line
, NULL
, 0);
925 (void) sscanf(rp
->msg
.first
->line
, "%ld", size
);
927 cip
->hasSIZE
= kCommandAvailable
;
929 } else if (UNIMPLEMENTED_CMD(rp
->code
)) {
930 cip
->hasSIZE
= kCommandNotAvailable
;
931 cip
->errNo
= kErrSIZENotAvailable
;
932 result
= kErrSIZENotAvailable
;
934 cip
->errNo
= kErrSIZEFailed
;
935 result
= kErrSIZEFailed
;
937 DoneWithResponse(cip
, rp
);
947 FTPMListOneFile(const FTPCIPtr cip
, const char *const file
, const MLstItemPtr mlip
)
952 /* We do a special check for older versions of NcFTPd which
953 * are based off of an incompatible previous version of IETF
956 * Roxen also seems to be way outdated, where MLST was on the
957 * data connection among other things.
961 (cip
->hasMLST
== kCommandNotAvailable
) ||
962 ((cip
->serverType
== kServerTypeNcFTPd
) && (cip
->ietfCompatLevel
< 19981201)) ||
963 (cip
->serverType
== kServerTypeRoxen
)
965 cip
->errNo
= kErrMLSTNotAvailable
;
971 result
= cip
->errNo
= kErrMallocFailed
;
972 Error(cip
, kDontPerror
, "Malloc failed.\n");
974 result
= RCmd(cip
, rp
, "MLST %s", file
);
977 (rp
->msg
.first
->line
!= NULL
) &&
978 (rp
->msg
.first
->next
!= NULL
) &&
979 (rp
->msg
.first
->next
->line
!= NULL
)
981 result
= UnMlsT(rp
->msg
.first
->next
->line
, mlip
);
983 cip
->errNo
= result
= kErrInvalidMLSTResponse
;
985 } else if (UNIMPLEMENTED_CMD(rp
->code
)) {
986 cip
->hasMLST
= kCommandNotAvailable
;
987 cip
->errNo
= kErrMLSTNotAvailable
;
988 result
= kErrMLSTNotAvailable
;
990 cip
->errNo
= kErrMLSTFailed
;
991 result
= kErrMLSTFailed
;
993 DoneWithResponse(cip
, rp
);
997 } /* FTPMListOneFile */
1002 /* We only use STAT to see if files or directories exist.
1003 * But since it is so rarely used in the wild, we need to
1004 * make sure the server supports the use where we pass
1005 * a pathname as a parameter.
1008 FTPFileExistsStat(const FTPCIPtr cip
, const char *const file
)
1016 return (kErrBadParameter
);
1017 if (strcmp(cip
->magic
, kLibraryMagic
))
1018 return (kErrBadMagic
);
1021 return (kErrBadParameter
);
1023 if (cip
->STATfileParamWorks
== kCommandNotAvailable
) {
1024 cip
->errNo
= result
= kErrSTATwithFileNotAvailable
;
1028 if (cip
->STATfileParamWorks
== kCommandAvailabilityUnknown
) {
1029 rp
= InitResponse();
1031 result
= kErrMallocFailed
;
1032 cip
->errNo
= kErrMallocFailed
;
1033 Error(cip
, kDontPerror
, "Malloc failed.\n");
1038 /* First, make sure that when we STAT a pathname
1039 * that does not exist, that we get an error back.
1041 * We also assume that a valid STAT response has
1042 * at least 3 lines of response text, typically
1043 * a "start" line, intermediate data, and then
1046 * We also can see a one-line case.
1048 result
= RCmd(cip
, rp
, "STAT %s", "NoSuchFile");
1049 if ((result
== 2) && ((rp
->msg
.nLines
>= 3) || (rp
->msg
.nLines
== 1))) {
1050 /* Hmmm.... it gave back a positive
1051 * response. So STAT <file> does not
1055 (rp
->msg
.first
->next
!= NULL
) &&
1056 (rp
->msg
.first
->next
->line
!= NULL
) &&
1058 (strstr(rp
->msg
.first
->next
->line
, "o such file") != NULL
) ||
1059 (strstr(rp
->msg
.first
->next
->line
, "ot found") != NULL
)
1062 /* OK, while we didn't want a 200
1063 * level response, some servers,
1064 * like wu-ftpd print an error
1065 * message "No such file or
1066 * directory" which we can special
1071 cip
->STATfileParamWorks
= kCommandNotAvailable
;
1072 cip
->errNo
= result
= kErrSTATwithFileNotAvailable
;
1073 DoneWithResponse(cip
, rp
);
1077 DoneWithResponse(cip
, rp
);
1079 /* We can't assume that we can simply say STAT rootdir/firstfile,
1080 * since the remote host may not be using / as a directory
1081 * delimiter. So we have to change to the root directory
1082 * and then do the STAT on that file.
1085 (FTPGetCWD(cip
, savedCwd
, sizeof(savedCwd
)) != kNoErr
) ||
1086 (FTPChdir(cip
, cip
->startingWorkingDirectory
) != kNoErr
)
1088 return (cip
->errNo
);
1091 /* OK, we get an error when we stat
1092 * a non-existant file, but now we need to
1093 * see if we get a positive reply when
1094 * we stat a file that does exist.
1096 * To do this, we list the root directory,
1097 * which we assume has one or more items.
1098 * If it doesn't, the user can't do anything
1099 * anyway. Then we stat the first item
1100 * we found to see if STAT says it exists.
1103 ((result
= FTPListToMemory2(cip
, "", &fileList
, "", 0, (int *) 0)) < 0) ||
1104 (fileList
.last
== NULL
) ||
1105 (fileList
.last
->line
== NULL
)
1107 /* Hmmm... well, in any case we can't use STAT. */
1108 cip
->STATfileParamWorks
= kCommandNotAvailable
;
1109 cip
->errNo
= result
= kErrSTATwithFileNotAvailable
;
1110 DisposeLineListContents(&fileList
);
1111 (void) FTPChdir(cip
, savedCwd
);
1115 rp
= InitResponse();
1117 result
= kErrMallocFailed
;
1118 cip
->errNo
= kErrMallocFailed
;
1119 Error(cip
, kDontPerror
, "Malloc failed.\n");
1120 DisposeLineListContents(&fileList
);
1121 (void) FTPChdir(cip
, savedCwd
);
1126 result
= RCmd(cip
, rp
, "STAT %s", fileList
.last
->line
);
1127 DisposeLineListContents(&fileList
);
1129 if ((result
!= 2) || (rp
->msg
.nLines
== 2)) {
1130 /* Hmmm.... it gave back a negative
1131 * response. So STAT <file> does not
1134 cip
->STATfileParamWorks
= kCommandNotAvailable
;
1135 cip
->errNo
= result
= kErrSTATwithFileNotAvailable
;
1136 DoneWithResponse(cip
, rp
);
1137 (void) FTPChdir(cip
, savedCwd
);
1140 (rp
->msg
.first
->next
!= NULL
) &&
1141 (rp
->msg
.first
->next
->line
!= NULL
) &&
1143 (strstr(rp
->msg
.first
->next
->line
, "o such file") != NULL
) ||
1144 (strstr(rp
->msg
.first
->next
->line
, "ot found") != NULL
)
1147 /* Same special-case of the second line of STAT response. */
1148 cip
->STATfileParamWorks
= kCommandNotAvailable
;
1149 cip
->errNo
= result
= kErrSTATwithFileNotAvailable
;
1150 DoneWithResponse(cip
, rp
);
1151 (void) FTPChdir(cip
, savedCwd
);
1154 DoneWithResponse(cip
, rp
);
1155 cip
->STATfileParamWorks
= kCommandAvailable
;
1157 /* Don't forget to change back to the original directory. */
1158 (void) FTPChdir(cip
, savedCwd
);
1161 rp
= InitResponse();
1163 result
= kErrMallocFailed
;
1164 cip
->errNo
= kErrMallocFailed
;
1165 Error(cip
, kDontPerror
, "Malloc failed.\n");
1169 result
= RCmd(cip
, rp
, "STAT %s", file
);
1172 if (((rp
->msg
.nLines
>= 3) || (rp
->msg
.nLines
== 1))) {
1174 (rp
->msg
.first
->next
!= NULL
) &&
1175 (rp
->msg
.first
->next
->line
!= NULL
) &&
1177 (strstr(rp
->msg
.first
->next
->line
, "o such file") != NULL
) ||
1178 (strstr(rp
->msg
.first
->next
->line
, "ot found") != NULL
)
1181 cip
->errNo
= kErrSTATFailed
;
1182 result
= kErrSTATFailed
;
1186 } else if (rp
->msg
.nLines
== 2) {
1187 cip
->errNo
= kErrSTATFailed
;
1188 result
= kErrSTATFailed
;
1193 cip
->errNo
= kErrSTATFailed
;
1194 result
= kErrSTATFailed
;
1196 DoneWithResponse(cip
, rp
);
1198 } /* FTPFileExistsStat */
1203 /* We only use STAT to see if files or directories exist.
1204 * But since it is so rarely used in the wild, we need to
1205 * make sure the server supports the use where we pass
1206 * a pathname as a parameter.
1209 FTPFileExistsNlst(const FTPCIPtr cip
, const char *const file
)
1212 LineList fileList
, rootFileList
;
1216 return (kErrBadParameter
);
1217 if (strcmp(cip
->magic
, kLibraryMagic
))
1218 return (kErrBadMagic
);
1221 return (kErrBadParameter
);
1223 if (cip
->NLSTfileParamWorks
== kCommandNotAvailable
) {
1224 cip
->errNo
= result
= kErrNLSTwithFileNotAvailable
;
1228 if (cip
->NLSTfileParamWorks
== kCommandAvailabilityUnknown
) {
1229 /* First, make sure that when we NLST a pathname
1230 * that does not exist, that we get an error back.
1232 * We also assume that a valid NLST response has
1233 * at least 3 lines of response text, typically
1234 * a "start" line, intermediate data, and then
1237 * We also can see a one-line case.
1240 ((FTPListToMemory2(cip
, "NoSuchFile", &fileList
, "", 0, (int *) 0)) == kNoErr
) &&
1241 (fileList
.nLines
>= 1) &&
1242 (strstr(fileList
.last
->line
, "o such file") == NULL
) &&
1243 (strstr(fileList
.last
->line
, "ot found") == NULL
) &&
1244 (strstr(fileList
.last
->line
, "o Such File") == NULL
) &&
1245 (strstr(fileList
.last
->line
, "ot Found") == NULL
)
1248 cip
->NLSTfileParamWorks
= kCommandNotAvailable
;
1249 cip
->errNo
= result
= kErrNLSTwithFileNotAvailable
;
1250 DisposeLineListContents(&fileList
);
1253 DisposeLineListContents(&fileList
);
1255 /* We can't assume that we can simply say NLST rootdir/firstfile,
1256 * since the remote host may not be using / as a directory
1257 * delimiter. So we have to change to the root directory
1258 * and then do the NLST on that file.
1261 (FTPGetCWD(cip
, savedCwd
, sizeof(savedCwd
)) != kNoErr
) ||
1262 (FTPChdir(cip
, cip
->startingWorkingDirectory
) != kNoErr
)
1264 return (cip
->errNo
);
1267 /* OK, we get an error when we list
1268 * a non-existant file, but now we need to
1269 * see if we get a positive reply when
1270 * we stat a file that does exist.
1272 * To do this, we list the root directory,
1273 * which we assume has one or more items.
1274 * If it doesn't, the user can't do anything
1275 * anyway. Then we do the first item
1276 * we found to see if NLST says it exists.
1279 ((result
= FTPListToMemory2(cip
, "", &rootFileList
, "", 0, (int *) 0)) < 0) ||
1280 (rootFileList
.last
== NULL
) ||
1281 (rootFileList
.last
->line
== NULL
)
1283 /* Hmmm... well, in any case we can't use NLST. */
1284 cip
->NLSTfileParamWorks
= kCommandNotAvailable
;
1285 cip
->errNo
= result
= kErrNLSTwithFileNotAvailable
;
1286 DisposeLineListContents(&rootFileList
);
1287 (void) FTPChdir(cip
, savedCwd
);
1292 ((FTPListToMemory2(cip
, rootFileList
.last
->line
, &fileList
, "", 0, (int *) 0)) == kNoErr
) &&
1293 (fileList
.nLines
>= 1) &&
1294 (strstr(fileList
.last
->line
, "o such file") == NULL
) &&
1295 (strstr(fileList
.last
->line
, "ot found") == NULL
) &&
1296 (strstr(fileList
.last
->line
, "o Such File") == NULL
) &&
1297 (strstr(fileList
.last
->line
, "ot Found") == NULL
)
1300 /* Good. We listed the item. */
1301 DisposeLineListContents(&fileList
);
1302 DisposeLineListContents(&rootFileList
);
1303 cip
->NLSTfileParamWorks
= kCommandAvailable
;
1305 /* Don't forget to change back to the original directory. */
1306 (void) FTPChdir(cip
, savedCwd
);
1308 cip
->NLSTfileParamWorks
= kCommandNotAvailable
;
1309 cip
->errNo
= result
= kErrNLSTwithFileNotAvailable
;
1310 DisposeLineListContents(&fileList
);
1311 DisposeLineListContents(&rootFileList
);
1312 (void) FTPChdir(cip
, savedCwd
);
1317 /* Check the requested item. */
1318 InitLineList(&fileList
);
1320 ((FTPListToMemory2(cip
, file
, &fileList
, "", 0, (int *) 0)) == kNoErr
) &&
1321 (fileList
.nLines
>= 1) &&
1322 (strstr(fileList
.last
->line
, "o such file") == NULL
) &&
1323 (strstr(fileList
.last
->line
, "ot found") == NULL
) &&
1324 (strstr(fileList
.last
->line
, "o Such File") == NULL
) &&
1325 (strstr(fileList
.last
->line
, "ot Found") == NULL
)
1328 /* The item existed. */
1331 cip
->errNo
= kErrNLSTFailed
;
1332 result
= kErrNLSTFailed
;
1335 DisposeLineListContents(&fileList
);
1337 } /* FTPFileExistsNlst*/
1342 /* This functions goes to a great deal of trouble to try and determine if the
1343 * remote file specified exists. Newer servers support commands that make
1344 * it relatively inexpensive to find the answer, but older servers do not
1345 * provide a standard way. This means we may try a whole bunch of things,
1346 * but the good news is that the library saves information about which things
1347 * worked so if you do this again it uses the methods that work.
1350 FTPFileExists2(const FTPCIPtr cip
, const char *const file
, const int tryMDTM
, const int trySIZE
, const int tryMLST
, const int trySTAT
, const int tryNLST
)
1358 result
= FTPFileModificationTime(cip
, file
, &mdtm
);
1359 if (result
== kNoErr
)
1361 if (result
== kErrMDTMFailed
) {
1362 cip
->errNo
= kErrNoSuchFileOrDirectory
;
1363 return (kErrNoSuchFileOrDirectory
);
1365 /* else keep going */
1369 result
= FTPFileSize(cip
, file
, &size
, kTypeBinary
);
1370 if (result
== kNoErr
)
1372 /* SIZE could fail if the server does
1373 * not support it for directories.
1375 * if (result == kErrSIZEFailed)
1376 * return (kErrNoSuchFileOrDirectory);
1378 /* else keep going */
1383 result
= FTPMListOneFile(cip
, file
, &mlsInfo
);
1384 if (result
== kNoErr
)
1386 if (result
== kErrMLSTFailed
) {
1387 cip
->errNo
= kErrNoSuchFileOrDirectory
;
1388 return (kErrNoSuchFileOrDirectory
);
1390 /* else keep going */
1394 result
= FTPFileExistsStat(cip
, file
);
1395 if (result
== kNoErr
)
1397 if (result
== kErrSTATFailed
) {
1398 cip
->errNo
= kErrNoSuchFileOrDirectory
;
1399 return (kErrNoSuchFileOrDirectory
);
1401 /* else keep going */
1405 result
= FTPFileExistsNlst(cip
, file
);
1406 if (result
== kNoErr
)
1408 if (result
== kErrNLSTFailed
) {
1409 cip
->errNo
= kErrNoSuchFileOrDirectory
;
1410 return (kErrNoSuchFileOrDirectory
);
1412 /* else keep going */
1415 cip
->errNo
= kErrCantTellIfFileExists
;
1416 return (kErrCantTellIfFileExists
);
1417 } /* FTPFileExists2 */
1423 FTPFileExists(const FTPCIPtr cip
, const char *const file
)
1425 return (FTPFileExists2(cip
, file
, 1, 1, 1, 1, 1));
1426 } /* FTPFileExists */
1433 FTPFileSizeAndModificationTime(const FTPCIPtr cip
, const char *const file
, longest_int
*const size
, const int type
, time_t *const mdtm
)
1439 return (kErrBadParameter
);
1440 if (strcmp(cip
->magic
, kLibraryMagic
))
1441 return (kErrBadMagic
);
1443 if ((mdtm
== NULL
) || (size
== NULL
) || (file
== NULL
))
1444 return (kErrBadParameter
);
1446 *mdtm
= kModTimeUnknown
;
1447 *size
= kSizeUnknown
;
1449 result
= FTPSetTransferType(cip
, type
);
1453 result
= FTPMListOneFile(cip
, file
, &mlsInfo
);
1455 /* Do it the regular way, where
1456 * we do a SIZE and then a MDTM.
1458 result
= FTPFileSize(cip
, file
, size
, type
);
1461 result
= FTPFileModificationTime(cip
, file
, mdtm
);
1464 *mdtm
= mlsInfo
.ftime
;
1465 *size
= mlsInfo
.fsize
;
1469 } /* FTPFileSizeAndModificationTime */
1475 FTPFileType(const FTPCIPtr cip
, const char *const file
, int *const ftype
)
1481 return (kErrBadParameter
);
1482 if (strcmp(cip
->magic
, kLibraryMagic
))
1483 return (kErrBadMagic
);
1485 if ((file
== NULL
) || (file
[0] == '\0')) {
1486 cip
->errNo
= kErrBadParameter
;
1487 return (kErrBadParameter
);
1490 if (ftype
== NULL
) {
1491 cip
->errNo
= kErrBadParameter
;
1492 return (kErrBadParameter
);
1496 result
= FTPMListOneFile(cip
, file
, &mlsInfo
);
1497 if (result
== kNoErr
) {
1498 *ftype
= mlsInfo
.ftype
;
1502 /* Preserve old working directory. */
1503 (void) FTPGetCWD(cip
, cip
->buf
, cip
->bufSize
);
1505 result
= FTPChdir(cip
, file
);
1506 if (result
== kNoErr
) {
1508 /* Yes it was a directory, now go back to
1511 (void) FTPChdir(cip
, cip
->buf
);
1513 /* Note: This improperly assumes that we
1514 * will be able to chdir back, which is
1520 result
= FTPFileExists2(cip
, file
, 1, 1, 0, 1, 1);
1521 if (result
!= kErrNoSuchFileOrDirectory
)
1522 result
= kErrFileExistsButCannotDetermineType
;
1531 FTPIsDir(const FTPCIPtr cip
, const char *const dir
)
1536 return (kErrBadParameter
);
1537 if (strcmp(cip
->magic
, kLibraryMagic
))
1538 return (kErrBadMagic
);
1540 if ((dir
== NULL
) || (dir
[0] == '\0')) {
1541 cip
->errNo
= kErrInvalidDirParam
;
1542 return (kErrInvalidDirParam
);
1545 result
= FTPFileType(cip
, dir
, &ftype
);
1546 if ((result
== kNoErr
) || (result
== kErrFileExistsButCannotDetermineType
)) {
1558 FTPIsRegularFile(const FTPCIPtr cip
, const char *const file
)
1563 return (kErrBadParameter
);
1564 if (strcmp(cip
->magic
, kLibraryMagic
))
1565 return (kErrBadMagic
);
1567 if ((file
== NULL
) || (file
[0] == '\0')) {
1568 cip
->errNo
= kErrBadParameter
;
1569 return (kErrBadParameter
);
1572 result
= FTPFileType(cip
, file
, &ftype
);
1573 if ((result
== kNoErr
) || (result
== kErrFileExistsButCannotDetermineType
)) {
1579 } /* FTPIsRegularFile */
1585 FTPSymlink(const FTPCIPtr cip
, const char *const lfrom
, const char *const lto
)
1587 if (strcmp(cip
->magic
, kLibraryMagic
))
1588 return (kErrBadMagic
);
1589 if ((cip
== NULL
) || (lfrom
== NULL
) || (lto
== NULL
))
1590 return (kErrBadParameter
);
1591 if ((lfrom
[0] == '\0') || (lto
[0] == '\0'))
1592 return (kErrBadParameter
);
1593 if (FTPCmd(cip
, "SITE SYMLINK %s %s", lfrom
, lto
) == 2)
1595 return (kErrSYMLINKFailed
);
1602 FTPUmask(const FTPCIPtr cip
, const char *const umsk
)
1605 return (kErrBadParameter
);
1606 if (strcmp(cip
->magic
, kLibraryMagic
))
1607 return (kErrBadMagic
);
1608 if ((umsk
== NULL
) || (umsk
[0] == '\0'))
1609 return (kErrBadParameter
);
1610 if (FTPCmd(cip
, "SITE UMASK %s", umsk
) == 2)
1612 return (kErrUmaskFailed
);
1619 GmTimeStr(char *const dst
, const size_t dstsize
, time_t t
)
1628 #ifdef HAVE_SNPRINTF
1629 buf
[sizeof(buf
) - 1] = '\0';
1630 (void) snprintf(buf
, sizeof(buf
) - 1, "%04d%02d%02d%02d%02d%02d",
1632 (void) sprintf(buf
, "%04d%02d%02d%02d%02d%02d",
1634 gtp
->tm_year
+ 1900,
1641 (void) Strncpy(dst
, buf
, dstsize
);
1649 FTPUtime(const FTPCIPtr cip
, const char *const file
, time_t actime
, time_t modtime
, time_t crtime
)
1651 char mstr
[64], astr
[64], cstr
[64];
1656 return (kErrBadParameter
);
1657 if (strcmp(cip
->magic
, kLibraryMagic
))
1658 return (kErrBadMagic
);
1660 if (cip
->hasUTIME
== kCommandNotAvailable
) {
1661 cip
->errNo
= kErrUTIMENotAvailable
;
1662 result
= kErrUTIMENotAvailable
;
1664 if ((actime
== (time_t) 0) || (actime
== (time_t) -1))
1665 (void) time(&actime
);
1666 if ((modtime
== (time_t) 0) || (modtime
== (time_t) -1))
1667 (void) time(&modtime
);
1668 if ((crtime
== (time_t) 0) || (crtime
== (time_t) -1))
1671 (void) GmTimeStr(astr
, sizeof(astr
), actime
);
1672 (void) GmTimeStr(mstr
, sizeof(mstr
), modtime
);
1673 (void) GmTimeStr(cstr
, sizeof(cstr
), crtime
);
1675 rp
= InitResponse();
1677 result
= kErrMallocFailed
;
1678 cip
->errNo
= kErrMallocFailed
;
1679 Error(cip
, kDontPerror
, "Malloc failed.\n");
1681 result
= RCmd(cip
, rp
, "SITE UTIME %s %s %s %s UTC", file
, astr
, mstr
, cstr
);
1683 DoneWithResponse(cip
, rp
);
1685 } else if (result
== 2) {
1686 cip
->hasUTIME
= kCommandAvailable
;
1688 } else if (UNIMPLEMENTED_CMD(rp
->code
)) {
1689 cip
->hasUTIME
= kCommandNotAvailable
;
1690 cip
->errNo
= kErrUTIMENotAvailable
;
1691 result
= kErrUTIMENotAvailable
;
1693 cip
->errNo
= kErrUTIMEFailed
;
1694 result
= kErrUTIMEFailed
;
1696 DoneWithResponse(cip
, rp
);