/src/mozilla-central/dom/clients/manager/ClientChannelHelper.cpp
Line | Count | Source (jump to first uncovered line) |
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 "ClientChannelHelper.h" |
8 | | |
9 | | #include "ClientManager.h" |
10 | | #include "ClientSource.h" |
11 | | #include "MainThreadUtils.h" |
12 | | #include "mozilla/dom/ServiceWorkerDescriptor.h" |
13 | | #include "mozilla/ipc/BackgroundUtils.h" |
14 | | #include "nsContentUtils.h" |
15 | | #include "nsIAsyncVerifyRedirectCallback.h" |
16 | | #include "nsIChannel.h" |
17 | | #include "nsIChannelEventSink.h" |
18 | | #include "nsIDocShell.h" |
19 | | #include "nsIHttpChannelInternal.h" |
20 | | #include "nsIInterfaceRequestor.h" |
21 | | #include "nsIInterfaceRequestorUtils.h" |
22 | | |
23 | | namespace mozilla { |
24 | | namespace dom { |
25 | | |
26 | | using mozilla::ipc::PrincipalInfoToPrincipal; |
27 | | |
28 | | namespace { |
29 | | |
30 | | class ClientChannelHelper final : public nsIInterfaceRequestor |
31 | | , public nsIChannelEventSink |
32 | | { |
33 | | nsCOMPtr<nsIInterfaceRequestor> mOuter; |
34 | | nsCOMPtr<nsISerialEventTarget> mEventTarget; |
35 | | |
36 | 0 | ~ClientChannelHelper() = default; |
37 | | |
38 | | NS_IMETHOD |
39 | | GetInterface(const nsIID& aIID, void** aResultOut) override |
40 | 0 | { |
41 | 0 | if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { |
42 | 0 | *aResultOut = static_cast<nsIChannelEventSink*>(this); |
43 | 0 | NS_ADDREF_THIS(); |
44 | 0 | return NS_OK; |
45 | 0 | } |
46 | 0 |
|
47 | 0 | if (mOuter) { |
48 | 0 | return mOuter->GetInterface(aIID, aResultOut); |
49 | 0 | } |
50 | 0 | |
51 | 0 | return NS_ERROR_NO_INTERFACE; |
52 | 0 | } |
53 | | |
54 | | NS_IMETHOD |
55 | | AsyncOnChannelRedirect(nsIChannel* aOldChannel, |
56 | | nsIChannel* aNewChannel, |
57 | | uint32_t aFlags, |
58 | | nsIAsyncVerifyRedirectCallback *aCallback) override |
59 | 0 | { |
60 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
61 | 0 |
|
62 | 0 | nsCOMPtr<nsILoadInfo> oldLoadInfo; |
63 | 0 | nsresult rv = aOldChannel->GetLoadInfo(getter_AddRefs(oldLoadInfo)); |
64 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
65 | 0 |
|
66 | 0 | nsCOMPtr<nsILoadInfo> newLoadInfo; |
67 | 0 | rv = aNewChannel->GetLoadInfo(getter_AddRefs(newLoadInfo)); |
68 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
69 | 0 |
|
70 | 0 | rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel); |
71 | 0 | if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_DOM_BAD_URI)) { |
72 | 0 | return rv; |
73 | 0 | } |
74 | 0 | |
75 | 0 | UniquePtr<ClientSource> reservedClient = oldLoadInfo->TakeReservedClientSource(); |
76 | 0 |
|
77 | 0 | // If its a same-origin redirect we just move our reserved client to the |
78 | 0 | // new channel. |
79 | 0 | if (NS_SUCCEEDED(rv)) { |
80 | 0 | if (reservedClient) { |
81 | 0 | newLoadInfo->GiveReservedClientSource(std::move(reservedClient)); |
82 | 0 | } |
83 | 0 | |
84 | 0 | // It seems sometimes necko passes two channels with the same LoadInfo. |
85 | 0 | // We only need to move the reserved/initial ClientInfo over if we |
86 | 0 | // actually have a different LoadInfo. |
87 | 0 | else if (oldLoadInfo != newLoadInfo) { |
88 | 0 | const Maybe<ClientInfo>& reservedClientInfo = |
89 | 0 | oldLoadInfo->GetReservedClientInfo(); |
90 | 0 |
|
91 | 0 | const Maybe<ClientInfo>& initialClientInfo = |
92 | 0 | oldLoadInfo->GetInitialClientInfo(); |
93 | 0 |
|
94 | 0 | MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() || |
95 | 0 | initialClientInfo.isNothing()); |
96 | 0 |
|
97 | 0 | if (reservedClientInfo.isSome()) { |
98 | 0 | newLoadInfo->SetReservedClientInfo(reservedClientInfo.ref()); |
99 | 0 | } |
100 | 0 |
|
101 | 0 | if (initialClientInfo.isSome()) { |
102 | 0 | newLoadInfo->SetInitialClientInfo(initialClientInfo.ref()); |
103 | 0 | } |
104 | 0 | } |
105 | 0 | } |
106 | 0 |
|
107 | 0 | // If it's a cross-origin redirect then we discard the old reserved client |
108 | 0 | // and create a new one. |
109 | 0 | else { |
110 | 0 | // If CheckSameOrigin() worked, then the security manager must exist. |
111 | 0 | nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); |
112 | 0 | MOZ_DIAGNOSTIC_ASSERT(ssm); |
113 | 0 |
|
114 | 0 | nsCOMPtr<nsIPrincipal> principal; |
115 | 0 | rv = ssm->GetChannelResultPrincipal(aNewChannel, getter_AddRefs(principal)); |
116 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
117 | 0 |
|
118 | 0 | // Create the new ClientSource. This should only happen for window |
119 | 0 | // Clients since support cross-origin redirects are blocked by the |
120 | 0 | // same-origin security policy. |
121 | 0 | reservedClient.reset(); |
122 | 0 | reservedClient = ClientManager::CreateSource(ClientType::Window, |
123 | 0 | mEventTarget, principal); |
124 | 0 | MOZ_DIAGNOSTIC_ASSERT(reservedClient); |
125 | 0 |
|
126 | 0 | newLoadInfo->GiveReservedClientSource(std::move(reservedClient)); |
127 | 0 | } |
128 | 0 |
|
129 | 0 | uint32_t redirectMode = nsIHttpChannelInternal::REDIRECT_MODE_MANUAL; |
130 | 0 | nsCOMPtr<nsIHttpChannelInternal> http = do_QueryInterface(aOldChannel); |
131 | 0 | if (http) { |
132 | 0 | MOZ_ALWAYS_SUCCEEDS(http->GetRedirectMode(&redirectMode)); |
133 | 0 | } |
134 | 0 |
|
135 | 0 | // Normally we keep the controller across channel redirects, but we must |
136 | 0 | // clear it when a document load redirects. Only do this for real |
137 | 0 | // redirects, however. |
138 | 0 | // |
139 | 0 | // This is effectively described in step 4.2 of: |
140 | 0 | // |
141 | 0 | // https://fetch.spec.whatwg.org/#http-fetch |
142 | 0 | // |
143 | 0 | // The spec sets the service-workers mode to none when the request is |
144 | 0 | // configured to *not* follow redirects. This prevents any further |
145 | 0 | // service workers from intercepting. The first service worker that |
146 | 0 | // had a shot at the FetchEvent remains the controller in this case. |
147 | 0 | if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) && |
148 | 0 | redirectMode != nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW) { |
149 | 0 | newLoadInfo->ClearController(); |
150 | 0 | } |
151 | 0 |
|
152 | 0 | nsCOMPtr<nsIChannelEventSink> outerSink = do_GetInterface(mOuter); |
153 | 0 | if (outerSink) { |
154 | 0 | return outerSink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, |
155 | 0 | aCallback); |
156 | 0 | } |
157 | 0 | |
158 | 0 | aCallback->OnRedirectVerifyCallback(NS_OK); |
159 | 0 | return NS_OK; |
160 | 0 | } |
161 | | |
162 | | public: |
163 | | ClientChannelHelper(nsIInterfaceRequestor* aOuter, |
164 | | nsISerialEventTarget* aEventTarget) |
165 | | : mOuter(aOuter) |
166 | | , mEventTarget(aEventTarget) |
167 | 0 | { |
168 | 0 | } |
169 | | |
170 | | NS_DECL_ISUPPORTS |
171 | | }; |
172 | | |
173 | | NS_IMPL_ISUPPORTS(ClientChannelHelper, nsIInterfaceRequestor, |
174 | | nsIChannelEventSink); |
175 | | |
176 | | } // anonymous namespace |
177 | | |
178 | | nsresult |
179 | | AddClientChannelHelper(nsIChannel* aChannel, |
180 | | Maybe<ClientInfo>&& aReservedClientInfo, |
181 | | Maybe<ClientInfo>&& aInitialClientInfo, |
182 | | nsISerialEventTarget* aEventTarget) |
183 | 0 | { |
184 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
185 | 0 |
|
186 | 0 | Maybe<ClientInfo> initialClientInfo(std::move(aInitialClientInfo)); |
187 | 0 | Maybe<ClientInfo> reservedClientInfo(std::move(aReservedClientInfo)); |
188 | 0 | MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() || |
189 | 0 | initialClientInfo.isNothing()); |
190 | 0 |
|
191 | 0 | nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); |
192 | 0 | NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE); |
193 | 0 |
|
194 | 0 | nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); |
195 | 0 | NS_ENSURE_TRUE(ssm, NS_ERROR_FAILURE); |
196 | 0 |
|
197 | 0 | nsCOMPtr<nsIPrincipal> channelPrincipal; |
198 | 0 | nsresult rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(channelPrincipal)); |
199 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
200 | 0 |
|
201 | 0 | // Only allow the initial ClientInfo to be set if the current channel |
202 | 0 | // principal matches. |
203 | 0 | if (initialClientInfo.isSome()) { |
204 | 0 | nsCOMPtr<nsIPrincipal> initialPrincipal = |
205 | 0 | PrincipalInfoToPrincipal(initialClientInfo.ref().PrincipalInfo(), nullptr); |
206 | 0 |
|
207 | 0 | bool equals = false; |
208 | 0 | rv = initialPrincipal ? initialPrincipal->Equals(channelPrincipal, &equals) |
209 | 0 | : NS_ERROR_FAILURE; |
210 | 0 | if (NS_FAILED(rv) || !equals) { |
211 | 0 | initialClientInfo.reset(); |
212 | 0 | } |
213 | 0 | } |
214 | 0 |
|
215 | 0 | // Only allow the reserved ClientInfo to be set if the current channel |
216 | 0 | // principal matches. |
217 | 0 | if (reservedClientInfo.isSome()) { |
218 | 0 | nsCOMPtr<nsIPrincipal> reservedPrincipal = |
219 | 0 | PrincipalInfoToPrincipal(reservedClientInfo.ref().PrincipalInfo(), nullptr); |
220 | 0 |
|
221 | 0 | bool equals = false; |
222 | 0 | rv = reservedPrincipal ? reservedPrincipal->Equals(channelPrincipal, &equals) |
223 | 0 | : NS_ERROR_FAILURE; |
224 | 0 | if (NS_FAILED(rv) || !equals) { |
225 | 0 | reservedClientInfo.reset(); |
226 | 0 | } |
227 | 0 | } |
228 | 0 |
|
229 | 0 | nsCOMPtr<nsIInterfaceRequestor> outerCallbacks; |
230 | 0 | rv = aChannel->GetNotificationCallbacks(getter_AddRefs(outerCallbacks)); |
231 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
232 | 0 |
|
233 | 0 | UniquePtr<ClientSource> reservedClient; |
234 | 0 | if (initialClientInfo.isNothing() && reservedClientInfo.isNothing()) { |
235 | 0 | // Wait to reserve the client until we are reasonably sure this method |
236 | 0 | // will succeed. We should only follow this path for window clients. |
237 | 0 | // Workers should always provide a reserved ClientInfo since their |
238 | 0 | // ClientSource object is owned by a different thread. |
239 | 0 | reservedClient = ClientManager::CreateSource(ClientType::Window, |
240 | 0 | aEventTarget, |
241 | 0 | channelPrincipal); |
242 | 0 | MOZ_DIAGNOSTIC_ASSERT(reservedClient); |
243 | 0 | } |
244 | 0 |
|
245 | 0 | RefPtr<ClientChannelHelper> helper = |
246 | 0 | new ClientChannelHelper(outerCallbacks, aEventTarget); |
247 | 0 |
|
248 | 0 | // Only set the callbacks helper if we are able to reserve the client |
249 | 0 | // successfully. |
250 | 0 | rv = aChannel->SetNotificationCallbacks(helper); |
251 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
252 | 0 |
|
253 | 0 | // Finally preserve the various client values on the nsILoadInfo once the |
254 | 0 | // redirect helper has been added to the channel. |
255 | 0 | if (reservedClient) { |
256 | 0 | loadInfo->GiveReservedClientSource(std::move(reservedClient)); |
257 | 0 | } |
258 | 0 |
|
259 | 0 | if (initialClientInfo.isSome()) { |
260 | 0 | loadInfo->SetInitialClientInfo(initialClientInfo.ref()); |
261 | 0 | } |
262 | 0 |
|
263 | 0 | if (reservedClientInfo.isSome()) { |
264 | 0 | loadInfo->SetReservedClientInfo(reservedClientInfo.ref()); |
265 | 0 | } |
266 | 0 |
|
267 | 0 | return NS_OK; |
268 | 0 | } |
269 | | |
270 | | } // namespace dom |
271 | | } // namespace mozilla |