Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "ThirdPartyUtil.h"
8 : #include "nsNetCID.h"
9 : #include "nsNetUtil.h"
10 : #include "nsIChannel.h"
11 : #include "nsIServiceManager.h"
12 : #include "nsIHttpChannelInternal.h"
13 : #include "nsIDOMWindow.h"
14 : #include "nsILoadContext.h"
15 : #include "nsIPrincipal.h"
16 : #include "nsIScriptObjectPrincipal.h"
17 : #include "nsIURI.h"
18 : #include "nsThreadUtils.h"
19 : #include "mozilla/Logging.h"
20 : #include "nsPIDOMWindow.h"
21 :
22 286 : NS_IMPL_ISUPPORTS(ThirdPartyUtil, mozIThirdPartyUtil)
23 :
24 : //
25 : // MOZ_LOG=thirdPartyUtil:5
26 : //
27 : static mozilla::LazyLogModule gThirdPartyLog("thirdPartyUtil");
28 : #undef LOG
29 : #define LOG(args) MOZ_LOG(gThirdPartyLog, mozilla::LogLevel::Debug, args)
30 :
31 : nsresult
32 2 : ThirdPartyUtil::Init()
33 : {
34 2 : NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_AVAILABLE);
35 :
36 : nsresult rv;
37 2 : mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
38 :
39 2 : return rv;
40 : }
41 :
42 : // Determine if aFirstDomain is a different base domain to aSecondURI; or, if
43 : // the concept of base domain does not apply, determine if the two hosts are not
44 : // string-identical.
45 : nsresult
46 14 : ThirdPartyUtil::IsThirdPartyInternal(const nsCString& aFirstDomain,
47 : nsIURI* aSecondURI,
48 : bool* aResult)
49 : {
50 14 : if (!aSecondURI) {
51 3 : return NS_ERROR_INVALID_ARG;
52 : }
53 :
54 : // Get the base domain for aSecondURI.
55 22 : nsCString secondDomain;
56 11 : nsresult rv = GetBaseDomain(aSecondURI, secondDomain);
57 11 : LOG(("ThirdPartyUtil::IsThirdPartyInternal %s =? %s", aFirstDomain.get(), secondDomain.get()));
58 11 : if (NS_FAILED(rv))
59 0 : return rv;
60 :
61 : // Check strict equality.
62 11 : *aResult = aFirstDomain != secondDomain;
63 11 : return NS_OK;
64 : }
65 :
66 : // Get the URI associated with a window.
67 : NS_IMETHODIMP
68 59 : ThirdPartyUtil::GetURIFromWindow(mozIDOMWindowProxy* aWin, nsIURI** result)
69 : {
70 : nsresult rv;
71 118 : nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin = do_QueryInterface(aWin);
72 59 : if (!scriptObjPrin) {
73 0 : return NS_ERROR_INVALID_ARG;
74 : }
75 :
76 59 : nsIPrincipal* prin = scriptObjPrin->GetPrincipal();
77 59 : if (!prin) {
78 0 : return NS_ERROR_INVALID_ARG;
79 : }
80 :
81 59 : if (prin->GetIsNullPrincipal()) {
82 1 : LOG(("ThirdPartyUtil::GetURIFromWindow can't use null principal\n"));
83 1 : return NS_ERROR_INVALID_ARG;
84 : }
85 :
86 58 : rv = prin->GetURI(result);
87 58 : return rv;
88 : }
89 :
90 : // Determine if aFirstURI is third party with respect to aSecondURI. See docs
91 : // for mozIThirdPartyUtil.
92 : NS_IMETHODIMP
93 3 : ThirdPartyUtil::IsThirdPartyURI(nsIURI* aFirstURI,
94 : nsIURI* aSecondURI,
95 : bool* aResult)
96 : {
97 3 : NS_ENSURE_ARG(aFirstURI);
98 3 : NS_ENSURE_ARG(aSecondURI);
99 2 : NS_ASSERTION(aResult, "null outparam pointer");
100 :
101 4 : nsCString firstHost;
102 2 : nsresult rv = GetBaseDomain(aFirstURI, firstHost);
103 2 : if (NS_FAILED(rv))
104 0 : return rv;
105 :
106 2 : return IsThirdPartyInternal(firstHost, aSecondURI, aResult);
107 : }
108 :
109 : // Determine if any URI of the window hierarchy of aWindow is foreign with
110 : // respect to aSecondURI. See docs for mozIThirdPartyUtil.
111 : NS_IMETHODIMP
112 55 : ThirdPartyUtil::IsThirdPartyWindow(mozIDOMWindowProxy* aWindow,
113 : nsIURI* aURI,
114 : bool* aResult)
115 : {
116 55 : NS_ENSURE_ARG(aWindow);
117 55 : NS_ASSERTION(aResult, "null outparam pointer");
118 :
119 : bool result;
120 :
121 : // Get the URI of the window, and its base domain.
122 : nsresult rv;
123 110 : nsCOMPtr<nsIURI> currentURI;
124 55 : rv = GetURIFromWindow(aWindow, getter_AddRefs(currentURI));
125 55 : if (NS_FAILED(rv))
126 0 : return rv;
127 :
128 110 : nsCString bottomDomain;
129 55 : rv = GetBaseDomain(currentURI, bottomDomain);
130 55 : if (NS_FAILED(rv))
131 50 : return rv;
132 :
133 5 : if (aURI) {
134 : // Determine whether aURI is foreign with respect to currentURI.
135 0 : rv = IsThirdPartyInternal(bottomDomain, aURI, &result);
136 0 : if (NS_FAILED(rv))
137 0 : return rv;
138 :
139 0 : if (result) {
140 0 : *aResult = true;
141 0 : return NS_OK;
142 : }
143 : }
144 :
145 10 : nsCOMPtr<nsPIDOMWindowOuter> current = nsPIDOMWindowOuter::From(aWindow), parent;
146 10 : nsCOMPtr<nsIURI> parentURI;
147 : do {
148 : // We use GetScriptableParent rather than GetParent because we consider
149 : // <iframe mozbrowser> to be a top-level frame.
150 5 : parent = current->GetScriptableParent();
151 5 : if (SameCOMIdentity(parent, current)) {
152 : // We're at the topmost content window. We already know the answer.
153 5 : *aResult = false;
154 5 : return NS_OK;
155 : }
156 :
157 0 : rv = GetURIFromWindow(parent, getter_AddRefs(parentURI));
158 0 : NS_ENSURE_SUCCESS(rv, rv);
159 :
160 0 : rv = IsThirdPartyInternal(bottomDomain, parentURI, &result);
161 0 : if (NS_FAILED(rv))
162 0 : return rv;
163 :
164 0 : if (result) {
165 0 : *aResult = true;
166 0 : return NS_OK;
167 : }
168 :
169 0 : current = parent;
170 0 : currentURI = parentURI;
171 : } while (1);
172 :
173 : NS_NOTREACHED("should've returned");
174 : return NS_ERROR_UNEXPECTED;
175 : }
176 :
177 : // Determine if the URI associated with aChannel or any URI of the window
178 : // hierarchy associated with the channel is foreign with respect to aSecondURI.
179 : // See docs for mozIThirdPartyUtil.
180 : NS_IMETHODIMP
181 9 : ThirdPartyUtil::IsThirdPartyChannel(nsIChannel* aChannel,
182 : nsIURI* aURI,
183 : bool* aResult)
184 : {
185 9 : LOG(("ThirdPartyUtil::IsThirdPartyChannel [channel=%p]", aChannel));
186 9 : NS_ENSURE_ARG(aChannel);
187 9 : NS_ASSERTION(aResult, "null outparam pointer");
188 :
189 : nsresult rv;
190 9 : bool doForce = false;
191 : nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
192 18 : do_QueryInterface(aChannel);
193 9 : if (httpChannelInternal) {
194 : uint32_t flags;
195 9 : rv = httpChannelInternal->GetThirdPartyFlags(&flags);
196 9 : NS_ENSURE_SUCCESS(rv, rv);
197 :
198 9 : doForce = (flags & nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
199 :
200 : // If aURI was not supplied, and we're forcing, then we're by definition
201 : // not foreign. If aURI was supplied, we still want to check whether it's
202 : // foreign with respect to the channel URI. (The forcing only applies to
203 : // whatever window hierarchy exists above the channel.)
204 9 : if (doForce && !aURI) {
205 0 : *aResult = false;
206 0 : return NS_OK;
207 : }
208 : }
209 :
210 9 : bool parentIsThird = false;
211 :
212 : // Obtain the URI from the channel, and its base domain.
213 18 : nsCOMPtr<nsIURI> channelURI;
214 9 : rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
215 9 : if (NS_FAILED(rv))
216 0 : return rv;
217 :
218 18 : nsCString channelDomain;
219 9 : rv = GetBaseDomain(channelURI, channelDomain);
220 9 : if (NS_FAILED(rv))
221 0 : return rv;
222 :
223 9 : if (!doForce) {
224 15 : if (nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo()) {
225 9 : parentIsThird = loadInfo->GetIsInThirdPartyContext();
226 18 : if (!parentIsThird &&
227 9 : loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) {
228 : // Check if the channel itself is third-party to its own requestor.
229 : // Unforunately, we have to go through the loading principal.
230 13 : nsCOMPtr<nsIURI> parentURI;
231 8 : loadInfo->LoadingPrincipal()->GetURI(getter_AddRefs(parentURI));
232 8 : rv = IsThirdPartyInternal(channelDomain, parentURI, &parentIsThird);
233 8 : if (NS_FAILED(rv))
234 3 : return rv;
235 : }
236 : } else {
237 0 : NS_WARNING("Found channel with no loadinfo, assuming third-party request");
238 0 : parentIsThird = true;
239 : }
240 : }
241 :
242 : // If we're not comparing to a URI, we have our answer. Otherwise, if
243 : // parentIsThird, we're not forcing and we know that we're a third-party
244 : // request.
245 6 : if (!aURI || parentIsThird) {
246 2 : *aResult = parentIsThird;
247 2 : return NS_OK;
248 : }
249 :
250 : // Determine whether aURI is foreign with respect to channelURI.
251 4 : return IsThirdPartyInternal(channelDomain, aURI, aResult);
252 : }
253 :
254 : NS_IMETHODIMP
255 5 : ThirdPartyUtil::GetTopWindowForChannel(nsIChannel* aChannel, mozIDOMWindowProxy** aWin)
256 : {
257 5 : NS_ENSURE_ARG(aWin);
258 :
259 : // Find the associated window and its parent window.
260 10 : nsCOMPtr<nsILoadContext> ctx;
261 5 : NS_QueryNotificationCallbacks(aChannel, ctx);
262 5 : if (!ctx) {
263 0 : return NS_ERROR_INVALID_ARG;
264 : }
265 :
266 10 : nsCOMPtr<mozIDOMWindowProxy> window;
267 5 : ctx->GetAssociatedWindow(getter_AddRefs(window));
268 5 : if (!window) {
269 1 : return NS_ERROR_INVALID_ARG;
270 : }
271 :
272 8 : nsCOMPtr<nsPIDOMWindowOuter> top = nsPIDOMWindowOuter::From(window)->GetTop();
273 4 : top.forget(aWin);
274 4 : return NS_OK;
275 : }
276 :
277 : // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
278 : // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
279 : // dot may be present. If aHostURI is an IP address, an alias such as
280 : // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
281 : // be the exact host. The result of this function should only be used in exact
282 : // string comparisons, since substring comparisons will not be valid for the
283 : // special cases elided above.
284 : NS_IMETHODIMP
285 100 : ThirdPartyUtil::GetBaseDomain(nsIURI* aHostURI,
286 : nsACString& aBaseDomain)
287 : {
288 100 : if (!aHostURI) {
289 50 : return NS_ERROR_INVALID_ARG;
290 : }
291 :
292 : // Get the base domain. this will fail if the host contains a leading dot,
293 : // more than one trailing dot, or is otherwise malformed.
294 50 : nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
295 50 : if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
296 : rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
297 : // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
298 : // such as 'co.uk', or the empty string. Uses the normalized host in such
299 : // cases.
300 50 : rv = aHostURI->GetAsciiHost(aBaseDomain);
301 : }
302 50 : NS_ENSURE_SUCCESS(rv, rv);
303 :
304 : // aHostURI (and thus aBaseDomain) may be the string '.'. If so, fail.
305 50 : if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.')
306 0 : return NS_ERROR_INVALID_ARG;
307 :
308 : // Reject any URIs without a host that aren't file:// URIs. This makes it the
309 : // only way we can get a base domain consisting of the empty string, which
310 : // means we can safely perform foreign tests on such URIs where "not foreign"
311 : // means "the involved URIs are all file://".
312 50 : if (aBaseDomain.IsEmpty()) {
313 0 : bool isFileURI = false;
314 0 : aHostURI->SchemeIs("file", &isFileURI);
315 0 : if (!isFileURI) {
316 0 : return NS_ERROR_INVALID_ARG;
317 : }
318 : }
319 :
320 50 : return NS_OK;
321 : }
|