/**********************************************************************/ /** Microsoft Windows NT **/ /** Copyright(c) Microsoft Corp., 1995 **/ /**********************************************************************/ /* newls.cxx Implements a simulated "ls" command for the FTP Server service, with buffering and possibly caching of the results generated. Functions exported by this module: SimulateLs() SpecialLs() FILE HISTORY: MuraliK 19-April-1995 Created. */ # include "ftpdp.hxx" # include "tsunami.hxx" # include "lsaux.hxx" # include /********************************************************************** * Private Globals **********************************************************************/ // Following message is required to send error msg when the file // or directory is absent. extern CHAR * p_NoFileOrDirectory; // This lives in engine.c. static const char * PSZ_DEFAULT_SEARCH_PATH = ""; static const char * PSZ_WILD_CHARACTERS = "*?<>"; // include DOS wilds! /********************************************************************** * Prototypes of Functions **********************************************************************/ DWORD FormatFileInfoLikeMsdos( IN OUT LS_BUFFER * plsb, IN const WIN32_FIND_DATA * pfdInfo, IN const LS_FORMAT_INFO * pFormatInfo ); DWORD FormatFileInfoLikeUnix( IN OUT LS_BUFFER * plsb, IN const WIN32_FIND_DATA * pfdInfo, IN const LS_FORMAT_INFO * pFormatInfo ); APIERR SimulateLsWorker( IN USER_DATA * pUserData, IN BOOL fUseDataSocket, IN CHAR * pszSearchPath, IN const LS_OPTIONS * pOptions, IN BOOL fSendHeader = FALSE, IN BOOL fSendBlank = FALSE ); APIERR SpecialLsWorker( IN USER_DATA *pUserData, IN BOOL fUseDataSocket, CHAR * pszSearchPath, BOOL fShowDirectories, IN OUT LS_BUFFER * plsb ); // // The following is a table consisting of the sort methods used // for generating various dir listing. The table is indexed by LS_SORT. // This is used for finding the appropriate compare function for // any given sort method. // THE ORDER OF FUNCTIONS IN THIS ARRAY MUST MATCH THE ORDER IN LS_SORT! // static PFN_CMP_WIN32_FIND_DATA CompareRoutines[] = { CompareNamesInFileInfo, // Normal sort order. CompareWriteTimesInFileInfo, CompareCreationTimesInFileInfo, CompareAccessTimesInFileInfo, CompareNamesRevInFileInfo, // Reversed sort order. CompareWriteTimesRevInFileInfo, CompareCreationTimesRevInFileInfo, CompareAccessTimesRevInFileInfo }; // method,direction are used for indexing. #define SORT_INDEX(method, dirn) ((INT)(method) + \ ((dirn) ? (INT)MaxLsSort : 0)) // constants and literals #define ILLEGAL_FILE_CHARS (UCHAR*)"*?<>/\\\"|:" /********************************************************************** * Functions **********************************************************************/ static BOOL SeparateOutFilterSpec( IN OUT CHAR * szPath, IN BOOL fHasWildCards, OUT LPCSTR * ppszFilterSpec) /*++ The path has the form c:\ftppath\foo\bar\*.* Check to see if the path is already a directory. If so set filter as nothing. This function identifies the last "\" and terminates the path at that point. The remaining forms a filter (here: *.*) --*/ { char * pszFilter; BOOL fDir = FALSE; IF_DEBUG( DIR_LIST) { DBGPRINTF((DBG_CONTEXT, "SeparateOutFilter( %s, %d)\n", szPath, fHasWildCards)); } DBG_ASSERT( ppszFilterSpec != NULL); *ppszFilterSpec = NULL; // initialize. if ( !fHasWildCards) { // Identify if the path is a directory DWORD dwAttribs = GetFileAttributes( szPath); if ( dwAttribs == INVALID_FILE_ATTRIBUTES) { return ( FALSE); } else { fDir = ( IS_DIR(dwAttribs)); } } if ( !fDir ) { pszFilter = (PCHAR)_mbsrchr( (PUCHAR)szPath, '\\'); //This has to exist, since valid path was supplied. DBG_ASSERT( pszFilter != NULL); *pszFilter = '\0'; // terminate the old path. pszFilter++; // skip past the terminating null character. *ppszFilterSpec = (*pszFilter == '\0') ? NULL : pszFilter; IF_DEBUG(DIR_LIST) { DBGPRINTF((DBG_CONTEXT, "Path = %s; Filter = %s\n", szPath, *ppszFilterSpec)); } } return (TRUE); } // SeparateOutFilterSpec() APIERR SimulateLs( IN USER_DATA * pUserData, IN OUT CHAR * pszArg, IN BOOL fUseDataSocket, IN BOOL fDefaultLong ) /*++ This function simulates an LS command. This simulated ls command supports the following switches: -C = Multi column, sorted down. -l = Long format output. -1 = One entry per line (default). -F = Directories have '/' appended. -t = Sort by time of last write. -c = Sort by time of creation. -u = Sort by time of last access. -r = Reverse sort direction. -a = Show all files (including .*). -A = Show all files (except . and ..). -R = Recursive listing. Arguments: pUserData -- the user initiating the request. pszArg -- contains the search path, preceded by switches. Note: The argument is destroyed during processing!!!! fUseDataSocket -- if TRUE use Data socket, else use control socket. fDefaultLong -- should the default be long ? ( if TRUE) Returns: APIERR, 0 on success. --*/ { APIERR serr = 0; LS_OPTIONS options; CHAR * pszToken = pszArg; CHAR * pszDelimiters = " \t"; DBG_ASSERT( pUserData != NULL ); // // Setup default ls options. // options.OutputFormat = (( fDefaultLong) ? LsOutputLongFormat : LsOutputSingleColumn); options.SortMethod = LsSortByName; options.fReverseSort = FALSE; options.fDecorate = FALSE; options.fShowAll = FALSE; options.fShowDotDot = FALSE; options.fRecursive = FALSE; options.lsStyle = ( TEST_UF( pUserData, MSDOS_DIR_OUTPUT) ? LsStyleMsDos : LsStyleUnix ); options.fFourDigitYear= TEST_UF( pUserData, 4_DIGIT_YEAR); // // Process switches in the input, if any // // simplify things by skipping whitespace... if (pszArg && isspace(*pszArg)) { while (isspace(*pszArg)) pszArg++; } // now we should be pointing to the options, or the filename if (pszArg && (*pszArg == '-')) { for( pszToken = strtok( pszArg, pszDelimiters ); // getfirst Tok. ( ( pszToken != NULL ) && ( *pszToken == '-' ) ); pszToken = strtok( NULL, pszDelimiters) // get next token ) { DBG_ASSERT( *pszToken == '-' ); // process all the switches in single token // for( pszToken++; *pszToken; pszToken++) is written as follows while ( *++pszToken) { switch( *pszToken ) { case 'C' : case '1' : options.OutputFormat = LsOutputSingleColumn; break; case 'l' : options.OutputFormat = LsOutputLongFormat; break; case 'F' : options.fDecorate = TRUE; break; case 'r' : options.fReverseSort = TRUE; break; case 't' : options.SortMethod = LsSortByWriteTime; break; case 'c' : options.SortMethod = LsSortByCreationTime; break; case 'u' : options.SortMethod = LsSortByAccessTime; break; case 'a' : options.fShowAll = TRUE; options.fShowDotDot = TRUE; break; case 'A' : options.fShowAll = TRUE; options.fShowDotDot = FALSE; break; case 'R' : options.fRecursive = TRUE; break; default: IF_DEBUG( COMMANDS ) { DBGPRINTF(( DBG_CONTEXT, "ls: skipping unsupported option '%c'\n", *pszToken )); } break; } // switch() } // process all switches in a token } // for } // // If the user is requesting an MSDOS-style long-format listing, // then enable display of "." and "..". This will make the MSDOS-style // long-format output look a little more like MSDOS. // options.fShowDotDot = ( options.fShowDotDot || ( options.lsStyle == LsStyleMsDos && ( options.OutputFormat == LsOutputLongFormat )) ); // // since LIST is sent out synchronously, bump up thread count // before beginning to send out the response for LIST // // A better method: // Make LIST generate response in a buffer and use async IO // operations for sending response. // TBD (To Be Done) // AtqSetInfo( AtqIncMaxPoolThreads, 0); // // At this point, pszToken is either NULL or points // to the first (of potentially many) LS search paths. // serr = SimulateLsWorker(pUserData, fUseDataSocket, pszToken, &options); // // bring down the thread count when response is completed // TBD: Use Async send reponse() // AtqSetInfo( AtqDecMaxPoolThreads, 0); return ( serr); } // SimulateLs() APIERR SpecialLs( USER_DATA * pUserData, CHAR * pszArg, IN BOOL fUseDataSocket ) /*++ This produces a special form of the directory listing that is required when an NLST command is received with no switches. Most of the FTP clients require this special form in order to get the MGET and MDEL commands to work. This produces atmost one level of directory information. Arguments: pUserData - the user initiating the request. pszArg - pointer to null-terminated string containing the argument. NULL=current directory for UserData. fUseDataSocket - if TRUE use Data Socket, else use the ContorlSocket. Returns: APIERR - 0 if successful, !0 if not. --*/ { APIERR dwError = 0; LS_BUFFER lsb; DBG_ASSERT( pUserData != NULL ); DBG_ASSERT( ( pszArg == NULL ) || ( *pszArg != '-' ) ); // No options if ((dwError = lsb.AllocateBuffer( DEFAULT_LS_BUFFER_ALLOC_SIZE)) != NO_ERROR) { IF_DEBUG(ERROR) { DBGPRINTF((DBG_CONTEXT, "Buffer allocation(%d bytes) failed.\n", DEFAULT_LS_BUFFER_ALLOC_SIZE)); } return (dwError); } // // since LIST is sent out synchronously, bump up thread count // before beginning to send out the response for LIST // // A better method: // Make LIST generate response in a buffer and use async IO // operations for sending response. // TBD (To Be Done) // AtqSetInfo( AtqIncMaxPoolThreads, 0); // // Let the worker do the dirty work. // dwError = SpecialLsWorker(pUserData, fUseDataSocket, pszArg, // search path (no switches) TRUE, // show directories &lsb); if ( dwError == NO_ERROR) { // send all the remaining bytes in the buffer and then free memory. if ( lsb.QueryCB() != 0) { SOCKET sock = ((fUseDataSocket) ? pUserData->QueryDataSocket() : pUserData->QueryControlSocket()); dwError = SockSend(pUserData, sock, lsb.QueryBuffer(), lsb.QueryCB()/sizeof(CHAR)); } lsb.FreeBuffer(); } // // bring down the thread count when response is completed // TBD: Use Async send reponse() // AtqSetInfo( AtqDecMaxPoolThreads, 0); return ( dwError); } // SpecialLs() // // Private functions. // APIERR SimulateLsWorker( USER_DATA * pUserData, IN BOOL fUseDataSocket, IN CHAR * pszSearchPath, IN const LS_OPTIONS * pOptions, IN BOOL fSendHeader, IN BOOL fSendBlank ) /*++ Worker function for SimulateLs function, forms directory listing for requested directory, formats the directory listing and sends it to the client. Arguments: pUserData - The user initiating the request. pszSearchPath - Search directory, NULL = current dir. pOptions - LS options set by command line switches. fSendHeader - if TRUE send header with directory name in it. fSendBlank - also add a blank if there is one that has to be sent. Returns: APIERR - 0 if successful, !0 if not. HISTORY: MuraliK 24-Apr-1995 ReCreated. --*/ { SOCKET sock; BOOL fLikeMsdos; CHAR szSearch[MAX_PATH+1]; CHAR rgchLowFileName[MAX_PATH+1]; // used for lower casing filename BOOL fMapToLowerCase = FALSE; DWORD dwAccessMask = 0; BOOL fImpersonated = FALSE; LS_BUFFER lsb; LS_FORMAT_INFO lsfi; // required only for long formatting. BOOL fHasWildCards = FALSE; DWORD dwError = NO_ERROR; APIERR serr = 0; TS_DIRECTORY_INFO tsDirInfo( pUserData->QueryInstance()->GetTsvcCache()); IF_DEBUG( DIR_LIST) { DBGPRINTF((DBG_CONTEXT, " SimulateLsWorker( %08x, %d, %s)\n", pUserData, fUseDataSocket, pszSearchPath)); } DBG_ASSERT( pUserData != NULL && pOptions != NULL); // // Check for emptiness of path or wildcards in search path. // We are only concerned about wild cards in user input. The reason // is all trailing '.' will be removed when we canonicalize // the path ( which user may not appreciate). // if ( IS_EMPTY_PATH(pszSearchPath)) { // we know pszSearchPath will not change the buffer! pszSearchPath = (char *) PSZ_DEFAULT_SEARCH_PATH; } else if (strpbrk( pszSearchPath, PSZ_WILD_CHARACTERS ) != NULL) { // // Search path contains wildcards. // fHasWildCards = TRUE; } // // Canonicalize the search path. // DWORD cbSize = sizeof(szSearch) - 1; // save space for '.' we add later dwError = pUserData->VirtualCanonicalize(szSearch, &cbSize, pszSearchPath, AccessTypeRead, &dwAccessMask); DBG_ASSERT( dwError != ERROR_INSUFFICIENT_BUFFER); if( dwError == NO_ERROR ) { FTP_LS_FILTER_INFO fls; // required for generating directory listing PFN_CMP_WIN32_FIND_DATA pfnCompare; // // VirtualCanonicalize() when sanitizing the path removes // trailing dots from the path. Replace them here // DBG_ASSERT( !fHasWildCards || strlen(pszSearchPath) >= 1); if( fHasWildCards && (pszSearchPath[strlen(pszSearchPath)-1] == '.')) { DBG_ASSERT( strlen(szSearch) < MAX_PATH); strcat( szSearch, "." ); } // // Build the directory list. // pfnCompare = CompareRoutines[SORT_INDEX(pOptions->SortMethod, pOptions->fReverseSort) ]; // Separate the filter out ( that is the last component) if (pUserData->ImpersonateUser()) { if (SeparateOutFilterSpec( szSearch, fHasWildCards, &fls.pszExpression) ) { fls.fFilterHidden = !pOptions->fShowAll; fls.fFilterSystem = !pOptions->fShowAll; fls.fFilterDotDot = !pOptions->fShowDotDot; fls.fRegExpression = ( fls.pszExpression != NULL && fHasWildCards); fls.fIgnoreCase = pUserData->QueryInstance()->QueryLowercaseFiles(); dwError = GetDirectoryInfo(pUserData, &tsDirInfo, szSearch, &fls, pfnCompare); } else { dwError = GetLastError(); } pUserData->RevertToSelf(); } else { dwError = GetLastError(); } } // // If there were any errors, tell them the bad news now. // if( dwError != NO_ERROR ) { return (dwError); } sock = ((fUseDataSocket) ? pUserData->QueryDataSocket() : pUserData->QueryControlSocket()); DBG_ASSERT( tsDirInfo.IsValid()); int cDirEntries = tsDirInfo.QueryFilesCount(); if ( cDirEntries > 0) { // // put out the header block before starting dir listing // if( fSendHeader ) { serr = SockPrintf2( pUserData, sock, "%s%s:", (fSendBlank)? "\r\n" : "", // send \r\n pszSearchPath); if ( serr != 0) { return (serr); } } } fLikeMsdos = (pOptions->lsStyle == LsStyleMsDos); lsfi.fFourDigitYear = pOptions->fFourDigitYear; if( !fLikeMsdos ) { // // Initialize the information in lsfi if we are doing // long format output. // if ( pOptions->OutputFormat == LsOutputLongFormat) { SYSTEMTIME timeNow; BOOL fUserRead, fUserWrite; // // Obtain the current time. // The Unix-like output requires current year // GetLocalTime( &timeNow ); lsfi.wCurrentYear = timeNow.wYear; lsfi.hUserToken = TsTokenToImpHandle(pUserData->QueryUserToken()); // // Since szSearch contains the complete path, we call // PathAccessCheck directly without resolving // from absolute to virtual // fUserRead = TEST_UF( pUserData, READ_ACCESS); fUserWrite = TEST_UF( pUserData, WRITE_ACCESS); lsfi.fVolumeReadable = PathAccessCheck(AccessTypeRead, dwAccessMask, fUserRead, fUserWrite); lsfi.fVolumeWritable = PathAccessCheck(AccessTypeWrite, dwAccessMask, fUserRead, fUserWrite); lsfi.pszPathPart = szSearch; lsfi.pszFileName = NULL; lsfi.pszDecorate = NULL; } // if ( long format output) // // We need to be impersonated only for UNIX-style listing. // For UNIX style listing, we make some NTsecurity queries // and they work only under the context of an impersonation. // if ( !(fImpersonated = pUserData->ImpersonateUser())) { dwError = GetLastError(); } } // // Loop for each directory entry // if (dwError != NO_ERROR || (dwError = lsb.AllocateBuffer( DEFAULT_LS_BUFFER_ALLOC_SIZE)) != NO_ERROR) { IF_DEBUG(ERROR) { DBGPRINTF((DBG_CONTEXT, "Impersonation or Buffer allocation(%d bytes)", " failed.\n", DEFAULT_LS_BUFFER_ALLOC_SIZE)); } if ( fImpersonated) { pUserData->RevertToSelf(); } return (dwError); } // // Only map to lower case if not a remote drive AND the lower-case file // names flag is set AND this is not a case perserving file system. // if (*szSearch != '\\') { fMapToLowerCase = pUserData->QueryInstance()->QueryLowercaseFiles(); } for( int idx = 0; serr == 0 && idx < cDirEntries; idx++) { const WIN32_FIND_DATA * pfdInfo = tsDirInfo[idx]; DBG_ASSERT( pfdInfo != NULL); const CHAR * pszFileName = pfdInfo->cFileName; DWORD dwAttribs = pfdInfo->dwFileAttributes; // // Dump it. // // We may need to convert all filenames to lower case if so desired!! // Also, if the filename was converted from UNICODE and ended up having // characters such as '/' or '\' that can be harmful on the client, sanitize. BOOL fHasSlash = !!_mbspbrk((UCHAR*)pszFileName, ILLEGAL_FILE_CHARS); if( fMapToLowerCase || fHasSlash) { // // copy file name to local scratch and change the ptr pszFileName // because we cannot destroy pfdInfo->cFileName // P_strncpy( rgchLowFileName, pszFileName, sizeof(rgchLowFileName)); pszFileName = rgchLowFileName; // lowercase if needed if( fMapToLowerCase ) { CharLower( rgchLowFileName); } // sanitize if need to if( fHasSlash ) { for( UCHAR *pch = (UCHAR*)rgchLowFileName; (pch = _mbspbrk(pch, ILLEGAL_FILE_CHARS)) != NULL; *pch++ = '^') ; } } IF_DEBUG( DIR_LIST) { DBGPRINTF((DBG_CONTEXT, "Dir list for %s\n", pszFileName)); } // // Send the partial data obtained so far. // Use buffering to minimize number of sends occuring // if ( dwError == NO_ERROR) { if ( lsb.QueryRemainingCB() < MIN_LS_BUFFER_SIZE) { // send the bytes available in buffer and reset the buffer serr = SockSend(pUserData, sock, lsb.QueryBuffer(), lsb.QueryCB()/sizeof(CHAR)); lsb.ResetAppendPtr(); } } else { serr = dwError; } // // Check for socket errors on send or pending OOB data. // if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) ) { break; } CHAR * pszDecorate = ( (pOptions->fDecorate && IS_DIR(dwAttribs) ) ? "/" : ""); if( pOptions->OutputFormat == LsOutputLongFormat ) { FILETIME ftLocal; // // Long format output. Just send the file/dir info. // // // Map the file's last write time to (local) system time. // if ( !FileTimeToLocalFileTime( PickFileTime( pfdInfo, pOptions), &ftLocal) || ! FileTimeToSystemTime( &ftLocal, &lsfi.stFile) ) { dwError = GetLastError(); IF_DEBUG( ERROR) { DBGPRINTF(( DBG_CONTEXT, "Error in converting largeintger time %lu\n", dwError)); } } else { lsfi.pszDecorate = pszDecorate; lsfi.pszFileName = pszFileName; if( fLikeMsdos ) { dwError = FormatFileInfoLikeMsdos(&lsb, pfdInfo, &lsfi); DBG_ASSERT( dwError != ERROR_INSUFFICIENT_BUFFER); } else { dwError = FormatFileInfoLikeUnix(&lsb, pfdInfo, &lsfi); DBG_ASSERT( dwError != ERROR_INSUFFICIENT_BUFFER); } } } else { // // Short format output. // INT cchSize = _snprintf(lsb.QueryAppendPtr(), lsb.QueryRemainingCB(), "%s%s\r\n", pszFileName, pszDecorate); DBG_ASSERT( cchSize > 0 ); if ((cchSize < 0) || (cchSize >= (INT)lsb.QueryRemainingCB()) ) { dwError = ERROR_INSUFFICIENT_BUFFER; } else { lsb.IncrementCB( cchSize * sizeof(CHAR)); } } } // for() // // Get out of being impersonated. // if ( fImpersonated) { pUserData->RevertToSelf(); } if ( dwError == NO_ERROR) { // send all the remaining bytes in the buffer and then free memory. if ( lsb.QueryCB() != 0) { serr = SockSend(pUserData, sock, lsb.QueryBuffer(), lsb.QueryCB()/sizeof(CHAR)); } lsb.FreeBuffer(); } else { return ( dwError); // an error has occured. stop processing } if( serr == 0 && !TEST_UF( pUserData, OOB_DATA) && pOptions->fRecursive ) { // // The user want's a recursive directory search... // CHAR szOriginal[ MAX_PATH*2]; CHAR * pszOriginalFilePart; // Obtain a copy of the path in the szOriginal so that we // can change it while recursively calling ourselves. if ( pszSearchPath == PSZ_DEFAULT_SEARCH_PATH) { // means that we had all files/dir of current directory. strcpy( szOriginal, fLikeMsdos ? ".\\" : "./" ); } else { DBG_ASSERT( strlen(pszSearchPath) < MAX_PATH); P_strncpy( szOriginal, pszSearchPath, MAX_PATH ); // strip off the wild cards if any present if( fHasWildCards ) { CHAR * pszTmp; pszTmp = (PCHAR)_mbsrchr( (PUCHAR)szOriginal, '\\'); pszTmp = pszTmp ? pszTmp : strrchr( szOriginal, '/' ); pszTmp = pszTmp ? pszTmp : strrchr( szOriginal, ':' ); pszTmp = ( pszTmp) ? pszTmp+1 : szOriginal; *pszTmp = '\0'; } else { CHAR ch; int cb = strlen( szOriginal); DBG_ASSERT( cb > 0); ch = *CharPrev( szOriginal, szOriginal + cb ); if( !IS_PATH_SEP( ch ) ) { // to add "/" DBG_ASSERT( strlen( szOriginal) + 2 < MAX_PATH); strcat( szOriginal, fLikeMsdos ? "\\" : "/" ); } } } DWORD szOriginalLen = strlen(szOriginal); DWORD MaxFilePartLen = sizeof(szOriginal) - szOriginalLen; pszOriginalFilePart = szOriginal + szOriginalLen; DBG_ASSERT( tsDirInfo.IsValid()); DBG_ASSERT( cDirEntries == tsDirInfo.QueryFilesCount()); for( int idx = 0; serr == 0 && idx < cDirEntries; idx++) { const WIN32_FIND_DATA * pfdInfo = tsDirInfo[idx]; DBG_ASSERT( pfdInfo != NULL); const char * pszFileName = pfdInfo->cFileName; DWORD dwAttribs = pfdInfo->dwFileAttributes; // // Filter out non-directories. // if( !IS_DIR( dwAttribs) ) { continue; } // // Also filter out directories with names containing illegal characters // if( _mbspbrk((UCHAR*)pszFileName, ILLEGAL_FILE_CHARS) ) { continue; } // // Dump it. // DBG_ASSERT( strlen( pszOriginalFilePart) + strlen(pszFileName) < MAX_PATH + 2); P_strncpy( pszOriginalFilePart, pszFileName, MaxFilePartLen); serr = SimulateLsWorker(pUserData, fUseDataSocket, szOriginal, pOptions, TRUE, TRUE); // // Check for socket errors on send or pending OOB data. // if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) ) { break; } } // for( directory looping) } // if ( fRecursive) // At the end of directory listing. Return back. IF_DEBUG( DIR_LIST) { DBGPRINTF((DBG_CONTEXT, "SimulateLsWorker() for User %08x, Dir %s returns %d\n", pUserData, pszSearchPath, serr)); } return serr; } // SimulateLsWorker() APIERR SpecialLsWorker( USER_DATA * pUserData, IN BOOL fUseDataSocket, CHAR * pszSearchPath, BOOL fShowDirectories, IN OUT LS_BUFFER * plsb ) /*++ This is the worker function for Special Ls function. It is similar to the the SimulateLsWorker, only in that it shows directory if the fShowDirectories flag is set. The reason for this comes from a special FTP command which inquires about all the files in the first level and second level of current directory, which is not a recursive listing at all. This function when it recursively calls itself, always sets the fShowDirectories as FALSE. Arguments: pUserData pointer to user data object that initiated the request. fUseDataSocket if TRUE use DataSocket of UserData else use the control socket of UserData. pszSearchPath pointer to null-terminated string for requested directory. NULL means use current directory. fShowDirectories only show directories if TRUE. plsb pointer to buffer to accumulate the data generated and send it out in a single bulk. Returns: APIERR 0 if successful. History: KeithMo 17-Mar-1993 Created. MuraliK 26-Apr-1995 ReCreated to use new way of generation. --*/ { CHAR chSeparator; CHAR * pszRecurse; SOCKET sock; BOOL fHasWildCards = FALSE; DWORD dwError = NO_ERROR; TS_DIRECTORY_INFO tsDirInfo( pUserData->QueryInstance()->GetTsvcCache()); CHAR szSearch[MAX_PATH+1]; CHAR szRecurse[MAX_PATH+1]; BOOL fMapToLowerCase = FALSE; CHAR rgchLowFileName[MAX_PATH+1]; // used for lower casing filename BOOL fHadOneComponent = FALSE; DBG_ASSERT( pUserData != NULL); IF_DEBUG( DIR_LIST) { DBGPRINTF((DBG_CONTEXT, "Entering SpecialLsWorker( %08x, %s)\n", pUserData, pszSearchPath)); } chSeparator = TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) ? '\\' : '/'; // // Check for wildcards in search path. // if( ( pszSearchPath != NULL ) && ( *pszSearchPath != '\0' ) ) { // // Setup for recursive directory search. We'll set things up // so we can strcpy a new directory to pszRecurse, then // recursively call ourselves with szRecurse as the search // path. // // We also use szRecurse as a "prefix" to display before each // file/directory. The FTP Client software needs this for the // MDEL & MGET commands. // if ( strlen(pszSearchPath) > sizeof(szRecurse) - 1 ) { return ERROR_BUFFER_OVERFLOW; } strcpy( szRecurse, pszSearchPath); // get slash. pszRecurse = (PCHAR)_mbsrchr( (PUCHAR)szRecurse, '\\'); pszRecurse = pszRecurse ? pszRecurse : strrchr( szRecurse, '/'); fHadOneComponent = (pszRecurse == NULL); if( strpbrk( szRecurse, PSZ_WILD_CHARACTERS) != NULL ) { // // Search path contains wildcards. // fHasWildCards = TRUE; // we do not care about components when wild card is present fHadOneComponent = FALSE; // // Strip the wildcard pattern from the search path. // look for both kind of slashes ( since precanonicalized) // // // If we found right-most dir component, skip path separator // else set it to start of search path. // pszRecurse = pszRecurse ? pszRecurse + 1 : szRecurse; } else { // // No wildcards, so the argument must be a path. // Ensure it is terminated with a path separator. // pszRecurse = CharPrev( szRecurse, szRecurse + strlen(szRecurse) ); if( !IS_PATH_SEP( *pszRecurse ) ) { *++pszRecurse = chSeparator; } pszRecurse++; // skip the path separator } } else { // // No arguments. // pszRecurse = szRecurse; // // Munge the arguments around a bit. NULL = *.* in current // directory. If the user specified a directory (like d:\foo) // then append *.*. // pszSearchPath = (char *) PSZ_DEFAULT_SEARCH_PATH; } *pszRecurse = '\0'; // // Canonicalize the search path. // DWORD cbSize = sizeof(szSearch) - 1; // save space for '.' we may need to add dwError = pUserData->VirtualCanonicalize(szSearch, &cbSize, pszSearchPath, AccessTypeRead); DBG_ASSERT( dwError != ERROR_INSUFFICIENT_BUFFER); if( dwError == NO_ERROR ) { FTP_LS_FILTER_INFO fls; // required for generating directory listing // // VirtualCanonicalize() when sanitizing the path removes // trailing dots from the path. Replace them here // if( fHasWildCards && (pszSearchPath[strlen(pszSearchPath)-1] == '.')) { strcat( szSearch, "." ); } // // Build the directory list. // if (pUserData->ImpersonateUser()) { if (SeparateOutFilterSpec( szSearch, fHasWildCards, &fls.pszExpression) ) { fls.fFilterHidden = TRUE; fls.fFilterSystem = TRUE; fls.fFilterDotDot = TRUE; fls.fRegExpression = ( fls.pszExpression != NULL && fHasWildCards); fls.fIgnoreCase = pUserData->QueryInstance()->QueryLowercaseFiles(); dwError = GetDirectoryInfo(pUserData, &tsDirInfo, szSearch, &fls, NULL); // unsorted list } else { dwError = GetLastError(); } pUserData->RevertToSelf(); } else { dwError = GetLastError(); } } // // If there were any errors, tell them the bad news now. // if( dwError != NO_ERROR ) { return ( dwError); } if ( fHadOneComponent) { // HARD CODE! Spend some time and understand this.... // // Adjust the szRecurse buffer to contain appropriate path // such that in presence of one component we generate proper // result. // // the given path is either invalid or non-directory // so reset the string stored in szRecurse. szRecurse[0] = '\0'; pszRecurse = szRecurse; } // // Only map to lower case if not a remote drive AND the lower-case file // names flag is set AND this is not a case perserving file system. // if (*szSearch != '\\') { fMapToLowerCase = pUserData->QueryInstance()->QueryLowercaseFiles(); } // // Loop until we're out of files to find. // sock = ((fUseDataSocket) ? pUserData->QueryDataSocket() : pUserData->QueryControlSocket()); int cDirEntries = tsDirInfo.QueryFilesCount(); for( int idx = 0; dwError == NO_ERROR && idx < cDirEntries; idx++) { const WIN32_FIND_DATA * pfdInfo = tsDirInfo[idx]; DBG_ASSERT( pfdInfo != NULL); const CHAR * pszFileName = pfdInfo->cFileName; DWORD dwAttribs = pfdInfo->dwFileAttributes; if ( !fShowDirectories && IS_DIR( dwAttribs)) { continue; } // // Dump it. // // We may need to convert all filenames to lower case if so desired!! // Also, if the filename was converted from UNICODE and ended up having // characters such as '/' or '\' that can be harmful on the client, sanitize. BOOL fHasSlash = !!_mbspbrk((UCHAR*)pszFileName, ILLEGAL_FILE_CHARS); if( fMapToLowerCase || fHasSlash) { // // copy file name to local scratch and change the ptr pszFileName // because we cannot destroy pfdInfo->cFileName // P_strncpy( rgchLowFileName, pszFileName, sizeof(rgchLowFileName)); pszFileName = rgchLowFileName; // lowercase if needed if( fMapToLowerCase ) { CharLower( rgchLowFileName); } // sanitize if need to if( fHasSlash ) { for( UCHAR *pch = (UCHAR*)rgchLowFileName; (pch = _mbspbrk(pch, ILLEGAL_FILE_CHARS)) != NULL; *pch++ = '^') ; } } // // Send the partial data obtained so far. // Use buffering to minimize number of sends occuring // if ( dwError == NO_ERROR) { if ( plsb->QueryRemainingCB() < MIN_LS_BUFFER_SIZE) { // send the bytes available in buffer and reset the buffer dwError = SockSend(pUserData, sock, plsb->QueryBuffer(), plsb->QueryCB()/sizeof(CHAR)); plsb->ResetAppendPtr(); } } // // Test for aborted directory listing or socket error. // if( TEST_UF( pUserData, OOB_DATA ) || ( dwError != NO_ERROR ) ) { break; } // // If no wildcards were given, then just dump out the // file/directory. If wildcards were given, AND this // is a directory, then recurse (one level only) into // the directory. The mere fact that we don't append // any wildcards to the recursed search path will // prevent a full depth-first recursion of the file system. // Do not recurse if directory name contains illegal characters (converted from Unicode) // if( fHasWildCards && IS_DIR(dwAttribs) && !_mbspbrk((UCHAR*)pszFileName, ILLEGAL_FILE_CHARS) ) { DBG_ASSERT(strcmp( pszFileName, "." ) != 0); DBG_ASSERT(strcmp( pszFileName, "..") != 0); DBG_ASSERT(strlen(szRecurse)+strlen( pszFileName) < MAX_PATH); // BUGBUG: need to understand this better and ensure this does not overflow strcpy( pszRecurse, pszFileName ); strcat( pszRecurse, "/"); // indicating this is a directory dwError = SpecialLsWorker(pUserData, fUseDataSocket, szRecurse, FALSE, plsb); } else { INT cchSize; *pszRecurse = '\0'; // as a side effect this terminates szRecurse. // // Short format output. // cchSize = _snprintf(plsb->QueryAppendPtr(), plsb->QueryRemainingCB(), "%s%s\r\n", szRecurse, pszFileName); DBG_ASSERT( cchSize > 0 ); if ( (cchSize < 0) || (cchSize >= (INT)plsb->QueryRemainingCB()) ) { dwError = ERROR_INSUFFICIENT_BUFFER; } else { plsb->IncrementCB( cchSize*sizeof(CHAR)); } } } // for IF_DEBUG( DIR_LIST) { DBGPRINTF((DBG_CONTEXT, "Leaving SpecialLsWorker() with Error = %d\n", dwError)); } return (dwError); } // SpecialLsWorker() /************************************************** * Formatting functions. **************************************************/ DWORD FormatFileInfoLikeMsdos( IN OUT LS_BUFFER * plsb, IN const WIN32_FIND_DATA * pfdInfo, IN const LS_FORMAT_INFO * pFormatInfo ) /*++ Forms an MSDOS like directory entry for the given dir info object. Arguments: plsb pointer to buffer into which the dir line is generated. pfdInfo pointer to dir information element. pFormatInfo pointer to information required for formatting. ( use the file name in pFormatInfo, becauze it may have been made into lower case if necessary) Returns: Win32 error code and NO_ERROR on success. History: MuraliK 25-Apr-1995 --*/ { DWORD dwError = NO_ERROR; CHAR szSizeOrDir[32]; DWORD cbReqd; DBG_ASSERT(plsb != NULL && pfdInfo != NULL && pFormatInfo != NULL); if ( IS_DIR( pfdInfo->dwFileAttributes)) { strcpy( szSizeOrDir, " " ); } else { LARGE_INTEGER li; li.HighPart = pfdInfo->nFileSizeHigh; li.LowPart = pfdInfo->nFileSizeLow; IsLargeIntegerToDecimalChar( &li, szSizeOrDir); } DBG_ASSERT( strlen(szSizeOrDir) <= 20); cbReqd = ( 10 // size for the date field + 10 // size for time field + 20 // space for size/dir + strlen( pFormatInfo->pszFileName) + 8 // addl space + decoration ... ) * sizeof(CHAR); DBG_ASSERT( cbReqd <= MIN_LS_BUFFER_SIZE); if ( cbReqd < plsb->QueryRemainingCB()) { register const SYSTEMTIME * pst = &pFormatInfo->stFile; WORD wHour; char * pszAmPm; DWORD cchUsed; wHour = pst->wHour; pszAmPm = ( wHour < 12 ) ? "AM" : "PM"; if ( wHour == 0 ) { wHour = 12; } else if ( wHour > 12) { wHour -= 12; } if (pFormatInfo->fFourDigitYear) { cchUsed = wsprintfA(plsb->QueryAppendPtr(), "%02u-%02u-%04u %02u:%02u%s %20s %s%s\r\n", pst->wMonth, pst->wDay, pst->wYear, wHour, pst->wMinute, pszAmPm, szSizeOrDir, pFormatInfo->pszFileName, pFormatInfo->pszDecorate); } else { cchUsed = wsprintfA(plsb->QueryAppendPtr(), "%02u-%02u-%02u %02u:%02u%s %20s %s%s\r\n", pst->wMonth, pst->wDay, pst->wYear%100, //instead of wYear - 1900 wHour, pst->wMinute, pszAmPm, szSizeOrDir, pFormatInfo->pszFileName, pFormatInfo->pszDecorate); } DBG_ASSERT( cchUsed * sizeof(CHAR) <= cbReqd); plsb->IncrementCB(cchUsed * sizeof(CHAR)); } else { dwError = ERROR_INSUFFICIENT_BUFFER; } return ( dwError); } // FormatFileInfoLikeMsdos() DWORD FormatFileInfoLikeUnix( IN OUT LS_BUFFER * plsb, IN const WIN32_FIND_DATA * pfdInfo, IN const LS_FORMAT_INFO * pFormatInfo ) /*++ This function formats file information for a UNIX stle client. Arguments: plsb pointer to buffer into which the dir line is generated. pfdInfo pointer to dir information element. pFormatInfo pointer to information required for long formatting. Returns: Win32 error code and NO_ERROR on success. History: MuraliK 25-Apr-1995 --*/ { DWORD dwError = NO_ERROR; CHAR * pszFileOwner; CHAR * pszFileGroup; const SYSTEMTIME * pst; DWORD dwMode; DWORD cLinks; LARGE_INTEGER li; CHAR attrib[4]; CHAR szTimeOrYear[12]; CHAR szSize[32]; DWORD cbReqd; static CHAR * apszMonths[] = { " ", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; DBG_ASSERT( plsb != NULL); DBG_ASSERT( pFormatInfo != NULL ); DBG_ASSERT( pFormatInfo->hUserToken != NULL ); DBG_ASSERT( pFormatInfo->pszPathPart != NULL ); DBG_ASSERT( pfdInfo != NULL ); // // Build the attribute triple. Note that we only build one, // and replicate it three times for the owner/group/other fields. // dwMode = ComputeModeBits( pFormatInfo->hUserToken, pFormatInfo->pszPathPart, pfdInfo, &cLinks, pFormatInfo->fVolumeReadable, pFormatInfo->fVolumeWritable ); attrib[0] = ( dwMode & FILE_MODE_R ) ? 'r' : '-'; attrib[1] = ( dwMode & FILE_MODE_W ) ? 'w' : '-'; attrib[2] = ( dwMode & FILE_MODE_X ) ? 'x' : '-'; attrib[3] = '\0'; pst = &pFormatInfo->stFile; // NYI: can we make the following a single wsprintf call ?? if( pst->wYear == pFormatInfo->wCurrentYear ) { // // The file's year matches the current year, so // display the hour & minute of the last write. // wsprintfA( szTimeOrYear, "%2u:%02u", pst->wHour, pst->wMinute ); } else { // // The file's year does not match the current // year, so display the year of the last write. // wsprintfA( szTimeOrYear, "%4u", pst->wYear ); } // // CODEWORK: How expensive would it be do // get the proper owner & group names? // pszFileOwner = "owner"; pszFileGroup = "group"; // // Get the size in a displayable form. // li.HighPart = pfdInfo->nFileSizeHigh; li.LowPart = pfdInfo->nFileSizeLow; IsLargeIntegerToDecimalChar( &li, szSize); // // Dump it. // DBG_ASSERT( strlen(szSize) <= 12); cbReqd = ( 3*strlen(attrib) + strlen( pszFileOwner) + strlen( pszFileGroup) + 12 + 20 // date + strlen( pFormatInfo->pszFileName) + strlen( pFormatInfo->pszDecorate) + 20 // 20 for spaces etc. ) * sizeof(CHAR); DBG_ASSERT( cbReqd < MIN_LS_BUFFER_SIZE); if ( cbReqd < plsb->QueryRemainingCB()) { DWORD cchUsed = wsprintfA( plsb->QueryAppendPtr(), "%c%s%s%s %3lu %-8s %-8s %12s %s %2u %5s %s%s\r\n", (IS_DIR(pfdInfo->dwFileAttributes) ? 'd' : '-'), attrib, attrib, attrib, cLinks, pszFileOwner, pszFileGroup, szSize, apszMonths[pst->wMonth], pst->wDay, szTimeOrYear, pFormatInfo->pszFileName, pFormatInfo->pszDecorate); DBG_ASSERT( cchUsed * sizeof(CHAR) <= cbReqd); plsb->IncrementCB( cchUsed*sizeof(CHAR)); } else { dwError = ERROR_INSUFFICIENT_BUFFER; } return ( dwError); } // FormatFileInfoLikeUnix() /************************ End of File ************************/