/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
 
#include "nsFileSpec.h"

#include "nsDebug.h"
#include "nsEscape.h"
#include "nsMemory.h"

#include "prtypes.h"
#include "plstr.h"
#include "plbase64.h"
#include "prmem.h"

#include "nsCOMPtr.h"
#include "nsIServiceManager.h"
#include "nsILocalFile.h"

#include <string.h>
#include <stdio.h>

#if defined(XP_WIN)
#include <mbstring.h>
#endif

#ifdef XP_OS2
extern unsigned char* _mbsrchr( const unsigned char*, int);
#endif

// return pointer to last instance of the given separator
static inline char *GetLastSeparator(const char *str, char sep)
{
#if defined(XP_WIN) || defined(XP_OS2)
    return (char*) _mbsrchr((const unsigned char *) str, sep);
#else
    return (char*) strrchr(str, sep);
#endif
}

#ifdef XP_MACOSX
#include <sys/stat.h>
#include <Aliases.h>
#include <TextUtils.h>
#endif

//========================================================================================
//            class nsSimpleCharString
//========================================================================================

//----------------------------------------------------------------------------------------
nsSimpleCharString::nsSimpleCharString()
//----------------------------------------------------------------------------------------
:   mData(nsnull)
{
    
} // nsSimpleCharString::nsSimpleCharString

//----------------------------------------------------------------------------------------
nsSimpleCharString::nsSimpleCharString(const char* inString)
//----------------------------------------------------------------------------------------
:   mData(nsnull)
{
    if (inString)
        CopyFrom(inString, strlen(inString));
} // nsSimpleCharString::nsSimpleCharString

//----------------------------------------------------------------------------------------
nsSimpleCharString::nsSimpleCharString(const nsString& inString)
//----------------------------------------------------------------------------------------
:   mData(nsnull)
{
    *this = inString;
} // nsSimpleCharString::nsSimpleCharString

//----------------------------------------------------------------------------------------
nsSimpleCharString::nsSimpleCharString(const nsSimpleCharString& inOther)
//----------------------------------------------------------------------------------------
{
    mData = inOther.mData;
    AddRefData();
} // nsSimpleCharString::nsSimpleCharString

//----------------------------------------------------------------------------------------
nsSimpleCharString::nsSimpleCharString(const char* inData, PRUint32 inLength)
//----------------------------------------------------------------------------------------
:   mData(nsnull)
{
    CopyFrom(inData, inLength);
} // nsSimpleCharString::nsSimpleCharString

//----------------------------------------------------------------------------------------
nsSimpleCharString::~nsSimpleCharString()
//----------------------------------------------------------------------------------------
{
    ReleaseData();
} // nsSimpleCharString::nsSimpleCharString

//----------------------------------------------------------------------------------------
void nsSimpleCharString::operator = (const char* inString)
//----------------------------------------------------------------------------------------
{
    if (inString)
        CopyFrom(inString, strlen(inString));
    else
        SetToEmpty();
} // nsSimpleCharString::operator =

//----------------------------------------------------------------------------------------
void nsSimpleCharString::operator = (const nsString& inString)
//----------------------------------------------------------------------------------------
{
    PRUint32 len = inString.Length();
    ReallocData(len);
    if (!mData)
        return;
    nsFixedCString dataString(mData->mString, len + 1);
    LossyCopyUTF16toASCII(inString, dataString);
    NS_ASSERTION(dataString.get() == mData->mString, "buffer too small");
} // nsSimpleCharString::operator =

//----------------------------------------------------------------------------------------
void nsSimpleCharString::operator = (const nsSimpleCharString& inOther)
//----------------------------------------------------------------------------------------
{
    if (mData == inOther.mData)
        return;
    ReleaseData();
    mData = inOther.mData;
    AddRefData();
} // nsSimpleCharString::operator =

//----------------------------------------------------------------------------------------
void nsSimpleCharString::operator += (const char* inOther)
//----------------------------------------------------------------------------------------
{
    if (!inOther)
        return;
    int newLength = Length() + strlen(inOther);
    ReallocData(newLength);
    strcat(mData->mString, inOther);
} // nsSimpleCharString::operator =

//----------------------------------------------------------------------------------------
nsSimpleCharString nsSimpleCharString::operator + (const char* inOther) const
//----------------------------------------------------------------------------------------
{
    nsSimpleCharString result(*this);
    result += inOther;
    return result;
} // nsSimpleCharString::operator =

//----------------------------------------------------------------------------------------
void nsSimpleCharString::Catenate(const char* inString1, const char* inString2)
//----------------------------------------------------------------------------------------
{
    if (!inString2)
    {
        *this += inString1;
        return;
    }
    int newLength = Length() + strlen(inString1) + strlen(inString2);
    ReallocData(newLength);
    strcat(mData->mString, inString1);
    strcat(mData->mString, inString2);
} // nsSimpleCharString::operator =

//----------------------------------------------------------------------------------------
void nsSimpleCharString::CopyFrom(const char* inData, PRUint32 inLength)
//----------------------------------------------------------------------------------------
{
    if (!inData)
        return;
    ReallocData(inLength);
    if (!mData)
        return;
    if (inLength != 0) {
        memcpy(mData->mString, inData, inLength);
    }
    mData->mString[inLength] = '\0';
} // nsSimpleCharString::CopyFrom

