|
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/. */ |
|
5 |
|
6 #include "nsURIChecker.h" |
|
7 #include "nsIAuthPrompt.h" |
|
8 #include "nsIHttpChannel.h" |
|
9 #include "nsNetUtil.h" |
|
10 #include "nsString.h" |
|
11 #include "nsIAsyncVerifyRedirectCallback.h" |
|
12 |
|
13 //----------------------------------------------------------------------------- |
|
14 |
|
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 } |
|
26 |
|
27 //----------------------------------------------------------------------------- |
|
28 |
|
29 NS_IMPL_ISUPPORTS(nsURIChecker, |
|
30 nsIURIChecker, |
|
31 nsIRequest, |
|
32 nsIRequestObserver, |
|
33 nsIStreamListener, |
|
34 nsIChannelEventSink, |
|
35 nsIInterfaceRequestor) |
|
36 |
|
37 nsURIChecker::nsURIChecker() |
|
38 : mStatus(NS_OK) |
|
39 , mIsPending(false) |
|
40 , mAllowHead(true) |
|
41 { |
|
42 } |
|
43 |
|
44 void |
|
45 nsURIChecker::SetStatusAndCallBack(nsresult aStatus) |
|
46 { |
|
47 mStatus = aStatus; |
|
48 mIsPending = false; |
|
49 |
|
50 if (mObserver) { |
|
51 mObserver->OnStartRequest(this, mObserverContext); |
|
52 mObserver->OnStopRequest(this, mObserverContext, mStatus); |
|
53 mObserver = nullptr; |
|
54 mObserverContext = nullptr; |
|
55 } |
|
56 } |
|
57 |
|
58 nsresult |
|
59 nsURIChecker::CheckStatus() |
|
60 { |
|
61 NS_ASSERTION(mChannel, "no channel"); |
|
62 |
|
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; |
|
68 |
|
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; |
|
74 |
|
75 uint32_t responseStatus; |
|
76 rv = httpChannel->GetResponseStatus(&responseStatus); |
|
77 if (NS_FAILED(rv)) |
|
78 return NS_BINDING_FAILED; |
|
79 |
|
80 // If it's between 200-299, it's valid: |
|
81 if (responseStatus / 100 == 2) |
|
82 return NS_BINDING_SUCCEEDED; |
|
83 |
|
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; |
|
91 |
|
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; |
|
95 |
|
96 nsCOMPtr<nsIURI> uri; |
|
97 uint32_t loadFlags; |
|
98 |
|
99 rv = lastChannel->GetOriginalURI(getter_AddRefs(uri)); |
|
100 nsresult tmp = lastChannel->GetLoadFlags(&loadFlags); |
|
101 if (NS_FAILED(tmp)) { |
|
102 rv = tmp; |
|
103 } |
|
104 |
|
105 // XXX we are carrying over the load flags, but what about other |
|
106 // parameters that may have been set on lastChannel?? |
|
107 |
|
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 } |
|
126 |
|
127 // If we get here, assume the resource does not exist. |
|
128 return NS_BINDING_FAILED; |
|
129 } |
|
130 |
|
131 //----------------------------------------------------------------------------- |
|
132 // nsIURIChecker methods: |
|
133 //----------------------------------------------------------------------------- |
|
134 |
|
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; |
|
141 |
|
142 rv = ios->NewChannelFromURI(aURI, getter_AddRefs(mChannel)); |
|
143 if (NS_FAILED(rv)) return rv; |
|
144 |
|
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 } |
|
169 |
|
170 NS_IMETHODIMP |
|
171 nsURIChecker::AsyncCheck(nsIRequestObserver *aObserver, |
|
172 nsISupports *aObserverContext) |
|
173 { |
|
174 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); |
|
175 |
|
176 // Hook us up to listen to redirects and the like (this creates a reference |
|
177 // cycle!) |
|
178 mChannel->SetNotificationCallbacks(this); |
|
179 |
|
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 } |
|
192 |
|
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 } |
|
200 |
|
201 //----------------------------------------------------------------------------- |
|
202 // nsIRequest methods: |
|
203 //----------------------------------------------------------------------------- |
|
204 |
|
205 NS_IMETHODIMP |
|
206 nsURIChecker::GetName(nsACString &aName) |
|
207 { |
|
208 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); |
|
209 return mChannel->GetName(aName); |
|
210 } |
|
211 |
|
212 NS_IMETHODIMP |
|
213 nsURIChecker::IsPending(bool *aPendingRet) |
|
214 { |
|
215 *aPendingRet = mIsPending; |
|
216 return NS_OK; |
|
217 } |
|
218 |
|
219 NS_IMETHODIMP |
|
220 nsURIChecker::GetStatus(nsresult* aStatusRet) |
|
221 { |
|
222 *aStatusRet = mStatus; |
|
223 return NS_OK; |
|
224 } |
|
225 |
|
226 NS_IMETHODIMP |
|
227 nsURIChecker::Cancel(nsresult status) |
|
228 { |
|
229 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); |
|
230 return mChannel->Cancel(status); |
|
231 } |
|
232 |
|
233 NS_IMETHODIMP |
|
234 nsURIChecker::Suspend() |
|
235 { |
|
236 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); |
|
237 return mChannel->Suspend(); |
|
238 } |
|
239 |
|
240 NS_IMETHODIMP |
|
241 nsURIChecker::Resume() |
|
242 { |
|
243 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); |
|
244 return mChannel->Resume(); |
|
245 } |
|
246 |
|
247 NS_IMETHODIMP |
|
248 nsURIChecker::GetLoadGroup(nsILoadGroup **aLoadGroup) |
|
249 { |
|
250 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); |
|
251 return mChannel->GetLoadGroup(aLoadGroup); |
|
252 } |
|
253 |
|
254 NS_IMETHODIMP |
|
255 nsURIChecker::SetLoadGroup(nsILoadGroup *aLoadGroup) |
|
256 { |
|
257 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); |
|
258 return mChannel->SetLoadGroup(aLoadGroup); |
|
259 } |
|
260 |
|
261 NS_IMETHODIMP |
|
262 nsURIChecker::GetLoadFlags(nsLoadFlags *aLoadFlags) |
|
263 { |
|
264 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); |
|
265 return mChannel->GetLoadFlags(aLoadFlags); |
|
266 } |
|
267 |
|
268 NS_IMETHODIMP |
|
269 nsURIChecker::SetLoadFlags(nsLoadFlags aLoadFlags) |
|
270 { |
|
271 NS_ENSURE_TRUE(mChannel, NS_ERROR_NOT_INITIALIZED); |
|
272 return mChannel->SetLoadFlags(aLoadFlags); |
|
273 } |
|
274 |
|
275 //----------------------------------------------------------------------------- |
|
276 // nsIRequestObserver methods: |
|
277 //----------------------------------------------------------------------------- |
|
278 |
|
279 NS_IMETHODIMP |
|
280 nsURIChecker::OnStartRequest(nsIRequest *aRequest, nsISupports *aCtxt) |
|
281 { |
|
282 NS_ASSERTION(aRequest == mChannel, "unexpected request"); |
|
283 |
|
284 nsresult rv = CheckStatus(); |
|
285 if (rv != NS_BASE_STREAM_WOULD_BLOCK) |
|
286 SetStatusAndCallBack(rv); |
|
287 |
|
288 // cancel the request (we don't care to look at the data). |
|
289 return NS_BINDING_ABORTED; |
|
290 } |
|
291 |
|
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 } |
|
305 |
|
306 //----------------------------------------------------------------------------- |
|
307 // nsIStreamListener methods: |
|
308 //----------------------------------------------------------------------------- |
|
309 |
|
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 } |
|
318 |
|
319 //----------------------------------------------------------------------------- |
|
320 // nsIInterfaceRequestor methods: |
|
321 //----------------------------------------------------------------------------- |
|
322 |
|
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 } |
|
333 |
|
334 //----------------------------------------------------------------------------- |
|
335 // nsIChannelEventSink methods: |
|
336 //----------------------------------------------------------------------------- |
|
337 |
|
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 } |