Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsURIChecker.h"
7 #include "nsIAuthPrompt.h"
8 #include "nsIHttpChannel.h"
9 #include "nsNetUtil.h"
10 #include "nsString.h"
11 #include "nsIAsyncVerifyRedirectCallback.h"
13 //-----------------------------------------------------------------------------
15 static bool
16 ServerIsNES3x(nsIHttpChannel *httpChannel)
17 {
18 nsAutoCString server;
19 httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Server"), server);
20 // case sensitive string comparison is OK here. the server string
21 // is a well-known value, so we should not have to worry about it
22 // being case-smashed or otherwise case-mutated.
23 return StringBeginsWith(server,
24 NS_LITERAL_CSTRING("Netscape-Enterprise/3."));
25 }
27 //-----------------------------------------------------------------------------
29 NS_IMPL_ISUPPORTS(nsURIChecker,
30 nsIURIChecker,
31 nsIRequest,
32 nsIRequestObserver,
33 nsIStreamListener,
34 nsIChannelEventSink,
35 nsIInterfaceRequestor)
37 nsURIChecker::nsURIChecker()
38 : mStatus(NS_OK)
39 , mIsPending(false)
40 , mAllowHead(true)
41 {
42 }
44 void
45 nsURIChecker::SetStatusAndCallBack(nsresult aStatus)
46 {
47 mStatus = aStatus;
48 mIsPending = false;
50 if (mObserver) {
51 mObserver->OnStartRequest(this, mObserverContext);
52 mObserver->OnStopRequest(this, mObserverContext, mStatus);
53 mObserver = nullptr;
54 mObserverContext = nullptr;
55 }
56 }
58 nsresult
59 nsURIChecker::CheckStatus()
60 {
61 NS_ASSERTION(mChannel, "no channel");
63 nsresult status;
64 nsresult rv = mChannel->GetStatus(&status);
65 // DNS errors and other obvious problems will return failure status
66 if (NS_FAILED(rv) || NS_FAILED(status))
67 return NS_BINDING_FAILED;
69 // If status is zero, it might still be an error if it's http:
70 // http has data even when there's an error like a 404.
71 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
72 if (!httpChannel)
73 return NS_BINDING_SUCCEEDED;
75 uint32_t responseStatus;
76 rv = httpChannel->GetResponseStatus(&responseStatus);
77 if (NS_FAILED(rv))
78 return NS_BINDING_FAILED;
80 // If it's between 200-299, it's valid:
81 if (responseStatus / 100 == 2)
82 return NS_BINDING_SUCCEEDED;
84 // If we got a 404 (not found), we need some extra checking:
85 // toplevel urls from Netscape Enterprise Server 3.6, like the old AOL-
86 // hosted http://www.mozilla.org, generate a 404 and will have to be
87 // retried without the head.
88 if (responseStatus == 404) {
89 if (mAllowHead && ServerIsNES3x(httpChannel)) {
90 mAllowHead = false;
92 // save the current value of mChannel in case we can't issue
93 // the new request for some reason.
94 nsCOMPtr<nsIChannel> lastChannel = mChannel;
96 nsCOMPtr<nsIURI> uri;
97 uint32_t loadFlags;
99 rv = lastChannel->GetOriginalURI(getter_AddRefs(uri));
100 nsresult tmp = lastChannel->GetLoadFlags(&loadFlags);
101 if (NS_FAILED(tmp)) {
102 rv = tmp;
103 }
105 // XXX we are carrying over the load flags, but what about other
106 // parameters that may have been set on lastChannel??
108 if (NS_SUCCEEDED(rv)) {
109 rv = Init(uri);
110 if (NS_SUCCEEDED(rv)) {
111 rv = mChannel->SetLoadFlags(loadFlags);
112 if (NS_SUCCEEDED(rv)) {
113 rv = AsyncCheck(mObserver, mObserverContext);
114 // if we succeeded in loading the new channel, then we
115 // want to return without notifying our observer.
116 if (NS_SUCCEEDED(rv))
117 return NS_BASE_STREAM_WOULD_BLOCK;
118 }
119 }
120 }
121 // it is important to update this so our observer will be able
122 // to access our baseChannel attribute if they want.
123 mChannel = lastChannel;
124 }
125 }
127 // If we get here, assume the resource does not exist.
128 return NS_BINDING_FAILED;
129 }
131 //-----------------------------------------------------------------------------
132 // nsIURIChecker methods:
133 //-----------------------------------------------------------------------------
135 NS_IMETHODIMP
136 nsURIChecker::Init(nsIURI *aURI)
137 {
138 nsresult rv;
139 nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
140 if (NS_FAILED(rv)) return rv;
142 rv = ios->NewChannelFromURI(aURI, getter_AddRefs(mChannel));
143 if (NS_FAILED(rv)) return rv;
145 if (mAllowHead) {
146 mAllowHead = false;
147 // See if it's an http channel, which needs special treatment:
148 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
149 if (httpChannel) {
150 // We can have an HTTP channel that has a non-HTTP URL if
151 // we're doing FTP via an HTTP proxy, for example. See for
152 // example bug 148813
153 bool isReallyHTTP = false;
154 aURI->SchemeIs("http", &isReallyHTTP);
155 if (!isReallyHTTP)
156 aURI->SchemeIs("https", &isReallyHTTP);
157 if (isReallyHTTP) {
158 httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("HEAD"));
159 // set back to true so we'll know that this request is issuing
160 // a HEAD request. this is used down in OnStartRequest to
161 // handle cases where we need to repeat the request as a normal
162 // GET to deal with server borkage.
163 mAllowHead = true;
164 }
165 }
166 }
167 return NS_OK;
168 }
170 NS_IMETHODIMP
171 nsURIChecker::AsyncCheck(nsIRequestObserver *aObserver,
172 nsISupports *aObserverContext)
173 {
174 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED);
176 // Hook us up to listen to redirects and the like (this creates a reference
177 // cycle!)
178 mChannel->SetNotificationCallbacks(this);
180 // and start the request:
181 nsresult rv = mChannel->AsyncOpen(this, nullptr);
182 if (NS_FAILED(rv))
183 mChannel = nullptr;
184 else {
185 // ok, wait for OnStartRequest to fire.
186 mIsPending = true;
187 mObserver = aObserver;
188 mObserverContext = aObserverContext;
189 }
190 return rv;
191 }
193 NS_IMETHODIMP
194 nsURIChecker::GetBaseChannel(nsIChannel **aChannel)
195 {
196 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED);
197 NS_ADDREF(*aChannel = mChannel);
198 return NS_OK;
199 }
201 //-----------------------------------------------------------------------------
202 // nsIRequest methods:
203 //-----------------------------------------------------------------------------
205 NS_IMETHODIMP
206 nsURIChecker::GetName(nsACString &aName)
207 {
208 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED);
209 return mChannel->GetName(aName);
210 }
212 NS_IMETHODIMP
213 nsURIChecker::IsPending(bool *aPendingRet)
214 {
215 *aPendingRet = mIsPending;
216 return NS_OK;
217 }
219 NS_IMETHODIMP
220 nsURIChecker::GetStatus(nsresult* aStatusRet)
221 {
222 *aStatusRet = mStatus;
223 return NS_OK;
224 }
226 NS_IMETHODIMP
227 nsURIChecker::Cancel(nsresult status)
228 {
229 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED);
230 return mChannel->Cancel(status);
231 }
233 NS_IMETHODIMP
234 nsURIChecker::Suspend()
235 {
236 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED);
237 return mChannel->Suspend();
238 }
240 NS_IMETHODIMP
241 nsURIChecker::Resume()
242 {
243 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED);
244 return mChannel->Resume();
245 }
247 NS_IMETHODIMP
248 nsURIChecker::GetLoadGroup(nsILoadGroup **aLoadGroup)
249 {
250 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED);
251 return mChannel->GetLoadGroup(aLoadGroup);
252 }
254 NS_IMETHODIMP
255 nsURIChecker::SetLoadGroup(nsILoadGroup *aLoadGroup)
256 {
257 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED);
258 return mChannel->SetLoadGroup(aLoadGroup);
259 }
261 NS_IMETHODIMP
262 nsURIChecker::GetLoadFlags(nsLoadFlags *aLoadFlags)
263 {
264 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED);
265 return mChannel->GetLoadFlags(aLoadFlags);
266 }
268 NS_IMETHODIMP
269 nsURIChecker::SetLoadFlags(nsLoadFlags aLoadFlags)
270 {
271 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED);
272 return mChannel->SetLoadFlags(aLoadFlags);
273 }
275 //-----------------------------------------------------------------------------
276 // nsIRequestObserver methods:
277 //-----------------------------------------------------------------------------
279 NS_IMETHODIMP
280 nsURIChecker::OnStartRequest(nsIRequest *aRequest, nsISupports *aCtxt)
281 {
282 NS_ASSERTION(aRequest == mChannel, "unexpected request");
284 nsresult rv = CheckStatus();
285 if (rv != NS_BASE_STREAM_WOULD_BLOCK)
286 SetStatusAndCallBack(rv);
288 // cancel the request (we don't care to look at the data).
289 return NS_BINDING_ABORTED;
290 }
292 NS_IMETHODIMP
293 nsURIChecker::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
294 nsresult statusCode)
295 {
296 // NOTE: we may have kicked off a subsequent request, so we should not do
297 // any cleanup unless this request matches the one we are currently using.
298 if (mChannel == request) {
299 // break reference cycle between us and the channel (see comment in
300 // AsyncCheckURI)
301 mChannel = nullptr;
302 }
303 return NS_OK;
304 }
306 //-----------------------------------------------------------------------------
307 // nsIStreamListener methods:
308 //-----------------------------------------------------------------------------
310 NS_IMETHODIMP
311 nsURIChecker::OnDataAvailable(nsIRequest *aRequest, nsISupports *aCtxt,
312 nsIInputStream *aInput, uint64_t aOffset,
313 uint32_t aCount)
314 {
315 NS_NOTREACHED("nsURIChecker::OnDataAvailable");
316 return NS_BINDING_ABORTED;
317 }
319 //-----------------------------------------------------------------------------
320 // nsIInterfaceRequestor methods:
321 //-----------------------------------------------------------------------------
323 NS_IMETHODIMP
324 nsURIChecker::GetInterface(const nsIID & aIID, void **aResult)
325 {
326 if (mObserver && aIID.Equals(NS_GET_IID(nsIAuthPrompt))) {
327 nsCOMPtr<nsIInterfaceRequestor> req = do_QueryInterface(mObserver);
328 if (req)
329 return req->GetInterface(aIID, aResult);
330 }
331 return QueryInterface(aIID, aResult);
332 }
334 //-----------------------------------------------------------------------------
335 // nsIChannelEventSink methods:
336 //-----------------------------------------------------------------------------
338 NS_IMETHODIMP
339 nsURIChecker::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
340 nsIChannel *aNewChannel,
341 uint32_t aFlags,
342 nsIAsyncVerifyRedirectCallback *callback)
343 {
344 // We have a new channel
345 mChannel = aNewChannel;
346 callback->OnRedirectVerifyCallback(NS_OK);
347 return NS_OK;
348 }