michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:set sw=4 sts=4 et cin: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: michael@0: /* michael@0: michael@0: The converts a filesystem directory into an "HTTP index" stream per michael@0: Lou Montulli's original spec: michael@0: michael@0: http://www.mozilla.org/projects/netlib/dirindexformat.html michael@0: michael@0: */ michael@0: michael@0: #include "nsEscape.h" michael@0: #include "nsDirectoryIndexStream.h" michael@0: #include "prlog.h" michael@0: #include "prtime.h" michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo* gLog; michael@0: #endif michael@0: michael@0: #include "nsISimpleEnumerator.h" michael@0: #ifdef THREADSAFE_I18N michael@0: #include "nsCollationCID.h" michael@0: #include "nsICollation.h" michael@0: #include "nsILocale.h" michael@0: #include "nsILocaleService.h" michael@0: #endif michael@0: #include "nsIFile.h" michael@0: #include "nsURLHelper.h" michael@0: #include "nsNativeCharsetUtils.h" michael@0: michael@0: // NOTE: This runs on the _file transport_ thread. michael@0: // The problem is that now that we're actually doing something with the data, michael@0: // we want to do stuff like i18n sorting. However, none of the collation stuff michael@0: // is threadsafe. michael@0: // So THIS CODE IS ASCII ONLY!!!!!!!! This is no worse than the current michael@0: // behaviour, though. See bug 99382. michael@0: // When this is fixed, #define THREADSAFE_I18N to get this code working michael@0: michael@0: //#define THREADSAFE_I18N michael@0: michael@0: nsDirectoryIndexStream::nsDirectoryIndexStream() michael@0: : mOffset(0), mStatus(NS_OK), mPos(0) michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (! gLog) michael@0: gLog = PR_NewLogModule("nsDirectoryIndexStream"); michael@0: #endif michael@0: michael@0: PR_LOG(gLog, PR_LOG_DEBUG, michael@0: ("nsDirectoryIndexStream[%p]: created", this)); michael@0: } michael@0: michael@0: static int compare(nsIFile* aElement1, nsIFile* aElement2, void* aData) michael@0: { michael@0: if (!NS_IsNativeUTF8()) { michael@0: // don't check for errors, because we can't report them anyway michael@0: nsAutoString name1, name2; michael@0: aElement1->GetLeafName(name1); michael@0: aElement2->GetLeafName(name2); michael@0: michael@0: // Note - we should do the collation to do sorting. Why don't we? michael@0: // Because that is _slow_. Using TestProtocols to list file:///dev/ michael@0: // goes from 3 seconds to 22. (This may be why nsXULSortService is michael@0: // so slow as well). michael@0: // Does this have bad effects? Probably, but since nsXULTree appears michael@0: // to use the raw RDF literal value as the sort key (which ammounts to an michael@0: // strcmp), it won't be any worse, I think. michael@0: // This could be made faster, by creating the keys once, michael@0: // but CompareString could still be smarter - see bug 99383 - bbaetz michael@0: // NB - 99393 has been WONTFIXed. So if the I18N code is ever made michael@0: // threadsafe so that this matters, we'd have to pass through a michael@0: // struct { nsIFile*, uint8_t* } with the pre-calculated key. michael@0: return Compare(name1, name2); michael@0: } michael@0: michael@0: nsAutoCString name1, name2; michael@0: aElement1->GetNativeLeafName(name1); michael@0: aElement2->GetNativeLeafName(name2); michael@0: michael@0: return Compare(name1, name2); michael@0: } michael@0: michael@0: nsresult michael@0: nsDirectoryIndexStream::Init(nsIFile* aDir) michael@0: { michael@0: nsresult rv; michael@0: bool isDir; michael@0: rv = aDir->IsDirectory(&isDir); michael@0: if (NS_FAILED(rv)) return rv; michael@0: NS_PRECONDITION(isDir, "not a directory"); michael@0: if (!isDir) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gLog, PR_LOG_DEBUG)) { michael@0: nsAutoCString path; michael@0: aDir->GetNativePath(path); michael@0: PR_LOG(gLog, PR_LOG_DEBUG, michael@0: ("nsDirectoryIndexStream[%p]: initialized on %s", michael@0: this, path.get())); michael@0: } michael@0: #endif michael@0: michael@0: // Sigh. We have to allocate on the heap because there are no michael@0: // assignment operators defined. michael@0: nsCOMPtr iter; michael@0: rv = aDir->GetDirectoryEntries(getter_AddRefs(iter)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Now lets sort, because clients expect it that way michael@0: // XXX - should we do so here, or when the first item is requested? michael@0: // XXX - use insertion sort instead? michael@0: michael@0: bool more; michael@0: nsCOMPtr elem; michael@0: while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) { michael@0: rv = iter->GetNext(getter_AddRefs(elem)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr file = do_QueryInterface(elem); michael@0: if (file) michael@0: mArray.AppendObject(file); // addrefs michael@0: } michael@0: } michael@0: michael@0: #ifdef THREADSAFE_I18N michael@0: nsCOMPtr ls = do_GetService(NS_LOCALESERVICE_CONTRACTID, michael@0: &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr locale; michael@0: rv = ls->GetApplicationLocale(getter_AddRefs(locale)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr cf = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, michael@0: &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr coll; michael@0: rv = cf->CreateCollation(locale, getter_AddRefs(coll)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mArray.Sort(compare, coll); michael@0: #else michael@0: mArray.Sort(compare, nullptr); michael@0: #endif michael@0: michael@0: mBuf.AppendLiteral("300: "); michael@0: nsAutoCString url; michael@0: rv = net_GetURLSpecFromFile(aDir, url); michael@0: if (NS_FAILED(rv)) return rv; michael@0: mBuf.Append(url); michael@0: mBuf.Append('\n'); michael@0: michael@0: mBuf.AppendLiteral("200: filename content-length last-modified file-type\n"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsDirectoryIndexStream::~nsDirectoryIndexStream() michael@0: { michael@0: PR_LOG(gLog, PR_LOG_DEBUG, michael@0: ("nsDirectoryIndexStream[%p]: destroyed", this)); michael@0: } michael@0: michael@0: nsresult michael@0: nsDirectoryIndexStream::Create(nsIFile* aDir, nsIInputStream** aResult) michael@0: { michael@0: nsDirectoryIndexStream* result = new nsDirectoryIndexStream(); michael@0: if (! result) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: nsresult rv; michael@0: rv = result->Init(aDir); michael@0: if (NS_FAILED(rv)) { michael@0: delete result; michael@0: return rv; michael@0: } michael@0: michael@0: *aResult = result; michael@0: NS_ADDREF(*aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsDirectoryIndexStream, nsIInputStream) michael@0: michael@0: // The below routines are proxied to the UI thread! michael@0: NS_IMETHODIMP michael@0: nsDirectoryIndexStream::Close() michael@0: { michael@0: mStatus = NS_BASE_STREAM_CLOSED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDirectoryIndexStream::Available(uint64_t* aLength) michael@0: { michael@0: if (NS_FAILED(mStatus)) michael@0: return mStatus; michael@0: michael@0: // If there's data in our buffer, use that michael@0: if (mOffset < (int32_t)mBuf.Length()) { michael@0: *aLength = mBuf.Length() - mOffset; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Returning one byte is not ideal, but good enough michael@0: *aLength = (mPos < mArray.Count()) ? 1 : 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDirectoryIndexStream::Read(char* aBuf, uint32_t aCount, uint32_t* aReadCount) michael@0: { michael@0: if (mStatus == NS_BASE_STREAM_CLOSED) { michael@0: *aReadCount = 0; michael@0: return NS_OK; michael@0: } michael@0: if (NS_FAILED(mStatus)) michael@0: return mStatus; michael@0: michael@0: uint32_t nread = 0; michael@0: michael@0: // If anything is enqueued (or left-over) in mBuf, then feed it to michael@0: // the reader first. michael@0: while (mOffset < (int32_t)mBuf.Length() && aCount != 0) { michael@0: *(aBuf++) = char(mBuf.CharAt(mOffset++)); michael@0: --aCount; michael@0: ++nread; michael@0: } michael@0: michael@0: // Room left? michael@0: if (aCount > 0) { michael@0: mOffset = 0; michael@0: mBuf.Truncate(); michael@0: michael@0: // Okay, now we'll suck stuff off of our iterator into the mBuf... michael@0: while (uint32_t(mBuf.Length()) < aCount) { michael@0: bool more = mPos < mArray.Count(); michael@0: if (!more) break; michael@0: michael@0: // don't addref, for speed - an addref happened when it michael@0: // was placed in the array, so it's not going to go stale michael@0: nsIFile* current = mArray.ObjectAt(mPos); michael@0: ++mPos; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gLog, PR_LOG_DEBUG)) { michael@0: nsAutoCString path; michael@0: current->GetNativePath(path); michael@0: PR_LOG(gLog, PR_LOG_DEBUG, michael@0: ("nsDirectoryIndexStream[%p]: iterated %s", michael@0: this, path.get())); michael@0: } michael@0: #endif michael@0: michael@0: // rjc: don't return hidden files/directories! michael@0: // bbaetz: why not? michael@0: nsresult rv; michael@0: #ifndef XP_UNIX michael@0: bool hidden = false; michael@0: current->IsHidden(&hidden); michael@0: if (hidden) { michael@0: PR_LOG(gLog, PR_LOG_DEBUG, michael@0: ("nsDirectoryIndexStream[%p]: skipping hidden file/directory", michael@0: this)); michael@0: continue; michael@0: } michael@0: #endif michael@0: michael@0: int64_t fileSize = 0; michael@0: current->GetFileSize( &fileSize ); michael@0: michael@0: PRTime fileInfoModifyTime = 0; michael@0: current->GetLastModifiedTime( &fileInfoModifyTime ); michael@0: fileInfoModifyTime *= PR_USEC_PER_MSEC; michael@0: michael@0: mBuf.AppendLiteral("201: "); michael@0: michael@0: // The "filename" field michael@0: char* escaped = nullptr; michael@0: if (!NS_IsNativeUTF8()) { michael@0: nsAutoString leafname; michael@0: rv = current->GetLeafName(leafname); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (!leafname.IsEmpty()) michael@0: escaped = nsEscape(NS_ConvertUTF16toUTF8(leafname).get(), url_Path); michael@0: } else { michael@0: nsAutoCString leafname; michael@0: rv = current->GetNativeLeafName(leafname); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (!leafname.IsEmpty()) michael@0: escaped = nsEscape(leafname.get(), url_Path); michael@0: } michael@0: if (escaped) { michael@0: mBuf += escaped; michael@0: mBuf.Append(' '); michael@0: nsMemory::Free(escaped); michael@0: } michael@0: michael@0: // The "content-length" field michael@0: mBuf.AppendInt(fileSize, 10); michael@0: mBuf.Append(' '); michael@0: michael@0: // The "last-modified" field michael@0: PRExplodedTime tm; michael@0: PR_ExplodeTime(fileInfoModifyTime, PR_GMTParameters, &tm); michael@0: { michael@0: char buf[64]; michael@0: PR_FormatTimeUSEnglish(buf, sizeof(buf), "%a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm); michael@0: mBuf.Append(buf); michael@0: } michael@0: michael@0: // The "file-type" field michael@0: bool isFile = true; michael@0: current->IsFile(&isFile); michael@0: if (isFile) { michael@0: mBuf.AppendLiteral("FILE "); michael@0: } michael@0: else { michael@0: bool isDir; michael@0: rv = current->IsDirectory(&isDir); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (isDir) { michael@0: mBuf.AppendLiteral("DIRECTORY "); michael@0: } michael@0: else { michael@0: bool isLink; michael@0: rv = current->IsSymlink(&isLink); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (isLink) { michael@0: mBuf.AppendLiteral("SYMBOLIC-LINK "); michael@0: } michael@0: } michael@0: } michael@0: michael@0: mBuf.Append('\n'); michael@0: } michael@0: michael@0: // ...and once we've either run out of directory entries, or michael@0: // filled up the buffer, then we'll push it to the reader. michael@0: while (mOffset < (int32_t)mBuf.Length() && aCount != 0) { michael@0: *(aBuf++) = char(mBuf.CharAt(mOffset++)); michael@0: --aCount; michael@0: ++nread; michael@0: } michael@0: } michael@0: michael@0: *aReadCount = nread; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDirectoryIndexStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDirectoryIndexStream::IsNonBlocking(bool *aNonBlocking) michael@0: { michael@0: *aNonBlocking = false; michael@0: return NS_OK; michael@0: }