/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape 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/NPL/ * * 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): Bradley Baetz * * Alternatively, the contents of this file may be used under the terms of * either 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 NPL, 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 NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsFTPDirListingConv.h" #include "nsMemory.h" #include "plstr.h" #include "prlog.h" #include "nsIAtom.h" #include "nsIServiceManager.h" #include "nsIGenericFactory.h" #include "nsXPIDLString.h" #include "nsReadableUtils.h" #include "nsCOMPtr.h" #include "nsEscape.h" #include "nsNetUtil.h" #include "nsIStringStream.h" #include "nsILocaleService.h" #include "nsIComponentManager.h" #include "nsDateTimeFormatCID.h" #include "nsIStreamListener.h" #include "nsCRT.h" #include "nsMimeTypes.h" #define IS_LWS(c) (PL_strchr(" \t\r\n",c) != 0) #define IS_FTYPE(c) (PL_strchr("-dlbcsp",c) != 0) #define IS_RPERM(c) (PL_strchr("r-",c) != 0) #define IS_WPERM(c) (PL_strchr("w-",c) != 0) #define IS_SPERM(c) (PL_strchr("sSx-",c) != 0) #define IS_TPERM(c) (PL_strchr("tTx-",c) != 0) static NS_DEFINE_CID(kLocaleServiceCID, NS_LOCALESERVICE_CID); static NS_DEFINE_CID(kDateTimeCID, NS_DATETIMEFORMAT_CID); #if defined(PR_LOGGING) // // Log module for FTP dir listing stream converter logging... // // To enable logging (see prlog.h for full details): // // set NSPR_LOG_MODULES=nsFTPDirListConv:5 // set NSPR_LOG_FILE=nspr.log // // this enables PR_LOG_DEBUG level information and places all output in // the file nspr.log // PRLogModuleInfo* gFTPDirListConvLog = nsnull; #endif /* PR_LOGGING */ // nsISupports implementation NS_IMPL_THREADSAFE_ISUPPORTS3(nsFTPDirListingConv, nsIStreamConverter, nsIStreamListener, nsIRequestObserver); // Common code of Convert and AsyncConvertData function static FTP_Server_Type DetermineServerType (nsCString &fromMIMEString, const PRUnichar *aFromType) { fromMIMEString.AssignWithConversion(aFromType); const char *from = fromMIMEString.get(); NS_ASSERTION(from, "nsCString/PRUnichar acceptance failed."); from = PL_strstr(from, "/ftp-dir-"); if (!from) return ERROR_TYPE; from += 9; fromMIMEString = from; if (-1 != fromMIMEString.Find("unix")) { return UNIX; } else if (-1 != fromMIMEString.Find("nt")) { return NT; } else if (-1 != fromMIMEString.Find("dcts")) { return DCTS; } else if (-1 != fromMIMEString.Find("ncsa")) { return NCSA; } else if (-1 != fromMIMEString.Find("peter_lewis")) { return PETER_LEWIS; } else if (-1 != fromMIMEString.Find("machten")) { return MACHTEN; } else if (-1 != fromMIMEString.Find("cms")) { return CMS; } else if (-1 != fromMIMEString.Find("tcpc")) { return TCPC; } else if (-1 != fromMIMEString.Find("os2")) { return OS_2; } return GENERIC; } // nsIStreamConverter implementation #define CONV_BUF_SIZE (4*1024) NS_IMETHODIMP nsFTPDirListingConv::Convert(nsIInputStream *aFromStream, const PRUnichar *aFromType, const PRUnichar *aToType, nsISupports *aCtxt, nsIInputStream **_retval) { nsresult rv; // set our internal state to reflect the server type nsCString fromMIMEString; mServerType = DetermineServerType(fromMIMEString, aFromType); if (mServerType == ERROR_TYPE) return NS_ERROR_FAILURE; char buffer[CONV_BUF_SIZE]; int i = 0; while (i < CONV_BUF_SIZE) { buffer[i] = '\0'; i++; } CBufDescriptor desc(buffer, PR_TRUE, CONV_BUF_SIZE); nsCAutoString aBuffer(desc); nsCString convertedData; NS_ASSERTION(aCtxt, "FTP dir conversion needs the context"); nsCOMPtr uri(do_QueryInterface(aCtxt, &rv)); if (NS_FAILED(rv)) return rv; rv = GetHeaders(convertedData, uri); if (NS_FAILED(rv)) return rv; // build up the body while (1) { PRUint32 amtRead = 0; rv = aFromStream->Read(buffer+aBuffer.Length(), CONV_BUF_SIZE-aBuffer.Length(), &amtRead); if (NS_FAILED(rv)) return rv; if (!amtRead) { // EOF break; } aBuffer = DigestBufferLines(buffer, convertedData); } // end body building #ifndef DEBUG_valeski PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() sending the following %d bytes...\n\n%s\n\n", convertedData.Length(), convertedData.get()) ); #else char *unescData = ToNewCString(convertedData); nsUnescape(unescData); printf("::OnData() sending the following %d bytes...\n\n%s\n\n", convertedData.Length(), unescData); nsMemory::Free(unescData); #endif // DEBUG_valeski // send the converted data out. return NS_NewCStringInputStream(_retval, convertedData); } // Stream converter service calls this to initialize the actual stream converter (us). NS_IMETHODIMP nsFTPDirListingConv::AsyncConvertData(const PRUnichar *aFromType, const PRUnichar *aToType, nsIStreamListener *aListener, nsISupports *aCtxt) { NS_ASSERTION(aListener && aFromType && aToType, "null pointer passed into FTP dir listing converter"); nsresult rv; // hook up our final listener. this guy gets the various On*() calls we want to throw // at him. mFinalListener = aListener; NS_ADDREF(mFinalListener); // set our internal state to reflect the server type nsCString fromMIMEString; mServerType = DetermineServerType(fromMIMEString, aFromType); if (mServerType == ERROR_TYPE) return NS_ERROR_FAILURE; // we need our own channel that represents the content-type of the // converted data. NS_ASSERTION(aCtxt, "FTP dir listing needs a context (the uri)"); nsIURI *uri; rv = aCtxt->QueryInterface(NS_GET_IID(nsIURI), (void**)&uri); if (NS_FAILED(rv)) return rv; rv = NS_NewInputStreamChannel(&mPartChannel, uri, nsnull, NS_LITERAL_CSTRING(APPLICATION_HTTP_INDEX_FORMAT), NS_LITERAL_CSTRING(""), -1); // XXX fix contentLength NS_RELEASE(uri); if (NS_FAILED(rv)) return rv; PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("nsFTPDirListingConv::AsyncConvertData() converting FROM raw %s, TO application/http-index-format\n", fromMIMEString.get())); return NS_OK; } // nsIStreamListener implementation NS_IMETHODIMP nsFTPDirListingConv::OnDataAvailable(nsIRequest* request, nsISupports *ctxt, nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count) { NS_ASSERTION(request, "FTP dir listing stream converter needs a request"); nsresult rv; nsCOMPtr channel = do_QueryInterface(request, &rv); if (NS_FAILED(rv)) return rv; PRUint32 read, streamLen; rv = inStr->Available(&streamLen); if (NS_FAILED(rv)) return rv; char *buffer = (char*)nsMemory::Alloc(streamLen + 1); rv = inStr->Read(buffer, streamLen, &read); if (NS_FAILED(rv)) return rv; // the dir listings are ascii text, null terminate this sucker. buffer[streamLen] = '\0'; PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("nsFTPDirListingConv::OnData(request = %x, ctxt = %x, inStr = %x, sourceOffset = %d, count = %d)\n", request, ctxt, inStr, sourceOffset, count)); if (!mBuffer.IsEmpty()) { // we have data left over from a previous OnDataAvailable() call. // combine the buffers so we don't lose any data. mBuffer.Append(buffer); nsMemory::Free(buffer); buffer = ToNewCString(mBuffer); mBuffer.Truncate(); } #ifndef DEBUG_valeski PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() received the following %d bytes...\n\n%s\n\n", streamLen, buffer) ); #else printf("::OnData() received the following %d bytes...\n\n%s\n\n", streamLen, buffer); #endif // DEBUG_valeski nsCString indexFormat; if (!mSentHeading) { // build up the 300: line nsCOMPtr uri; rv = channel->GetURI(getter_AddRefs(uri)); if (NS_FAILED(rv)) return rv; rv = GetHeaders(indexFormat, uri); if (NS_FAILED(rv)) return rv; mSentHeading = PR_TRUE; } char *line = buffer; line = DigestBufferLines(line, indexFormat); #ifndef DEBUG_valeski PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() sending the following %d bytes...\n\n%s\n\n", indexFormat.Length(), indexFormat.get()) ); #else char *unescData = ToNewCString(indexFormat); nsUnescape(unescData); printf("::OnData() sending the following %d bytes...\n\n%s\n\n", indexFormat.Length(), unescData); nsMemory::Free(unescData); #endif // DEBUG_valeski // if there's any data left over, buffer it. if (line && *line) { mBuffer.Append(line); PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("::OnData() buffering the following %d bytes...\n\n%s\n\n", PL_strlen(line), line) ); } nsMemory::Free(buffer); // send the converted data out. nsCOMPtr inputData; rv = NS_NewCStringInputStream(getter_AddRefs(inputData), indexFormat); if (NS_FAILED(rv)) return rv; rv = mFinalListener->OnDataAvailable(mPartChannel, ctxt, inputData, 0, indexFormat.Length()); if (NS_FAILED(rv)) return rv; return NS_OK; } // nsIRequestObserver implementation NS_IMETHODIMP nsFTPDirListingConv::OnStartRequest(nsIRequest* request, nsISupports *ctxt) { // we don't care about start. move along... but start masqeurading // as the http-index channel now. return mFinalListener->OnStartRequest(mPartChannel, ctxt); } NS_IMETHODIMP nsFTPDirListingConv::OnStopRequest(nsIRequest* request, nsISupports *ctxt, nsresult aStatus) { // we don't care about stop. move along... nsresult rv; nsCOMPtr channel = do_QueryInterface(request, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr loadgroup; rv = channel->GetLoadGroup(getter_AddRefs(loadgroup)); if (NS_FAILED(rv)) return rv; if (loadgroup) (void)loadgroup->RemoveRequest(mPartChannel, nsnull, aStatus); return mFinalListener->OnStopRequest(mPartChannel, ctxt, aStatus); } // nsFTPDirListingConv methods nsFTPDirListingConv::nsFTPDirListingConv() { NS_INIT_ISUPPORTS(); mFinalListener = nsnull; mServerType = GENERIC; mPartChannel = nsnull; mSentHeading = PR_FALSE; } nsFTPDirListingConv::~nsFTPDirListingConv() { NS_IF_RELEASE(mFinalListener); NS_IF_RELEASE(mPartChannel); } nsresult nsFTPDirListingConv::Init() { #if defined(PR_LOGGING) // // Initialize the global PRLogModule for FTP Protocol logging // if necessary... // if (nsnull == gFTPDirListConvLog) { gFTPDirListConvLog = PR_NewLogModule("nsFTPDirListingConv"); } #endif /* PR_LOGGING */ return NS_OK; } nsresult nsFTPDirListingConv::GetHeaders(nsACString& headers, nsIURI* uri) { nsresult rv = NS_OK; // build up 300 line headers.Append("300: "); // Bug 111117 - don't print the password nsCAutoString pw; nsCAutoString spec; uri->GetPassword(pw); if (!pw.IsEmpty()) { rv = uri->SetPassword(NS_LITERAL_CSTRING("")); if (NS_FAILED(rv)) return rv; rv = uri->GetAsciiSpec(spec); if (NS_FAILED(rv)) return rv; headers.Append(spec); rv = uri->SetPassword(pw); if (NS_FAILED(rv)) return rv; } else { rv = uri->GetAsciiSpec(spec); if (NS_FAILED(rv)) return rv; headers.Append(spec); } headers.Append(char(nsCRT::LF)); // END 300: // build up the column heading; 200: headers.Append("200: filename content-length last-modified file-type\n"); // END 200: return rv; } PRInt8 nsFTPDirListingConv::MonthNumber(const char *month) { NS_ASSERTION(month && month[1] && month[2], "bad month"); if (!month || !month[0] || !month[1] || !month[2]) return -1; char c1 = month[1], c2 = month[2]; PRInt8 rv = -1; //PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("nsFTPDirListingConv::MonthNumber(month = %s) ", month) ); switch (*month) { case 'f': case 'F': rv = 1; break; case 'm': case 'M': // c1 == 'a' || c1 == 'A' if (c2 == 'r' || c2 == 'R') rv = 2; else // c2 == 'y' || c2 == 'Y' rv = 4; break; case 'a': case 'A': if (c1 == 'p' || c1 == 'P') rv = 3; else // c1 == 'u' || c1 == 'U' rv = 7; break; case 'j': case 'J': if (c1 == 'u' || c1 == 'U') { if (c2 == 'n' || c2 == 'N') rv = 5; else // c2 == 'l' || c2 == 'L' rv = 6; } else { // c1 == 'a' || c1 == 'A' rv = 0; } break; case 's': case 'S': rv = 8; break; case 'o': case 'O': rv = 9; break; case 'n': case 'N': rv = 10; break; case 'd': case 'D': rv = 11; break; default: rv = -1; } //PR_LOG(gFTPDirListConvLog, PR_LOG_DEBUG, ("returning %d\n", rv) ); return rv; } // Return true if the string is of the form: // "Sep 1 1990 " or // "Sep 11 11:59 " or // "Dec 12 1989 " or // "FCv 23 1990 " ... PRBool nsFTPDirListingConv::IsLSDate(char *aCStr) { /* must start with three alpha characters */ if (!nsCRT::IsAsciiAlpha(*aCStr++) || !nsCRT::IsAsciiAlpha(*aCStr++) || !nsCRT::IsAsciiAlpha(*aCStr++)) return PR_FALSE; /* space */ if (*aCStr != ' ') return PR_FALSE; aCStr++; /* space or digit */ if ((*aCStr != ' ') && !nsCRT::IsAsciiDigit(*aCStr)) return PR_FALSE; aCStr++; /* digit */ if (!nsCRT::IsAsciiDigit(*aCStr)) return PR_FALSE; aCStr++; /* space */ if (*aCStr != ' ') return PR_FALSE; aCStr++; /* space or digit */ if ((*aCStr != ' ') && !nsCRT::IsAsciiDigit(*aCStr)) return PR_FALSE; aCStr++; /* digit */ if (!nsCRT::IsAsciiDigit(*aCStr)) return PR_FALSE; aCStr++; /* colon or digit */ if ((*aCStr != ':') && !nsCRT::IsAsciiDigit(*aCStr)) return PR_FALSE; aCStr++; /* digit */ if (!nsCRT::IsAsciiDigit(*aCStr)) return PR_FALSE; aCStr++; /* space or digit */ if ((*aCStr != ' ') && !nsCRT::IsAsciiDigit(*aCStr)) return PR_FALSE; aCStr++; /* space */ if (*aCStr != ' ') return PR_FALSE; aCStr++; return PR_TRUE; } // Converts a date string from 'ls -l' to a PRTime number // "Sep 1 1990 " or // "Sep 11 11:59 " or // "Dec 12 1989 " or // "FCv 23 1990 " ... // Returns 0 on error. PRBool nsFTPDirListingConv::ConvertUNIXDate(char *aCStr, PRExplodedTime& outDate) { PRExplodedTime curTime; InitPRExplodedTime(curTime); char *bcol = aCStr; /* Column begin */ char *ecol; /* Column end */ // MONTH char tmpChar = bcol[3]; bcol[3] = '\0'; if ((curTime.tm_month = MonthNumber(bcol)) < 0) return PR_FALSE; bcol[3] = tmpChar; // DAY ecol = &bcol[3]; while (*(++ecol) == ' ') ; while (*(++ecol) != ' ') ; *ecol = '\0'; bcol = ecol+1; while (*(--ecol) != ' ') ; PRInt32 error; nsCAutoString day(ecol); curTime.tm_mday = day.ToInteger(&error, 10); // YEAR if ((ecol = PL_strchr(bcol, ':')) == NULL) { nsCAutoString intStr(bcol); curTime.tm_year = intStr.ToInteger(&error, 10); } else { // TIME /* If the time is given as hh:mm, then the file is less than 1 year * old, but we might shift calandar year. This is avoided by checking * if the date parsed is future or not. */ *ecol = '\0'; nsCAutoString intStr(++ecol); curTime.tm_min = intStr.ToInteger(&error, 10); // Right side of ':' intStr = bcol; curTime.tm_hour = intStr.ToInteger(&error, 10); // Left side of ':' PRExplodedTime nowETime; PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &nowETime); curTime.tm_year = nowETime.tm_year; PRBool thisCalendarYear = PR_FALSE; if (nowETime.tm_month > curTime.tm_month) { thisCalendarYear = PR_TRUE; } else if (nowETime.tm_month == curTime.tm_month && nowETime.tm_mday > curTime.tm_mday) { thisCalendarYear = PR_TRUE; } else if (nowETime.tm_month == curTime.tm_month && nowETime.tm_mday == curTime.tm_mday && nowETime.tm_hour > curTime.tm_hour) { thisCalendarYear = PR_TRUE; } else if (nowETime.tm_month == curTime.tm_month && nowETime.tm_mday == curTime.tm_mday && nowETime.tm_hour == curTime.tm_hour && nowETime.tm_min >= curTime.tm_min) { thisCalendarYear = PR_TRUE; } if (!thisCalendarYear) curTime.tm_year--; } // set the out param outDate = curTime; return PR_TRUE; } PRBool nsFTPDirListingConv::ConvertDOSDate(char *aCStr, PRExplodedTime& outDate) { PRExplodedTime curTime, nowTime; PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &nowTime); PRInt16 currCentury = nowTime.tm_year / 100; InitPRExplodedTime(curTime); curTime.tm_month = ((aCStr[0]-'0')*10 + (aCStr[1]-'0')) - 1; curTime.tm_mday = (((aCStr[3]-'0')*10) + aCStr[4]-'0'); curTime.tm_year = currCentury*100 + ((aCStr[6]-'0')*10) + aCStr[7]-'0'; // year is only 2-digit, so we've converted. If its in the future, // it must have been last century if (curTime.tm_year > nowTime.tm_year) { curTime.tm_year -= 100; } curTime.tm_hour = (((aCStr[10]-'0')*10) + aCStr[11]-'0'); if (aCStr[15] == 'P') curTime.tm_hour += 12; curTime.tm_min = (((aCStr[13]-'0')*10) + aCStr[14]-'0'); outDate = curTime; return PR_TRUE; } nsresult nsFTPDirListingConv::ParseLSLine(char *aLine, indexEntry *aEntry) { PRInt32 base=1; PRInt32 size_num=0; char save_char; char *ptr, *escName; // insure that we have enough data to parse here // using 27 for a minimal LIST line if (PL_strlen(aLine) <= 27) { NS_WARNING("ls -l is incorrectly formatted"); aEntry->mName.Adopt(nsEscape(aLine, url_Path)); // initialize the time struct to 0 InitPRExplodedTime(aEntry->mMDTM); return NS_OK; } for (ptr = &aLine[PL_strlen(aLine) - 1]; (ptr > aLine+13) && (!nsCRT::IsAsciiSpace(*ptr) || !IsLSDate(ptr-12)); ptr--) ; /* null body */ save_char = *ptr; *ptr = '\0'; if (ptr > aLine+13) { ConvertUNIXDate(ptr-12, aEntry->mMDTM); } else { // must be a dl listing // unterminate the line *ptr = save_char; // find the first whitespace and terminate for(ptr=aLine; *ptr != '\0'; ptr++) if (nsCRT::IsAsciiSpace(*ptr)) { *ptr = '\0'; break; } escName = nsEscape(aLine, url_Path); aEntry->mName.Adopt(escName); // initialize the time struct to 0 to be safe InitPRExplodedTime(aEntry->mMDTM); return NS_OK; } escName = nsEscape(ptr+1, url_Path); aEntry->mName.Adopt(escName); // parse size if (ptr > aLine+15) { ptr -= 14; while (nsCRT::IsAsciiDigit(*ptr)) { size_num += ((PRInt32) (*ptr - '0')) * base; base *= 10; ptr--; } aEntry->mContentLen = size_num; } return NS_OK; } void nsFTPDirListingConv::InitPRExplodedTime(PRExplodedTime& aTime) { aTime.tm_usec = 0; aTime.tm_sec = 0; aTime.tm_min = 0; aTime.tm_hour = 0; aTime.tm_mday = 0; aTime.tm_month= 0; aTime.tm_year = 0; aTime.tm_wday = 0; aTime.tm_yday = 0; aTime.tm_params.tp_gmt_offset = 0; aTime.tm_params.tp_dst_offset = 0; } /** * Can this line possibly be a ls-l line? * Ideally, one should parse the line into tokens, etc. but * that's for later. At the moment, just test the line for * length and examine the first token. An absolutely minimal * line has the form * -rwxrwxrwx 0 Dec 31 23:59 F * which is 27 characters. If the line is at least 27 bytes * long and the first token matches the regular expression * [-dlbcps][r-][w-][sSx-][r-][w-][sSx-][r-][w-][tTx-] * then accept the line *provided* there is another token on * the line. * As written, this function *assumes* leading whitespace has * been removed. That's what the rest of the code assumes. It * may not be a reasonable assumption. * DigestBufferLines hints that the regular expression test should * be done case insensitively. That would relax this test so it is * *not* done. If definitive evidence exists of a server that does * this, then it can be changed. * This function uses a lot of macros for clarity. They probably * should be improved. */ PRBool nsFTPDirListingConv::ls_lCandidate(const char *lsLine) { const char *cp; if (PL_strlen(lsLine) < 27) return PR_FALSE; if (!IS_FTYPE(lsLine[0])) return PR_FALSE; // shorter tests first if (!IS_RPERM(lsLine[1])) return PR_FALSE; if (!IS_WPERM(lsLine[2])) return PR_FALSE; if (!IS_RPERM(lsLine[4])) return PR_FALSE; if (!IS_WPERM(lsLine[5])) return PR_FALSE; if (!IS_RPERM(lsLine[7])) return PR_FALSE; if (!IS_WPERM(lsLine[8])) return PR_FALSE; if (!IS_SPERM(lsLine[3])) return PR_FALSE; if (!IS_SPERM(lsLine[6])) return PR_FALSE; for (cp = &lsLine[10]; *cp; ++cp) if (!IS_LWS(*cp)) return PR_TRUE; return PR_FALSE; } char * nsFTPDirListingConv::DigestBufferLines(char *aBuffer, nsCString &aString) { nsresult rv; char *line = aBuffer; char *eol; PRBool cr = PR_FALSE; // while we have new lines, parse 'em into application/http-index-format. while ( line && (eol = PL_strchr(line, nsCRT::LF)) ) { // yank any carriage returns too. if (eol > line && *(eol-1) == nsCRT::CR) { eol--; *eol = '\0'; cr = PR_TRUE; } else { *eol = '\0'; cr = PR_FALSE; } indexEntry *thisEntry = nsnull; NS_NEWXPCOM(thisEntry, indexEntry); if (!thisEntry) return nsnull; // XXX we need to handle comments in the raw stream. // special case windows servers who masquerade as unix servers if (NT == mServerType && !nsCRT::IsAsciiSpace(line[8])) mServerType = UNIX; // check for an eplf response if (line[0] == '+') mServerType = EPLF; char *escName = nsnull; switch (mServerType) { case UNIX: case PETER_LEWIS: case MACHTEN: { // don't bother w/ these lines. if (!PL_strncmp(line, "total ", 6) || !PL_strncmp(line, "ls: total", 9) || (PL_strstr(line, "Permission denied") != NULL) || (PL_strstr(line, "not available") != NULL)) { NS_DELETEXPCOM(thisEntry); if (cr) line = eol+2; else line = eol+1; continue; } PRInt32 len = PL_strlen(line); // check first character of ls -l output // For example: "dr-x--x--x" is what we're starting with. // sanity check for dir permission bits if ((line[0] == 'D' || line[0] == 'd') && ls_lCandidate(line)) { /* it's a directory */ thisEntry->mType = Dir; thisEntry->mSupressSize = PR_TRUE; } else if ((line[0] == 'L' || line[0] == 'l') && ls_lCandidate(line)) { /** * Dir Links are not displayed properly * we need a more robust implementation */ thisEntry->mType = Link; thisEntry->mSupressSize = PR_TRUE; /* strip off " -> pathname" */ PRInt32 i; for (i = len - 1; (i > 3) && (!nsCRT::IsAsciiSpace(line[i]) || (line[i-1] != '>') || (line[i-2] != '-') || (line[i-3] != ' ')); i--) ; /* null body */ if (i > 3) { line[i-3] = '\0'; len = i - 3; } } rv = ParseLSLine(line, thisEntry); if ( NS_FAILED(rv) || (thisEntry->mName.Equals("..")) || (thisEntry->mName.Equals(".")) ) { NS_DELETEXPCOM(thisEntry); if (cr) line = eol+2; else line = eol+1; continue; } break; // END UNIX, PETER_LEWIS, MACHTEN } case NCSA: case TCPC: { escName = nsEscape(line, url_Path); thisEntry->mName.Adopt(escName); if (thisEntry->mName.Last() == '/') { thisEntry->mType = Dir; thisEntry->mName.Truncate(thisEntry->mName.Length()-1); } break; // END NCSA, TCPC } case CMS: { escName = nsEscape(line, url_Path); thisEntry->mName.Adopt(escName); break; // END CMS } case NT: { // don't bother w/ these lines. if (!PL_strncmp(line, "total ", 6) || !PL_strncmp(line, "ls: total", 9) || (PL_strstr(line, "Permission denied") != NULL) || (PL_strstr(line, "not available") != NULL)) { NS_DELETEXPCOM(thisEntry); if (cr) line = eol+2; else line = eol+1; continue; } char *date, *size_s, *name; if (PL_strlen(line) > 37) { date = line; line[17] = '\0'; size_s = &line[18]; line[38] = '\0'; name = &line[39]; if (PL_strstr(size_s, "")) { thisEntry->mType = Dir; } else { nsCAutoString size(size_s); size.StripWhitespace(); thisEntry->mContentLen = atol(size.get()); } ConvertDOSDate(date, thisEntry->mMDTM); escName = nsEscape(name, url_Path); thisEntry->mName.Adopt(escName); } else { escName = nsEscape(line, url_Path); thisEntry->mName.Adopt(escName); } break; // END NT } case EPLF: { int flagcwd = 0; int when = 0; int flagsize = 0; unsigned long size = 0; PRBool processing = PR_TRUE; while (*line && processing) switch (*line) { case '\t': { if (flagcwd) { thisEntry->mType = Dir; thisEntry->mContentLen = 0; } else { thisEntry->mType = File; thisEntry->mContentLen = size; } escName = nsEscape(line+1, url_Path); thisEntry->mName.Adopt(escName); thisEntry->mSupressSize = !flagsize; // Mutiply what the last modification date to get usecs. PRInt64 usecs = LL_Zero(); PRInt64 seconds = LL_Zero(); PRInt64 multiplier = LL_Zero(); LL_I2L(seconds, when); LL_I2L(multiplier, PR_USEC_PER_SEC); LL_MUL(usecs, seconds, multiplier); PR_ExplodeTime(usecs, PR_LocalTimeParameters, &thisEntry->mMDTM); processing = PR_FALSE; } break; case 's': flagsize = 1; size = 0; while (*++line && nsCRT::IsAsciiDigit(*line)) size = size * 10 + (*line - '0'); break; case 'm': while (*++line && nsCRT::IsAsciiDigit(*line)) when = when * 10 + (*line - '0'); break; case '/': flagcwd = 1; default: while (*line) if (*line++ == ',') break; } break; //END EPLF } case OS_2: { if (!PL_strncmp(line, "total ", 6) || (PL_strstr(line, "not authorized") != NULL) || (PL_strstr(line, "Path not found") != NULL) || (PL_strstr(line, "No Files") != NULL)) { NS_DELETEXPCOM(thisEntry); if (cr) line = eol+2; else line = eol+1; continue; } char *name; nsCAutoString str; if (PL_strstr(line, "DIR")) { thisEntry->mType = Dir; thisEntry->mSupressSize = PR_TRUE; } else thisEntry->mType = File; PRInt32 error; line[18] = '\0'; str = line; str.StripWhitespace(); thisEntry->mContentLen = str.ToInteger(&error, 10); InitPRExplodedTime(thisEntry->mMDTM); line[37] = '\0'; str = &line[35]; thisEntry->mMDTM.tm_month = str.ToInteger(&error, 10) - 1; line[40] = '\0'; str = &line[38]; thisEntry->mMDTM.tm_mday = str.ToInteger(&error, 10); line[43] = '\0'; str = &line[41]; thisEntry->mMDTM.tm_year = str.ToInteger(&error, 10); line[48] = '\0'; str = &line[46]; thisEntry->mMDTM.tm_hour = str.ToInteger(&error, 10); line[51] = '\0'; str = &line[49]; thisEntry->mMDTM.tm_min = str.ToInteger(&error, 10); name = &line[53]; escName = nsEscape(name, url_Path); thisEntry->mName.Adopt(escName); break; } default: { escName = nsEscape(line, url_Path); thisEntry->mName.Adopt(escName); break; // END default (catches GENERIC, DCTS) } } // end switch (mServerType) // blast the index entry into the indexFormat buffer as a 201: line. aString.Append("201: "); // FILENAME aString.Append(thisEntry->mName); if (thisEntry->mType == Dir) aString.Append('/'); aString.Append(' '); // CONTENT LENGTH if (!thisEntry->mSupressSize) { aString.AppendInt(thisEntry->mContentLen); } else { aString.Append('0'); } aString.Append(' '); // MODIFIED DATE char buffer[256] = ""; // Note: The below is the RFC822/1123 format, as required by // the application/http-index-format specs // viewers of such a format can then reformat this into the // current locale (or anything else they choose) // make sure we don't have a null time struct if ((thisEntry->mMDTM.tm_month + thisEntry->mMDTM.tm_mday + thisEntry->mMDTM.tm_year + thisEntry->mMDTM.tm_hour + thisEntry->mMDTM.tm_min) != nsnull) { PR_FormatTimeUSEnglish(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S", &thisEntry->mMDTM ); } char *escapedDate = nsEscape(buffer, url_Path); aString.Append(escapedDate); nsMemory::Free(escapedDate); aString.Append(' '); // ENTRY TYPE switch (thisEntry->mType) { case Dir: aString.Append("DIRECTORY"); break; case Link: aString.Append("SYMBOLIC-LINK"); break; default: aString.Append("FILE"); } aString.Append(' '); aString.Append(char(nsCRT::LF)); // complete this line // END 201: NS_DELETEXPCOM(thisEntry); if (cr) line = eol+2; else line = eol+1; } // end while(eol) return line; } nsresult NS_NewFTPDirListingConv(nsFTPDirListingConv** aFTPDirListingConv) { NS_PRECONDITION(aFTPDirListingConv != nsnull, "null ptr"); if (! aFTPDirListingConv) return NS_ERROR_NULL_POINTER; *aFTPDirListingConv = new nsFTPDirListingConv(); if (! *aFTPDirListingConv) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*aFTPDirListingConv); return (*aFTPDirListingConv)->Init(); }