Back to index

lightning-sunbird  0.9+nobinonly
nsFTPDirListingConv.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Bradley Baetz <bbaetz@student.usyd.edu.au>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsFTPDirListingConv.h"
00040 #include "nsMemory.h"
00041 #include "plstr.h"
00042 #include "prlog.h"
00043 #include "nsIAtom.h"
00044 #include "nsIServiceManager.h"
00045 #include "nsIGenericFactory.h"
00046 #include "nsXPIDLString.h"
00047 #include "nsReadableUtils.h"
00048 #include "nsCOMPtr.h"
00049 #include "nsEscape.h"
00050 #include "nsNetUtil.h"
00051 #include "nsIStringStream.h"
00052 #include "nsILocaleService.h"
00053 #include "nsIComponentManager.h"
00054 #include "nsDateTimeFormatCID.h"
00055 #include "nsIStreamListener.h"
00056 #include "nsCRT.h"
00057 #include "nsMimeTypes.h"
00058 #include "nsAutoPtr.h"
00059 
00060 #include "ParseFTPList.h"
00061 
00062 #if defined(PR_LOGGING)
00063 //
00064 // Log module for FTP dir listing stream converter logging...
00065 //
00066 // To enable logging (see prlog.h for full details):
00067 //
00068 //    set NSPR_LOG_MODULES=nsFTPDirListConv:5
00069 //    set NSPR_LOG_FILE=nspr.log
00070 //
00071 // this enables PR_LOG_DEBUG level information and places all output in
00072 // the file nspr.log
00073 //
00074 PRLogModuleInfo* gFTPDirListConvLog = nsnull;
00075 
00076 #endif /* PR_LOGGING */
00077 
00078 // nsISupports implementation
00079 NS_IMPL_THREADSAFE_ISUPPORTS3(nsFTPDirListingConv,
00080                               nsIStreamConverter,
00081                               nsIStreamListener, 
00082                               nsIRequestObserver)
00083 
00084 
00085 // nsIStreamConverter implementation
00086 NS_IMETHODIMP
00087 nsFTPDirListingConv::Convert(nsIInputStream *aFromStream,
00088                              const char *aFromType,
00089                              const char *aToType,
00090                              nsISupports *aCtxt, nsIInputStream **_retval) {
00091     return NS_ERROR_NOT_IMPLEMENTED;
00092 }
00093 
00094 
00095 // Stream converter service calls this to initialize the actual stream converter (us).
00096 NS_IMETHODIMP
00097 nsFTPDirListingConv::AsyncConvertData(const char *aFromType, const char *aToType,
00098                                       nsIStreamListener *aListener, nsISupports *aCtxt) {
00099     NS_ASSERTION(aListener && aFromType && aToType, "null pointer passed into FTP dir listing converter");
00100     nsresult rv;
00101 
00102     // hook up our final listener. this guy gets the various On*() calls we want to throw
00103     // at him.
00104     mFinalListener = aListener;
00105     NS_ADDREF(mFinalListener);
00106 
00107     // we need our own channel that represents the content-type of the
00108     // converted data.
00109     NS_ASSERTION(aCtxt, "FTP dir listing needs a context (the uri)");
00110     nsIURI *uri;
00111     rv = aCtxt->QueryInterface(NS_GET_IID(nsIURI), (void**)&uri);
00112     if (NS_FAILED(rv)) return rv;
00113 
00114     // XXX this seems really wrong!!
00115     rv = NS_NewInputStreamChannel(&mPartChannel,
00116                                   uri,
00117                                   nsnull,
00118                                   NS_LITERAL_CSTRING(APPLICATION_HTTP_INDEX_FORMAT));
00119     NS_RELEASE(uri);
00120     if (NS_FAILED(rv)) return rv;
00121 
00122     PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, 
00123         ("nsFTPDirListingConv::AsyncConvertData() converting FROM raw, TO application/http-index-format\n"));
00124 
00125     return NS_OK;
00126 }
00127 
00128 
00129 // nsIStreamListener implementation
00130 NS_IMETHODIMP
00131 nsFTPDirListingConv::OnDataAvailable(nsIRequest* request, nsISupports *ctxt,
00132                                   nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count) {
00133     NS_ASSERTION(request, "FTP dir listing stream converter needs a request");
00134     
00135     nsresult rv;
00136 
00137     nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
00138     NS_ENSURE_SUCCESS(rv, rv);
00139     
00140     PRUint32 read, streamLen;
00141 
00142     rv = inStr->Available(&streamLen);
00143     NS_ENSURE_SUCCESS(rv, rv);
00144 
00145     nsAutoArrayPtr<char> buffer(new char[streamLen + 1]);
00146     NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY);
00147 
00148     rv = inStr->Read(buffer, streamLen, &read);
00149     NS_ENSURE_SUCCESS(rv, rv);
00150 
00151     // the dir listings are ascii text, null terminate this sucker.
00152     buffer[streamLen] = '\0';
00153 
00154     PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("nsFTPDirListingConv::OnData(request = %x, ctxt = %x, inStr = %x, sourceOffset = %d, count = %d)\n", request, ctxt, inStr, sourceOffset, count));
00155 
00156     if (!mBuffer.IsEmpty()) {
00157         // we have data left over from a previous OnDataAvailable() call.
00158         // combine the buffers so we don't lose any data.
00159         mBuffer.Append(buffer);
00160 
00161         buffer = new char[mBuffer.Length()+1];
00162         NS_ENSURE_TRUE(buffer, NS_ERROR_OUT_OF_MEMORY);
00163 
00164         strncpy(buffer, mBuffer.get(), mBuffer.Length()+1);
00165         mBuffer.Truncate();
00166     }
00167 
00168 #ifndef DEBUG_dougt
00169     PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() received the following %d bytes...\n\n%s\n\n", streamLen, buffer.get()) );
00170 #else
00171     printf("::OnData() received the following %d bytes...\n\n%s\n\n", streamLen, buffer);
00172 #endif // DEBUG_dougt
00173 
00174     nsCAutoString indexFormat;
00175     if (!mSentHeading) {
00176         // build up the 300: line
00177         nsCOMPtr<nsIURI> uri;
00178         rv = channel->GetURI(getter_AddRefs(uri));
00179         NS_ENSURE_SUCCESS(rv, rv);
00180 
00181         rv = GetHeaders(indexFormat, uri);
00182         NS_ENSURE_SUCCESS(rv, rv);
00183 
00184         mSentHeading = PR_TRUE;
00185     }
00186 
00187     char *line = buffer;
00188     line = DigestBufferLines(line, indexFormat);
00189 
00190 #ifndef DEBUG_dougt
00191     PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() sending the following %d bytes...\n\n%s\n\n", 
00192         indexFormat.Length(), indexFormat.get()) );
00193 #else
00194     char *unescData = ToNewCString(indexFormat);
00195     NS_ENSURE_TRUE(unescData, NS_ERROR_OUT_OF_MEMORY);
00196     
00197     nsUnescape(unescData);
00198     printf("::OnData() sending the following %d bytes...\n\n%s\n\n", indexFormat.Length(), unescData);
00199     nsMemory::Free(unescData);
00200 #endif // DEBUG_dougt
00201 
00202     // if there's any data left over, buffer it.
00203     if (line && *line) {
00204         mBuffer.Append(line);
00205         PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() buffering the following %d bytes...\n\n%s\n\n",
00206             PL_strlen(line), line) );
00207     }
00208 
00209     // send the converted data out.
00210     nsCOMPtr<nsIInputStream> inputData;
00211 
00212     rv = NS_NewCStringInputStream(getter_AddRefs(inputData), indexFormat);
00213     NS_ENSURE_SUCCESS(rv, rv);
00214 
00215     rv = mFinalListener->OnDataAvailable(mPartChannel, ctxt, inputData, 0, indexFormat.Length());
00216 
00217     return rv;
00218 }
00219 
00220 
00221 // nsIRequestObserver implementation
00222 NS_IMETHODIMP
00223 nsFTPDirListingConv::OnStartRequest(nsIRequest* request, nsISupports *ctxt) {
00224     // we don't care about start. move along... but start masqeurading 
00225     // as the http-index channel now.
00226     return mFinalListener->OnStartRequest(mPartChannel, ctxt);
00227 }
00228 
00229 NS_IMETHODIMP
00230 nsFTPDirListingConv::OnStopRequest(nsIRequest* request, nsISupports *ctxt,
00231                                    nsresult aStatus) {
00232     // we don't care about stop. move along...
00233 
00234     nsresult rv;
00235 
00236     nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
00237     if (NS_FAILED(rv)) return rv;
00238 
00239 
00240     nsCOMPtr<nsILoadGroup> loadgroup;
00241     rv = channel->GetLoadGroup(getter_AddRefs(loadgroup));
00242     if (NS_FAILED(rv)) return rv;
00243 
00244     if (loadgroup)
00245         (void)loadgroup->RemoveRequest(mPartChannel, nsnull, aStatus);
00246 
00247     return mFinalListener->OnStopRequest(mPartChannel, ctxt, aStatus);
00248 }
00249 
00250 
00251 // nsFTPDirListingConv methods
00252 nsFTPDirListingConv::nsFTPDirListingConv() {
00253     mFinalListener      = nsnull;
00254     mPartChannel        = nsnull;
00255     mSentHeading        = PR_FALSE;
00256 }
00257 
00258 nsFTPDirListingConv::~nsFTPDirListingConv() {
00259     NS_IF_RELEASE(mFinalListener);
00260     NS_IF_RELEASE(mPartChannel);
00261 }
00262 
00263 nsresult
00264 nsFTPDirListingConv::Init() {
00265 #if defined(PR_LOGGING)
00266     //
00267     // Initialize the global PRLogModule for FTP Protocol logging 
00268     // if necessary...
00269     //
00270     if (nsnull == gFTPDirListConvLog) {
00271         gFTPDirListConvLog = PR_NewLogModule("nsFTPDirListingConv");
00272     }
00273 #endif /* PR_LOGGING */
00274 
00275     return NS_OK;
00276 }
00277 
00278 nsresult
00279 nsFTPDirListingConv::GetHeaders(nsACString& headers,
00280                                 nsIURI* uri)
00281 {
00282     nsresult rv = NS_OK;
00283     // build up 300 line
00284     headers.AppendLiteral("300: ");
00285 
00286     // Bug 111117 - don't print the password
00287     nsCAutoString pw;
00288     nsCAutoString spec;
00289     uri->GetPassword(pw);
00290     if (!pw.IsEmpty()) {
00291          rv = uri->SetPassword(EmptyCString());
00292          if (NS_FAILED(rv)) return rv;
00293          rv = uri->GetAsciiSpec(spec);
00294          if (NS_FAILED(rv)) return rv;
00295          headers.Append(spec);
00296          rv = uri->SetPassword(pw);
00297          if (NS_FAILED(rv)) return rv;
00298     } else {
00299         rv = uri->GetAsciiSpec(spec);
00300         if (NS_FAILED(rv)) return rv;
00301         
00302         headers.Append(spec);
00303     }
00304     headers.Append(char(nsCRT::LF));
00305     // END 300:
00306 
00307     // build up the column heading; 200:
00308     headers.AppendLiteral("200: filename content-length last-modified file-type\n");
00309     // END 200:
00310     return rv;
00311 }
00312 
00313 char *
00314 nsFTPDirListingConv::DigestBufferLines(char *aBuffer, nsCString &aString) {
00315     char *line = aBuffer;
00316     char *eol;
00317     PRBool cr = PR_FALSE;
00318 
00319     // while we have new lines, parse 'em into application/http-index-format.
00320     while ( line && (eol = PL_strchr(line, nsCRT::LF)) ) {
00321         // yank any carriage returns too.
00322         if (eol > line && *(eol-1) == nsCRT::CR) {
00323             eol--;
00324             *eol = '\0';
00325             cr = PR_TRUE;
00326         } else {
00327             *eol = '\0';
00328             cr = PR_FALSE;
00329         }
00330 
00331         list_state state;
00332         list_result result;
00333 
00334         int type = ParseFTPList(line, &state, &result );
00335 
00336         // if it is other than a directory, file, or link -OR- if it is a 
00337         // directory named . or .., skip over this line.
00338         if ((type != 'd' && type != 'f' && type != 'l') || 
00339             (result.fe_type == 'd' && result.fe_fname[0] == '.' &&
00340             (result.fe_fnlen == 1 || (result.fe_fnlen == 2 &&  result.fe_fname[1] == '.'))) )
00341         {
00342             if (cr)
00343                 line = eol+2;
00344             else
00345                 line = eol+1;
00346             
00347             continue;
00348         }
00349 
00350         // blast the index entry into the indexFormat buffer as a 201: line.
00351         aString.AppendLiteral("201: ");
00352         // FILENAME
00353 
00354 
00355         const char* offset = strstr(result.fe_fname, " -> ");
00356         if (offset) {
00357             result.fe_fnlen = offset - result.fe_fname;
00358         }
00359 
00360         nsCAutoString buf;
00361         aString.Append('\"');
00362         aString.Append(NS_EscapeURL(Substring(result.fe_fname, 
00363                                               result.fe_fname+result.fe_fnlen),
00364                                     esc_Minimal|esc_OnlyASCII|esc_Forced,buf));
00365         aString.AppendLiteral("\" ");
00366  
00367         // CONTENT LENGTH
00368         
00369         if (type != 'd') 
00370         {
00371             for (int i = 0; i < int(sizeof(result.fe_size)); ++i)
00372             {
00373                 if (result.fe_size[i] != '\0')
00374                     aString.Append((const char*)&result.fe_size[i], 1);
00375             }
00376             
00377             aString.Append(' ');
00378         }
00379         else
00380             aString.AppendLiteral("0 ");
00381 
00382 
00383         // MODIFIED DATE
00384         char buffer[256] = "";
00385         // Note: The below is the RFC822/1123 format, as required by
00386         // the application/http-index-format specs
00387         // viewers of such a format can then reformat this into the
00388         // current locale (or anything else they choose)
00389         PR_FormatTimeUSEnglish(buffer, sizeof(buffer),
00390                                "%a, %d %b %Y %H:%M:%S", &result.fe_time );
00391 
00392         char *escapedDate = nsEscape(buffer, url_Path);
00393         aString.Append(escapedDate);
00394         nsMemory::Free(escapedDate);
00395         aString.Append(' ');
00396 
00397         // ENTRY TYPE
00398         if (type == 'd')
00399             aString.AppendLiteral("DIRECTORY");
00400         else if (type == 'l')
00401             aString.AppendLiteral("SYMBOLIC-LINK");
00402         else
00403             aString.AppendLiteral("FILE");
00404         
00405         aString.Append(' ');
00406 
00407         aString.Append(char(nsCRT::LF)); // complete this line
00408         // END 201:
00409 
00410         if (cr)
00411             line = eol+2;
00412         else
00413             line = eol+1;
00414     } // end while(eol)
00415 
00416     return line;
00417 }
00418 
00419 nsresult
00420 NS_NewFTPDirListingConv(nsFTPDirListingConv** aFTPDirListingConv)
00421 {
00422     NS_PRECONDITION(aFTPDirListingConv != nsnull, "null ptr");
00423     if (! aFTPDirListingConv)
00424         return NS_ERROR_NULL_POINTER;
00425 
00426     *aFTPDirListingConv = new nsFTPDirListingConv();
00427     if (! *aFTPDirListingConv)
00428         return NS_ERROR_OUT_OF_MEMORY;
00429 
00430     NS_ADDREF(*aFTPDirListingConv);
00431     return (*aFTPDirListingConv)->Init();
00432 }