//----------------------------------------------------------------------------------------
void nsSimpleCharString::SetToEmpty()
//----------------------------------------------------------------------------------------
{
    ReleaseData();
} // nsSimpleCharString::SetToEmpty

//----------------------------------------------------------------------------------------
void nsSimpleCharString::Unescape()
//----------------------------------------------------------------------------------------
{
    if (!mData)
        return;
    ReallocData(mData->mLength);
    if (!mData)
        return;
    nsUnescape(mData->mString);
    mData->mLength = strlen(mData->mString);       
} // nsSimpleCharString::Unescape


//----------------------------------------------------------------------------------------
void nsSimpleCharString::AddRefData()
//----------------------------------------------------------------------------------------
{
    if (mData)
        ++mData->mRefCount;
} // nsSimpleCharString::AddRefData

//----------------------------------------------------------------------------------------
void nsSimpleCharString::ReleaseData()
//----------------------------------------------------------------------------------------
{
    if (!mData)
        return;
    NS_ASSERTION(mData->mRefCount > 0, "String deleted too many times!");
    if (--mData->mRefCount == 0)
        PR_Free(mData);
    mData = nsnull;
} // nsSimpleCharString::ReleaseData

//----------------------------------------------------------------------------------------
inline PRUint32 CalculateAllocLength(PRUint32 logicalLength)
// Round up to the next multiple of 256.
//----------------------------------------------------------------------------------------
{
    return ((1 + (logicalLength >> 8)) << 8);
}

//----------------------------------------------------------------------------------------
void nsSimpleCharString::ReallocData(PRUint32 inLength)
// Reallocate mData to a new length.  Since this presumably precedes a change to the string,
// we want to detach ourselves if the data is shared by another string, even if the length
// requested would not otherwise require a reallocation.
//----------------------------------------------------------------------------------------
{
    PRUint32 newAllocLength = CalculateAllocLength(inLength);
    PRUint32 oldAllocLength = CalculateAllocLength(Length());
    if (mData)
    {
        NS_ASSERTION(mData->mRefCount > 0, "String deleted too many times!");
        if (mData->mRefCount == 1)
        {
            // We are the sole owner, so just change its length, if necessary.
            if (newAllocLength > oldAllocLength)
                mData = (Data*)PR_Realloc(mData, newAllocLength + sizeof(Data));
            mData->mLength = inLength;
            mData->mString[inLength] = '\0'; // we may be truncating
            return;
        }
    }
    PRUint32 copyLength = Length();
    if (inLength < copyLength)
        copyLength = inLength;
    Data* newData = (Data*)PR_Malloc(newAllocLength + sizeof(Data));
    // If data was already allocated when we get to here, then we are cloning the data
    // from a shared pointer.
    if (mData)
    {
        memcpy(newData, mData, sizeof(Data) + copyLength);
        mData->mRefCount--; // Say goodbye
    }
    else
        newData->mString[0] = '\0';

    mData = newData;
    mData->mRefCount = 1;
    mData->mLength = inLength;
} // nsSimpleCharString::ReleaseData


//========================================================================================
NS_NAMESPACE nsFileSpecHelpers
//========================================================================================
{
    enum
    {    kMaxFilenameLength = 31                // should work on Macintosh, Unix, and Win32.
    ,    kMaxAltDigitLength    = 5
    ,    kMaxCoreLeafNameLength    = (kMaxFilenameLength - (kMaxAltDigitLength + 1))
    };
    NS_NAMESPACE_PROTOTYPE void Canonify(nsSimpleCharString& ioPath, PRBool inMakeDirs);
    NS_NAMESPACE_PROTOTYPE void MakeAllDirectories(const char* inPath, int mode);
#if defined(XP_WIN) || defined(XP_OS2)
    NS_NAMESPACE_PROTOTYPE void NativeToUnix(nsSimpleCharString& ioPath);
    NS_NAMESPACE_PROTOTYPE void UnixToNative(nsSimpleCharString& ioPath);
#endif
} NS_NAMESPACE_END

//----------------------------------------------------------------------------------------
nsresult ns_file_convert_result(PRInt32 nativeErr)
//----------------------------------------------------------------------------------------
{
    return nativeErr ?
        NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES,((nativeErr)&0xFFFF))
        : NS_OK;
}

//----------------------------------------------------------------------------------------
void nsSimpleCharString::LeafReplace(char inSeparator, const char* inLeafName)
//----------------------------------------------------------------------------------------
{
    // Find the existing leaf name
    if (IsEmpty())
        return;
    if (!inLeafName)
    {
        SetToEmpty();
        return;
    }
    char* chars = mData->mString;
    char* lastSeparator = GetLastSeparator(chars, inSeparator);
    int oldLength = Length();
    PRBool trailingSeparator = (lastSeparator + 1 == chars + oldLength);
    if (trailingSeparator)
    {
        char savedCh = *lastSeparator;
        char *savedLastSeparator = lastSeparator;
        *lastSeparator = '\0';
        lastSeparator = GetLastSeparator(chars, inSeparator);
        *savedLastSeparator = savedCh;
    }
    if (lastSeparator)
        lastSeparator++; // point at the trailing string
    else
        lastSeparator = chars; // the full monty

    PRUint32 savedLastSeparatorOffset = (lastSeparator - chars);
    int newLength =
        (lastSeparator - chars) + strlen(inLeafName) + (trailingSeparator != 0);
    ReallocData(newLength);

    chars = mData->mString; // it might have moved.
    chars[savedLastSeparatorOffset] = '\0'; // strip the current leaf name

    strcat(chars, inLeafName);
    if (trailingSeparator)
    {
        // If the original ended in a slash, then the new one should, too.
        char sepStr[2] = "/";
        *sepStr = inSeparator;
        strcat(chars, sepStr);
    }
} // nsSimpleCharString::LeafReplace

