Back to index

lightning-sunbird  0.9+nobinonly
nsGopherDirListingConv.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
00002  *
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is the gopher-directory to http-index code.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Bradley Baetz.
00020  * Portions created by the Initial Developer are Copyright (C) 2000
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Bradley Baetz <bbaetz@student.usyd.edu.au>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 /* This code is based on the ftp directory translation code */
00041 
00042 #include "plstr.h"
00043 #include "nsMemory.h"
00044 #include "nsCRT.h"
00045 #include "nsIServiceManager.h"
00046 #include "nsIGenericFactory.h"
00047 #include "nsString.h"
00048 #include "nsReadableUtils.h"
00049 #include "nsCOMPtr.h"
00050 #include "nsIURI.h"
00051 #include "nsEscape.h"
00052 #include "nsIStreamListener.h"
00053 #include "nsIStreamConverter.h"
00054 #include "nsIStringStream.h"
00055 #include "nsIRequestObserver.h"
00056 #include "nsNetUtil.h"
00057 #include "nsMimeTypes.h"
00058 
00059 #include "nsGopherDirListingConv.h"
00060 
00061 // nsISupports implementation
00062 NS_IMPL_THREADSAFE_ISUPPORTS3(nsGopherDirListingConv,
00063                               nsIStreamConverter,
00064                               nsIStreamListener,
00065                               nsIRequestObserver)
00066 
00067 // nsIStreamConverter implementation
00068 
00069 #define CONV_BUF_SIZE (4*1024)
00070 
00071 NS_IMETHODIMP
00072 nsGopherDirListingConv::Convert(nsIInputStream *aFromStream,
00073                                 const char *aFromType,
00074                                 const char *aToType,
00075                                 nsISupports *aCtxt, nsIInputStream **_retval) {
00076     
00077     nsresult rv;
00078 
00079     char buffer[CONV_BUF_SIZE] = {0};
00080     nsFixedCString aBuffer(buffer, CONV_BUF_SIZE, 0);
00081     nsCAutoString convertedData;
00082 
00083     NS_ASSERTION(aCtxt, "Gopher dir conversion needs the context");
00084     // build up the 300: line
00085     nsCAutoString spec;
00086     mUri = do_QueryInterface(aCtxt, &rv);
00087     if (NS_FAILED(rv)) return rv;
00088     
00089     rv = mUri->GetAsciiSpec(spec);
00090     if (NS_FAILED(rv)) return rv;
00091 
00092     convertedData.AppendLiteral("300: ");
00093     convertedData.Append(spec);
00094     convertedData.Append(char(nsCRT::LF));
00095     // END 300:
00096     
00097     //Column headings
00098     // We should also send the content-type, probably. The stuff in
00099     // nsGopherChannel::GetContentType should then be moved out - 
00100     // maybe into an nsIGopherURI interface/class?
00101 
00102     // Should also possibly use different hosts as a symlink, but the directory
00103     // viewer stuff doesn't support SYM-FILE or SYM-DIRECTORY
00104     convertedData.AppendLiteral("200: description filename file-type\n");
00105 
00106     // build up the body
00107     while (1) {
00108         PRUint32 amtRead = 0;
00109 
00110         rv = aFromStream->Read(buffer+aBuffer.Length(),
00111                                CONV_BUF_SIZE-aBuffer.Length(), &amtRead);
00112         if (NS_FAILED(rv)) return rv;
00113 
00114         if (!amtRead) {
00115             // EOF
00116             break;
00117         }
00118 
00119         aBuffer = DigestBufferLines(buffer, convertedData);
00120     }
00121     
00122     // send the converted data out.
00123     return NS_NewCStringInputStream(_retval, convertedData);
00124 }
00125 
00126 // Stream converter service calls this to initialize the actual
00127 // stream converter (us).
00128 NS_IMETHODIMP
00129 nsGopherDirListingConv::AsyncConvertData(const char *aFromType,
00130                                          const char *aToType,
00131                                          nsIStreamListener *aListener,
00132                                          nsISupports *aCtxt) {
00133     NS_ASSERTION(aListener && aFromType && aToType,
00134                  "null pointer passed into gopher dir listing converter");
00135     nsresult rv;
00136 
00137     // hook up our final listener. this guy gets the various On*() calls
00138     // we want to throw at him.
00139     mFinalListener = aListener;
00140     NS_ADDREF(mFinalListener);
00141     
00142     // we need our own channel that represents the content-type of the
00143     // converted data.
00144     NS_ASSERTION(aCtxt, "Gopher dir listing needs a context (the uri)");
00145     mUri = do_QueryInterface(aCtxt,&rv);
00146     if (NS_FAILED(rv)) return rv;
00147 
00148     // XXX this seems really wrong!!
00149     rv = NS_NewInputStreamChannel(&mPartChannel,
00150                                   mUri,
00151                                   nsnull,
00152                                   NS_LITERAL_CSTRING(APPLICATION_HTTP_INDEX_FORMAT));
00153     if (NS_FAILED(rv)) return rv;
00154 
00155     return NS_OK;
00156 }
00157 
00158 // nsIStreamListener implementation
00159 NS_IMETHODIMP
00160 nsGopherDirListingConv::OnDataAvailable(nsIRequest *request,
00161                                         nsISupports *ctxt,
00162                                         nsIInputStream *inStr,
00163                                         PRUint32 sourceOffset,
00164                                         PRUint32 count) {
00165     nsresult rv;
00166 
00167     PRUint32 read, streamLen;
00168     nsCAutoString indexFormat;
00169     indexFormat.SetCapacity(72); // quick guess 
00170 
00171     rv = inStr->Available(&streamLen);
00172     if (NS_FAILED(rv)) return rv;
00173 
00174     char *buffer = (char*)nsMemory::Alloc(streamLen + 1);
00175     if (!buffer) return NS_ERROR_OUT_OF_MEMORY;
00176     rv = inStr->Read(buffer, streamLen, &read);
00177     if (NS_FAILED(rv)) return rv;
00178 
00179     // the dir listings are ascii text, null terminate this sucker.
00180     buffer[streamLen] = '\0';
00181 
00182     if (!mBuffer.IsEmpty()) {
00183         // we have data left over from a previous OnDataAvailable() call.
00184         // combine the buffers so we don't lose any data.
00185         mBuffer.Append(buffer);
00186         nsMemory::Free(buffer);
00187         buffer = ToNewCString(mBuffer);
00188         mBuffer.Truncate();
00189     }
00190 
00191     if (!mSentHeading) {
00192         // build up the 300: line
00193         nsCAutoString spec;
00194         rv = mUri->GetAsciiSpec(spec);
00195         if (NS_FAILED(rv)) return rv;
00196 
00197         //printf("spec is %s\n",spec.get());
00198         
00199         indexFormat.AppendLiteral("300: ");
00200         indexFormat.Append(spec);
00201         indexFormat.Append(char(nsCRT::LF));
00202         // END 300:
00203 
00204         // build up the column heading; 200:
00205         indexFormat.AppendLiteral("200: description filename file-type\n");
00206         // END 200:
00207         
00208         mSentHeading = PR_TRUE;
00209     }
00210     char *line = DigestBufferLines(buffer, indexFormat);
00211     // if there's any data left over, buffer it.
00212     if (line && *line) {
00213         mBuffer.Append(line);
00214     }
00215     
00216     nsMemory::Free(buffer);
00217     
00218     // send the converted data out.
00219     nsCOMPtr<nsIInputStream> inputData;
00220     
00221     rv = NS_NewCStringInputStream(getter_AddRefs(inputData), indexFormat);
00222     if (NS_FAILED(rv)) return rv;
00223     
00224     rv = mFinalListener->OnDataAvailable(mPartChannel, ctxt, inputData,
00225                                          0, indexFormat.Length());
00226     if (NS_FAILED(rv)) return rv;
00227     
00228     return NS_OK;
00229 }
00230 
00231 // nsIRequestObserver implementation
00232 NS_IMETHODIMP
00233 nsGopherDirListingConv::OnStartRequest(nsIRequest *request, nsISupports *ctxt) {
00234 
00235     // If the underlying transport failed, we need to
00236     // propogate it to the consumer by canceling our part
00237     // channel.
00238     nsresult status;
00239     request->GetStatus(&status);
00240     
00241     if (NS_FAILED(status))
00242         mPartChannel->Cancel(status);
00243 
00244     return mFinalListener->OnStartRequest(mPartChannel, ctxt);
00245 }
00246 
00247 NS_IMETHODIMP
00248 nsGopherDirListingConv::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
00249                                       nsresult aStatus) {
00250     // we don't care about stop. move along...
00251     nsCOMPtr<nsILoadGroup> loadgroup;
00252     nsresult rv = mPartChannel->GetLoadGroup(getter_AddRefs(loadgroup));
00253     if (NS_FAILED(rv)) return rv;
00254     if (loadgroup)
00255         (void)loadgroup->RemoveRequest(mPartChannel, nsnull, aStatus);
00256 
00257     return mFinalListener->OnStopRequest(mPartChannel, ctxt, aStatus);
00258 }
00259 
00260 // nsGopherDirListingConv methods
00261 nsGopherDirListingConv::nsGopherDirListingConv() {
00262     mFinalListener      = nsnull;
00263     mPartChannel        = nsnull;
00264     mSentHeading        = PR_FALSE;
00265 }
00266 
00267 nsGopherDirListingConv::~nsGopherDirListingConv() {
00268     NS_IF_RELEASE(mFinalListener);
00269     NS_IF_RELEASE(mPartChannel);
00270 }
00271 
00272 nsresult
00273 nsGopherDirListingConv::Init() {
00274     return NS_OK;
00275 }
00276 
00277 char*
00278 nsGopherDirListingConv::DigestBufferLines(char* aBuffer, nsCAutoString& aString) {
00279     char *line = aBuffer;
00280     char *eol;
00281     PRBool cr = PR_FALSE;
00282 
00283     // while we have new lines, parse 'em into application/http-index-format.
00284     while (line && (eol = PL_strchr(line, nsCRT::LF)) ) {
00285         // yank any carriage returns too.
00286         if (eol > line && *(eol-1) == nsCRT::CR) {
00287             eol--;
00288             *eol = '\0';
00289             cr = PR_TRUE;
00290         } else {
00291             *eol = '\0';
00292             cr = PR_FALSE;
00293         }
00294 
00295         if (line[0]=='.' && line[1]=='\0') {
00296             if (cr)
00297                 line = eol+2;
00298             else
00299                 line = eol+1;
00300             continue;
00301         }
00302 
00303         char type;
00304         nsCAutoString desc, selector, host;
00305         PRInt32 port = GOPHER_PORT;
00306 
00307         type = line[0];
00308         line++;
00309         char* tabPos = PL_strchr(line,'\t');
00310 
00311         /* Get the description */
00312         if (tabPos) {
00313             /* if the description is not empty */
00314             if (tabPos != line) {
00315                 char* descStr = PL_strndup(line,tabPos-line);
00316                 if (!descStr) return nsnull;
00317                 char* escName = nsEscape(descStr,url_Path);
00318                 if (!escName) {
00319                     PL_strfree(descStr);
00320                     return nsnull;
00321                 }
00322                 desc = escName;
00323                 NS_Free(escName);
00324                 PL_strfree(descStr);
00325             } else {
00326                 desc = "%20";
00327             }
00328             line = tabPos+1;
00329             tabPos = PL_strchr(line,'\t');
00330         }
00331 
00332         /* Get selector */
00333         if (tabPos) {
00334             char* sel = PL_strndup(line,tabPos-line);
00335             if (!sel) return nsnull;
00336             char* escName = nsEscape(sel,url_Path);
00337             if (!escName) {
00338                 PL_strfree(sel);
00339                 return nsnull;
00340             }
00341             selector = escName;
00342             NS_Free(escName);
00343             PL_strfree(sel);
00344             line = tabPos+1;
00345             tabPos = PL_strchr(line,'\t');
00346         }
00347 
00348         /* Host and Port - put together because there is
00349            no tab after the port */
00350         if (tabPos) {
00351             host = nsCString(line,tabPos-line);
00352             line = tabPos+1;
00353             tabPos = PL_strchr(line,'\t');
00354             if (tabPos==NULL)
00355                 tabPos = PL_strchr(line,'\0');
00356 
00357             /* Port */
00358             nsCAutoString portStr(line,tabPos-line);
00359             port = atol(portStr.get());
00360             line = tabPos+1;
00361         }
00362         
00363         // Now create the url
00364         nsCAutoString filename;
00365         if (type != '8' && type != 'T') {
00366             filename.AssignLiteral("gopher://");
00367             filename.Append(host);
00368             if (port != GOPHER_PORT) {
00369                 filename.Append(':');
00370                 filename.AppendInt(port);
00371             }
00372             filename.Append('/');
00373             filename.Append(type);
00374             filename.Append(selector);
00375         } else {
00376             // construct telnet/tn3270 url.
00377             // Moz doesn't support these, so this is UNTESTED!!!!!
00378             // (I do get the correct error message though)
00379             if (type == '8')
00380                 // telnet
00381                 filename.AssignLiteral("telnet://");
00382             else
00383                 // tn3270
00384                 filename.AssignLiteral("tn3270://");
00385             if (!selector.IsEmpty()) {
00386                 filename.Append(selector);
00387                 filename.Append('@');
00388             }
00389             filename.Append(host);
00390             if (port != 23) { // telnet port
00391                 filename.Append(':');
00392                 filename.AppendInt(port);
00393             }
00394         }
00395 
00396         if (tabPos) {
00397             /* Don't display error messages or informative messages
00398                because they could be selected, and they'll be sorted
00399                out of order.
00400                If FTP displays .messages/READMEs ever, then I could use the
00401                same method to display these
00402             */
00403             if (type != '3' && type != 'i') {
00404                 aString.AppendLiteral("201: ");
00405                 aString.Append(desc);
00406                 aString.Append(' ');
00407                 aString.Append(filename);
00408                 aString.Append(' ');
00409                 if (type == '1')
00410                     aString.AppendLiteral("DIRECTORY");
00411                 else
00412                     aString.AppendLiteral("FILE");
00413                 aString.Append(char(nsCRT::LF));
00414             } else if(type == 'i'){
00415                 aString.AppendLiteral("101: ");
00416                 aString.Append(desc);
00417                 aString.Append(char(nsCRT::LF));
00418             }
00419         } else {
00420             NS_WARNING("Error parsing gopher directory response.\n");
00421             //printf("Got: %s\n",filename.get());
00422         }
00423         
00424         if (cr)
00425             line = eol+2;
00426         else
00427             line = eol+1;
00428     }
00429     return line;
00430 }
00431 
00432 nsresult
00433 NS_NewGopherDirListingConv(nsGopherDirListingConv** aGopherDirListingConv)
00434 {
00435     NS_PRECONDITION(aGopherDirListingConv, "null ptr");
00436     if (! aGopherDirListingConv)
00437         return NS_ERROR_NULL_POINTER;
00438 
00439     *aGopherDirListingConv = new nsGopherDirListingConv();
00440     if (! *aGopherDirListingConv)
00441         return NS_ERROR_OUT_OF_MEMORY;
00442 
00443     NS_ADDREF(*aGopherDirListingConv);
00444     return (*aGopherDirListingConv)->Init();
00445 }