#include "nsNetFile.h" #include "prmem.h" #include "nsString.h" #include "plstr.h" #include "prerror.h" #if !defined(XP_UNIX) && !defined(XP_BEOS) #include "direct.h" #endif #include #ifndef _MAX_PATH #define _MAX_PATH 1024 #endif char USER_DIR[_MAX_PATH]; char CACHE_DIR[_MAX_PATH]; char DEF_DIR[_MAX_PATH]; static NS_DEFINE_IID(kINetFileIID, NS_INETFILE_IID); NS_IMPL_ISUPPORTS(nsNetFile, kINetFileIID); typedef struct _nsFileAssoc { nsString name; nsString dirTok; } nsFileAssoc; // Comparison routine for the fileAssoc objects. PR_IMPLEMENT(int) PL_CompareFileAssoc(const void *v1, const void *v2) { nsFileAssoc *a = (nsFileAssoc*)v1; nsFileAssoc *b = (nsFileAssoc*)v2; return *a->name.GetUnicode() == *b->name.GetUnicode(); } nsNetFile::nsNetFile() { NS_INIT_REFCNT(); #ifdef XP_PC mDirDel = '\\'; #elif XP_MAC mDirDel = ':'; #else mDirDel = '/'; #endif mTokDel = '%'; mHTDirs = PL_NewHashTable(5, PL_HashString, PL_CompareStrings, PL_CompareStrings, nsnull, nsnull); mHTFiles = PL_NewHashTable(10, PL_HashString, PL_CompareStrings, PL_CompareFileAssoc, nsnull, nsnull); } PR_CALLBACK PRIntn NS_RemoveStringEntries(PLHashEntry* he, PRIntn i, void* arg) { char *entry = (char*)he->value; NS_ASSERTION(entry != nsnull, "null entry"); PR_Free(entry); return HT_ENUMERATE_REMOVE; } nsNetFile::~nsNetFile() { // Clear the hash table. PL_HashTableEnumerateEntries(mHTDirs, NS_RemoveStringEntries, 0); PL_HashTableDestroy(mHTDirs); PL_HashTableEnumerateEntries(mHTFiles, NS_RemoveStringEntries, 0); PL_HashTableDestroy(mHTFiles); } nsresult nsNetFile::GetFilePath(const char *aName, char **aRes) { nsString newPath, fileName; char *tmpString; char *path = nsnull, *locName = nsnull; char *tokBegin, *dirTok, *fileTok, *nextTok; nsFileAssoc *faFile = nsnull; NS_PRECONDITION( (aName != nsnull), "Null pointer."); locName = URLSyntaxToLocal(aName); if (!locName) return NS_ERROR_OUT_OF_MEMORY; // If we were handed an absolute path, spit it back. if ( (*locName == mDirDel) // mac and unix || (*locName && (*(locName + 1) == ':') ) ) // pc { newPath = locName; PR_Free(locName); locName = nsnull; tmpString = newPath.ToNewCString(); if (!tmpString) return NS_ERROR_OUT_OF_MEMORY; path = PL_strdup(tmpString); delete[] tmpString; tmpString = nsnull; *aRes = path; return NS_OK; } // First see if this has been explicitly registered faFile = (nsFileAssoc*)PL_HashTableLookup(mHTFiles, locName); if (faFile) { char *dir = nsnull; if (faFile->dirTok.Length()) { char *csDT; tmpString = faFile->dirTok.ToNewCString(); if (!tmpString) { return NS_ERROR_OUT_OF_MEMORY; } csDT = PL_strdup(tmpString); delete[] tmpString; tmpString = nsnull; dir = (char*)PL_HashTableLookup(mHTDirs, csDT); PR_Free(csDT); } // If it wasn't in the table give the file a default dir. if (!dir) { dir = (char*)PL_HashTableLookup(mHTDirs, DEF_DIR_TOK); NS_PRECONDITION( (dir != nsnull), "No default dir registered."); return NS_ERROR_FAILURE; } newPath = dir; if (dir[PL_strlen(path)-1] != mDirDel) { newPath.Append(mDirDel); } newPath.Append(faFile->name); tmpString = newPath.ToNewCString(); *aRes = PL_strdup(tmpString); delete[] tmpString; return NS_OK; } // If we've gotten this far, this file wasn't registered, or the user wants // to use a different directory with the file. // Lookup the token(s) in our hash table(s). dirTok = nsnull; fileTok = nsnull; tokBegin = PL_strchr(locName, mTokDel); if (tokBegin) { nextTok = PL_strchr(tokBegin+1, mTokDel); if (nextTok) { // Check for more (could be another token or a filename). if (*(nextTok+1)) { // Set the first one as the dir. char tmp = *(nextTok+1); *(nextTok+1) = '\0'; dirTok = PL_strdup(tokBegin); *(nextTok+1) = tmp; tokBegin = nextTok+1; nextTok = PL_strchr(tokBegin+1, mTokDel); } if (nextTok) { *(nextTok+1) = '\0'; fileTok = PL_strdup(tokBegin); *(nextTok+1) = mTokDel; } else { // no file token fileTok = PL_strdup(tokBegin); } } } // If the user passed in a dir token, use it. if (dirTok) { char *path = (char*)PL_HashTableLookup(mHTDirs, dirTok); PR_Free(dirTok); // If it wasn't in the table, fail. if (!path) { PR_Free(fileTok); return NS_ERROR_FAILURE; } newPath = path; if (path[PL_strlen(path)-1] != mDirDel) { newPath.Append(mDirDel); } } if (fileTok) { faFile = (nsFileAssoc*)PL_HashTableLookup(mHTFiles, fileTok); if (faFile) fileName = faFile->name; else fileName = fileTok; // It wasn't a token after all; } else { fileName = (char *)aName; } newPath.Append(fileName); PR_Free(fileTok); tmpString = newPath.ToNewCString(); *aRes = PL_strdup(tmpString); delete[] tmpString; PR_FREEIF(locName); return NS_OK; } nsresult nsNetFile::GetTemporaryFilePath(const char *aName, char **aRes) { return NS_OK; } nsresult nsNetFile::GetUniqueFilePath(const char *aName, char **aRes) { return NS_OK; } void nsNetFile::GenerateGlobalRandomBytes(void *aDest, size_t aLen) { /* This is a temporary implementation to avoid */ /* the cache filename horkage. This needs to have a more */ /* secure/free implementation here - Gagan */ char* output=(char*)aDest; size_t i; srand((unsigned int) PR_IntervalToMilliseconds(PR_IntervalNow())); for (i=0;i> (i_letter * 5)) & 0x1F); // Convert any numbers to their equivalent ascii code // if(ca_time[i_letter] <= 9) { ca_time[i_letter] += '0'; // Convert the character to it's equivalent ascii code // } else { ca_time[i_letter] += 'A' - 10; } } // End the created time string. // ca_time[i_letter] = '\0'; // Reverse the time string. // _strrev(ca_time); // Create the fully qualified path and file name. // sprintf(file_buf, "%s\\%s%s%s", dir, prefix, ca_time, ext); // Determine if the file exists, and mark that we've tried yet // another file name (mark to be used later). // // Use the system call instead of XP_Stat since we already // know the name and we don't want recursion // status = PR_GetFileInfo(file_buf, &statinfo); i_numtries++; // If it does not exists, we are successful, return the name. // if(status == PR_FAILURE) { /* don't generate a directory as part of the * cache temp names. When the cache file name * is passed into the other XP_File functions * we will append the cache directory to the name * to get the complete path. * This will allow the cache to be moved around * and for netscape to be used to generate external * cache FAT's. :lou */ sprintf(file_buf, "%s%s%s", prefix, ca_time, ext); *aRes = file_buf; break; } } // End while return NS_OK; } #if 0 // Here for reference. PUBLIC char * xp_TempFileName(int type, const char * request_prefix, const char * extension, char* file_buf) { const char * directory = NULL; char * ext = NULL; // file extension if any char * prefix = NULL; // file prefix if any XP_Bool bDirSlash = FALSE; XP_StatStruct statinfo; int status; // // based on the type of request determine what directory we should be // looking into // switch(type) { case xpCache: directory = theApp.m_pCacheDir; ext = ".MOZ"; prefix = CACHE_PREFIX; break; #ifdef OLD_MOZ_MAIL_NEWS case xpSNewsRC: case xpNewsRC: case xpNewsgroups: case xpSNewsgroups: case xpTemporaryNewsRC: directory = g_MsgPrefs.m_csNewsDir; ext = (char *)extension; prefix = (char *)request_prefix; break; case xpMailFolderSummary: case xpMailFolder: directory = g_MsgPrefs.m_csMailDir; ext = (char *)extension; prefix = (char *)request_prefix; break; case xpAddrBook: //changed jonm-- to support multi-profile //directory = theApp.m_pInstallDir->GetCharValue(); directory = (const char *)theApp.m_UserDirectory; if ((request_prefix == 0) || (XP_STRLEN (request_prefix) == 0)) prefix = "abook"; ext = ".nab"; break; #endif // OLD_MOZ_MAIL_NEWS case xpCacheFAT: directory = theApp.m_pCacheDir; prefix = "fat"; ext = "db"; break; case xpJPEGFile: directory = theApp.m_pTempDir; ext = ".jpg"; prefix = (char *)request_prefix; break; case xpPKCS12File: directory = theApp.m_pTempDir; ext = ".p12"; prefix = (char *)request_prefix; break; case xpURL: { if (request_prefix) { if ( XP_STRRCHR(request_prefix, '/') ) { const char *end; XP_StatStruct st; directory = (char *)request_prefix; end = directory + XP_STRLEN(directory) - 1; if ( *end == '/' || *end == '\\' ) { bDirSlash = TRUE; } if (XP_Stat (directory, &st, xpURL)) XP_MakeDirectoryR (directory, xpURL); ext = (char *)extension; prefix = (char *)"su"; break; } } // otherwise, fall through } case xpTemporary: default: directory = theApp.m_pTempDir; ext = (char *)extension; prefix = (char *)request_prefix; break; } if(!directory) return(NULL); if(!prefix) prefix = "X"; if(!ext) ext = ".TMP"; // We need to base our temporary file names on time, and not on sequential // addition because of the cache not being updated when the user // crashes and files that have been deleted are over written with // other files; bad data. // The 52 valid DOS file name characters are // 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_^$~!#%&-{}@`'() // We will only be using the first 32 of the choices. // // Time name format will be M+++++++.MOZ // Where M is the single letter prefix (can be longer....) // Where +++++++ is the 7 character time representation (a full 8.3 // file name will be made). // Where .MOZ is the file extension to be used. // // In the event that the time requested is the same time as the last call // to this function, then the current time is incremented by one, // as is the last time called to facilitate unique file names. // In the event that the invented file name already exists (can't // really happen statistically unless the clock is messed up or we // manually incremented the time), then the times are incremented // until an open name can be found. // // time_t (the time) has 32 bits, or 4,294,967,296 combinations. // We will map the 32 bits into the 7 possible characters as follows: // Starting with the lsb, sets of 5 bits (32 values) will be mapped // over to the appropriate file name character, and then // incremented to an approprate file name character. // The left over 2 bits will be mapped into the seventh file name // character. // int i_letter, i_timechars, i_numtries = 0; char ca_time[8]; time_t this_call = (time_t)0; // We have to base the length of our time string on the length // of the incoming prefix.... // i_timechars = 8 - strlen(prefix); // Infinite loop until the conditions are satisfied. // There is no danger, unless every possible file name is used. // while(1) { // We used to use the time to generate this. // Now, we use some crypto to avoid bug #47027 RNG_GenerateGlobalRandomBytes((void *)&this_call, sizeof(this_call)); // Convert the time into a 7 character string. // Strip only the signifigant 5 bits. // We'll want to reverse the string to make it look coherent // in a directory of file names. // for(i_letter = 0; i_letter < i_timechars; i_letter++) { ca_time[i_letter] = (char)((this_call >> (i_letter * 5)) & 0x1F); // Convert any numbers to their equivalent ascii code // if(ca_time[i_letter] <= 9) { ca_time[i_letter] += '0'; } // Convert the character to it's equivalent ascii code // else { ca_time[i_letter] += 'A' - 10; } } // End the created time string. // ca_time[i_letter] = '\0'; // Reverse the time string. // _strrev(ca_time); // Create the fully qualified path and file name. // if (bDirSlash) sprintf(file_buf, "%s%s%s%s", directory, prefix, ca_time, ext); else sprintf(file_buf, "%s\\%s%s%s", directory, prefix, ca_time, ext); // Determine if the file exists, and mark that we've tried yet // another file name (mark to be used later). // // Use the system call instead of XP_Stat since we already // know the name and we don't want recursion // status = _stat(file_buf, &statinfo); i_numtries++; // If it does not exists, we are successful, return the name. // if(status == -1) { /* don't generate a directory as part of the * cache temp names. When the cache file name * is passed into the other XP_File functions * we will append the cache directory to the name * to get the complete path. * This will allow the cache to be moved around * and for netscape to be used to generate external * cache FAT's. :lou */ if(type == xpCache ) sprintf(file_buf, "%s%s%s", prefix, ca_time, ext); // TRACE("Temp file name is %s\n", file_buf); return(file_buf); } // If there is no room for additional characters in the time, // we'll have to return NULL here, or we go infinite. // This is a one case scenario where the requested prefix is // actually 8 letters long. // Infinite loops could occur with a 7, 6, 5, etc character prefixes // if available files are all eaten up (rare to impossible), in // which case, we should check at some arbitrary frequency of // tries before we give up instead of attempting to Vulcanize // this code. Live long and prosper. // if(i_timechars == 0) { break; } else if(i_numtries == 0x00FF) { break; } } // Requested name is thought to be impossible to generate. // TRACE("No more temp file names....\n"); return(NULL); } #endif // 0 nsresult nsNetFile::OpenFile(const char *aPath, nsFileMode aMode, nsFile** aRes) { nsFile *file = nsnull; char *path = nsnull; NS_PRECONDITION( (aPath != nsnull), "Null pointer."); if (!aPath) return NS_ERROR_NULL_POINTER; if (!*aPath) return NS_ERROR_FAILURE; file = (nsFile*) PR_Malloc(sizeof(nsFile)); if (!file) return NS_ERROR_OUT_OF_MEMORY; // Get the correct path. if (GetFilePath(aPath, &path) == NS_ERROR_OUT_OF_MEMORY) return NS_ERROR_OUT_OF_MEMORY; // We should never be opening a relative file. NS_PRECONDITION( ( (*path != mDirDel) && (*path && (*path+1 != ':'))), "Opening a relative path."); file->fd = PR_Open(path, (convertToPRFlag(aMode)), 0600); PR_Free(path); path = nsnull; if (!file->fd) { PR_Free(file); return NS_ERROR_NULL_POINTER; } *aRes = file; return NS_OK; } nsresult nsNetFile::CloseFile(nsFile* aFile) { PRStatus rv; NS_PRECONDITION( (aFile != nsnull), "Null pointer."); if (!aFile->fd) return NS_ERROR_NULL_POINTER; rv = PR_Close(aFile->fd); if (rv == PR_FAILURE) return NS_ERROR_FAILURE; PR_Free(aFile); aFile = nsnull; return NS_OK; } nsresult nsNetFile::FileRead(nsFile *aFile, char **aBuf, PRInt32 *aBuflen, PRInt32 *aBytesRead) { NS_PRECONDITION( (aFile != nsnull), "Null pointer."); if (*aBuflen < 1) return NS_OK; *aBytesRead = PR_Read(aFile->fd, *aBuf, *aBuflen); if (*aBytesRead == -1) { return NS_ERROR_FAILURE; } return NS_OK; } nsresult nsNetFile::FileReadLine(nsFile *aFile, char **aBuf, PRInt32 *aBuflen, PRInt32 *aBytesRead) { PRInt32 readBytes; PRInt32 idx = 0; char *tmpBuf = nsnull; NS_PRECONDITION( (aFile != nsnull), "Null pointer."); if (*aBuflen < 1) { *aBytesRead = 0; return NS_OK; } readBytes = PR_Read(aFile->fd, *aBuf, *aBuflen); tmpBuf = *aBuf; if (readBytes > 0) { while (idx < readBytes) { if (tmpBuf[idx] == 10) { // LF if (tmpBuf[idx+1]) tmpBuf[idx+1] = '\0'; // Back the file pointer up. PR_Seek(aFile->fd, ((idx+1) - readBytes), PR_SEEK_CUR); *aBytesRead = idx+1; return NS_OK; } idx++; } } if (readBytes == *aBuflen) return NS_ERROR_FAILURE; return NS_ERROR_FAILURE; } nsresult nsNetFile::FileWrite(nsFile *aFile, const char *aBuf, PRInt32 *aLen, PRInt32 *aBytesWritten) { PRErrorCode error; // for testing, not propogated. NS_PRECONDITION( (aFile != nsnull), "Null pointer."); if (*aLen < 1) return NS_OK; *aBytesWritten = PR_Write(aFile->fd, aBuf, *aLen); if (*aBytesWritten == -1) { error = PR_GetError(); return NS_ERROR_FAILURE; } return NS_OK; } nsresult nsNetFile::FileSync(nsFile *aFile) { NS_PRECONDITION( (aFile != nsnull), "Null pointer."); if (PR_Sync(aFile->fd) == PR_FAILURE) return NS_ERROR_FAILURE; return NS_OK; } nsresult nsNetFile::FileRemove(const char *aPath) { char *path = nsnull; NS_PRECONDITION( (aPath != nsnull), "Null pointer."); // Only remove absolute paths. if (GetFilePath(aPath, &path) == NS_ERROR_OUT_OF_MEMORY) return NS_ERROR_OUT_OF_MEMORY; // We should never be deleting a relative file. NS_PRECONDITION( ( (*path != mDirDel) && (*path && (*path+1 != ':'))), "Deleting a relative path."); PR_Free(path); return NS_OK; } nsresult nsNetFile::FileRename(const char *aPathOld, const char *aPathNew) { return NS_OK; } nsresult nsNetFile::OpenDir(const char *aPath, nsDir** aRes) { return NS_OK; } nsresult nsNetFile::CloseDir(nsDir *aDir) { return NS_OK; } nsresult nsNetFile::CreateDir(const char *aPath, PRBool aRecurse) { return NS_OK; } nsresult nsNetFile::SetDirectory(const char *aToken, const char *aDir) { nsresult rv = NS_OK; if (!PL_HashTableAdd(mHTDirs, PL_strdup(aToken), PL_strdup(aDir))) { rv = NS_ERROR_FAILURE; } return rv; } // Associate a filename with a token, and optionally a dir token. nsresult nsNetFile::SetFileAssoc(const char *aToken, const char *aFile, const char *aDirToken) { nsString val(aFile); nsFileAssoc *theFile = new nsFileAssoc; if (aDirToken) { // Check for existence. char *dir = (char*)PL_HashTableLookup(mHTDirs, aDirToken); if (!dir) return NS_ERROR_FAILURE; theFile->dirTok = aDirToken; } theFile->name = aFile; if (!PL_HashTableAdd(mHTFiles, aToken, theFile)) { return NS_ERROR_FAILURE; } return NS_OK; } PRIntn nsNetFile::convertToPRFlag(nsFileMode aMode) { switch (aMode) { case nsRead: return PR_RDONLY; case nsWrite: return PR_WRONLY; case nsReadWrite: return PR_RDWR; case nsReadBinary: return PR_RDONLY; case nsWriteBinary: return PR_WRONLY; case nsReadWriteBinary: return PR_RDWR; case nsOverWrite: return (PR_TRUNCATE | PR_WRONLY | PR_CREATE_FILE); default: return PR_RDONLY; } } char *nsNetFile::URLSyntaxToLocal(const char *aPath) #ifdef XP_PC { char *p, *newName; if(!aPath) return nsnull; // If the name is only '/' or begins '//' keep the // whole name else strip the leading '/' PRBool bChopSlash = PR_FALSE; if(aPath[0] == '/') bChopSlash = PR_TRUE; // save just / as a path if(aPath[0] == '/' && aPath[1] == '\0') bChopSlash = PR_FALSE; // spanky Win9X path name if(aPath[0] == '/' && aPath[1] == '/') bChopSlash = PR_FALSE; if(bChopSlash) newName = PL_strdup(&(aPath[1])); else newName = PL_strdup(aPath); if(!newName) return nsnull; if( newName[1] == '|' ) newName[1] = ':'; for(p = newName; *p; p++){ if( *p == '/' ) *p = '\\'; } return(newName); } #elif XP_MAC { return nsnull; } #else // UNIX { return nsnull; } #endif extern "C" { /* * Factory for creating instance of the NetFile... */ NS_NET nsresult NS_NewINetFile(nsINetFile** aInstancePtrResult, nsISupports* aOuter) { if (nsnull != aOuter) { return NS_ERROR_NO_AGGREGATION; } #ifdef XP_MAC // Perform static initialization... if (nsnull == netFileInit) { netFileInit = new nsNetFileInit; } #endif /* XP_MAC */ // XXX on the mac this never gets shutdown // The Netlib Service is created by the nsNetFileInit class... if (nsnull == gNetFile) { return NS_ERROR_OUT_OF_MEMORY; } return gNetFile->QueryInterface(kINetFileIID, (void**)aInstancePtrResult); } NS_NET nsresult NS_InitINetFile(void) { /* XXX: For now only allow a single instance of the Netlib Service */ if (nsnull == gNetFile) { gNetFile = new nsNetFile(); if (nsnull == gNetFile) { return NS_ERROR_OUT_OF_MEMORY; } } NS_ADDREF(gNetFile); return NS_OK; } NS_NET nsresult NS_ShutdownINetFile() { nsNetFile *service = gNetFile; // Release the container... if (nsnull != service) { NS_RELEASE(service); } return NS_OK; } }; /* extern "C" */