1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/* vim:set ts=4 sw=4 sts=4 cin et:
4
* ***** BEGIN LICENSE BLOCK *****
5
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
7
* The contents of this file are subject to the Mozilla Public License Version
8
* 1.1 (the "License"); you may not use this file except in compliance with
9
* the License. You may obtain a copy of the License at
10
* http://www.mozilla.org/MPL/
12
* Software distributed under the License is distributed on an "AS IS" basis,
13
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14
* for the specific language governing rights and limitations under the
17
* The Original Code is the Gopher protocol code.
19
* The Initial Developer of the Original Code is
21
* Portions created by the Initial Developer are Copyright (C) 2000
22
* the Initial Developer. All Rights Reserved.
25
* Bradley Baetz <bbaetz@student.usyd.edu.au>
26
* Darin Fisher <darin@netscape.com>
28
* Alternatively, the contents of this file may be used under the terms of
29
* either the GNU General Public License Version 2 or later (the "GPL"), or
30
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31
* in which case the provisions of the GPL or the LGPL are applicable instead
32
* of those above. If you wish to allow use of your version of this file only
33
* under the terms of either the GPL or the LGPL, and not to allow others to
34
* use your version of this file under the terms of the MPL, indicate your
35
* decision by deleting the provisions above and replace them with the notice
36
* and other provisions required by the GPL or the LGPL. If you do not delete
37
* the provisions above, a recipient may use your version of this file under
38
* the terms of any one of the MPL, the GPL or the LGPL.
40
* ***** END LICENSE BLOCK ***** */
42
#include "nsGopherChannel.h"
43
#include "nsGopherHandler.h"
44
#include "nsBaseContentStream.h"
45
#include "nsIAsyncInputStream.h"
46
#include "nsIAsyncOutputStream.h"
47
#include "nsISocketTransportService.h"
48
#include "nsISocketTransport.h"
49
#include "nsIStringBundle.h"
50
#include "nsITXTToHTMLConv.h"
51
#include "nsIPrompt.h"
52
#include "nsServiceManagerUtils.h"
53
#include "nsThreadUtils.h"
54
#include "nsStreamUtils.h"
55
#include "nsMimeTypes.h"
58
#include "nsAutoPtr.h"
63
// Specifies the maximum number of output stream buffer segments that we can
64
// allocate before giving up. At 4k per segment, this corresponds to a max
65
// gopher request of 400k, which should be plenty.
66
#define GOPHER_MAX_WRITE_SEGMENT_COUNT 100
68
//-----------------------------------------------------------------------------
70
class nsGopherContentStream : public nsBaseContentStream
71
, public nsIInputStreamCallback
72
, public nsIOutputStreamCallback
75
NS_DECL_ISUPPORTS_INHERITED
76
NS_DECL_NSIINPUTSTREAMCALLBACK
77
NS_DECL_NSIOUTPUTSTREAMCALLBACK
79
// stream methods that we override:
80
NS_IMETHOD Available(PRUint32 *result);
81
NS_IMETHOD ReadSegments(nsWriteSegmentFun writer, void *closure,
82
PRUint32 count, PRUint32 *result);
83
NS_IMETHOD CloseWithStatus(nsresult status);
85
nsGopherContentStream(nsGopherChannel *channel)
86
: nsBaseContentStream(PR_TRUE) // non-blocking
90
nsresult OpenSocket(nsIEventTarget *target);
91
nsresult OnSocketWritable();
92
nsresult ParseTypeAndSelector(char &type, nsCString &selector);
93
nsresult PromptForQueryString(nsCString &result);
94
void UpdateContentType(char type);
95
nsresult SendRequest();
98
virtual void OnCallbackPending();
101
nsRefPtr<nsGopherChannel> mChannel;
102
nsCOMPtr<nsISocketTransport> mSocket;
103
nsCOMPtr<nsIAsyncOutputStream> mSocketOutput;
104
nsCOMPtr<nsIAsyncInputStream> mSocketInput;
107
NS_IMPL_ISUPPORTS_INHERITED2(nsGopherContentStream,
109
nsIInputStreamCallback,
110
nsIOutputStreamCallback)
113
nsGopherContentStream::Available(PRUint32 *result)
116
return mSocketInput->Available(result);
118
return nsBaseContentStream::Available(result);
122
nsGopherContentStream::ReadSegments(nsWriteSegmentFun writer, void *closure,
123
PRUint32 count, PRUint32 *result)
125
// Insert a thunk here so that the input stream passed to the writer is
126
// this input stream instead of mSocketInput.
128
nsWriteSegmentThunk thunk = { this, writer, closure };
129
return mSocketInput->ReadSegments(NS_WriteSegmentThunk, &thunk, count,
133
return nsBaseContentStream::ReadSegments(writer, closure, count, result);
137
nsGopherContentStream::CloseWithStatus(nsresult status)
140
mSocket->Close(status);
142
mSocketInput = nsnull;
143
mSocketOutput = nsnull;
145
return nsBaseContentStream::CloseWithStatus(status);
149
nsGopherContentStream::OnInputStreamReady(nsIAsyncInputStream *stream)
151
// Forward this notification
152
DispatchCallbackSync();
157
nsGopherContentStream::OnOutputStreamReady(nsIAsyncOutputStream *stream)
159
// If we're already closed, mSocketOutput is going to be null and we'll
160
// just be getting notified that it got closed (by outselves). In that
161
// case, nothing to do here.
162
if (!mSocketOutput) {
163
NS_ASSERTION(NS_FAILED(Status()), "How did that happen?");
167
// We have to close ourselves if we hit an error here in order to propagate
168
// the error to our consumer. Otherwise, just forward the notification so
169
// that the consumer will know to start reading.
171
nsresult rv = OnSocketWritable();
179
nsGopherContentStream::OnCallbackPending()
183
// We have a callback, so failure means we should close the stream.
185
rv = OpenSocket(CallbackTarget());
186
} else if (mSocketInput) {
187
rv = mSocketInput->AsyncWait(this, 0, 0, CallbackTarget());
195
nsGopherContentStream::OpenSocket(nsIEventTarget *target)
197
// This function is called to get things started.
199
// We begin by opening a socket to the specified host and wait for the
200
// socket to become writable.
203
nsresult rv = mChannel->URI()->GetAsciiHost(host);
207
return NS_ERROR_MALFORMED_URI;
209
// For security reasons, don't allow anything expect the default
210
// gopher port (70). See bug 71916 - bbaetz@cs.mcgill.ca
211
PRInt32 port = GOPHER_PORT;
213
// Create socket tranport
214
nsCOMPtr<nsISocketTransportService> sts =
215
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
218
rv = sts->CreateTransport(nsnull, 0, host, port, mChannel->ProxyInfo(),
219
getter_AddRefs(mSocket));
223
// Setup progress and status notifications
224
rv = mSocket->SetEventSink(mChannel, target);
228
nsCOMPtr<nsIOutputStream> output;
229
rv = mSocket->OpenOutputStream(0, 0, GOPHER_MAX_WRITE_SEGMENT_COUNT,
230
getter_AddRefs(output));
233
mSocketOutput = do_QueryInterface(output);
234
NS_ENSURE_STATE(mSocketOutput);
236
return mSocketOutput->AsyncWait(this, 0, 0, target);
240
nsGopherContentStream::OnSocketWritable()
242
// Write to output stream (we can do this in one big chunk)
243
nsresult rv = SendRequest();
248
nsCOMPtr<nsIInputStream> input;
249
rv = mSocket->OpenInputStream(0, 0, 0, getter_AddRefs(input));
252
mSocketInput = do_QueryInterface(input, &rv);
254
NS_ASSERTION(CallbackTarget(), "where is my pending callback?");
255
rv = mSocketInput->AsyncWait(this, 0, 0, CallbackTarget());
261
nsGopherContentStream::ParseTypeAndSelector(char &type, nsCString &selector)
263
nsCAutoString buffer;
264
nsresult rv = mChannel->URI()->GetPath(buffer); // unescaped down below
269
if (buffer[0] == '\0' || (buffer[0] == '/' && buffer[1] == '\0')) {
273
NS_ENSURE_STATE(buffer[1] != '\0');
275
type = buffer[1]; // Ignore leading '/'
277
// Do it this way in case selector contains embedded nulls after
279
char *sel = buffer.BeginWriting() + 2;
280
PRInt32 count = nsUnescapeCount(sel);
281
selector.Assign(sel, count);
283
// NOTE: FindCharInSet cannot be used to search for a null byte.
284
if (selector.FindCharInSet("\t\n\r") != kNotFound ||
285
selector.FindChar('\0') != kNotFound) {
286
// gopher selectors cannot containt tab, cr, lf, or \0
287
return NS_ERROR_MALFORMED_URI;
295
nsGopherContentStream::PromptForQueryString(nsCString &result)
297
nsCOMPtr<nsIPrompt> prompter;
298
mChannel->GetCallback(prompter);
300
NS_ERROR("We need a prompter!");
301
return NS_ERROR_FAILURE;
304
nsCOMPtr<nsIStringBundle> bundle;
305
nsCOMPtr<nsIStringBundleService> bundleSvc =
306
do_GetService(NS_STRINGBUNDLE_CONTRACTID);
308
bundleSvc->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
310
nsXPIDLString promptTitle, promptText;
312
bundle->GetStringFromName(NS_LITERAL_STRING("GopherPromptTitle").get(),
313
getter_Copies(promptTitle));
314
bundle->GetStringFromName(NS_LITERAL_STRING("GopherPromptText").get(),
315
getter_Copies(promptText));
317
if (promptTitle.IsEmpty())
318
promptTitle.AssignLiteral("Search");
319
if (promptText.IsEmpty())
320
promptText.AssignLiteral("Enter a search term:");
323
PRBool res = PR_FALSE;
324
prompter->Prompt(promptTitle.get(), promptText.get(),
325
getter_Copies(value), NULL, NULL, &res);
326
if (!res || value.IsEmpty())
327
return NS_ERROR_FAILURE;
329
CopyUTF16toUTF8(value, result); // XXX Is UTF-8 the right thing?
334
nsGopherContentStream::UpdateContentType(char type)
336
const char *contentType = nsnull;
341
case '2': // CSO search - unhandled, should not be selectable
342
case '3': // "Error" - should not be selectable
343
case 'i': // info line- should not be selectable
344
contentType = TEXT_HTML;
347
case '7': // search - returns a directory listing
348
contentType = APPLICATION_HTTP_INDEX_FORMAT;
352
contentType = IMAGE_GIF;
354
case 'T': // tn3270 - type doesn't make sense
355
case '8': // telnet - type doesn't make sense
356
contentType = TEXT_PLAIN;
358
case '5': // "DOS binary archive of some sort" - is the mime-type correct?
359
case '9': // "Binary file!"
360
contentType = APPLICATION_OCTET_STREAM;
362
case '4': // "BinHexed Macintosh file"
363
contentType = APPLICATION_BINHEX;
366
contentType = APPLICATION_UUENCODE;
371
mChannel->SetContentType(nsDependentCString(contentType));
375
nsGopherContentStream::SendRequest()
378
nsCAutoString request; // used to build request data
380
nsresult rv = ParseTypeAndSelector(type, request);
384
// So, we use the selector as is unless it is a search url
386
// Note that we don't use the "standard" nsIURL parsing stuff here
387
// because the only special character is ?, and its possible to search
388
// for a string containing a #, and so on
390
// XXX - should this find the last or first entry?
391
// '?' is valid in both the search string and the url
392
// so no matter what this does, it may be incorrect
393
// This only affects people codeing the query directly into the URL
394
PRInt32 pos = request.RFindChar('?');
395
if (pos != kNotFound) {
396
// Just replace it with a tab
397
request.SetCharAt('\t', pos);
399
// We require a query string here - if we don't have one,
400
// then we need to ask the user
401
nsCAutoString search;
402
rv = PromptForQueryString(search);
406
request.Append('\t');
407
request.Append(search);
409
// and update our uri (XXX should probably redirect instead to avoid
410
// confusing consumers of the channel)
412
rv = mChannel->URI()->GetAsciiSpec(spec);
418
rv = mChannel->URI()->SetSpec(spec);
424
request.Append(CRLF);
427
rv = mSocketOutput->Write(request.get(), request.Length(), &n);
430
NS_ENSURE_STATE(n == request.Length());
432
// Now, push stream converters appropriately based on our 'type'
433
if (type == '1' || type == '7') {
434
rv = mChannel->PushStreamConverter("text/gopher-dir",
435
APPLICATION_HTTP_INDEX_FORMAT);
438
} else if (type == '0') {
439
nsCOMPtr<nsIStreamListener> converter;
440
rv = mChannel->PushStreamConverter(TEXT_PLAIN, TEXT_HTML, PR_TRUE,
441
getter_AddRefs(converter));
444
nsCOMPtr<nsITXTToHTMLConv> config = do_QueryInterface(converter);
447
mChannel->URI()->GetSpec(spec);
448
config->SetTitle(NS_ConvertUTF8toUTF16(spec).get());
449
config->PreFormatHTML(PR_TRUE);
453
UpdateContentType(type);
457
//-----------------------------------------------------------------------------
459
NS_IMPL_ISUPPORTS_INHERITED1(nsGopherChannel,
464
nsGopherChannel::GetProxyInfo(nsIProxyInfo** aProxyInfo)
466
*aProxyInfo = ProxyInfo();
467
NS_IF_ADDREF(*aProxyInfo);
472
nsGopherChannel::OpenContentStream(PRBool async, nsIInputStream **result)
474
// Implement nsIChannel::Open in terms of nsIChannel::AsyncOpen
476
return NS_ERROR_NOT_IMPLEMENTED;
478
nsRefPtr<nsIInputStream> stream = new nsGopherContentStream(this);
480
return NS_ERROR_OUT_OF_MEMORY;
483
stream.swap(*result);
488
nsGopherChannel::GetStatusArg(nsresult status, nsString &statusArg)
491
URI()->GetHost(host);
492
CopyUTF8toUTF16(host, statusArg);