Files
Mozilla/mozilla/netwerk/protocol/gopher/src/nsGopherChannel.cpp
dbaron%dbaron.org 855a1b80fa Bug 178643: Remove uses of NS_INIT_ISUPPORTS, since it is no longer needed. r=timeless sr=jag
git-svn-id: svn://10.0.0.236/trunk@136019 18797224-902f-48f8-a5cc-f745e15eee43
2003-01-08 22:35:09 +00:00

779 lines
23 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Mozilla 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/MPL/
*
* 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 the Gopher protocol code.
*
* The Initial Developer of the Original Code is Bradley Baetz.
* Portions created by Bradley Baetz are Copyright (C) 2000 Bradley Baetz.
* All Rights Reserved.
*
* Contributor(s):
* Bradley Baetz <bbaetz@student.usyd.edu.au>
*/
// gopher implementation - based on datetime and finger implimentations
// and documentation
#include "nsGopherChannel.h"
#include "nsIServiceManager.h"
#include "nsILoadGroup.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsXPIDLString.h"
#include "nsReadableUtils.h"
#include "nsISocketTransportService.h"
#include "nsIStringStream.h"
#include "nsMimeTypes.h"
#include "nsIStreamConverterService.h"
#include "nsEscape.h"
#include "nsITXTToHTMLConv.h"
#include "nsIProgressEventSink.h"
#include "nsNetUtil.h"
#include "prlog.h"
#include "nsIPref.h"
#include "nsCRT.h"
static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
static NS_DEFINE_CID(kStreamConverterServiceCID, NS_STREAMCONVERTERSERVICE_CID);
#ifdef PR_LOGGING
extern PRLogModuleInfo* gGopherLog;
#endif
#define BUFFER_SEG_SIZE (4*1024)
#define BUFFER_MAX_SIZE (64*1024)
// nsGopherChannel methods
nsGopherChannel::nsGopherChannel()
: mContentLength(-1),
mActAsObserver(PR_TRUE),
mListFormat(FORMAT_HTML),
mType(-1),
mStatus(NS_OK)
{
}
nsGopherChannel::~nsGopherChannel()
{
#ifdef PR_LOGGING
nsCAutoString spec;
mUrl->GetAsciiSpec(spec);
PR_LOG(gGopherLog, PR_LOG_ALWAYS, ("~nsGopherChannel() for %s", spec.get()));
#endif
}
NS_IMPL_THREADSAFE_ISUPPORTS5(nsGopherChannel,
nsIChannel,
nsIRequest,
nsIStreamListener,
nsIRequestObserver,
nsIDirectoryListing);
nsresult
nsGopherChannel::Init(nsIURI* uri, nsIProxyInfo* proxyInfo)
{
nsresult rv;
nsCOMPtr<nsIURL> url = do_QueryInterface(uri, &rv);
if (NS_FAILED(rv))
return NS_ERROR_MALFORMED_URI;
mUrl = uri;
mProxyInfo = proxyInfo;
nsCAutoString buffer;
rv = url->GetPath(buffer); // unescaped down below
if (NS_FAILED(rv))
return rv;
rv = url->GetAsciiHost(mHost);
if (NS_FAILED(rv))
return rv;
rv = url->GetPort(&mPort);
if (NS_FAILED(rv))
return rv;
// For security reasons, don't allow anything expect the default
// gopher port (70). See bug 71916 - bbaetz@cs.mcgill.ca
/*
if (mPort==-1)
mPort=GOPHER_PORT;
*/
mPort=GOPHER_PORT;
// No path given
if (buffer[0]=='\0' || (buffer[0]=='/' && buffer[1]=='\0')) {
mType = '1';
mSelector.Truncate();
} else {
mType = buffer[1]; // Ignore leading '/'
// Do it this way in case selector contains embedded nulls after
// unescaping
char* selector = nsCRT::strdup(buffer.get()+2);
PRInt32 count = nsUnescapeCount(selector);
mSelector.Assign(selector,count);
nsCRT::free(selector);
if (mSelector.FindCharInSet(nsCString("\t\n\r\0",4)) != -1) {
// gopher selectors cannot containt tab, cr, lf, or \0
return NS_ERROR_MALFORMED_URI;
}
}
PR_LOG(gGopherLog,
PR_LOG_DEBUG,
("Host: mHost = %s, mPort = %d\n", mHost.get(), mPort));
PR_LOG(gGopherLog,
PR_LOG_DEBUG,
("Status: mType = %c, mSelector = %s\n", mType, mSelector.get()));
return NS_OK;
}
NS_METHOD
nsGopherChannel::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult)
{
nsGopherChannel* gc = new nsGopherChannel();
if (!gc)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(gc);
nsresult rv = gc->QueryInterface(aIID, aResult);
NS_RELEASE(gc);
return rv;
}
////////////////////////////////////////////////////////////////////////////////
// nsIRequest methods:
NS_IMETHODIMP
nsGopherChannel::GetName(nsACString &result)
{
return mUrl->GetHostPort(result);
}
NS_IMETHODIMP
nsGopherChannel::IsPending(PRBool *result)
{
NS_NOTREACHED("nsGopherChannel::IsPending");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsGopherChannel::GetStatus(nsresult *status)
{
*status = mStatus;
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::Cancel(nsresult status)
{
PR_LOG(gGopherLog,
PR_LOG_DEBUG,
("nsGopherChannel::Cancel() called [this=%x, status=%x]\n",
this,status));
NS_ASSERTION(NS_FAILED(status), "shouldn't cancel with a success code");
mStatus = status;
if (mTransportRequest) {
return mTransportRequest->Cancel(status);
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsGopherChannel::Suspend(void)
{
NS_NOTREACHED("nsGopherChannel::Suspend");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsGopherChannel::Resume(void)
{
NS_NOTREACHED("nsGopherChannel::Resume");
return NS_ERROR_NOT_IMPLEMENTED;
}
////////////////////////////////////////////////////////////////////////////////
// nsIChannel methods:
NS_IMETHODIMP
nsGopherChannel::GetOriginalURI(nsIURI* *aURI)
{
*aURI = mOriginalURI ? mOriginalURI : mUrl;
NS_ADDREF(*aURI);
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::SetOriginalURI(nsIURI* aURI)
{
mOriginalURI = aURI;
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::GetURI(nsIURI* *aURI)
{
*aURI = mUrl;
NS_IF_ADDREF(*aURI);
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::Open(nsIInputStream **_retval)
{
nsresult rv = NS_OK;
PRInt32 port;
rv = mUrl->GetPort(&port);
if (NS_FAILED(rv))
return rv;
rv = NS_CheckPortSafety(port, "gopher");
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsISocketTransportService> socketService =
do_GetService(kSocketTransportServiceCID, &rv);
if (NS_FAILED(rv)) return rv;
rv = socketService->CreateTransport(mHost,
mPort,
mProxyInfo,
BUFFER_SEG_SIZE,
BUFFER_MAX_SIZE,
getter_AddRefs(mTransport));
if (NS_FAILED(rv)) return rv;
mTransport->SetNotificationCallbacks(mCallbacks,
(mLoadFlags & LOAD_BACKGROUND));
return mTransport->OpenInputStream(0, (PRUint32)-1, 0, _retval);
}
NS_IMETHODIMP
nsGopherChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *ctxt)
{
PR_LOG(gGopherLog, PR_LOG_DEBUG, ("nsGopherChannel::AsyncOpen() called [this=%x]\n",
this));
nsresult rv;
PRInt32 port;
rv = mUrl->GetPort(&port);
if (NS_FAILED(rv))
return rv;
rv = NS_CheckPortSafety(port, "gopher");
if (NS_FAILED(rv))
return rv;
mListener = aListener;
mResponseContext = ctxt;
nsCOMPtr<nsISocketTransportService> socketService =
do_GetService(kSocketTransportServiceCID, &rv);
if (NS_FAILED(rv)) return rv;
rv = socketService->CreateTransport(mHost,
mPort,
mProxyInfo,
BUFFER_SEG_SIZE,
BUFFER_MAX_SIZE,
getter_AddRefs(mTransport));
if (NS_FAILED(rv)) return rv;
mTransport->SetNotificationCallbacks(mCallbacks,
(mLoadFlags & LOAD_BACKGROUND));
return SendRequest(mTransport);
}
NS_IMETHODIMP
nsGopherChannel::GetLoadFlags(PRUint32 *aLoadFlags)
{
*aLoadFlags = mLoadFlags;
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::SetLoadFlags(PRUint32 aLoadFlags)
{
mLoadFlags = aLoadFlags;
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::GetContentType(nsACString &aContentType)
{
if (!mContentType.IsEmpty()) {
aContentType = mContentType;
return NS_OK;
}
switch(mType) {
case '0':
aContentType = NS_LITERAL_CSTRING(TEXT_HTML);
break;
case '1':
switch (mListFormat) {
case nsIDirectoryListing::FORMAT_RAW:
aContentType = NS_LITERAL_CSTRING("text/gopher-dir");
break;
default:
NS_WARNING("Unknown directory type");
// fall through
case nsIDirectoryListing::FORMAT_HTML:
aContentType = NS_LITERAL_CSTRING(TEXT_HTML);
break;
case nsIDirectoryListing::FORMAT_HTTP_INDEX:
aContentType = NS_LITERAL_CSTRING(APPLICATION_HTTP_INDEX_FORMAT);
break;
}
break;
case '2': // CSO search - unhandled, should not be selectable
aContentType = NS_LITERAL_CSTRING(TEXT_HTML);
break;
case '3': // "Error" - should not be selectable
aContentType = NS_LITERAL_CSTRING(TEXT_HTML);
break;
case '4': // "BinHexed Macintosh file"
aContentType = NS_LITERAL_CSTRING(APPLICATION_BINHEX);
break;
case '5':
// "DOS binary archive of some sort" - is the mime-type correct?
aContentType = NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM);
break;
case '6':
aContentType = NS_LITERAL_CSTRING(APPLICATION_UUENCODE);
break;
case '7': // search - returns a directory listing
aContentType = NS_LITERAL_CSTRING(APPLICATION_HTTP_INDEX_FORMAT);
break;
case '8': // telnet - type doesn't make sense
aContentType = NS_LITERAL_CSTRING(TEXT_PLAIN);
break;
case '9': // "Binary file!"
aContentType = NS_LITERAL_CSTRING(APPLICATION_OCTET_STREAM);
break;
case 'g':
aContentType = NS_LITERAL_CSTRING(IMAGE_GIF);
break;
case 'i': // info line- should not be selectable
aContentType = NS_LITERAL_CSTRING(TEXT_HTML);
break;
case 'I':
aContentType = NS_LITERAL_CSTRING(IMAGE_GIF);
break;
case 'T': // tn3270 - type doesn't make sense
aContentType = NS_LITERAL_CSTRING(TEXT_PLAIN);
break;
default:
NS_WARNING("Unknown gopher type");
aContentType = NS_LITERAL_CSTRING(UNKNOWN_CONTENT_TYPE);
}
PR_LOG(gGopherLog,PR_LOG_DEBUG,
("GetContentType returning %s\n", PromiseFlatCString(aContentType).get()));
// XXX do we want to cache this result?
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::SetContentType(const nsACString &aContentType)
{
// only changes mContentCharset if a charset is parsed
NS_ParseContentType(aContentType, mContentType, mContentCharset);
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::GetContentCharset(nsACString &aContentCharset)
{
aContentCharset = mContentCharset;
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::SetContentCharset(const nsACString &aContentCharset)
{
mContentCharset = aContentCharset;
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::GetContentLength(PRInt32 *aContentLength)
{
*aContentLength = mContentLength;
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::SetContentLength(PRInt32 aContentLength)
{
NS_NOTREACHED("nsGopherChannel::SetContentLength");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsGopherChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup)
{
*aLoadGroup = mLoadGroup;
NS_IF_ADDREF(*aLoadGroup);
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
{
mLoadGroup = aLoadGroup;
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::GetOwner(nsISupports* *aOwner)
{
*aOwner = mOwner.get();
NS_IF_ADDREF(*aOwner);
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::SetOwner(nsISupports* aOwner)
{
mOwner = aOwner;
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aNotificationCallbacks)
{
*aNotificationCallbacks = mCallbacks.get();
NS_IF_ADDREF(*aNotificationCallbacks);
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
{
mCallbacks = aNotificationCallbacks;
if (mCallbacks) {
mCallbacks->GetInterface(NS_GET_IID(nsIPrompt),
getter_AddRefs(mPrompter));
}
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
{
*aSecurityInfo = nsnull;
return NS_OK;
}
// nsIRequestObserver methods
NS_IMETHODIMP
nsGopherChannel::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
{
PR_LOG(gGopherLog,
PR_LOG_DEBUG,
("nsGopherChannel::OnStartRequest called [this=%x, aRequest=%x]\n",
this, aRequest));
if (!mActAsObserver) {
// acting as a listener
return mListener->OnStartRequest(this, aContext);
} else {
// we don't want to pass our AsyncWrite's OnStart through
// we just ignore this
return NS_OK;
}
}
NS_IMETHODIMP
nsGopherChannel::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
nsresult aStatus)
{
PR_LOG(gGopherLog,
PR_LOG_DEBUG,
("nsGopherChannel::OnStopRequest called [this=%x, aRequest=%x, aStatus=%x]\n",
this,aRequest,aStatus));
nsresult rv = NS_OK;
if (NS_FAILED(aStatus) || !mActAsObserver) {
if (mListener) {
rv = mListener->OnStopRequest(this, mResponseContext, aStatus);
if (NS_FAILED(rv)) return rv;
}
if (mLoadGroup) {
rv = mLoadGroup->RemoveRequest(this, nsnull, aStatus);
}
mTransport = 0;
return rv;
} else {
// at this point we know the request has been sent.
// we're no longer acting as an observer.
mActAsObserver = PR_FALSE;
nsCOMPtr<nsIStreamListener> converterListener;
nsCOMPtr<nsIStreamConverterService> StreamConvService =
do_GetService(kStreamConverterServiceCID, &rv);
if (NS_FAILED(rv)) return rv;
// What we now do depends on what type of file we have
if (mType=='1' || mType=='7') {
// Send the directory format back for a directory
switch (mListFormat) {
case nsIDirectoryListing::FORMAT_RAW:
converterListener = this;
break;
default:
// fall through
case nsIDirectoryListing::FORMAT_HTML:
// XXX - work arround bug 126417. We have to do the chaining
// manually so that we don't crash
{
nsCOMPtr<nsIStreamListener> tmpListener;
rv = StreamConvService->AsyncConvertData(
NS_LITERAL_STRING(APPLICATION_HTTP_INDEX_FORMAT).get(),
NS_LITERAL_STRING(TEXT_HTML).get(),
this,
mUrl,
getter_AddRefs(tmpListener));
if (NS_FAILED(rv)) break;
rv = StreamConvService->AsyncConvertData(NS_LITERAL_STRING("text/gopher-dir").get(),
NS_LITERAL_STRING(APPLICATION_HTTP_INDEX_FORMAT).get(),
tmpListener,
mUrl,
getter_AddRefs(converterListener));
}
break;
case nsIDirectoryListing::FORMAT_HTTP_INDEX:
rv = StreamConvService->AsyncConvertData(NS_LITERAL_STRING("text/gopher-dir").get(),
NS_LITERAL_STRING(APPLICATION_HTTP_INDEX_FORMAT).get(),
this,
mUrl,
getter_AddRefs(converterListener));
break;
}
if (NS_FAILED(rv)) return rv;
} else if (mType=='0') {
// Convert general file
rv = StreamConvService->AsyncConvertData(NS_LITERAL_STRING("text/plain").get(),
NS_LITERAL_STRING("text/html").get(),
this,
mResponseContext,
getter_AddRefs(converterListener));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsITXTToHTMLConv> converter(do_QueryInterface(converterListener));
if (converter) {
nsCAutoString spec;
rv = mUrl->GetSpec(spec);
converter->SetTitle(NS_ConvertUTF8toUCS2(spec).get());
converter->PreFormatHTML(PR_TRUE);
}
} else
// other file type - no conversions
converterListener = this;
return mTransport->AsyncRead(converterListener, mResponseContext, 0,
(PRUint32)-1, 0, getter_AddRefs(mTransportRequest));
}
}
// nsIStreamListener method
NS_IMETHODIMP
nsGopherChannel::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
nsIInputStream *aInputStream,
PRUint32 aSourceOffset, PRUint32 aLength)
{
PR_LOG(gGopherLog, PR_LOG_DEBUG,
("OnDataAvailable called - [this=%x, aLength=%d]\n",this,aLength));
mContentLength = aLength;
return mListener->OnDataAvailable(this, aContext, aInputStream,
aSourceOffset, aLength);
}
nsresult
nsGopherChannel::SendRequest(nsITransport* aTransport)
{
nsresult rv = NS_OK;
nsCOMPtr<nsISupports> result;
nsCOMPtr<nsIInputStream> charstream;
// Note - you have to keep this as a class member, because the char input
// stream doesn't copy its buffer
mRequest.Assign(mSelector);
// So, we use the selector as is unless it is a search url
if (mType=='7') {
// Note that we don't use the "standard" nsIURL parsing stuff here
// because the only special character is ?, and its possible to search
// for a string containing a #, and so on
// XXX - should this find the last or first entry?
// '?' is valid in both the search string and the url
// so no matter what this does, it may be incorrect
// This only affects people codeing the query directly into the URL
PRInt32 pos = mRequest.RFindChar('?');
if (pos == -1) {
// We require a query string here - if we don't have one,
// then we need to ask the user
if (!mPrompter) {
if (mLoadGroup) {
nsCOMPtr<nsIInterfaceRequestor> cbs;
rv = mLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
if (NS_SUCCEEDED(rv))
mPrompter = do_GetInterface(cbs);
}
if (!mPrompter) {
NS_ERROR("We need a prompter!");
return NS_ERROR_FAILURE;
}
}
if (!mStringBundle) {
nsCOMPtr<nsIStringBundleService> bundleSvc =
do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
rv = bundleSvc->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(mStringBundle));
if (NS_FAILED(rv)) return rv;
}
nsXPIDLString promptTitle;
nsXPIDLString promptText;
if (mStringBundle)
rv = mStringBundle->GetStringFromName(NS_LITERAL_STRING("GopherPromptTitle").get(),
getter_Copies(promptTitle));
if (NS_FAILED(rv) || !mStringBundle)
promptTitle.Assign(NS_LITERAL_STRING("Search"));
if (mStringBundle)
rv = mStringBundle->GetStringFromName(NS_LITERAL_STRING("GopherPromptText").get(),
getter_Copies(promptText));
if (NS_FAILED(rv) || !mStringBundle)
promptText.Assign(NS_LITERAL_STRING("Enter a search term:"));
nsXPIDLString search;
PRBool res;
mPrompter->Prompt(promptTitle.get(),
promptText.get(),
getter_Copies(search),
NULL,
NULL,
&res);
if (!res || !(*search.get()))
return NS_ERROR_FAILURE;
if (mLoadGroup) {
rv = mLoadGroup->AddRequest(this, nsnull);
if (NS_FAILED(rv)) return rv;
}
mRequest.Append('\t');
mRequest.AppendWithConversion(search.get());
// and update our uri
nsCAutoString spec;
rv = mUrl->GetAsciiSpec(spec);
if (NS_FAILED(rv))
return rv;
spec.Append('?');
spec.AppendWithConversion(search.get());
rv = mUrl->SetSpec(spec);
if (NS_FAILED(rv))
return rv;
} else {
// Just replace it with a tab
mRequest.SetCharAt('\t',pos);
}
}
mRequest.Append(CRLF);
rv = NS_NewCharInputStream(getter_AddRefs(result), mRequest.get());
if (NS_FAILED(rv)) return rv;
charstream = do_QueryInterface(result, &rv);
if (NS_FAILED(rv)) return rv;
PR_LOG(gGopherLog,PR_LOG_DEBUG,
("Sending: %s\n", mRequest.get()));
rv = NS_AsyncWriteFromStream(getter_AddRefs(mTransportRequest),
aTransport, charstream,
0, mRequest.Length(), 0,
this, nsnull);
return rv;
}
NS_IMETHODIMP
nsGopherChannel::SetListFormat(PRUint32 format) {
if (format != FORMAT_PREF &&
format != FORMAT_RAW &&
format != FORMAT_HTML &&
format != FORMAT_HTTP_INDEX) {
return NS_ERROR_FAILURE;
}
// Convert the pref value
if (format == FORMAT_PREF) {
nsresult rv;
nsCOMPtr<nsIPref> prefs = do_GetService(NS_PREF_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
PRInt32 sFormat;
rv = prefs->GetIntPref("network.dir.format", &sFormat);
if (NS_FAILED(rv))
format = FORMAT_HTML; // default
else
format = sFormat;
if (format == FORMAT_PREF) {
NS_WARNING("Who set the directory format pref to 'read from prefs'??");
return NS_ERROR_FAILURE;
}
}
mListFormat = format;
return NS_OK;
}
NS_IMETHODIMP
nsGopherChannel::GetListFormat(PRUint32 *format) {
*format = mListFormat;
return NS_OK;
}