//----------------------------------------------------------------------------------------
char* nsSimpleCharString::GetLeaf(char inSeparator) const
// Returns a pointer to an allocated string representing the leaf.
//----------------------------------------------------------------------------------------
{
    if (IsEmpty())
        return nsnull;

    char* chars = mData->mString;
    const char* lastSeparator = GetLastSeparator(chars, inSeparator);
    // If there was no separator, then return a copy of our path.
    if (!lastSeparator)
        return nsCRT::strdup(*this);

    // So there's at least one separator.  What's just after it?
    // If the separator was not the last character, return the trailing string.
    const char* leafPointer = lastSeparator + 1;
    if (*leafPointer)
        return nsCRT::strdup(leafPointer);

    // So now, separator was the last character. Poke in a null instead.
    *(char*)lastSeparator = '\0'; // Should use const_cast, but Unix has old compiler.
    leafPointer = GetLastSeparator(chars, inSeparator);
    char* result = leafPointer ? nsCRT::strdup(++leafPointer) : nsCRT::strdup(chars);
    // Restore the poked null before returning.
    *(char*)lastSeparator = inSeparator;
#if defined(XP_WIN) || defined(XP_OS2)
    // If it's a drive letter use the colon notation.
    if (!leafPointer && result[1] == '|' && result[2] == 0)
        result[1] = ':';
#endif
    return result;
} // nsSimpleCharString::GetLeaf

#if (defined(XP_UNIX) || defined(XP_WIN) || defined(XP_OS2) || defined(XP_BEOS))

//----------------------------------------------------------------------------------------
void nsFileSpecHelpers::MakeAllDirectories(const char* inPath, int mode)
// Make the path a valid one by creating all the intermediate directories.  Does NOT
// make the leaf into a directory.  This should be a unix path.
//----------------------------------------------------------------------------------------
{
    if (!inPath)
        return;
        
    char* pathCopy = nsCRT::strdup( inPath );
    if (!pathCopy)
        return;

    const char kSeparator = '/'; // I repeat: this should be a unix-style path.
    const int kSkipFirst = 1;

#if defined(XP_WIN) || defined(XP_OS2)
    // Either this is a relative path, or we ensure that it has
    // a drive letter specifier.
    NS_ASSERTION( pathCopy[0] != '/' || (pathCopy[1] && (pathCopy[2] == '|' || pathCopy[2] == '/')),
        "Not a UNC path and no drive letter!" );
#endif
    char* currentStart = pathCopy;
    char* currentEnd = strchr(currentStart + kSkipFirst, kSeparator);
    if (currentEnd)
    {
        nsFileSpec spec;
        *currentEnd = '\0';
        
#if defined(XP_WIN) || defined(XP_OS2)
        /* 
           if we have a drive letter path, we must make sure that the inital path has a '/' on it, or
           Canonify will turn "/c|" into a path relative to the running executable.
        */
        if (pathCopy[0] == '/' && pathCopy[1] && pathCopy[2] == '|')
        {
            char* startDir = (char*)PR_Malloc(strlen(pathCopy) + 2);
            strcpy(startDir, pathCopy);
            strcat(startDir, "/");

            spec = nsFilePath(startDir, PR_FALSE);
            
            PR_Free(startDir);
        }
        else
        {
            // move after server name and share name in UNC path
            if (pathCopy[0] == '/' &&
                currentEnd == currentStart+kSkipFirst)
            {
                *currentEnd = '/';
                currentStart = strchr(pathCopy+2, kSeparator);
                currentStart = strchr(currentStart+1, kSeparator);
                currentEnd = strchr(currentStart+1, kSeparator);
                if (currentEnd)
                    *currentEnd = '\0';
            }
            spec = nsFilePath(pathCopy, PR_FALSE);
        }
#else
        spec = nsFilePath(pathCopy, PR_FALSE);
#endif        
        do
        {
            // If the node doesn't exist, and it is not the initial node in a full path,
            // then make a directory (We cannot make the initial (volume) node).
            if (!spec.Exists() && *currentStart != kSeparator)
                spec.CreateDirectory(mode);
            
            currentStart = ++currentEnd;
            currentEnd = strchr(currentStart, kSeparator);
            if (!currentEnd)
                break;
            
            *currentEnd = '\0';

            spec += currentStart; // "lengthen" the path, adding the next node.
        } while (currentEnd);
    }
    nsCRT::free(pathCopy);
} // nsFileSpecHelpers::MakeAllDirectories

#endif // XP_UNIX || XP_WIN || XP_OS2 || XP_BEOS

