/*++ Copyright (c) 1994 Microsoft Corporation Module Name: dirlist.cxx Abstract: Contains functions for parsing directory output from LIST command Contents: ParseDirList IsFilespecWild ClearFindList (DetermineDirectoryFormat) (IsNtDateFormat) (GetToken) (IsUnixAttributeFormat) (ParseNtDirectory) (ParseUnixDirectory) (ParseOs2Directory) (ParseMacDirectory) (ExtractFileSize) (_ExtractFilename) (ExtractNtDate) (ExtractUnixDate) (ExtractOs2Attributes) (ParseWord) (ExtractInteger) Author: Richard L Firth (rfirth) 26-Jul-1995 Environment: Win32(s) user-mode DLL Revision History: 26-Jul-1995 rfirth Created --*/ #include #include "ftpapih.h" // // private manifests // #define MAX_YEAR_SUPPORTED 2100 #define TOKEN_BUFFER_LENGTH 128 #define RELATIVELY_SMALL_AMOUNT_OF_LS_DATA 512 // arbitrary, but allow for // prolix error text // // private types // typedef enum { State_Start, State_Error, State_Continue, State_Done } PARSE_STATE; typedef PARSE_STATE (*DIR_PARSER)(LPSTR*, LPDWORD, LPWIN32_FIND_DATA); // // private macros // #define ClearFileTime(fileTime) \ (fileTime).dwLowDateTime = 0; \ (fileTime).dwHighDateTime = 0; #define ClearFindDataFields(lpFind) \ ClearFileTime((lpFind)->ftCreationTime); \ ClearFileTime((lpFind)->ftLastAccessTime); \ (lpFind)->dwReserved0 = 0; \ (lpFind)->dwReserved1 = 0; \ (lpFind)->cAlternateFileName[0] = '\0'; // // private prototypes // PRIVATE BOOL DetermineDirectoryFormat( IN OUT LPSTR* lpBuffer, IN OUT LPDWORD lpBufferLength, OUT DIR_PARSER* ParserFunction ); PRIVATE BOOL IsNtDateFormat( IN LPSTR lpBuffer, IN DWORD dwBufferLength ); PRIVATE BOOL GetToken( IN LPSTR lpszBuffer, IN DWORD dwBufferLength, OUT LPSTR lpszToken, IN OUT LPDWORD lpdwTokenLength ); PRIVATE BOOL IsUnixAttributeFormat( IN LPSTR lpBuffer, IN DWORD dwBufferLength ); PRIVATE PARSE_STATE ParseNtDirectory( IN OUT LPSTR* lpBuffer, IN OUT LPDWORD lpBufferLength, IN OUT LPWIN32_FIND_DATA lpFindData ); PRIVATE PARSE_STATE ParseUnixDirectory( IN OUT LPSTR* lpBuffer, IN OUT LPDWORD lpBufferLength, IN OUT LPWIN32_FIND_DATA lpFindData ); PRIVATE PARSE_STATE ParseOs2Directory( IN OUT LPSTR* lpBuffer, IN OUT LPDWORD lpBufferLength, IN OUT LPWIN32_FIND_DATA lpFindData ); PRIVATE PARSE_STATE ParseMacDirectory( IN OUT LPSTR* lpBuffer, IN OUT LPDWORD lpBufferLength, IN OUT LPWIN32_FIND_DATA lpFindData ); PRIVATE BOOL ExtractFileSize( IN OUT LPSTR* lpBuffer, IN OUT LPDWORD lpBufferLength, IN OUT LPWIN32_FIND_DATA lpFindData ); PRIVATE BOOL _ExtractFilename( IN OUT LPSTR* lpBuffer, IN OUT LPDWORD lpBufferLength, IN OUT LPWIN32_FIND_DATA lpFindData ); PRIVATE BOOL ExtractNtDate( IN OUT LPSTR* lpBuffer, IN OUT LPDWORD lpBufferLength, IN OUT LPWIN32_FIND_DATA lpFindData ); PRIVATE BOOL ExtractUnixDate( IN OUT LPSTR* lpBuffer, IN OUT LPDWORD lpBufferLength, IN OUT LPWIN32_FIND_DATA lpFindData ); PRIVATE BOOL ExtractOs2Attributes( IN OUT LPSTR* lpBuffer, IN OUT LPDWORD lpBufferLength, IN OUT LPWIN32_FIND_DATA lpFindData ); PRIVATE BOOL ParseWord( IN OUT LPSTR* lpBuffer, IN OUT LPDWORD lpBufferLength, IN WORD LowerBound, IN WORD UpperBound, OUT LPWORD lpNumber ); PRIVATE BOOL ExtractInteger( IN OUT LPSTR* lpBuffer, IN OUT LPDWORD lpBufferLength, OUT LPINT lpNumber ); // // private data // // // DefaultSystemTime - if we fail to parse the time/date field for any reason, // we will return this default time // PRIVATE static SYSTEMTIME DefaultSystemTime = {1980, 1, 0, 1, 12, 0, 0, 0}; // // functions // DWORD ParseDirList( IN LPSTR lpBuffer, IN DWORD dwBufferLength, IN LPSTR lpszFilespec OPTIONAL, IN OUT PLIST_ENTRY lpList ) /*++ Routine Description: Creates a list of WIN32_FIND_DATA structures given the output from the LIST command run at the FTP server Arguments: lpBuffer - pointer to buffer containing LIST output lpBufferLength - length of Buffer - no trailing \0 lpszFilespec - pointer to file specification used to generate listing. May contain path components. May be NULL, in which case we perform no filtering based on name (results should be an exact match with request) lpList - pointer to LIST_ENTRY list to add to Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_NOT_ENOUGH_MEMORY --*/ { DEBUG_ENTER((DBG_FTP, Dword, "ParseDirList", "%x, %d, %x", lpBuffer, dwBufferLength, lpList )); DWORD error; DIR_PARSER directoryParser; BOOL needBuffer; LPSTR lpszOriginalBuffer; DWORD dwOriginalBufferLength; // // remember the initial buffer pointer and length, in case we can't determine // the format - DetermineDirectoryFormat() will alter the input buffer pointer // and length // lpszOriginalBuffer = lpBuffer; dwOriginalBufferLength = dwBufferLength; // // find out the format of the directory listing. Currently we understand // NT and the basic Unix directory listing formats // if (!DetermineDirectoryFormat(&lpBuffer, &dwBufferLength, &directoryParser)) { DEBUG_PRINT(FTP, ERROR, ("Can't determine directory format\n" )); // // if we received a relatively small amount of data, then there is a // good chance that what we actually received is an error message from // the ls command, or operating system, etc. Make it an extended error. // This will reduce (but not eliminate) the chances of getting back an // internal error // if (dwBufferLength <= RELATIVELY_SMALL_AMOUNT_OF_LS_DATA) { error = InternetSetLastError(0, lpszOriginalBuffer, dwOriginalBufferLength, SLE_APPEND | SLE_ZERO_TERMINATE ); // // return internal error if we failed to add the text for any reason // error = (error == ERROR_SUCCESS) ? ERROR_INTERNET_EXTENDED_ERROR : ERROR_INTERNET_INTERNAL_ERROR ; } else { // // BUGBUG - error code? // error = ERROR_INTERNET_INTERNAL_ERROR; } goto quit; } // // the list must be currently empty // INET_ASSERT(IsListEmpty(lpList)); // // the app may have specified a path. Chances are that if there are // wildcards within the path then the server would have returned an // error. But if the app requested e.g. foo\bar\*.exe then the server // would have returned the directory results for foo\bar. Therefore, // we must skip any path components, or all tests against the filespec // will fail // if (ARGUMENT_PRESENT(lpszFilespec)) { LPSTR lpszSpec; lpszSpec = strrchr(lpszFilespec, '\\'); if (lpszSpec == NULL) { lpszSpec = strrchr(lpszFilespec, '/'); } if (lpszSpec != NULL) { lpszFilespec = lpszSpec + 1; } DEBUG_PRINT(FTP, INFO, ("lpszFilespec = %s\n", lpszFilespec )); } // // loop round, parsing the listing until we reach the end or get an // error // needBuffer = TRUE; error = ERROR_SUCCESS; while ((dwBufferLength != 0) && (error == ERROR_SUCCESS)) { PLIST_ENTRY dirEntry; LPWIN32_FIND_DATA lpFind; // // we need to allocate a buffer for the WIN32_FIND_DATA structure // unless we already have one from the previous iteration (because // the filename didn't match our target criteria) // if (needBuffer) { dirEntry = (PLIST_ENTRY)ALLOCATE_FIXED_MEMORY( sizeof(LIST_ENTRY) + sizeof(WIN32_FIND_DATA) ); lpFind = (LPWIN32_FIND_DATA)(dirEntry + 1); needBuffer = FALSE; DEBUG_PRINT(FTP, INFO, ("Allocated WIN32_FIND_DATA @ %x\n", lpFind )); } if (dirEntry == NULL) { error = ERROR_NOT_ENOUGH_MEMORY; DEBUG_PRINT(FTP, ERROR, ("Failed to allocate WIN32_FIND_DATA\n" )); } else { PARSE_STATE state; // // zero initialize the WIN32_FIND_DATA fields we don't fill in // below // ClearFindDataFields(lpFind); // // and parse the rest of the information out of the returned FTP // directory listing // state = directoryParser(&lpBuffer, &dwBufferLength, lpFind); // // if the parser returns State_Continue or State_Done then we need // to add the structure to the list if the caller wants it, else we // free it and quit // if (state != State_Error) { BOOL addIt; // // before we put this entry on the list, see if the caller wants // it // if (ARGUMENT_PRESENT(lpszFilespec)) { addIt = MyFsRtlIsNameInExpression(lpszFilespec, lpFind->cFileName, TRUE // case-sensitive ); } else { addIt = TRUE; } if (addIt) { DEBUG_PRINT(FTP, INFO, ("Match: file %q, target %q\n", lpFind->cFileName, lpszFilespec )); InsertTailList(lpList, (PLIST_ENTRY)dirEntry); needBuffer = TRUE; } else { DEBUG_PRINT(FTP, INFO, ("No match: file %q, target %q\n", lpFind->cFileName, lpszFilespec )); } } // // if we had an error or there's no more buffer to parse but we // didn't keep the last entry, then we need to free the unused // WIN32_FIND_DATA and get out // if ((state == State_Error) || ((state == State_Done) && !needBuffer)) { FREE_MEMORY(dirEntry); if (state == State_Error) { DEBUG_PRINT(FTP, ERROR, ("State_Error\n" )); // // BUGBUG - error code // error = ERROR_INTERNET_INTERNAL_ERROR; } } } } quit: // // if we had an error then free up any data structures that we allocated // if (error != ERROR_SUCCESS) { ClearFindList(lpList); } DEBUG_LEAVE(error); return error; } BOOL IsFilespecWild( IN LPCSTR lpszFilespec ) /*++ Routine Description: Returns TRUE if lpszFilespec is a wild-card file specifier Arguments: lpszFilespec - pointer to string containing file specification. Cannot be a NULL string Return Value: BOOL --*/ { int len; int i; INET_ASSERT(ARGUMENT_PRESENT(lpszFilespec)); // // check if the file specifier contains a '*' or a '?'. If so, then the // caller is making a DOS-style search request and we have to perform our // own filtering, otherwise, we can leave the server to return what the // caller asked for // for (i = 0, len = strlen(lpszFilespec); i < len; ++i) { if ((lpszFilespec[i] == '*') || (lpszFilespec[i] == '?')) { return TRUE; } } return FALSE; } VOID ClearFindList( IN PLIST_ENTRY lpList ) /*++ Routine Description: Dequeues and deallocates all WIN32_FIND_DATA structures on a directory list Arguments: lpList - pointer to list to clear Return Value: None. --*/ { DEBUG_ENTER((DBG_FTP, None, "ClearFindList", "%x", lpList )); while (!IsListEmpty(lpList)) { PLIST_ENTRY lpHead; lpHead = RemoveHeadList(lpList); DEBUG_PRINT(FTP, INFO, ("Freeing WIN32_FIND_DATA @ %x, FileName=%q\n", lpHead, ((LPWIN32_FIND_DATA)(lpHead + 1))->cFileName )); FREE_MEMORY(lpHead); } DEBUG_LEAVE(0); } // // private functions // PRIVATE BOOL DetermineDirectoryFormat( IN LPSTR* lpBuffer, IN LPDWORD lpdwBufferLength, OUT DIR_PARSER* lpfnParserFunction ) /*++ Routine Description: Determines whether the directory listing is in Unix or NT (or other?) format and returns a pointer to the parser function to use The buffer pointer and length may be adjusted past any prologue information Arguments: lpBuffer - pointer to pointer to buffer containing directory listing lpdwBufferLength - pointer to length of Buffer lpfnParserFunction - returned directory parser function Return Value: BOOL Success - TRUE Failure - FALSE --*/ { DEBUG_ENTER((DBG_FTP, Bool, "DetermineDirectoryFormat", "%x [%.40q], %x [%d], %x", lpBuffer, *lpBuffer, lpdwBufferLength, *lpdwBufferLength, lpfnParserFunction )); BOOL success; if (!SkipWhitespace(lpBuffer, lpdwBufferLength)) { success = FALSE; goto quit; } if (IsNtDateFormat(*lpBuffer, *lpdwBufferLength)) { DEBUG_PRINT(FTP, INFO, ("format is NT\n" )); *lpfnParserFunction = ParseNtDirectory; success = TRUE; goto quit; } // // we think the directory output is from Unix. The listing probably // starts with "total #" or a number, or other random garbage. We // know that a Unix dir listing starts with the ls attributes, so // we'll search for those, but keep our search within a reasonable // distance of the start // LPSTR buffer; DWORD length; char tokenBuffer[TOKEN_BUFFER_LENGTH]; int lengthChecked; int iteration; int dataLength; buffer = *lpBuffer; length = *lpdwBufferLength; lengthChecked = 0; iteration = 0; dataLength = min((int)*lpdwBufferLength, RELATIVELY_SMALL_AMOUNT_OF_LS_DATA); while (lengthChecked < dataLength) { DWORD tokenLength; DWORD previousLength; tokenLength = sizeof(tokenBuffer); if (!GetToken(buffer, length, tokenBuffer, &tokenLength)) { success = FALSE; goto quit; } lengthChecked += tokenLength; if (IsUnixAttributeFormat(tokenBuffer, tokenLength)) { DEBUG_PRINT(FTP, INFO, ("format is Unix\n" )); *lpfnParserFunction = ParseUnixDirectory; *lpBuffer = buffer; *lpdwBufferLength = length; success = TRUE; goto quit; } else if ((iteration == 0) && (tokenLength == 5) && !strnicmp(tokenBuffer, "total", 5)) { // // there may be nothing in the directory listing, except // "total 0". If this is this case, then we recognize the // format // buffer += tokenLength; length -= tokenLength; tokenLength = sizeof(tokenBuffer) - 1; // for '\0' if (!GetToken(buffer, length, tokenBuffer, &tokenLength)) { success = FALSE; goto quit; } tokenBuffer[tokenLength] = '\0'; if (isdigit(tokenBuffer[0]) && (atoi(tokenBuffer) == 0)) { DEBUG_PRINT(FTP, INFO, ("format is Unix - empty directory\n" )); *lpfnParserFunction = ParseUnixDirectory; SkipLine(&buffer, &length); *lpBuffer = buffer; *lpdwBufferLength = length; success = TRUE; goto quit; } } // // try the next line // previousLength = length; SkipLine(&buffer, &length); lengthChecked += previousLength - length; ++iteration; } // // not NT or Unix. Lets try for OS/2. The format of an OS/2 directory entry // is: // // [][DIR|]