/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** 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) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * 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 "nsUnknownDecoder.h" #include "nsIServiceManager.h" #include "nsIStreamConverterService.h" #include "nsIPipe.h" #include "nsIInputStream.h" #include "nsIOutputStream.h" #include "nsMimeTypes.h" #include "netCore.h" #include "nsXPIDLString.h" #include "nsIPref.h" #define MAX_BUFFER_SIZE 1024 static NS_DEFINE_CID(kStreamConverterServiceCID, NS_STREAMCONVERTERSERVICE_CID); static NS_DEFINE_IID(kPrefServiceCID, NS_PREF_CID); nsUnknownDecoder::nsUnknownDecoder() { NS_INIT_ISUPPORTS(); mBuffer = nsnull; mBufferLen = 0; mRequireHTMLsuffix = PR_FALSE; nsresult rv; nsCOMPtr pPrefService = do_GetService(kPrefServiceCID, &rv); if (NS_SUCCEEDED(rv)) { rv = pPrefService->GetBoolPref("security.requireHTMLsuffix", &mRequireHTMLsuffix); } } nsUnknownDecoder::~nsUnknownDecoder() { if (mBuffer) { delete [] mBuffer; mBuffer = nsnull; } } // ---- // // nsISupports implementation... // // ---- NS_IMPL_ADDREF(nsUnknownDecoder); NS_IMPL_RELEASE(nsUnknownDecoder); NS_INTERFACE_MAP_BEGIN(nsUnknownDecoder) NS_INTERFACE_MAP_ENTRY(nsIStreamConverter) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END // ---- // // nsIStreamConverter methods... // // ---- NS_IMETHODIMP nsUnknownDecoder::Convert(nsIInputStream *aFromStream, const PRUnichar *aFromType, const PRUnichar *aToType, nsISupports *aCtxt, nsIInputStream **aResultStream) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsUnknownDecoder::AsyncConvertData(const PRUnichar *aFromType, const PRUnichar *aToType, nsIStreamListener *aListener, nsISupports *aCtxt) { NS_ASSERTION(aListener && aFromType && aToType, "null pointer passed into multi mixed converter"); // hook up our final listener. this guy gets the various On*() calls we want to throw // at him. // mNextListener = aListener; return (aListener) ? NS_OK : NS_ERROR_FAILURE; } // ---- // // nsIStreamListener methods... // // ---- NS_IMETHODIMP nsUnknownDecoder::OnDataAvailable(nsIRequest* request, nsISupports *aCtxt, nsIInputStream *aStream, PRUint32 aSourceOffset, PRUint32 aCount) { nsresult rv = NS_OK; if (!mNextListener) return NS_ERROR_FAILURE; if (mContentType.IsEmpty()) { PRUint32 count, len; // If the buffer has not been allocated by now, just fail... if (!mBuffer) return NS_ERROR_OUT_OF_MEMORY; // // Determine how much of the stream should be read to fill up the // sniffer buffer... // if (mBufferLen + aCount >= MAX_BUFFER_SIZE) { count = MAX_BUFFER_SIZE-mBufferLen; } else { count = aCount; } // Read the data into the buffer... rv = aStream->Read((mBuffer+mBufferLen), count, &len); if (NS_FAILED(rv)) return rv; mBufferLen += len; aCount -= len; if (aCount) { // // Adjust the source offset... The call to FireListenerNotifications(...) // will make the first OnDataAvailable(...) call with an offset of 0. // So, this offset needs to be adjusted to reflect that... // aSourceOffset += mBufferLen; DetermineContentType(request); NS_ASSERTION(!mContentType.IsEmpty(), "Content type should be known by now."); rv = FireListenerNotifications(request, aCtxt); } } if (aCount) { NS_ASSERTION(!mContentType.IsEmpty(), "Content type should be known by now."); rv = mNextListener->OnDataAvailable(request, aCtxt, aStream, aSourceOffset, aCount); } return rv; } // ---- // // nsIRequestObserver methods... // // ---- NS_IMETHODIMP nsUnknownDecoder::OnStartRequest(nsIRequest* request, nsISupports *aCtxt) { nsresult rv = NS_OK; if (!mNextListener) return NS_ERROR_FAILURE; // Allocate the sniffer buffer... if (NS_SUCCEEDED(rv) && !mBuffer) { mBuffer = new char[MAX_BUFFER_SIZE]; if (!mBuffer) { rv = NS_ERROR_OUT_OF_MEMORY; } } // Do not pass the OnStartRequest on to the next listener (yet)... return rv; } NS_IMETHODIMP nsUnknownDecoder::OnStopRequest(nsIRequest* request, nsISupports *aCtxt, nsresult aStatus) { nsresult rv = NS_OK; if (!mNextListener) return NS_ERROR_FAILURE; // // The total amount of data is less than the size of the sniffer buffer. // Analyze the buffer now... // if (mContentType.IsEmpty()) { DetermineContentType(request); NS_ASSERTION(!mContentType.IsEmpty(), "Content type should be known by now."); rv = FireListenerNotifications(request, aCtxt); if (NS_FAILED(rv)) { aStatus = rv; } } rv = mNextListener->OnStopRequest(request, aCtxt, aStatus); mNextListener = 0; return rv; } void nsUnknownDecoder::DetermineContentType(nsIRequest* request) { PRUint32 i; NS_ASSERTION(mContentType.IsEmpty(), "Content type is already known."); if (!mContentType.IsEmpty()) return; CBufDescriptor bufDesc((const char*)mBuffer, PR_TRUE, mBufferLen, mBufferLen); nsCAutoString str(bufDesc); // // If the buffer begins with "#!" or "%!" then it is a script of some // sort... // // This false match happened all the time... For example, CGI scripts // written in sh or perl that emit HTML. // if (str.EqualsWithConversion("#!", PR_FALSE, 2) || str.EqualsWithConversion("%!", PR_FALSE, 2)) { mContentType = TEXT_PLAIN; } // // If the buffer begins with a mailbox delimiter then it is not HTML // else if (str.EqualsWithConversion("From ", PR_TRUE, 5) || str.EqualsWithConversion(">From ", PR_TRUE, 6)) { mContentType = TEXT_PLAIN; } // // If the buffer contains "common" HTML tags then lets call it HTML :-) // else { /* * To prevent a possible attack, we will not consider this to be html * content if it comes from the local file system */ PRBool isLocalFile = PR_FALSE; if (request) { nsCOMPtr aChannel = do_QueryInterface(request); if (!request) { NS_WARNING("QI failed"); return; } nsCOMPtr pURL; if (NS_SUCCEEDED(aChannel->GetURI(getter_AddRefs(pURL)))) pURL->SchemeIs("file", &isLocalFile); } if (!mRequireHTMLsuffix || !isLocalFile) { PRInt32 offset; offset = str.Find("= 0) { mContentType = TEXT_HTML; } } } if (mContentType.IsEmpty()) { // sniff the first couple bytes to see if we are dealing with an image // Unlike html, it is safe to sniff for image content types for local files so // we don't need to check for isLocalFile here.. SniffForImageMimeType((const char*)mBuffer, mBufferLen); } // // See if the buffer has any embedded nulls. If not, then lets just // call it text/plain... // if (mContentType.IsEmpty()) { for (i=0; i= 4 && !nsCRT::strncmp(buf, "GIF8", 4)) { mContentType = NS_LITERAL_CSTRING("image/gif"); return; } /* or a PNG? */ if (len >= 4 && ((unsigned char)buf[0]==0x89 && (unsigned char)buf[1]==0x50 && (unsigned char)buf[2]==0x4E && (unsigned char)buf[3]==0x47)) { mContentType = NS_LITERAL_CSTRING("image/png"); return; } /* maybe a JPEG (JFIF)? */ /* JFIF files start with SOI APP0 but older files can start with SOI DQT * so we test for SOI followed by any marker, i.e. FF D8 FF * this will also work for SPIFF JPEG files if they appear in the future. * * (JFIF is 0XFF 0XD8 0XFF 0XE0 0X4A 0X46 0X49 0X46 0X00) */ if (len >= 3 && ((unsigned char)buf[0])==0xFF && ((unsigned char)buf[1])==0xD8 && ((unsigned char)buf[2])==0xFF) { mContentType = NS_LITERAL_CSTRING("image/jpeg"); return; } /* or how about ART? */ /* ART begins with JG (4A 47). Major version offset 2. * Minor version offset 3. Offset 4 must be NULL. */ if (len >= 5 && ((unsigned char) buf[0])==0x4a && ((unsigned char) buf[1])==0x47 && ((unsigned char) buf[4])==0x00 ) { mContentType = NS_LITERAL_CSTRING("image/x-jg"); return; } /* none of the above? I give up */ } nsresult nsUnknownDecoder::FireListenerNotifications(nsIRequest* request, nsISupports *aCtxt) { nsresult rv = NS_OK; if (!mNextListener) return NS_ERROR_FAILURE; if (!mBuffer) return NS_ERROR_OUT_OF_MEMORY; nsCOMPtr channel = do_QueryInterface(request, &rv); if (NS_FAILED(rv)) return rv; // Set the new content type on the channel... rv = channel->SetContentType(mContentType); NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to set content type on channel!"); if (NS_FAILED(rv)) return rv; // Fire the OnStartRequest(...) rv = mNextListener->OnStartRequest(request, aCtxt); // Fire the first OnDataAvailable for the data that was read from the // stream into the sniffer buffer... if (NS_SUCCEEDED(rv) && (mBufferLen > 0)) { PRUint32 len = 0; nsCOMPtr in; nsCOMPtr out; // Create a pipe and fill it with the data from the sniffer buffer. rv = NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), MAX_BUFFER_SIZE, MAX_BUFFER_SIZE); if (NS_SUCCEEDED(rv)) { rv = out->Write(mBuffer, mBufferLen, &len); if (NS_SUCCEEDED(rv)) { if (len == mBufferLen) { rv = mNextListener->OnDataAvailable(request, aCtxt, in, 0, len); } else { NS_ASSERTION(0, "Unable to write all the data into the pipe."); rv = NS_ERROR_FAILURE; } } } } delete [] mBuffer; mBuffer = nsnull; mBufferLen = 0; return rv; }