#if defined(XP_WIN)
#include "nsFileSpecWin.cpp" // Windows-specific implementations
#elif defined(XP_BEOS)
#include "nsFileSpecBeOS.cpp" // BeOS-specific implementations
#elif defined(XP_UNIX)
#include "nsFileSpecUnix.cpp" // Unix-specific implementations
#elif defined(XP_OS2)
#include "nsFileSpecOS2.cpp" // OS/2-specific implementations
#endif

//========================================================================================
//                                nsFileURL implementation
//========================================================================================

//----------------------------------------------------------------------------------------
nsFileURL::nsFileURL(const char* inString, PRBool inCreateDirs)
//----------------------------------------------------------------------------------------
{
    if (!inString)
        return;
    NS_ASSERTION(strstr(inString, kFileURLPrefix) == inString, "Not a URL!");
    // Make canonical and absolute. Since it's a parameter to this constructor,
    // inString is escaped. We want to make an nsFilePath, which requires
    // an unescaped string.
    nsSimpleCharString unescapedPath(inString + kFileURLPrefixLength);
    unescapedPath.Unescape();
    nsFilePath path(unescapedPath, inCreateDirs);
    *this = path;
} // nsFileURL::nsFileURL

//----------------------------------------------------------------------------------------
nsFileURL::nsFileURL(const nsString& inString, PRBool inCreateDirs)
//----------------------------------------------------------------------------------------
{
    NS_LossyConvertUTF16toASCII cstring(inString);
    if (!inString.Length())
        return;
    NS_ASSERTION(strstr(cstring.get(), kFileURLPrefix) == cstring.get(),
                 "Not a URL!");
    // Make canonical and absolute. Since it's a parameter to this constructor,
    // inString is escaped. We want to make an nsFilePath, which requires
    // an unescaped string.
    nsSimpleCharString unescapedPath(cstring.get() + kFileURLPrefixLength);
    unescapedPath.Unescape();
    nsFilePath path(unescapedPath, inCreateDirs);
    *this = path;
} // nsFileURL::nsFileURL

//----------------------------------------------------------------------------------------
nsFileURL::nsFileURL(const nsFileURL& inOther)
//----------------------------------------------------------------------------------------
: mURL(inOther.mURL)
{
} // nsFileURL::nsFileURL

//----------------------------------------------------------------------------------------
nsFileURL::nsFileURL(const nsFilePath& inOther)
//----------------------------------------------------------------------------------------
{
    *this = inOther;
} // nsFileURL::nsFileURL

//----------------------------------------------------------------------------------------
nsFileURL::nsFileURL(const nsFileSpec& inOther)
//----------------------------------------------------------------------------------------
{
    *this = inOther;
} // nsFileURL::nsFileURL

//----------------------------------------------------------------------------------------
nsFileURL::~nsFileURL()
//----------------------------------------------------------------------------------------
{
}

//----------------------------------------------------------------------------------------
void nsFileURL::operator = (const char* inString)
//----------------------------------------------------------------------------------------
{
    // XXX is this called by nsFileSpecImpl.cpp::SetURLString?
    // if so, there's a bug...

    mURL = inString;
    NS_ASSERTION(strstr(inString, kFileURLPrefix) == inString, "Not a URL!");
} // nsFileURL::operator =

//----------------------------------------------------------------------------------------
void nsFileURL::operator +=(const char* inRelativeUnixPath)
//----------------------------------------------------------------------------------------
{
    char* escapedPath = nsEscape(inRelativeUnixPath, url_Path);
    mURL += escapedPath;
    nsCRT::free(escapedPath);
} // nsFileURL::operator +=

//----------------------------------------------------------------------------------------
nsFileURL nsFileURL::operator +(const char* inRelativeUnixPath) const
//----------------------------------------------------------------------------------------
{
   nsFileURL result(*this);
   result += inRelativeUnixPath;
   return result;
}  // nsFileURL::operator +

//----------------------------------------------------------------------------------------
void nsFileURL::operator = (const nsFileURL& inOther)
//----------------------------------------------------------------------------------------
{
    mURL = inOther.mURL;
} // nsFileURL::operator =

//----------------------------------------------------------------------------------------
void nsFileURL::operator = (const nsFilePath& inOther)
//----------------------------------------------------------------------------------------
{
    mURL = kFileURLPrefix;
    char* original = (char*)(const char*)inOther; // we shall modify, but restore.
    if (!original || !*original) return;
#if defined(XP_WIN) || defined(XP_OS2)
    // because we don't want to escape the '|' character, change it to a letter.
    // Note that a UNC path will not have a '|' character.
    char oldchar = original[2];
    original[2] = 'x';
    char* escapedPath = nsEscape(original, url_Path);
    original[2] = escapedPath[2] = oldchar; // restore it
#else
    char* escapedPath = nsEscape(original, url_Path);
#endif
    if (escapedPath)
        mURL += escapedPath;
    nsCRT::free(escapedPath);
} // nsFileURL::operator =

//----------------------------------------------------------------------------------------
void nsFileURL::operator = (const nsFileSpec& inOther)
//----------------------------------------------------------------------------------------
{
    *this = nsFilePath(inOther);
    if (mURL[mURL.Length() - 1] != '/' && inOther.IsDirectory())
        mURL += "/";
} // nsFileURL::operator =


