Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / netwerk / protocol / gopher / src / nsGopherChannel.cpp
blob317d73ba70a02c83d940c83c2a4e3f648fc575ad
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
15 * License.
17 * The Original Code is the Gopher protocol code.
19 * The Initial Developer of the Original Code is
20 * Bradley Baetz.
21 * Portions created by the Initial Developer are Copyright (C) 2000
22 * the Initial Developer. All Rights Reserved.
24 * Contributor(s):
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"
56 #include "nsNetCID.h"
57 #include "nsCOMPtr.h"
58 #include "nsAutoPtr.h"
59 #include "nsEscape.h"
60 #include "nsCRT.h"
61 #include "netCore.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
74 public:
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
87 , mChannel(channel) {
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();
97 protected:
98 virtual void OnCallbackPending();
100 private:
101 nsRefPtr<nsGopherChannel> mChannel;
102 nsCOMPtr<nsISocketTransport> mSocket;
103 nsCOMPtr<nsIAsyncOutputStream> mSocketOutput;
104 nsCOMPtr<nsIAsyncInputStream> mSocketInput;
107 NS_IMPL_ISUPPORTS_INHERITED2(nsGopherContentStream,
108 nsBaseContentStream,
109 nsIInputStreamCallback,
110 nsIOutputStreamCallback)
112 NS_IMETHODIMP
113 nsGopherContentStream::Available(PRUint32 *result)
115 if (mSocketInput)
116 return mSocketInput->Available(result);
118 return nsBaseContentStream::Available(result);
121 NS_IMETHODIMP
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.
127 if (mSocketInput) {
128 nsWriteSegmentThunk thunk = { this, writer, closure };
129 return mSocketInput->ReadSegments(NS_WriteSegmentThunk, &thunk, count,
130 result);
133 return nsBaseContentStream::ReadSegments(writer, closure, count, result);
136 NS_IMETHODIMP
137 nsGopherContentStream::CloseWithStatus(nsresult status)
139 if (mSocket) {
140 mSocket->Close(status);
141 mSocket = nsnull;
142 mSocketInput = nsnull;
143 mSocketOutput = nsnull;
145 return nsBaseContentStream::CloseWithStatus(status);
148 NS_IMETHODIMP
149 nsGopherContentStream::OnInputStreamReady(nsIAsyncInputStream *stream)
151 // Forward this notification
152 DispatchCallbackSync();
153 return NS_OK;
156 NS_IMETHODIMP
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?");
164 return NS_OK;
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();
172 if (NS_FAILED(rv))
173 CloseWithStatus(rv);
175 return NS_OK;
178 void
179 nsGopherContentStream::OnCallbackPending()
181 nsresult rv;
183 // We have a callback, so failure means we should close the stream.
184 if (!mSocket) {
185 rv = OpenSocket(CallbackTarget());
186 } else if (mSocketInput) {
187 rv = mSocketInput->AsyncWait(this, 0, 0, CallbackTarget());
190 if (NS_FAILED(rv))
191 CloseWithStatus(rv);
194 nsresult
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.
202 nsCAutoString host;
203 nsresult rv = mChannel->URI()->GetAsciiHost(host);
204 if (NS_FAILED(rv))
205 return rv;
206 if (host.IsEmpty())
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);
216 if (NS_FAILED(rv))
217 return rv;
218 rv = sts->CreateTransport(nsnull, 0, host, port, mChannel->ProxyInfo(),
219 getter_AddRefs(mSocket));
220 if (NS_FAILED(rv))
221 return rv;
223 // Setup progress and status notifications
224 rv = mSocket->SetEventSink(mChannel, target);
225 if (NS_FAILED(rv))
226 return rv;
228 nsCOMPtr<nsIOutputStream> output;
229 rv = mSocket->OpenOutputStream(0, 0, GOPHER_MAX_WRITE_SEGMENT_COUNT,
230 getter_AddRefs(output));
231 if (NS_FAILED(rv))
232 return rv;
233 mSocketOutput = do_QueryInterface(output);
234 NS_ENSURE_STATE(mSocketOutput);
236 return mSocketOutput->AsyncWait(this, 0, 0, target);
239 nsresult
240 nsGopherContentStream::OnSocketWritable()
242 // Write to output stream (we can do this in one big chunk)
243 nsresult rv = SendRequest();
244 if (NS_FAILED(rv))
245 return rv;
247 // Open input stream
248 nsCOMPtr<nsIInputStream> input;
249 rv = mSocket->OpenInputStream(0, 0, 0, getter_AddRefs(input));
250 if (NS_FAILED(rv))
251 return rv;
252 mSocketInput = do_QueryInterface(input, &rv);
254 NS_ASSERTION(CallbackTarget(), "where is my pending callback?");
255 rv = mSocketInput->AsyncWait(this, 0, 0, CallbackTarget());
257 return rv;
260 nsresult
261 nsGopherContentStream::ParseTypeAndSelector(char &type, nsCString &selector)
263 nsCAutoString buffer;
264 nsresult rv = mChannel->URI()->GetPath(buffer); // unescaped down below
265 if (NS_FAILED(rv))
266 return rv;
268 // No path given
269 if (buffer[0] == '\0' || (buffer[0] == '/' && buffer[1] == '\0')) {
270 type = '1';
271 selector.Truncate();
272 } else {
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
278 // unescaping.
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;
291 return NS_OK;
294 nsresult
295 nsGopherContentStream::PromptForQueryString(nsCString &result)
297 nsCOMPtr<nsIPrompt> prompter;
298 mChannel->GetCallback(prompter);
299 if (!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);
307 if (bundleSvc)
308 bundleSvc->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
310 nsXPIDLString promptTitle, promptText;
311 if (bundle) {
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:");
322 nsXPIDLString value;
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?
330 return NS_OK;
333 void
334 nsGopherContentStream::UpdateContentType(char type)
336 const char *contentType = nsnull;
338 switch(type) {
339 case '0':
340 case 'h':
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;
345 break;
346 case '1':
347 case '7': // search - returns a directory listing
348 contentType = APPLICATION_HTTP_INDEX_FORMAT;
349 break;
350 case 'g':
351 case 'I':
352 contentType = IMAGE_GIF;
353 break;
354 case 'T': // tn3270 - type doesn't make sense
355 case '8': // telnet - type doesn't make sense
356 contentType = TEXT_PLAIN;
357 break;
358 case '5': // "DOS binary archive of some sort" - is the mime-type correct?
359 case '9': // "Binary file!"
360 contentType = APPLICATION_OCTET_STREAM;
361 break;
362 case '4': // "BinHexed Macintosh file"
363 contentType = APPLICATION_BINHEX;
364 break;
365 case '6':
366 contentType = APPLICATION_UUENCODE;
367 break;
370 if (contentType)
371 mChannel->SetContentType(nsDependentCString(contentType));
374 nsresult
375 nsGopherContentStream::SendRequest()
377 char type;
378 nsCAutoString request; // used to build request data
380 nsresult rv = ParseTypeAndSelector(type, request);
381 if (NS_FAILED(rv))
382 return rv;
384 // So, we use the selector as is unless it is a search url
385 if (type == '7') {
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);
398 } else {
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);
403 if (NS_FAILED(rv))
404 return rv;
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)
411 nsCAutoString spec;
412 rv = mChannel->URI()->GetAsciiSpec(spec);
413 if (NS_FAILED(rv))
414 return rv;
416 spec.Append('?');
417 spec.Append(search);
418 rv = mChannel->URI()->SetSpec(spec);
419 if (NS_FAILED(rv))
420 return rv;
424 request.Append(CRLF);
426 PRUint32 n;
427 rv = mSocketOutput->Write(request.get(), request.Length(), &n);
428 if (NS_FAILED(rv))
429 return rv;
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);
436 if (NS_FAILED(rv))
437 return rv;
438 } else if (type == '0') {
439 nsCOMPtr<nsIStreamListener> converter;
440 rv = mChannel->PushStreamConverter(TEXT_PLAIN, TEXT_HTML, PR_TRUE,
441 getter_AddRefs(converter));
442 if (NS_FAILED(rv))
443 return rv;
444 nsCOMPtr<nsITXTToHTMLConv> config = do_QueryInterface(converter);
445 if (config) {
446 nsCAutoString spec;
447 mChannel->URI()->GetSpec(spec);
448 config->SetTitle(NS_ConvertUTF8toUTF16(spec).get());
449 config->PreFormatHTML(PR_TRUE);
453 UpdateContentType(type);
454 return NS_OK;
457 //-----------------------------------------------------------------------------
459 NS_IMPL_ISUPPORTS_INHERITED1(nsGopherChannel,
460 nsBaseChannel,
461 nsIProxiedChannel)
463 NS_IMETHODIMP
464 nsGopherChannel::GetProxyInfo(nsIProxyInfo** aProxyInfo)
466 *aProxyInfo = ProxyInfo();
467 NS_IF_ADDREF(*aProxyInfo);
468 return NS_OK;
471 nsresult
472 nsGopherChannel::OpenContentStream(PRBool async, nsIInputStream **result,
473 nsIChannel** channel)
475 // Implement nsIChannel::Open in terms of nsIChannel::AsyncOpen
476 if (!async)
477 return NS_ERROR_NOT_IMPLEMENTED;
479 nsRefPtr<nsIInputStream> stream = new nsGopherContentStream(this);
480 if (!stream)
481 return NS_ERROR_OUT_OF_MEMORY;
483 *result = nsnull;
484 stream.swap(*result);
485 return NS_OK;
488 PRBool
489 nsGopherChannel::GetStatusArg(nsresult status, nsString &statusArg)
491 nsCAutoString host;
492 URI()->GetHost(host);
493 CopyUTF8toUTF16(host, statusArg);
494 return PR_TRUE;