/*++ Copyright (c) 1994 Microsoft Corporation Module Name: ftpapir.cxx Abstract: Contains the remote-side FTP API worker functions. In each case, the API proper validates the arguments. The worker functions contained herein just perform the requested operation with the supplied arguments. These functions are the remote side of the RPC interface. If the DLL is the abstract0 version (no RPC) then the A forms of the functions simply call the w functions Contents: wFtpFindFirstFile wFtpDeleteFile wFtpRenameFile wFtpOpenFile wFtpCreateDirectory wFtpRemoveDirectory wFtpSetCurrentDirectory wFtpGetCurrentDirectory wFtpCommand wFtpFindNextFile wFtpFindClose wFtpConnect wFtpMakeConnection wFtpDisconnect wFtpReadFile wFtpWriteFile wFtpQueryDataAvailable wFtpCloseFile wFtpFindServerType wFtpGetFileSize Author: Heath Hunnicutt [t-heathh] 13-Jul-1994 Environment: Win32(s) user-level DLL Revision History: 09-Mar-1995 rfirth Created new file/worker functions from functions contained in findfile.c, ftphelp.c --*/ #include #include "ftpapih.h" // // private macros // #define CASE_OF(constant) case constant: return # constant // // private debug functions // #if INET_DEBUG PRIVATE DEBUG_FUNCTION LPSTR InternetMapFtpServerType( IN FTP_SERVER_TYPE ServerType ); #else #define InternetMapFtpServerType(x) (VOID)(x) #endif // INET_DEBUG // // external functions // extern DWORD InbLocalEndCacheWrite( IN HINTERNET hFtpFile, IN LPSTR lpszFileExtension, IN BOOL fNormal ); // // functions // DWORD wFtpFindFirstFile( IN HINTERNET hFtpSession, IN LPCSTR lpszFilespec, OUT LPWIN32_FIND_DATA lpFindFileData OPTIONAL, OUT LPHINTERNET lphInternet ) /*++ Routine Description: Download the remote site's directory listing and parse it into WIN32_FIND_DATA structures that we can pass back to the app. If the FTP session is currently involved in a data transfer, such as a FtpOpenFile()....FtpCloseFile() series of calls, this function will fail. Arguments: hFtpSession - Handle to an FTP session, as returned from FtpOpen() lpszFilespec - Pointer to a string containing a file specification to find. May be empty, but not NULL lpFindFileData - Pointer to a buffer that will contain WIN32_FIND_DATA information when this call succeeds. If this parameter is not supplied, then any find data will be returned via InternetFindNextFile() lphInternet - place to return open find handle Return Value: DWORD Success - ERROR_SUCCESS *lphInternet contains new find handle Failure - ERROR_INVALID_HANDLE The session handle is not recognized ERROR_FTP_TRANSFER_IN_PROGRESS The data connection is already in use ERROR_NO_MORE_FILES The end of the directory listing has been reached ERROR_INTERNET_EXTENDED_ERROR Call InternetGetLastResponseInfo() for the text ERROR_INTERNET_INTERNAL_ERROR Something bad happened --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpFindFirstFile", "%#x, %q, %#x, %#x", hFtpSession, lpszFilespec, lpFindFileData, lphInternet )); LPINTERNET_THREAD_INFO lpThreadInfo = InternetGetThreadInfo(); LPSTR lpBuffer = NULL; DWORD error; if (lpThreadInfo == NULL) { INET_ASSERT(FALSE); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } LPFTP_SESSION_INFO lpSessionInfo; if (!FindFtpSession(hFtpSession, &lpSessionInfo)) { error = ERROR_INVALID_HANDLE; goto quit; } // // acquire the session lock while we check and optionally set the active // find flag // AcquireFtpSessionLock(lpSessionInfo); if (!(lpSessionInfo->Flags & FFTP_FIND_ACTIVE)) { lpSessionInfo->Flags |= FFTP_FIND_ACTIVE; error = ERROR_SUCCESS; } else { error = ERROR_FTP_TRANSFER_IN_PROGRESS; } ReleaseFtpSessionLock(lpSessionInfo); // // if we already have a directory listing on this connection, then we can // not allow another one, until the current listing is cleared out by the // app calling InternetCloseHandle() // if (error != ERROR_SUCCESS) { goto deref_exit; } // // the filespec may have a path component. We assume that any wild-cards // will only be in the filename part. We use the path part in the directory // request and the filename part when parsing the directory output // char pathBuf[INTERNET_MAX_PATH_LENGTH + 1]; LPSTR lpszPathPart; LPSTR lpszFilePart; BOOL isWild; DWORD dwFilePartLength; lpszFilePart = (LPSTR)lpszFilespec; lpszPathPart = NULL; dwFilePartLength = lstrlen(lpszFilePart); if (*lpszFilePart != '\0') { LPSTR pathSeparator; pathSeparator = _memrchr(lpszFilePart, '\\', dwFilePartLength); if (pathSeparator == NULL) { pathSeparator = _memrchr(lpszFilePart, '/', dwFilePartLength); } if (pathSeparator != NULL) { int len = (int) (pathSeparator - lpszFilePart) + 1; if (len < sizeof(pathBuf)) { memcpy(pathBuf, lpszFilePart, len); pathBuf[len] = '\0'; lpszPathPart = pathBuf; lpszFilePart = pathSeparator + 1; DEBUG_PRINT(FTP, INFO, ("lpszPathPart = %q, lpszFilePart = %q\n", lpszPathPart, lpszFilePart )); } } // // determine whether the caller is asking for a fuzzy file match, or // (typically) the request is for the contents of a directory // isWild = IsFilespecWild(lpszFilePart); } else { // // empty string - not asking for wildcard search // isWild = FALSE; } // // and ask the FTP server for the directory listing // FTP_RESPONSE_CODE rcResponse; error = Command(lpSessionInfo, TRUE, FTP_TRANSFER_TYPE_ASCII, &rcResponse, ((lpszPathPart == NULL) && (isWild || (*lpszFilePart == '\0'))) ? "LIST" : "LIST %s", (lpszPathPart == NULL) ? lpszFilePart : isWild ? lpszPathPart : lpszFilespec ); // // quit early if we failed to send the command, or the server didn't // understand it // if (error != ERROR_SUCCESS) { goto cleanup; } // // presumably, the server has sent us a directory listing. Receive it // DWORD bufferLength; DWORD bufferLeft; DWORD bytesReceived; BOOL eof; bufferLength = 0; bufferLeft = 0; bytesReceived = 0; error = lpSessionInfo->socketData->Receive((LPVOID *)&lpBuffer, &bufferLength, &bufferLeft, &bytesReceived, 0, SF_EXPAND | SF_COMPRESS | SF_RECEIVE_ALL | SF_INDICATE, &eof ); // // we are done with the data connection // lpSessionInfo->socketData->Close(); // // quit now if we had an error while receiving // if (error != ERROR_SUCCESS) { goto cleanup; } // // if the previous response was preliminary then get the final response from // the FTP server // if (rcResponse.Major != FTP_RESPONSE_COMPLETE) { error = GetReply(lpSessionInfo, &rcResponse); if (error != ERROR_SUCCESS) { goto cleanup; } // // check response for failure // if (rcResponse.Major != FTP_RESPONSE_COMPLETE) { // // <-- Return "command failed" error code // if(rcResponse.Status == FTP_RESPONSE_ACTION_NOT_TAKEN) { error = ERROR_NO_MORE_FILES; } else { error = ERROR_INTERNET_EXTENDED_ERROR; } goto cleanup; } } if (bytesReceived == 0) { DEBUG_PRINT(WORKER, ERROR, ("ReceiveData() returns 0 bytes\n" )); error = ERROR_NO_MORE_FILES; goto cleanup; } // // trap bad servers which return a not-found message in the data stream. We // only do this if we are not performing a wild-card search (because the // wild-card match will fail to match anything if the target file or path // cannot be found) // LPSTR lpszSearch; DWORD dwSearch; lpszSearch = (lpszPathPart == NULL) ? lpszFilePart : (LPSTR)lpszFilespec; dwSearch = lstrlen(lpszSearch); if (!isWild && (bytesReceived > dwSearch)) { if (!_strnicmp(lpBuffer, lpszSearch, dwSearch) && (lpBuffer[dwSearch] == ':')) { static char testChars[] = {'\r', '\n', '\0'}; LPSTR lpStartOfString = lpBuffer + dwSearch + 1; LPSTR lpEndOfString; for (int i = 0; i < ARRAY_ELEMENTS(testChars); ++i) { lpEndOfString = strchr(lpStartOfString, testChars[i]); if (lpEndOfString != NULL) { break; } } // // we should have found at least one of the target characters // INET_ASSERT(lpEndOfString != NULL); if (lpEndOfString != NULL) { int lengthToTest = (int) (lpEndOfString - lpStartOfString); // // BUGBUG - internationalization? // if (strnistr(lpStartOfString, "not found", lengthToTest) || strnistr(lpStartOfString, "cannot find", lengthToTest)) { error = ERROR_NO_MORE_FILES; goto cleanup; } } else { error = ERROR_INTERNET_INTERNAL_ERROR; goto cleanup; } } } INET_ASSERT(lpBuffer != NULL); INET_ASSERT((int)bytesReceived > 0); error = ParseDirList(lpBuffer, bytesReceived, isWild ? (LPSTR)lpszFilePart : NULL, &lpSessionInfo->FindFileList ); // // ParseDirList() may have failed // if (error != ERROR_SUCCESS) { goto cleanup; } // // if there's nothing in the list then no files matching the caller's // specification were found // if (IsListEmpty(&lpSessionInfo->FindFileList)) { error = ERROR_NO_MORE_FILES; } else { // // if the caller supplied an output buffer then return the first entry // and remove it from the list // if (ARGUMENT_PRESENT(lpFindFileData)) { PLIST_ENTRY pEntry; pEntry = RemoveHeadList(&lpSessionInfo->FindFileList); CopyMemory(lpFindFileData, (LPWIN32_FIND_DATA)(pEntry + 1), sizeof(*lpFindFileData) ); FREE_MEMORY(pEntry); } // // FTP can only have one active operation per session, so we just return // this session handle as the find handle // *lphInternet = hFtpSession; error = ERROR_SUCCESS; } cleanup: if (lpSessionInfo->socketData->IsValid()) { lpSessionInfo->socketData->SetLinger(TRUE, 0); lpSessionInfo->socketData->Close(); } if (lpBuffer != NULL) { (void)FREE_MEMORY((HLOCAL)lpBuffer); } // // if we failed then reset the active find flag. We set it, so we know it // is safe to reset without acquiring the session lock // if (error != ERROR_SUCCESS) { lpSessionInfo->Flags &= ~FFTP_FIND_ACTIVE; } deref_exit: DereferenceFtpSession(lpSessionInfo); quit: DEBUG_LEAVE(error); return error; } DWORD wFtpDeleteFile( IN HINTERNET hFtpSession, IN LPCSTR lpszFileName ) /*++ Routine Description: Deletes a file at an FTP server Arguments: hFtpSession - identifies the FTP server lpszFileName - name of file to delete Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpDeleteFile", "%#x, %q", hFtpSession, lpszFileName )); LPFTP_SESSION_INFO lpSessionInfo; DWORD error; if (FindFtpSession(hFtpSession, &lpSessionInfo)) { FTP_RESPONSE_CODE rcResponse; error = Command(lpSessionInfo, FALSE, FTP_TRANSFER_TYPE_UNKNOWN, &rcResponse, "DELE %s", lpszFileName ); if ((error == ERROR_SUCCESS) && (rcResponse.Major != FTP_RESPONSE_COMPLETE)) { error = ERROR_INTERNET_EXTENDED_ERROR; } DereferenceFtpSession(lpSessionInfo); } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } DWORD wFtpRenameFile( IN HINTERNET hFtpSession, IN LPCSTR lpszExisting, IN LPCSTR lpszNew ) /*++ Routine Description: Renames a file at an FTP server Arguments: hFtpSession - identifies FTP server lpszExisting - current file name lpszNew - new file name Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpRenameFile", "%#x, %q, %q", hFtpSession, lpszExisting, lpszNew )); LPFTP_SESSION_INFO lpSessionInfo; DWORD error; if (FindFtpSession(hFtpSession, &lpSessionInfo)) { FTP_RESPONSE_CODE rcResponse; error = Command(lpSessionInfo, FALSE, FTP_TRANSFER_TYPE_UNKNOWN, &rcResponse, "RNFR %s", lpszExisting ); if ((error == ERROR_SUCCESS) && (rcResponse.Major != FTP_RESPONSE_CONTINUE)) { error = ERROR_INTERNET_EXTENDED_ERROR; } if (error == ERROR_SUCCESS) { error = Command(lpSessionInfo, FALSE, FTP_TRANSFER_TYPE_UNKNOWN, &rcResponse, "RNTO %s", lpszNew ); if ((error == ERROR_SUCCESS) && (rcResponse.Major != FTP_RESPONSE_COMPLETE)) { error = ERROR_INTERNET_EXTENDED_ERROR; } } DereferenceFtpSession(lpSessionInfo); } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } DWORD wFtpOpenFile( IN HINTERNET hFtpSession, IN LPCSTR lpszFileName, IN DWORD dwAccess, IN DWORD dwFlags, OUT LPHINTERNET lphInternet ) /*++ Routine Description: Initiates the connection to read or write a file at the FTP server Arguments: hFtpSession - identifies FTP server lpszFileName - name of file to open dwAccess - access mode - GENERIC_READ or GENERIC_WRITE dwFlags - flags controlling how to transfer the data lphInternet - where to return the open file handle Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpOpenFile", "%#x, %q, %#x, %#x, %#x", hFtpSession, lpszFileName, dwAccess, dwFlags, lphInternet )); LPFTP_SESSION_INFO lpSessionInfo; DWORD error; if (FindFtpSession(hFtpSession, &lpSessionInfo)) { // // control session must be established // if (! lpSessionInfo->socketControl->IsValid()) { error = ERROR_FTP_DROPPED; } else if ((lpSessionInfo->socketData->IsValid()) || (lpSessionInfo->Flags & FFTP_FILE_ACTIVE)) { // // there is a (file) transfer in progress if the socket is valid, // or we are awaiting a call to InternetCloseHandle() before we can // open another file (FFTP_FILE_ACTIVE is set. This stops another // thread from closing our socket handle) // error = ERROR_FTP_TRANSFER_IN_PROGRESS; } else { FTP_RESPONSE_CODE rcResponse; INET_ASSERT(!lpSessionInfo->socketData->IsValid()); // // Clear the session's "known size bit" before we download the next file, // this is to make sure we don't read an extranous size value off it. // lpSessionInfo->Flags &= ~(FFTP_KNOWN_FILE_SIZE); // // send the connection set-up commands, and issue either the send // or the receive command // // Either "RETR filename" or "STOR filename" // error = NegotiateDataConnection(lpSessionInfo, dwFlags, &rcResponse, (dwAccess & GENERIC_READ) ? "RETR %s" : "STOR %s", lpszFileName ); if (error == ERROR_SUCCESS) { // // Check response for failure // if ((rcResponse.Major != FTP_RESPONSE_PRELIMINARY) && (rcResponse.Major != FTP_RESPONSE_COMPLETE)) { ICSocket * socketData; // // BUGBUG - RLF - don't know if this is what's intended // here, but the code just used to check // socketData != INVALID_SOCKET. Since socketData // was getting set to INVALID_SOCKET at the top // of this routine, this branch would never be // taken // socketData = lpSessionInfo->socketData; if (socketData->IsValid()) { ResetSocket(socketData); } error = ERROR_INTERNET_EXTENDED_ERROR; } else { lpSessionInfo->dwTransferAccess = dwAccess; // // Some FTP servers will send us back both the preliminary // response and the complete response so quickly that we // will never see the preliminary. // // In order for FtpCloseFile() to know that the completion // response has been received, we store the response // structure in the Session Info. // // The response structure only needs to be stored between // API calls in this situation, it is not generally // referred to. // SetSessionLastResponseCode(lpSessionInfo, &rcResponse); // // set the abort flag if the file was opened for read - this // lets the server know it can clean up the session if we // close early // if (dwAccess & GENERIC_READ) { lpSessionInfo->Flags |= FFTP_ABORT_TRANSFER; } // // FTP can only have one active operation per session, so // we just return this session handle as the find handle // *lphInternet = hFtpSession; // // this session has an active file operation // lpSessionInfo->Flags |= FFTP_FILE_ACTIVE; // // N.B. error == ERROR_SUCCESS from above test after call // to NegotiateDataConnection // INET_ASSERT(error == ERROR_SUCCESS); } } } DereferenceFtpSession(lpSessionInfo); } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } DWORD wFtpCreateDirectory( IN HINTERNET hFtpSession, IN LPCSTR lpszDirectory ) /*++ Routine Description: Creates a directory at the FTP server Arguments: hFtpSession - identifies the FTP server lpszDirectory - directory to create Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpCreateDirectory", "%#x, %q", hFtpSession, lpszDirectory )); LPFTP_SESSION_INFO lpSessionInfo; DWORD error; if (FindFtpSession(hFtpSession, &lpSessionInfo)) { FTP_RESPONSE_CODE rcResponse; error = Command(lpSessionInfo, FALSE, FTP_TRANSFER_TYPE_UNKNOWN, &rcResponse, "MKD %s", lpszDirectory ); if ((error == ERROR_SUCCESS) && (rcResponse.Major != FTP_RESPONSE_COMPLETE)) { error = ERROR_INTERNET_EXTENDED_ERROR; } DereferenceFtpSession(lpSessionInfo); } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } DWORD wFtpRemoveDirectory( IN HINTERNET hFtpSession, IN LPCSTR lpszDirectory ) /*++ Routine Description: Removes the named directory at the FTP server Arguments: hFtpSession - identifies the FTP server lpszDirectory - directory to remove Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpRemoveDirectory", "%#x, %q", hFtpSession, lpszDirectory )); LPFTP_SESSION_INFO lpSessionInfo; DWORD error; if (FindFtpSession(hFtpSession, &lpSessionInfo)) { FTP_RESPONSE_CODE rcResponse; error = Command(lpSessionInfo, FALSE, FTP_TRANSFER_TYPE_UNKNOWN, &rcResponse, "RMD %s", lpszDirectory ); if ((error == ERROR_SUCCESS) && (rcResponse.Major != FTP_RESPONSE_COMPLETE)) { error = ERROR_INTERNET_EXTENDED_ERROR; } DereferenceFtpSession(lpSessionInfo); } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } DWORD wFtpSetCurrentDirectory( IN HINTERNET hFtpSession, IN LPCSTR lpszDirectory ) /*++ Routine Description: Sets the current directory for this FTP server session Arguments: hFtpSession - identifies the FTP server/session lpszDirectory - name of directory to set Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpSetCurrentDirectory", "%#x, %q", hFtpSession, lpszDirectory )); LPFTP_SESSION_INFO lpSessionInfo; DWORD error; if (FindFtpSession(hFtpSession, &lpSessionInfo)) { FTP_RESPONSE_CODE rcResponse; error = Command(lpSessionInfo, FALSE, FTP_TRANSFER_TYPE_UNKNOWN, &rcResponse, "CWD %s", lpszDirectory ); if ((error == ERROR_SUCCESS) && (rcResponse.Major != FTP_RESPONSE_COMPLETE)) { error = ERROR_INTERNET_EXTENDED_ERROR; } DereferenceFtpSession(lpSessionInfo); } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } DWORD wFtpGetCurrentDirectory( IN HINTERNET hFtpSession, IN DWORD cchCurrentDirectory, OUT LPSTR lpszCurrentDirectory, OUT LPDWORD lpdwBytesReturned ) /*++ Routine Description: Gets the current working directory at the FTP server for this session Arguments: hFtpSession - identifies FTP server cchCurrentDirectory - number of characters in lpszCurrentDirectory lpszCurrentDirectory - buffer where current directory string is written lpdwBytesReturned - number of characters in output string NOT including terminating NUL Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_INVALID_HANDLE ERROR_INSUFFICIENT_BUFFER The buffer in lpszCurrentDirectory is not large enough to hold the directory string. *lpdwBytesReturned will have the required size --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpGetCurrentDirectory", "%#x, %d, %#x, %#x", hFtpSession, cchCurrentDirectory, lpszCurrentDirectory, lpdwBytesReturned )); LPFTP_SESSION_INFO lpSessionInfo; DWORD cchCopied; DWORD error; if (FindFtpSession(hFtpSession, &lpSessionInfo)) { FTP_RESPONSE_CODE rcResponse; error = Command(lpSessionInfo, FALSE, FTP_TRANSFER_TYPE_UNKNOWN, &rcResponse, "PWD" ); if ((error == ERROR_SUCCESS) && (rcResponse.Major != FTP_RESPONSE_COMPLETE)) { error = ERROR_INTERNET_EXTENDED_ERROR; } if (error == ERROR_SUCCESS) { LPSTR pchResponse; // // parse the returned directory name out of the response text // pchResponse = InternetLockErrorText(); if (pchResponse != NULL) { pchResponse = strstr(pchResponse, "257 "); if (pchResponse != NULL) { pchResponse = strchr(pchResponse, '\"'); if (pchResponse != NULL) { int idx; ++pchResponse; for (idx = 0, cchCopied = 0; pchResponse[idx] != '\0'; idx++) { if (pchResponse[idx] == '\"') { if (pchResponse[idx + 1] == '\"') { continue; } break; } if (cchCopied < cchCurrentDirectory) { lpszCurrentDirectory[cchCopied] = pchResponse[idx]; } cchCopied++; } if (cchCopied < cchCurrentDirectory) { lpszCurrentDirectory[cchCopied] = '\0'; error = ERROR_SUCCESS; } else { error = ERROR_INSUFFICIENT_BUFFER; ++cchCopied; } } else { error = ERROR_INTERNET_EXTENDED_ERROR; } } //InternetUnlockErrorText(); } } DereferenceFtpSession(lpSessionInfo); } else { error = ERROR_INVALID_HANDLE; } if ((error == ERROR_SUCCESS) || (error == ERROR_INSUFFICIENT_BUFFER)) { *lpdwBytesReturned = cchCopied; } DEBUG_LEAVE(error); return error; } DWORD wFtpCommand( IN HINTERNET hFtpSession, IN BOOL fExpectResponse, IN DWORD dwFlags, IN LPCSTR lpszCommand ) /*++ Routine Description: Runs arbitrary command at an FTP server. Direct connect over Internet Arguments: hFtpSession - identifies the FTP server fExpectResponse - TRUE if we expect a response from the server dwFlags - type of response - ASCII text or BINARY data lpszCommand - pointer to string describing command to run Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpCommand", "%#x, %#x, %#x, %q", hFtpSession, fExpectResponse, dwFlags, lpszCommand )); LPFTP_SESSION_INFO lpSessionInfo; DWORD error; // // Look up the given handle. // if (FindFtpSession(hFtpSession, &lpSessionInfo)) { FTP_RESPONSE_CODE rcResponse; // // Issue the command. // error = Command(lpSessionInfo, fExpectResponse, dwFlags, &rcResponse, lpszCommand ); if (fExpectResponse && (error == ERROR_SUCCESS)) { INET_ASSERT(lpSessionInfo->socketData->IsValid()); lpSessionInfo->dwTransferAccess |= (GENERIC_READ|GENERIC_WRITE); } #if DBG else { INET_ASSERT(! lpSessionInfo->socketData->IsValid()); } if (error == ERROR_SUCCESS) { INET_ASSERT(lpSessionInfo->socketControl->IsValid()); } #endif DereferenceFtpSession(lpSessionInfo); } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } // // Internet subordinate functions // DWORD wFtpFindNextFile( IN HINTERNET hFtpSession, OUT LPWIN32_FIND_DATA lpFindFileData ) /*++ Routine Description: Returns the next file found from a call to FtpFindFirstFile(). Arguments: hFtpSession - Handle to an FTP session, as returned from FtpConnect() lpFindFileData - Pointer to a buffer that will contain WIN32_FIND_DATA information when this call succeeds. Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_NO_MORE_FILES The end of the file list has been reached. ERROR_INVALID_HANDLE Can't find session that knows about hFind --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpFindNextFile", "%#x, %#x", hFtpSession, lpFindFileData )); LPFTP_SESSION_INFO lpSessionInfo; DWORD error; if (FindFtpSession(hFtpSession, &lpSessionInfo)) { // // ISSUE this code is cut & paste from find first - they should both call a // fn instead // if (!IsListEmpty(&lpSessionInfo->FindFileList)) { PLIST_ENTRY pEntry; // // Enumerate the first entry and advance pointers // pEntry = RemoveHeadList(&lpSessionInfo->FindFileList); INET_ASSERT(pEntry != NULL); CopyMemory(lpFindFileData, (LPWIN32_FIND_DATA)(pEntry + 1), sizeof(WIN32_FIND_DATA) ); FREE_MEMORY(pEntry); error = ERROR_SUCCESS; } else { error = ERROR_NO_MORE_FILES; } DereferenceFtpSession(lpSessionInfo); } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } DWORD wFtpFindClose( IN HINTERNET hFtpSession ) /*++ Routine Description: Frees the WIN32_FIND_DATA structures in the directory list for this session Arguments: hFtpSession - handle of an FTP session, created by InternetConnect Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_INVALID_HANDLE --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpFindClose", "%#x", hFtpSession )); LPFTP_SESSION_INFO lpSessionInfo; DWORD error; if (FindFtpSession(hFtpSession, &lpSessionInfo)) { ClearFindList(&lpSessionInfo->FindFileList); // // this session no longer has an active directory listing // lpSessionInfo->Flags &= ~FFTP_FIND_ACTIVE; DereferenceFtpSession(lpSessionInfo); error = ERROR_SUCCESS; } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } DWORD wFtpConnect( IN LPCSTR lpszServerName, IN INTERNET_PORT nServerPort, IN LPCSTR lpszUsername, IN LPCSTR lpszPassword, IN DWORD dwService, IN DWORD dwFlags, OUT LPHINTERNET lphInternet ) /*++ Routine Description: Creates a new FTP session object Arguments: lpszServerName - pointer to string identifying FTP server nServerPort - port number to connect to lpszUsername - pointer to string identifying user name to log on as lpszPassword - pointer to string identifying password to use with user name dwService - service type parameter (unused) dwFlags - session flags. Currently only INTERNET_FLAG_PASSIVE is defined lphInternet - returned handle of created FTP session Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_NOT_ENOUGH_MEMORY Ran out of memory while creating the session object ERROR_INTERNET_OUT_OF_HANDLES Ran out of handles while creating the session object ERROR_INTERNET_SHUTDOWN The DLL is being unloaded --*/ { INET_ASSERT(lpszUsername != NULL); INET_ASSERT(lpszPassword != NULL); DEBUG_ENTER((DBG_FTP, Dword, "wFtpConnect", "%q, %d, %q, %q, %d, %#x, %#x", lpszServerName, nServerPort, lpszUsername, lpszPassword, dwService, dwFlags, lphInternet )); DWORD error; LPFTP_SESSION_INFO sessionInfo; UNREFERENCED_PARAMETER(lpszUsername); UNREFERENCED_PARAMETER(lpszPassword); UNREFERENCED_PARAMETER(dwService); // // create a new FTP session object // error = CreateFtpSession((LPSTR)lpszServerName, nServerPort, // // if INTERNET_FLAG_PASSIVE then create a passive // session object // (dwFlags & INTERNET_FLAG_PASSIVE) ? FFTP_PASSIVE_MODE : 0, &sessionInfo ); if (error == ERROR_SUCCESS) { // // return the FTP_SESSION_INFO handle // *lphInternet = sessionInfo->Handle; } DEBUG_LEAVE(error); return error; } DWORD wFtpMakeConnection( IN HINTERNET hFtpSession, IN LPCSTR lpszUsername, IN LPCSTR lpszPassword ) /*++ Routine Description: Connect with and log into an FTP server. This function is cancellable Arguments: hFtpSession - handle of an FTP session, created by InternetConnect pszUsername - pointer to string identifying user name to log on as pszPassword - pointer to string identifying password to use with user name Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_INTERNET_INCORRECT_USER_NAME The server didn't like the user name ERROR_INTERNET_INCORRECT_PASSWORD The server didn't like the password ERROR_INTERNET_LOGIN_FAILURE The server rejected the login request ERROR_FTP_DROPPED The connection has been closed ERROR_FTP_TRANSFER_IN_PROGRESS There is already a transfer in progress on this connection ERROR_INTERNET_NAME_NOT_RESOLVED Couldn't resolve the server name WSA error Couldn't connect to the server, or problems while communicating with it --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpMakeConnection", "%#x, %q, %q", hFtpSession, lpszUsername, lpszPassword )); LPFTP_SESSION_INFO sessionInfo; DWORD error; if (FindFtpSession(hFtpSession, &sessionInfo)) { // // resolve the FTP server's host name and connect to the server // error = FtpOpenServer(sessionInfo); if (error == ERROR_SUCCESS) { FTP_RESPONSE_CODE rcResponse; // // set send and receive timeouts on the control channel socket. // Ignore any errors // sessionInfo->socketControl->SetTimeout( SEND_TIMEOUT, GetTimeoutValue(INTERNET_OPTION_CONTROL_SEND_TIMEOUT) ); sessionInfo->socketControl->SetTimeout( RECEIVE_TIMEOUT, GetTimeoutValue(INTERNET_OPTION_CONTROL_RECEIVE_TIMEOUT) ); // // check greeting and store in per-thread response text buffer // error = GetReply(sessionInfo, &rcResponse); if (error == ERROR_SUCCESS) { // // check that the server sent us an affirmative response // if (rcResponse.Major == FTP_RESPONSE_COMPLETE) { // // send the user name // error = Command(sessionInfo, FALSE, FTP_TRANSFER_TYPE_UNKNOWN, &rcResponse, "USER %s", lpszUsername ); // // BUGBUG - is it possible to get success from Command(), // but an error from the server - e.g. 332, need // account for login? // if (error == ERROR_SUCCESS) { // // send the password if required // if (rcResponse.Major == FTP_RESPONSE_CONTINUE) { error = Command(sessionInfo, FALSE, FTP_TRANSFER_TYPE_UNKNOWN, &rcResponse, "PASS %s", lpszPassword ); // // if we failed to send the password, or the password // was rejected, or we are attempting to log on as // "anonymous" and it turns out that the server does // not allow anonymous logon, then return a password // error. The caller can still check the response // from the server // if (((error == ERROR_SUCCESS) && (rcResponse.Major != FTP_RESPONSE_COMPLETE)) || (error == ERROR_INTERNET_EXTENDED_ERROR)) { if (stricmp(lpszUsername, "anonymous") == 0) { error = ERROR_INTERNET_LOGIN_FAILURE; } else { error = ERROR_INTERNET_INCORRECT_PASSWORD; } } } else if (rcResponse.Major != FTP_RESPONSE_COMPLETE) { error = ERROR_INTERNET_INCORRECT_USER_NAME; } // // get the server type // //if (error == ERROR_SUCCESS) { // error = wFtpFindServerType(hFtpSession); //} } } else { error = ERROR_INTERNET_LOGIN_FAILURE; } } } // // success or fail: unlock the session object // DereferenceFtpSession(sessionInfo); // // if we failed to login then let wFtpDisconnect() clean up - it will // also send a "QUIT" to the server (if we have a control connection) // which will ensure a clean exit // if (error != ERROR_SUCCESS) { // // if we experience an error during disconnect, we will just ignore // it and return the error generated during our failed login attempt // (void)wFtpDisconnect(hFtpSession, CF_EXPEDITED_CLOSE); } } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } DWORD wFtpDisconnect( IN HINTERNET hFtpSession, IN DWORD dwFlags ) /*++ Routine Description: Closes the connection, issues the quit command, etc., Arguments: hFtpSession - FTP session created by wFtpConnect dwFlags - controlling operation. Can be: CF_EXPEDITED_CLOSE - Don't send QUIT to the server, just close the control connection Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_INVALID_HANDLE --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpDisconnect", "%#x, %#x", hFtpSession, dwFlags )); LPFTP_SESSION_INFO lpSessionInfo; DWORD error; LPINTERNET_THREAD_INFO lpThreadInfo = InternetGetThreadInfo(); if (lpThreadInfo == NULL) { INET_ASSERT(FALSE); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } if (FindFtpSession(hFtpSession, &lpSessionInfo)) { ICSocket * socketControl; ICSocket * socketData; socketControl = lpSessionInfo->socketControl; socketData = lpSessionInfo->socketData; // // kill any active data transfer // if (socketData->IsValid()) { // // set the non-blocking state depending on whether we are called in // an app thread context, or in the async scheduler thread context // //socketData->SetNonBlockingMode(lpThreadInfo->IsAsyncWorkerThread); if (dwFlags & CF_EXPEDITED_CLOSE) { error = socketData->Close(); } else { error = wFtpCloseFile(hFtpSession); if (error != ERROR_SUCCESS) { DEBUG_PRINT(WORKER, ERROR, ("wFtpCloseFile() returns %d\n", error )); } } } INET_ASSERT(!lpSessionInfo->socketData->IsValid()); // // perform graceful close to the server if we have a control connection // if (socketControl->IsValid()) { // // set the non-blocking state depending on whether we are called in // an app thread context, or in the async scheduler thread context // //socketControl->SetNonBlockingMode(lpThreadInfo->IsAsyncWorkerThread); if (!(dwFlags & CF_EXPEDITED_CLOSE)) { FTP_RESPONSE_CODE rcResponse; Command(lpSessionInfo, FALSE, FTP_TRANSFER_TYPE_UNKNOWN, &rcResponse, "QUIT" ); } lpSessionInfo->socketControl->Disconnect(SF_INDICATE); } // // finally kill the FTP_SESSION_INFO structure // TerminateFtpSession(lpSessionInfo); error = ERROR_SUCCESS; } else { error = ERROR_INVALID_HANDLE; } quit: DEBUG_LEAVE(error); return error; } DWORD wFtpReadFile( IN HINTERNET hFtpSession, IN LPVOID lpBuffer, IN DWORD dwNumberOfBytesToRead, OUT LPDWORD lpdwNumberOfBytesRead ) /*++ Routine Description: Reads data from the FTP server. We use the data channel Arguments: hFtpSession - handle identifying FTP session lpBuffer - pointer to buffer for received data dwNumberOfBytesToRead - size of lpBuffer in bytes lpdwNumberOfBytesRead - returned number of bytes received Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_INVALID_HANDLE Couldn't find hFtpSession ERROR_ACCESS_DENIED This session doesn't have read access (?) ERROR_FTP_DROPPED The data channel has been closed --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpReadFile", "%#x, %#x, %d, %#x", hFtpSession, lpBuffer, dwNumberOfBytesToRead, lpdwNumberOfBytesRead )); LPINTERNET_THREAD_INFO lpThreadInfo = InternetGetThreadInfo(); DWORD error; if (lpThreadInfo == NULL) { INET_ASSERT(FALSE); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } LPFTP_SESSION_INFO lpSessionInfo; ICSocket * socketData; BOOL eof; DWORD bytesReceived; // // initialize variables in case we quit early (i.e. via goto) // bytesReceived = 0; // // find the FTP_SESSION_INFO and ensure it is set up to receive data // if (!FindFtpSession(hFtpSession, &lpSessionInfo)) { error = ERROR_INVALID_HANDLE; goto quit; } // // if FFTP_EOF is set then we already reached the end the file // if (lpSessionInfo->Flags & FFTP_EOF) { error = ERROR_SUCCESS; goto unlock_and_quit; } // // get the data socket. If it has become INVALID_SOCKET then the server // closed the connection // socketData = lpSessionInfo->socketData; if (!socketData->IsValid()) { error = ERROR_FTP_DROPPED; goto unlock_and_quit; } if (!(lpSessionInfo->dwTransferAccess & GENERIC_READ)) { error = ERROR_ACCESS_DENIED; goto unlock_and_quit; } // // read until we fill the users buffer, get an error, or get to EOF // DWORD bufferRemaining; bufferRemaining = dwNumberOfBytesToRead; error = socketData->Receive( &lpBuffer, &dwNumberOfBytesToRead, // lpdwBufferLength &bufferRemaining, // lpdwBufferRemaining &bytesReceived, // lpdwBytesReceived 0, // dwExtraSpace SF_RECEIVE_ALL | SF_INDICATE, &eof ); if (error == ERROR_SUCCESS) { // // if we got to EOF then the server will have closed the data // connection. We need to close the socket at our end. If this is // a passive connection then we initiate session termination // if (eof) { (void)socketData->Close(); INET_ASSERT(lpSessionInfo->socketData == socketData); // // reset the abort flag - we no longer have to send and ABOR command // when we close the handle // lpSessionInfo->Flags &= ~FFTP_ABORT_TRANSFER; // // set EOF in the FTP_SESSION_INFO flags so we know next time // we call this function that the session is not dropped, but // that we already reached the end of the data // lpSessionInfo->Flags |= FFTP_EOF; } } // // BUGBUG - in error case we should probably close the socket, set // INVALID_SOCKET in the FTP_SESSION_INFO, etc. // unlock_and_quit: // // update the output parameters if we succeeded // if (error == ERROR_SUCCESS) { *lpdwNumberOfBytesRead = bytesReceived; } DereferenceFtpSession(lpSessionInfo); quit: DEBUG_LEAVE(error); return error; } DWORD wFtpWriteFile( IN HINTERNET hFtpSession, IN LPVOID lpBuffer, IN DWORD dwNumberOfBytesToWrite, OUT LPDWORD lpdwNumberOfBytesWritten ) /*++ Routine Description: Writes data to the FTP server. We use the data channel Arguments: hFtpSession - handle identifying FTP session lpBuffer - pointer to buffer containing data to write dwNumberOfBytesToWrite - size of lpBuffer in bytes lpdwNumberOfBytesWritten - returned number of bytes sent Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_INVALID_HANDLE Couldn't find hFtpSession ERROR_ACCESS_DENIED This session doesn't have write access (?) ERROR_FTP_DROPPED The data channel has been closed --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpWriteFile", "%#x, %#x, %d, %#x", hFtpSession, lpBuffer, dwNumberOfBytesToWrite, lpdwNumberOfBytesWritten )); LPINTERNET_THREAD_INFO lpThreadInfo = InternetGetThreadInfo(); DWORD error; if (lpThreadInfo == NULL) { INET_ASSERT(FALSE); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } LPFTP_SESSION_INFO lpSessionInfo; ICSocket * socketData; int nSent; // // find the FTP_SESSION_INFO and ensure it is set up to send data // if (!FindFtpSession(hFtpSession, &lpSessionInfo)) { error = ERROR_INVALID_HANDLE; goto quit; } socketData = lpSessionInfo->socketData; if (! socketData->IsValid()) { error = ERROR_FTP_DROPPED; goto unlock_and_quit; } if (!(lpSessionInfo->dwTransferAccess & GENERIC_WRITE)) { error = ERROR_ACCESS_DENIED; goto unlock_and_quit; } error = socketData->Send(lpBuffer, dwNumberOfBytesToWrite, SF_INDICATE); if (error == ERROR_SUCCESS) { *lpdwNumberOfBytesWritten = dwNumberOfBytesToWrite; } else { // // we had a failure. We should check the control socket for any error // info from the server // FTP_RESPONSE_CODE response = {0}; response.Major = FTP_RESPONSE_PERMANENT_FAILURE; // SetSessionLastResponseCode(lpSessionInfo, &response); } unlock_and_quit: DereferenceFtpSession(lpSessionInfo); quit: DEBUG_LEAVE(error); return error; } DWORD wFtpQueryDataAvailable( IN HINTERNET hFtpSession, OUT LPDWORD lpdwNumberOfBytesAvailable ) /*++ Routine Description: Determines amount of data available to be received on a data (file) socket Arguments: hFtpSession - identifies FTP session lpdwNumberOfBytesAvailable - returned number of bytes available Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_INVALID_HANDLE --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpQueryDataAvailable", "%#x, %#x", hFtpSession, lpdwNumberOfBytesAvailable )); LPINTERNET_THREAD_INFO lpThreadInfo = InternetGetThreadInfo(); DWORD error; HINTERNET_HANDLE_TYPE handleType; error = RGetHandleType(lpThreadInfo->hObjectMapped, &handleType); if (error != ERROR_SUCCESS) { return (error); } *lpdwNumberOfBytesAvailable = 0; if (lpThreadInfo == NULL) { INET_ASSERT(FALSE); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } LPFTP_SESSION_INFO lpSessionInfo; if (FindFtpSession(hFtpSession, &lpSessionInfo)) { // // if we are currently performing a directory list then return the size // of a dir list entry // if (lpSessionInfo->Flags & FFTP_FIND_ACTIVE) { *lpdwNumberOfBytesAvailable = !IsListEmpty(&lpSessionInfo->FindFileList) ? sizeof(WIN32_FIND_DATA) : 0; } else { // // otherwise, if we are receiving data, find out how much // ICSocket * socketData; socketData = lpSessionInfo->socketData; if (socketData->IsValid()) { error = socketData->DataAvailable(lpdwNumberOfBytesAvailable); } else { // // there is no data connection // *lpdwNumberOfBytesAvailable = 0; error = ERROR_SUCCESS; } } DereferenceFtpSession(lpSessionInfo); } else { error = ERROR_INVALID_HANDLE; } quit: if ((error == ERROR_SUCCESS) && (*lpdwNumberOfBytesAvailable == 0)) { InbLocalEndCacheWrite(lpThreadInfo->hObjectMapped, ((handleType==TypeFtpFindHandleHtml) ?"htm":NULL), TRUE); } DEBUG_LEAVE(error); return error; } DWORD wFtpCloseFile( IN HINTERNET hFtpSession ) /*++ Routine Description: Terminates the connection used for file transfer. The connection may already be closed (by the server during a READ, or by the client during a WRITE) in which case we just need to receive the confirmation (226) on the control socket. If the connection is still open, or the abort flag is set for this connection, then this is an abnormal termination, and we need to send an ABORt command Arguments: hFtpSession - Identifies the session on which to terminate file transfer Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_INVALID_HANDLE Couldn't find the FTP_SESSION_INFO corresponding to hFtpSession --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpCloseFile", "%#x", hFtpSession )); LPFTP_SESSION_INFO lpSessionInfo; DWORD error; if (FindFtpSession(hFtpSession, &lpSessionInfo)) { BOOL getResponse; ICSocket * socketData; FTP_RESPONSE_CODE rcResponse; socketData = lpSessionInfo->socketData; if (socketData->IsValid()) { // // if we are performing a read/write operation and the transfer // isn't complete then abort the connection // if (lpSessionInfo->Flags & FFTP_ABORT_TRANSFER) { AbortTransfer(lpSessionInfo); ResetSocket(lpSessionInfo->socketData); } else { // // in all other cases - completed READ, complete or incomplete // WRITE - just close the socket // lpSessionInfo->socketData->Close(); } } else if (lpSessionInfo->Flags & FFTP_ABORT_TRANSFER) { // // we have no data socket, but the abort transfer flag is set. We // are probably closing a file we opened for read without having // read any data. In this case we send an abort anyway // AbortTransfer(lpSessionInfo); } // // get the server response - we expect either 226 to a good transfer, // or 426 for an aborted transfer... // GetSessionLastResponseCode(lpSessionInfo, &rcResponse); if (rcResponse.Major == FTP_RESPONSE_PRELIMINARY) { error = GetReply(lpSessionInfo, &rcResponse); SetLastError(ERROR_SUCCESS); // a-thkesa;. added from Win CE fix of BUG WinSE: 23985 if ((error == ERROR_SUCCESS) && (rcResponse.Major != FTP_RESPONSE_COMPLETE)) { error = ERROR_INTERNET_EXTENDED_ERROR; SetLastError(error); // a-thkesa;. added from Win CE fix of BUG WinSE: 23985 } } else if (rcResponse.Major == FTP_RESPONSE_PERMANENT_FAILURE){ error = ERROR_SUCCESS; } else { if(rcResponse.Major != FTP_RESPONSE_COMPLETE) error = GetReply(lpSessionInfo, &rcResponse); else error = ERROR_SUCCESS; } // // reset the ABORT, FILE_ACTIVE and EOF flags // lpSessionInfo->Flags &= ~(FFTP_ABORT_TRANSFER | FFTP_EOF | FFTP_FILE_ACTIVE ); DereferenceFtpSession(lpSessionInfo); } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } DWORD wFtpFindServerType( IN HINTERNET hFtpSession ) /*++ Routine Description: Determines the type of server we are talking to (NT or Unix) Arguments: hFtpSession - identifies FTP_SESSION_INFO. The structure ServerType field will be updated with the discovered info Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_INVALID_HANDLE Couldn't find the FTP_SESSION_INFO corresponding to hFtpSession --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpFindServerType", "%#x", hFtpSession )); LPFTP_SESSION_INFO lpSessionInfo; DWORD error; if (FindFtpSession(hFtpSession, &lpSessionInfo)) { FTP_RESPONSE_CODE rcResponse; error = Command(lpSessionInfo, FALSE, FTP_TRANSFER_TYPE_UNKNOWN, &rcResponse, "SYST" ); if (error == ERROR_SUCCESS) { LPSTR lpszResponse = InternetLockErrorText(); if (lpszResponse != NULL) { FTP_SERVER_TYPE serverType = FTP_SERVER_TYPE_UNKNOWN; // // "215 " must be first token in response text // lpszResponse = strstr(lpszResponse, "215 "); if (lpszResponse != NULL) { // // check for existence of "Windows_NT" or "Unix" (case // insensitive comparison) // // // BUGBUG - find out from MuraliK/TerryK the values these // ids can have // static struct { LPCSTR lpszSystemName; FTP_SERVER_TYPE ServerType; } FtpServerTypes[] = { "Windows_NT", FTP_SERVER_TYPE_NT, "Unix", FTP_SERVER_TYPE_UNIX }; DWORD textLength = strlen(lpszResponse); for (int i = 0; i < ARRAY_ELEMENTS(FtpServerTypes); ++i) { if (strnistr(lpszResponse, (LPSTR)FtpServerTypes[i].lpszSystemName, textLength ) != NULL) { serverType = FtpServerTypes[i].ServerType; DEBUG_PRINT(FTP, INFO, ("serverType = %s (%d)\n", InternetMapFtpServerType(serverType), serverType )); break; } } } lpSessionInfo->ServerType = serverType; //InternetUnlockErrorText(); } } DereferenceFtpSession(lpSessionInfo); } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } #if 0 // // We don't use this today, because FtpGetFileSize does not support // issuing backround commands through the FTP Control socket while // the user is doing an FTP download (with FtpOpenFile) // DWORD wFtpGetFileSize( IN HINTERNET hMappedFtpSession, IN LPFTP_SESSION_INFO lpSessionInfo, OUT LPDWORD lpdwFileSizeLow, OUT LPDWORD lpdwFileSizeHigh ) /*++ Routine Description: Finds size of a file at server Arguments: hFtpSession - identifies mapped FTP handle obj lpSessionInfo - LPFTP_SESSION_INFO structure ptr. lpdwFileSizeLow - pointer to low dword of file size lpdwFileSizeHigh - optional output pointer to high dword of file size Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_INVALID_HANDLE Couldn't find the FTP_SESSION_INFO corresponding to hFtpSession --*/ { DEBUG_ENTER((DBG_FTP, Dword, "wFtpGetFileSize", "%#x, %#x, %#x, %#x", hMappedFtpSession, lpSessionInfo, lpdwFileSizeLow, lpdwFileSizeHigh )); DWORD error = ERROR_INTERNET_INTERNAL_ERROR; FTP_FILE_HANDLE_OBJECT * pFileMapped = (FTP_FILE_HANDLE_OBJECT *) hMappedFtpSession; *lpdwFileSizeLow = 0; *lpdwFileSizeHigh = 0; if (lpSessionInfo) { FTP_RESPONSE_CODE rcResponse; error = Command(lpSessionInfo, FALSE, FTP_TRANSFER_TYPE_UNKNOWN, &rcResponse, "SIZE %s", pFileMapped->GetFileName() ); if (error == ERROR_SUCCESS) { LPSTR lpszResponse = InternetLockErrorText(); if (lpszResponse != NULL) { FTP_SERVER_TYPE serverType = FTP_SERVER_TYPE_UNKNOWN; // // "213 " must be first token in response text of file size // lpszResponse = strstr(lpszResponse, "213 "); if (lpszResponse != NULL) { *lpdwFileSizeLow = atoi(lpszResponse); error = ERROR_SUCCESS; } } } } else { error = ERROR_INVALID_HANDLE; } DEBUG_LEAVE(error); return error; } #endif // // private debug functions // #if INET_DEBUG PRIVATE DEBUG_FUNCTION LPSTR InternetMapFtpServerType( IN FTP_SERVER_TYPE ServerType ) { switch (ServerType) { CASE_OF(FTP_SERVER_TYPE_UNKNOWN); CASE_OF(FTP_SERVER_TYPE_NT); CASE_OF(FTP_SERVER_TYPE_UNIX); } return "?"; } #endif // INET_DEBUG