//========================================================================================
//                                nsFilePath implementation
//========================================================================================

//----------------------------------------------------------------------------------------
nsFilePath::nsFilePath(const nsFilePath& inPath)
//----------------------------------------------------------------------------------------
    : mPath(inPath.mPath)
{
}

//----------------------------------------------------------------------------------------
nsFilePath::nsFilePath(const char* inString, PRBool inCreateDirs)
//----------------------------------------------------------------------------------------
:    mPath(inString)
{
    if (mPath.IsEmpty())
    	return;
    	
    NS_ASSERTION(strstr(inString, kFileURLPrefix) != inString, "URL passed as path");

#if defined(XP_WIN) || defined(XP_OS2)
    nsFileSpecHelpers::UnixToNative(mPath);
#endif
    // Make canonical and absolute.
    nsFileSpecHelpers::Canonify(mPath, inCreateDirs);
#if defined(XP_WIN) || defined(XP_OS2)
    // Assert native path is of one of these forms:
    //    -  regular: X:\some\path
    //    -  UNC: \\some_machine\some\path
    NS_ASSERTION( mPath[1] == ':' || (mPath[0] == '\\' && mPath[1] == '\\'),
                 "unexpected canonical path" );
    nsFileSpecHelpers::NativeToUnix(mPath);
#endif
}

//----------------------------------------------------------------------------------------
nsFilePath::nsFilePath(const nsString& inString, PRBool inCreateDirs)
//----------------------------------------------------------------------------------------
:    mPath(inString)
{
    if (mPath.IsEmpty())
    	return;

    NS_ASSERTION(strstr((const char*)mPath, kFileURLPrefix) != (const char*)mPath, "URL passed as path");
#if defined(XP_WIN) || defined(XP_OS2)
    nsFileSpecHelpers::UnixToNative(mPath);
#endif
    // Make canonical and absolute.
    nsFileSpecHelpers::Canonify(mPath, inCreateDirs);
#if defined(XP_WIN) || defined(XP_OS2)
    NS_ASSERTION( mPath[1] == ':' || (mPath[0] == '\\' && mPath[1] == '\\'),
                 "unexpected canonical path" );
    nsFileSpecHelpers::NativeToUnix(mPath);
#endif
}

//----------------------------------------------------------------------------------------
nsFilePath::nsFilePath(const nsFileURL& inOther)
//----------------------------------------------------------------------------------------
{
    mPath = (const char*)inOther.mURL + kFileURLPrefixLength;
    mPath.Unescape();
}

#if (defined XP_UNIX || defined XP_BEOS)
//----------------------------------------------------------------------------------------
nsFilePath::nsFilePath(const nsFileSpec& inOther)
//----------------------------------------------------------------------------------------
:    mPath(inOther.mPath)
{
}
#endif // XP_UNIX || XP_BEOS

//----------------------------------------------------------------------------------------
nsFilePath::~nsFilePath()
//----------------------------------------------------------------------------------------
{
}

#if (defined XP_UNIX || defined XP_BEOS)
//----------------------------------------------------------------------------------------
void nsFilePath::operator = (const nsFileSpec& inOther)
//----------------------------------------------------------------------------------------
{
    // XXX bug here, again if.

    mPath = inOther.mPath;
}
#endif // XP_UNIX

//----------------------------------------------------------------------------------------
void nsFilePath::operator = (const char* inString)
//----------------------------------------------------------------------------------------
{

    NS_ASSERTION(strstr(inString, kFileURLPrefix) != inString, "URL passed as path");
    mPath = inString;
    if (mPath.IsEmpty())
    	return;
#if defined(XP_WIN) || defined(XP_OS2)
    nsFileSpecHelpers::UnixToNative(mPath);
#endif
    // Make canonical and absolute.
    nsFileSpecHelpers::Canonify(mPath, PR_FALSE /* XXX? */);
#if defined(XP_WIN) || defined(XP_OS2)
    nsFileSpecHelpers::NativeToUnix(mPath);
#endif
}

//----------------------------------------------------------------------------------------
void nsFilePath::operator = (const nsFileURL& inOther)
//----------------------------------------------------------------------------------------
{
    mPath = (const char*)nsFilePath(inOther);
}

//----------------------------------------------------------------------------------------
void nsFilePath::operator = (const nsFilePath& inOther)
//----------------------------------------------------------------------------------------
{
    mPath = inOther.mPath;
}

//----------------------------------------------------------------------------------------
void nsFilePath::operator +=(const char* inRelativeUnixPath)
//----------------------------------------------------------------------------------------
{
	NS_ASSERTION(inRelativeUnixPath, "Attempt append relative path with null path");

    char* escapedPath = nsEscape(inRelativeUnixPath, url_Path);
    mPath += escapedPath;
    nsCRT::free(escapedPath);
} // nsFilePath::operator +=

//----------------------------------------------------------------------------------------
nsFilePath nsFilePath::operator +(const char* inRelativeUnixPath) const
//----------------------------------------------------------------------------------------
{
   NS_ASSERTION(inRelativeUnixPath, "Attempt append relative path with null path");

   nsFilePath resultPath(*this);
   resultPath += inRelativeUnixPath;
   return resultPath;
}  // nsFilePath::operator +


