michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 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: #include "nsFTPDirListingConv.h" michael@0: #include "nsMemory.h" michael@0: #include "plstr.h" michael@0: #include "prlog.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsEscape.h" michael@0: #include "nsStringStream.h" michael@0: #include "nsIStreamListener.h" michael@0: #include "nsCRT.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsIChannel.h" michael@0: #include "nsIURI.h" michael@0: michael@0: #include "ParseFTPList.h" michael@0: #include michael@0: michael@0: #if defined(PR_LOGGING) michael@0: // michael@0: // Log module for FTP dir listing stream converter logging... michael@0: // michael@0: // To enable logging (see prlog.h for full details): michael@0: // michael@0: // set NSPR_LOG_MODULES=nsFTPDirListConv:5 michael@0: // set NSPR_LOG_FILE=nspr.log michael@0: // michael@0: // this enables PR_LOG_DEBUG level information and places all output in michael@0: // the file nspr.log michael@0: // michael@0: PRLogModuleInfo* gFTPDirListConvLog = nullptr; michael@0: michael@0: #endif /* PR_LOGGING */ michael@0: michael@0: // nsISupports implementation michael@0: NS_IMPL_ISUPPORTS(nsFTPDirListingConv, michael@0: nsIStreamConverter, michael@0: nsIStreamListener, michael@0: nsIRequestObserver) michael@0: michael@0: michael@0: // nsIStreamConverter implementation michael@0: NS_IMETHODIMP michael@0: nsFTPDirListingConv::Convert(nsIInputStream *aFromStream, michael@0: const char *aFromType, michael@0: const char *aToType, michael@0: nsISupports *aCtxt, nsIInputStream **_retval) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: michael@0: // Stream converter service calls this to initialize the actual stream converter (us). michael@0: NS_IMETHODIMP michael@0: nsFTPDirListingConv::AsyncConvertData(const char *aFromType, const char *aToType, michael@0: nsIStreamListener *aListener, nsISupports *aCtxt) { michael@0: NS_ASSERTION(aListener && aFromType && aToType, "null pointer passed into FTP dir listing converter"); michael@0: michael@0: // hook up our final listener. this guy gets the various On*() calls we want to throw michael@0: // at him. michael@0: mFinalListener = aListener; michael@0: NS_ADDREF(mFinalListener); michael@0: michael@0: PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, michael@0: ("nsFTPDirListingConv::AsyncConvertData() converting FROM raw, TO application/http-index-format\n")); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // nsIStreamListener implementation michael@0: NS_IMETHODIMP michael@0: nsFTPDirListingConv::OnDataAvailable(nsIRequest* request, nsISupports *ctxt, michael@0: nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count) { michael@0: NS_ASSERTION(request, "FTP dir listing stream converter needs a request"); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(request, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t read, streamLen; michael@0: michael@0: uint64_t streamLen64; michael@0: rv = inStr->Available(&streamLen64); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: streamLen = (uint32_t)std::min(streamLen64, uint64_t(UINT32_MAX - 1)); michael@0: michael@0: nsAutoArrayPtr buffer(new char[streamLen + 1]); michael@0: NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: rv = inStr->Read(buffer, streamLen, &read); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // the dir listings are ascii text, null terminate this sucker. michael@0: buffer[streamLen] = '\0'; michael@0: michael@0: PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("nsFTPDirListingConv::OnData(request = %x, ctxt = %x, inStr = %x, sourceOffset = %llu, count = %u)\n", request, ctxt, inStr, sourceOffset, count)); michael@0: michael@0: if (!mBuffer.IsEmpty()) { michael@0: // we have data left over from a previous OnDataAvailable() call. michael@0: // combine the buffers so we don't lose any data. michael@0: mBuffer.Append(buffer); michael@0: michael@0: buffer = new char[mBuffer.Length()+1]; michael@0: NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: strncpy(buffer, mBuffer.get(), mBuffer.Length()+1); michael@0: mBuffer.Truncate(); michael@0: } michael@0: michael@0: #ifndef DEBUG_dougt michael@0: PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() received the following %d bytes...\n\n%s\n\n", streamLen, buffer.get()) ); michael@0: #else michael@0: printf("::OnData() received the following %d bytes...\n\n%s\n\n", streamLen, buffer); michael@0: #endif // DEBUG_dougt michael@0: michael@0: nsAutoCString indexFormat; michael@0: if (!mSentHeading) { michael@0: // build up the 300: line michael@0: nsCOMPtr uri; michael@0: rv = channel->GetURI(getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = GetHeaders(indexFormat, uri); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mSentHeading = true; michael@0: } michael@0: michael@0: char *line = buffer; michael@0: line = DigestBufferLines(line, indexFormat); michael@0: michael@0: #ifndef DEBUG_dougt michael@0: PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() sending the following %d bytes...\n\n%s\n\n", michael@0: indexFormat.Length(), indexFormat.get()) ); michael@0: #else michael@0: char *unescData = ToNewCString(indexFormat); michael@0: NS_ENSURE_TRUE(unescData, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsUnescape(unescData); michael@0: printf("::OnData() sending the following %d bytes...\n\n%s\n\n", indexFormat.Length(), unescData); michael@0: nsMemory::Free(unescData); michael@0: #endif // DEBUG_dougt michael@0: michael@0: // if there's any data left over, buffer it. michael@0: if (line && *line) { michael@0: mBuffer.Append(line); michael@0: PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() buffering the following %d bytes...\n\n%s\n\n", michael@0: strlen(line), line) ); michael@0: } michael@0: michael@0: // send the converted data out. michael@0: nsCOMPtr inputData; michael@0: michael@0: rv = NS_NewCStringInputStream(getter_AddRefs(inputData), indexFormat); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mFinalListener->OnDataAvailable(request, ctxt, inputData, 0, indexFormat.Length()); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: // nsIRequestObserver implementation michael@0: NS_IMETHODIMP michael@0: nsFTPDirListingConv::OnStartRequest(nsIRequest* request, nsISupports *ctxt) { michael@0: // we don't care about start. move along... but start masqeurading michael@0: // as the http-index channel now. michael@0: return mFinalListener->OnStartRequest(request, ctxt); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFTPDirListingConv::OnStopRequest(nsIRequest* request, nsISupports *ctxt, michael@0: nsresult aStatus) { michael@0: // we don't care about stop. move along... michael@0: michael@0: return mFinalListener->OnStopRequest(request, ctxt, aStatus); michael@0: } michael@0: michael@0: michael@0: // nsFTPDirListingConv methods michael@0: nsFTPDirListingConv::nsFTPDirListingConv() { michael@0: mFinalListener = nullptr; michael@0: mSentHeading = false; michael@0: } michael@0: michael@0: nsFTPDirListingConv::~nsFTPDirListingConv() { michael@0: NS_IF_RELEASE(mFinalListener); michael@0: } michael@0: michael@0: nsresult michael@0: nsFTPDirListingConv::Init() { michael@0: #if defined(PR_LOGGING) michael@0: // michael@0: // Initialize the global PRLogModule for FTP Protocol logging michael@0: // if necessary... michael@0: // michael@0: if (nullptr == gFTPDirListConvLog) { michael@0: gFTPDirListConvLog = PR_NewLogModule("nsFTPDirListingConv"); michael@0: } michael@0: #endif /* PR_LOGGING */ michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsFTPDirListingConv::GetHeaders(nsACString& headers, michael@0: nsIURI* uri) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: // build up 300 line michael@0: headers.AppendLiteral("300: "); michael@0: michael@0: // Bug 111117 - don't print the password michael@0: nsAutoCString pw; michael@0: nsAutoCString spec; michael@0: uri->GetPassword(pw); michael@0: if (!pw.IsEmpty()) { michael@0: rv = uri->SetPassword(EmptyCString()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = uri->GetAsciiSpec(spec); michael@0: if (NS_FAILED(rv)) return rv; michael@0: headers.Append(spec); michael@0: rv = uri->SetPassword(pw); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } else { michael@0: rv = uri->GetAsciiSpec(spec); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: headers.Append(spec); michael@0: } michael@0: headers.Append(char(nsCRT::LF)); michael@0: // END 300: michael@0: michael@0: // build up the column heading; 200: michael@0: headers.AppendLiteral("200: filename content-length last-modified file-type\n"); michael@0: // END 200: michael@0: return rv; michael@0: } michael@0: michael@0: char * michael@0: nsFTPDirListingConv::DigestBufferLines(char *aBuffer, nsCString &aString) { michael@0: char *line = aBuffer; michael@0: char *eol; michael@0: bool cr = false; michael@0: michael@0: list_state state; michael@0: michael@0: // while we have new lines, parse 'em into application/http-index-format. michael@0: while ( line && (eol = PL_strchr(line, nsCRT::LF)) ) { michael@0: // yank any carriage returns too. michael@0: if (eol > line && *(eol-1) == nsCRT::CR) { michael@0: eol--; michael@0: *eol = '\0'; michael@0: cr = true; michael@0: } else { michael@0: *eol = '\0'; michael@0: cr = false; michael@0: } michael@0: michael@0: list_result result; michael@0: michael@0: int type = ParseFTPList(line, &state, &result ); michael@0: michael@0: // if it is other than a directory, file, or link -OR- if it is a michael@0: // directory named . or .., skip over this line. michael@0: if ((type != 'd' && type != 'f' && type != 'l') || michael@0: (result.fe_type == 'd' && result.fe_fname[0] == '.' && michael@0: (result.fe_fnlen == 1 || (result.fe_fnlen == 2 && result.fe_fname[1] == '.'))) ) michael@0: { michael@0: if (cr) michael@0: line = eol+2; michael@0: else michael@0: line = eol+1; michael@0: michael@0: continue; michael@0: } michael@0: michael@0: // blast the index entry into the indexFormat buffer as a 201: line. michael@0: aString.AppendLiteral("201: "); michael@0: // FILENAME michael@0: michael@0: // parsers for styles 'U' and 'W' handle sequence " -> " themself michael@0: if (state.lstyle != 'U' && state.lstyle != 'W') { michael@0: const char* offset = strstr(result.fe_fname, " -> "); michael@0: if (offset) { michael@0: result.fe_fnlen = offset - result.fe_fname; michael@0: } michael@0: } michael@0: michael@0: nsAutoCString buf; michael@0: aString.Append('\"'); michael@0: aString.Append(NS_EscapeURL(Substring(result.fe_fname, michael@0: result.fe_fname+result.fe_fnlen), michael@0: esc_Minimal|esc_OnlyASCII|esc_Forced,buf)); michael@0: aString.AppendLiteral("\" "); michael@0: michael@0: // CONTENT LENGTH michael@0: michael@0: if (type != 'd') michael@0: { michael@0: for (int i = 0; i < int(sizeof(result.fe_size)); ++i) michael@0: { michael@0: if (result.fe_size[i] != '\0') michael@0: aString.Append((const char*)&result.fe_size[i], 1); michael@0: } michael@0: michael@0: aString.Append(' '); michael@0: } michael@0: else michael@0: aString.AppendLiteral("0 "); michael@0: michael@0: michael@0: // MODIFIED DATE michael@0: char buffer[256] = ""; michael@0: // Note: The below is the RFC822/1123 format, as required by michael@0: // the application/http-index-format specs michael@0: // viewers of such a format can then reformat this into the michael@0: // current locale (or anything else they choose) michael@0: PR_FormatTimeUSEnglish(buffer, sizeof(buffer), michael@0: "%a, %d %b %Y %H:%M:%S", &result.fe_time ); michael@0: michael@0: char *escapedDate = nsEscape(buffer, url_Path); michael@0: aString.Append(escapedDate); michael@0: nsMemory::Free(escapedDate); michael@0: aString.Append(' '); michael@0: michael@0: // ENTRY TYPE michael@0: if (type == 'd') michael@0: aString.AppendLiteral("DIRECTORY"); michael@0: else if (type == 'l') michael@0: aString.AppendLiteral("SYMBOLIC-LINK"); michael@0: else michael@0: aString.AppendLiteral("FILE"); michael@0: michael@0: aString.Append(' '); michael@0: michael@0: aString.Append(char(nsCRT::LF)); // complete this line michael@0: // END 201: michael@0: michael@0: if (cr) michael@0: line = eol+2; michael@0: else michael@0: line = eol+1; michael@0: } // end while(eol) michael@0: michael@0: return line; michael@0: } michael@0: michael@0: nsresult michael@0: NS_NewFTPDirListingConv(nsFTPDirListingConv** aFTPDirListingConv) michael@0: { michael@0: NS_PRECONDITION(aFTPDirListingConv != nullptr, "null ptr"); michael@0: if (! aFTPDirListingConv) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: *aFTPDirListingConv = new nsFTPDirListingConv(); michael@0: if (! *aFTPDirListingConv) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(*aFTPDirListingConv); michael@0: return (*aFTPDirListingConv)->Init(); michael@0: }