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
,
473 nsIChannel
** channel
)
475 // Implement nsIChannel::Open in terms of nsIChannel::AsyncOpen
477 return NS_ERROR_NOT_IMPLEMENTED
;
479 nsRefPtr
<nsIInputStream
> stream
= new nsGopherContentStream(this);
481 return NS_ERROR_OUT_OF_MEMORY
;
484 stream
.swap(*result
);
489 nsGopherChannel::GetStatusArg(nsresult status
, nsString
&statusArg
)
492 URI()->GetHost(host
);
493 CopyUTF8toUTF16(host
, statusArg
);