//========================================================================================
//                                nsFileSpec implementation
//========================================================================================

//----------------------------------------------------------------------------------------
nsFileSpec::nsFileSpec()
//----------------------------------------------------------------------------------------
:    mError(NS_OK)		// XXX shouldn't this be NS_ERROR_NOT_INITIALIZED?
{
//    NS_ASSERTION(0, "nsFileSpec is unsupported - use nsIFile!");
}

//----------------------------------------------------------------------------------------
void nsFileSpec::Clear()
//----------------------------------------------------------------------------------------
{
    mPath.SetToEmpty();
    mError = NS_ERROR_NOT_INITIALIZED;
}

//----------------------------------------------------------------------------------------
nsFileSpec::~nsFileSpec()
//----------------------------------------------------------------------------------------
{
    // mPath cleans itself up
}

//----------------------------------------------------------------------------------------
nsFileSpec::nsFileSpec(const nsPersistentFileDescriptor& inDescriptor)
//----------------------------------------------------------------------------------------
{
    *this = inDescriptor;
}

//----------------------------------------------------------------------------------------
nsFileSpec::nsFileSpec(const nsFileURL& inURL)
//----------------------------------------------------------------------------------------
{
    *this = nsFilePath(inURL); // convert to unix path first
}

//----------------------------------------------------------------------------------------
void nsFileSpec::MakeUnique(const char* inSuggestedLeafName, PRBool inCreateFile)
//----------------------------------------------------------------------------------------
{
    if (inSuggestedLeafName && *inSuggestedLeafName)
        SetLeafName(inSuggestedLeafName);
    MakeUnique(inCreateFile);
} // nsFileSpec::MakeUnique

//----------------------------------------------------------------------------------------
void nsFileSpec::MakeUnique(PRBool inCreateFile)
//----------------------------------------------------------------------------------------
{
    // XXX: updated path starts empty. In case of error this will cause
    // any callers to fail badly, but that seems better than letting them
    // re-use the default name which has failed to be unique.
    nsCAutoString path;
    nsCOMPtr<nsILocalFile> localFile;
    NS_NewNativeLocalFile(nsDependentCString(*this), PR_TRUE, getter_AddRefs(localFile));
    if (localFile)
    {
        nsresult rv;

        if (inCreateFile)
            rv = localFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
        else
            rv = localFile->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700);

        if (NS_SUCCEEDED(rv))
            localFile->GetNativePath(path);
    }

    NS_ASSERTION(!path.IsEmpty(), "MakeUnique() failed!");
    *this = path.get(); // reset the filepath to point to the unique location

} // nsFileSpec::MakeUnique

//----------------------------------------------------------------------------------------
void nsFileSpec::operator = (const nsFileURL& inURL)
//----------------------------------------------------------------------------------------
{
    *this = nsFilePath(inURL); // convert to unix path first
}

//----------------------------------------------------------------------------------------
void nsFileSpec::operator = (const nsPersistentFileDescriptor& inDescriptor)
//----------------------------------------------------------------------------------------
{

    nsCAutoString data;
    inDescriptor.GetData(data);

#ifdef XP_MACOSX
    // Decode descriptor into a Handle (which is actually an AliasHandle)
    char* decodedData = PL_Base64Decode(data.get(), data.Length(), nsnull);
    Handle aliasH = nsnull;
    mError = NS_FILE_RESULT(::PtrToHand(decodedData, &aliasH, (data.Length() * 3) / 4));
    PR_Free(decodedData);
    if (NS_FAILED(mError))
        return; // not enough memory?

    Boolean changed;
    FSRef fileRef;
    mError = NS_FILE_RESULT(::FSResolveAlias(nsnull, (AliasHandle)aliasH, &fileRef, &changed));
    ::DisposeHandle(aliasH);

    UInt8 pathBuf[PATH_MAX];
    mError = NS_FILE_RESULT(::FSRefMakePath(&fileRef, pathBuf, PATH_MAX));
    if (NS_FAILED(mError))
      return;
    mPath = (const char*)pathBuf;
#else
    mPath = data.get();
    mError = NS_OK;
#endif
}

//========================================================================================
//                                UNIX & WIN nsFileSpec implementation
//========================================================================================

#if (defined XP_UNIX || defined XP_BEOS)
//----------------------------------------------------------------------------------------
nsFileSpec::nsFileSpec(const nsFilePath& inPath)
//----------------------------------------------------------------------------------------
:    mPath((const char*)inPath)
,    mError(NS_OK)
{
//    NS_ASSERTION(0, "nsFileSpec is unsupported - use nsIFile!");
}

//----------------------------------------------------------------------------------------
void nsFileSpec::operator = (const nsFilePath& inPath)
//----------------------------------------------------------------------------------------
{
    mPath = (const char*)inPath;
    mError = NS_OK;
}
#endif // XP_UNIX || XP_BEOS

#if (defined(XP_UNIX) || defined(XP_WIN) || defined(XP_OS2) || defined(XP_BEOS))
//----------------------------------------------------------------------------------------
nsFileSpec::nsFileSpec(const nsFileSpec& inSpec)
//----------------------------------------------------------------------------------------
:    mPath(inSpec.mPath)
,    mError(NS_OK)
{
//    NS_ASSERTION(0, "nsFileSpec is unsupported - use nsIFile!");
}

//----------------------------------------------------------------------------------------
nsFileSpec::nsFileSpec(const char* inString, PRBool inCreateDirs)
//----------------------------------------------------------------------------------------
:    mPath(inString)
,    mError(NS_OK)
{
//    NS_ASSERTION(0, "nsFileSpec is unsupported - use nsIFile!");
    // Make canonical and absolute.
    nsFileSpecHelpers::Canonify(mPath, inCreateDirs);
}

//----------------------------------------------------------------------------------------
nsFileSpec::nsFileSpec(const nsString& inString, PRBool inCreateDirs)
//----------------------------------------------------------------------------------------
:    mPath(inString)
,    mError(NS_OK)
{
//    NS_ASSERTION(0, "nsFileSpec is unsupported - use nsIFile!");
    // Make canonical and absolute.
    nsFileSpecHelpers::Canonify(mPath, inCreateDirs);
}

//----------------------------------------------------------------------------------------
void nsFileSpec::operator = (const nsFileSpec& inSpec)
//----------------------------------------------------------------------------------------
{
    mPath = inSpec.mPath;
    mError = inSpec.Error();
}

//----------------------------------------------------------------------------------------
void nsFileSpec::operator = (const char* inString)
//----------------------------------------------------------------------------------------
{
    mPath = inString;
    // Make canonical and absolute.
    nsFileSpecHelpers::Canonify(mPath, PR_FALSE /* XXX? */);
    mError = NS_OK;
}
#endif // XP_UNIX,XP_WIN,XP_OS2,XP_BEOS

//----------------------------------------------------------------------------------------
nsFileSpec nsFileSpec::operator + (const char* inRelativePath) const
//----------------------------------------------------------------------------------------
{
	NS_ASSERTION(inRelativePath, "Attempt to append name with a null string");

    nsFileSpec resultSpec = *this;
    resultSpec += inRelativePath;
    return resultSpec;
} // nsFileSpec::operator +

//----------------------------------------------------------------------------------------
PRBool nsFileSpec::operator == (const nsFileSpec& inOther) const
//----------------------------------------------------------------------------------------
{
    PRBool amEmpty = mPath.IsEmpty();
    PRBool heEmpty = inOther.mPath.IsEmpty();
    if (amEmpty) // we're the same if he's empty...
        return heEmpty;
    if (heEmpty) // ('cuz I'm not...)
        return PR_FALSE;
    
    nsSimpleCharString      str = mPath;
    nsSimpleCharString      inStr = inOther.mPath;
    
    // Length() is size of buffer, not length of string
    PRUint32 strLast = str.Length() - 1, inLast = inStr.Length() - 1;
#if defined(XP_WIN) || defined(XP_OS2)
#define DIR_SEPARATOR '\\'      // XXX doesn't NSPR have this?
    /* windows does not care about case. */
#ifdef XP_OS2
#define DIR_STRCMP     strcmp
#else
#define DIR_STRCMP    _stricmp
#endif
#else
#define DIR_SEPARATOR '/'
#if defined(VMS)
#define DIR_STRCMP     strcasecmp
#else
#define DIR_STRCMP     strcmp
#endif
#endif    
    if(str[strLast] == DIR_SEPARATOR)
        str[strLast] = '\0';

    if(inStr[inLast] == DIR_SEPARATOR)
        inStr[inLast] = '\0';

    if (DIR_STRCMP(str, inStr) == 0)
           return PR_TRUE;
#undef DIR_SEPARATOR
#undef DIR_STRCMP
   return PR_FALSE;
}

//----------------------------------------------------------------------------------------
PRBool nsFileSpec::operator != (const nsFileSpec& inOther) const
//----------------------------------------------------------------------------------------
{
    return (! (*this == inOther) );
}

//----------------------------------------------------------------------------------------
// This is the only automatic conversion to const char*
// that is provided, and it allows the
// path to be "passed" to NSPR file routines.  This practice
// is VERY EVIL and should only be used to support legacy
// code.  Using it guarantees bugs on Macintosh. The path is NOT allocated, so do
// not even think of deleting (or freeing) it.
const char* nsFileSpec::GetCString() const
//----------------------------------------------------------------------------------------
{
    return mPath;
}

//----------------------------------------------------------------------------------------
// Is our spec a child of the provided parent?
PRBool nsFileSpec::IsChildOf(nsFileSpec &possibleParent)
//----------------------------------------------------------------------------------------
{
    nsFileSpec iter = *this, parent;
#ifdef DEBUG
    int depth = 0;
#endif
    while (1) {
#ifdef DEBUG
        // sanity
        NS_ASSERTION(depth < 100, "IsChildOf has lost its little mind");
        if (depth > 100)
            return PR_FALSE;
#endif
        if (iter == possibleParent)
            return PR_TRUE;

        iter.GetParent(parent); // shouldn't this be an error on parent?
        if (iter.Failed())
            return PR_FALSE;

        if (iter == parent)     // hit bottom
            return PR_FALSE;
        
        iter = parent;
#ifdef DEBUG
        depth++;
#endif
    }

    // not reached, but I bet some compiler will whine
    return PR_FALSE;
}


//========================================================================================
//    class nsPersistentFileDescriptor
//========================================================================================

//----------------------------------------------------------------------------------------
nsPersistentFileDescriptor::nsPersistentFileDescriptor(const nsPersistentFileDescriptor& inDesc)
//----------------------------------------------------------------------------------------
    : mDescriptorString(inDesc.mDescriptorString)
{
} // nsPersistentFileDescriptor::nsPersistentFileDescriptor

//----------------------------------------------------------------------------------------
void nsPersistentFileDescriptor::operator = (const nsPersistentFileDescriptor& inDesc)
//----------------------------------------------------------------------------------------
{
    mDescriptorString = inDesc.mDescriptorString;
} // nsPersistentFileDescriptor::operator =

//----------------------------------------------------------------------------------------
nsPersistentFileDescriptor::nsPersistentFileDescriptor(const nsFileSpec& inSpec)
//----------------------------------------------------------------------------------------
{
    *this = inSpec;
} // nsPersistentFileDescriptor::nsPersistentFileDescriptor

//----------------------------------------------------------------------------------------
void nsPersistentFileDescriptor::operator = (const nsFileSpec& inSpec)
//----------------------------------------------------------------------------------------
{
#ifdef XP_MACOSX
    if (inSpec.Error())
        return;
    
    FSRef fileRef;
    Boolean isDir;
    OSErr err = ::FSPathMakeRef((const UInt8*)inSpec.GetCString(), &fileRef, &isDir);
    if (err != noErr)
        return;
    
    AliasHandle    aliasH;
    err = ::FSNewAlias(nsnull, &fileRef, &aliasH);
    if (err != noErr)
        return;

    PRUint32 bytes = ::GetHandleSize((Handle) aliasH);
    ::HLock((Handle)aliasH);
    char* buf = PL_Base64Encode((const char*)*aliasH, bytes, nsnull);
    ::DisposeHandle((Handle) aliasH);

    mDescriptorString = buf;
    PR_Free(buf);
#else
    mDescriptorString = inSpec.GetCString();
#endif // XP_MACOSX
} // nsPersistentFileDescriptor::operator =

//----------------------------------------------------------------------------------------
nsPersistentFileDescriptor::~nsPersistentFileDescriptor()
//----------------------------------------------------------------------------------------
{
} // nsPersistentFileDescriptor::~nsPersistentFileDescriptor

//----------------------------------------------------------------------------------------
void nsPersistentFileDescriptor::GetData(nsAFlatCString& outData) const
//----------------------------------------------------------------------------------------
{
    outData.Assign(mDescriptorString, mDescriptorString.Length());
}

//----------------------------------------------------------------------------------------
void nsPersistentFileDescriptor::SetData(const nsAFlatCString& inData)
//----------------------------------------------------------------------------------------
{
    mDescriptorString.CopyFrom(inData.get(), inData.Length());
}

//----------------------------------------------------------------------------------------
void nsPersistentFileDescriptor::SetData(const char* inData, PRInt32 inSize)
//----------------------------------------------------------------------------------------
{
    mDescriptorString.CopyFrom(inData, inSize);
}

//========================================================================================
//    class nsNSPRPath
//========================================================================================

//----------------------------------------------------------------------------------------
nsNSPRPath::operator const char*() const
// NSPR expects a UNIX path on unix and Macintosh, but a native path on windows. NSPR
// cannot be changed, so we have to do the dirty work.
//----------------------------------------------------------------------------------------
{
#if defined(XP_WIN) || defined(XP_OS2)
    if (!modifiedNSPRPath)
    {
        // If this is the first call, initialize modifiedNSPRPath. Start by cloning
        // mFilePath, but strip the leading separator, if present
        const char* unixPath = (const char*)mFilePath;
        if (!unixPath)
            return nsnull;

        ((nsNSPRPath*)this)->modifiedNSPRPath
                = nsCRT::strdup(*unixPath == '/' ? unixPath + 1: unixPath);
        
        // Replace the bar
        if (modifiedNSPRPath[1] == '|')
             modifiedNSPRPath[1] = ':';
        
        // Remove the ending separator only if it is not the last separator
        int len = strlen(modifiedNSPRPath);
        if (modifiedNSPRPath[len - 1 ] == '/' && modifiedNSPRPath[len - 2 ] != ':')
            modifiedNSPRPath[len - 1 ] = '\0';     
    }
    return modifiedNSPRPath;    
#else
    return (const char*)mFilePath;
#endif
}

//----------------------------------------------------------------------------------------
nsNSPRPath::~nsNSPRPath()
//----------------------------------------------------------------------------------------
{
#if defined(XP_WIN) || defined(XP_OS2)
    if (modifiedNSPRPath)
        nsCRT::free(modifiedNSPRPath);
#endif
}


nsresult 
NS_FileSpecToIFile(nsFileSpec* fileSpec, nsILocalFile* *result)
{
    nsresult rv;

    nsCOMPtr<nsILocalFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));

    if (!file) return NS_ERROR_FAILURE;

    rv = file->InitWithNativePath(nsDependentCString(fileSpec->GetNativePathCString()));

    if (NS_FAILED(rv)) return rv;

    *result = file;
    NS_ADDREF(*result);
    return NS_OK;
}




