Closed Bug 1171215 Opened 9 years ago Closed 9 years ago

[e10s] Websockets upgrade request does not include cookies with e10s

Categories

(Core :: Networking: WebSockets, defect)

40 Branch
defect
Not set
major

Tracking

()

RESOLVED FIXED
mozilla45
Tracking Status
e10s m8+ ---
firefox45 --- fixed

People

(Reporter: me, Assigned: mrbkap)

References

Details

Attachments

(1 file, 11 obsolete files)

33.35 KB, patch
sicking
: review+
ckerschb
: review+
Details | Diff | Splinter Review
I have an app that connects to wss:// on the same origin, and it seems that with e10s enabled, no cookie headers are sent over the websocket. Once I disable e10s, cookie headers are there as expected.
Blocks: e10s
Status: NEW → UNCONFIRMED
Ever confirmed: false
interesting. michal do you want to dive into it?
Flags: needinfo?(michal.novotny)
I'll have a look at it.
Assignee: nobody → michal.novotny
Flags: needinfo?(michal.novotny)
Summary: [e10s] Secure Websockets do not send cookies with e10s enabled → [e10s] Websockets do not send cookies with e10s enabled
I can also confirm that regular unencrypted websockets don't seem to send any cookie headers under e10s.
Is there any testcase available showing this problem?
Flags: needinfo?(me)
I've set up a simple server to echo headers received by http and websocket here:

  http://wstest.silverwind.io/

The page sets a cookie 'some=value', you need to refresh the page once to see it in the headers. If you don't see the cookie appearing on the websocket headers (which are obtained from the upgrade request), you have reproduced the bug.
Flags: needinfo?(me)
I can't reproduce the bug. I tested it with latest m-c source as well as with firefox developer edition (on linux and windows). I always see an expected behavior, i.e. during the first load the cookie is present only in websocket headers, during any subsequent load the cookie is present in both headers.

Do I understand correctly that you see the cookie in HTTP headers but not in websocket headers when reloading the page? Or is it missing in HTTP headers too? What addons do you have installed?

Could you please provide a NSPR log of the load and reload? Use NSPR_LOG_MODULE=nsHttp:5,cookie:5 and don't forget to set GECKO_SEPARATE_NSPR_LOGS=1.
You're right. It looks to be addon or profile related as I'm not seeing it in safe mode. I'll try to track it down further.
Or rather, scrap that. I was having an weird issue with page rendering in safe mode (OS X) so I disabled Hardware acceleration, unknowingly disabling e10s in that test too.

So, it's still happening for me. The issue is that the websocket upgrade request never includes any cookie. not on first load or any subsequent loads.

I'll try your logging suggestion and/or a fresh profile.
I think I found the cause. From the log:

2018988032[102025400]: ===== COOKIE NOT SENT =====
2018988032[102025400]: request URL: http://wstest.silverwind.io/
2018988032[102025400]: current time: Mon Jul 13 22:25:18 2015 GMT
2018988032[102025400]: rejected because context is third party

You should be able to reproduce with third-party cookies disabled and e10s enabled. For some reason the Upgrade request is wrongly considered third-party it seems. If you need more details from the log, I could mail it to you on request.
silverwind, thanks for the test case. I can reproduce it when third-party cookies are disabled. Websocket's http channel is considered as third party channel in e10s but not in single process. There is no associated window in the parent process so we fail here at http://hg.mozilla.org/mozilla-central/annotate/e786406bc683/dom/base/ThirdPartyUtil.cpp#l275. I don't know this code. Blake, could you please have a look at it?
Flags: needinfo?(mrbkap)
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true
Steve, would anyone on your team be a logical person to figure out why Websocket bootstrap channels are being treated as 3rd party (only in e10s)?
Flags: needinfo?(sworkman)
This is because we're sticking a ton of stuff on nsIHttpChannelInternal. For bug 1049299, I added two flags, "THIRD_PARTY_IS_{THIRD,SAME}_PARTY" that are computed in the child and passed to the parent so that the parent doesn't have to do any computation on DOM windows that we can't pass to it. However, because that information only lives on nsIHttpChannelInternal[1], the websocket channel ends up trying to do a computation that it doesn't have enough data for.

The easiest way to fix this bug would probably be to pull the third-party stuff out of nsIHttpChannelInternal and into some other interface (nsIThirdPartyChannel?) and pass the same information, hopefully refactored, with websocket channels in order to use it in ThirdPartyUtil.cpp.

[1] http://hg.mozilla.org/mozilla-central/annotate/e786406bc683/dom/base/ThirdPartyUtil.cpp#l194
Flags: needinfo?(mrbkap)
Looks like Blake had the answer here. Clearing needinfo.
Flags: needinfo?(sworkman)
Assignee: michal.novotny → mrbkap
Flags: needinfo?(sworkman)
Flags: needinfo?(sworkman)
Summary: [e10s] Websockets do not send cookies with e10s enabled → [e10s] Websockets upgrade request does not include cookies with e10s
Todo: Check what happens for shared workers and dedicated workers (and dedicated workers in shared workers) for the cases where the workers are created by iframes that are both same- and cross-origin to their parents.

For shared workers, we should consider the request to be third party and block cookies if the pref requests it and for dedicated workers ideally we should figure out if we can reach a document and use that to check thirdparty-ness.
Attached patch Patch v1 (obsolete) — Splinter Review
I still need to write tests for this (and to test the answers to comment 14: shared workers' cookies are always considered to be third-party and dedicated workers' cookies are correctly attributed to the page that created them) but this patch seems to work. I have a try run at:

https://treeherder.mozilla.org/#/jobs?repo=try&revision=af002fd1a978
Attachment #8675978 - Flags: review?(mozilla)
Attachment #8675978 - Flags: review?(jonas)
Comment on attachment 8675978 [details] [diff] [review]
Patch v1

This is rather orange on try.
Attachment #8675978 - Flags: review?(mozilla)
Attachment #8675978 - Flags: review?(jonas)
I need to think about this more. The loadinfo doesn't have enough information in its constructor (oddly enough) to do this calculation.
We can probably do this in nsIOService::NewChannel...Internal.
Attached patch Patch v2 (obsolete) — Splinter Review
This patch seems more correct. I still have to make sure that redirects work correctly and I need to step through e10s's codepath since I think it currently tries to calculate third-partyness in the parent as well.

Jonas, what do you think?
Attachment #8678393 - Flags: feedback?(jonas)
Attachment #8675978 - Attachment is obsolete: true
Comment on attachment 8678393 [details] [diff] [review]
Patch v2

Review of attachment 8678393 [details] [diff] [review]:
-----------------------------------------------------------------

Looks like a good approach.

::: dom/base/ThirdPartyUtil.cpp
@@ +216,5 @@
> +    // the child and send the result to the parent via the loadinfo.
> +    // Note: All channels *should* have a loadinfo, but it's possible for
> +    // extensions to create one that doesn't. In that case, we'll try to check
> +    // the parent chain using the notification callbacks but note that this
> +    // won't work in e10s.

Is it really worth doing complex fallback here? It should be easy for addons to create channels with loadinfo these days, and if they don't, it doesn't seem like a big deal that we give them a static default (i.e. always 3rd party, or never 3rd party)

::: netwerk/base/nsILoadInfo.idl
@@ +230,5 @@
> +   * We don't have enough information in the constructor to know if this is a
> +   * third party load request, so we are called back when we do have that
> +   * information and can compute the answer.
> +   */
> +  [noscript] void notifyLoadingURI(in nsIURI aURI);

Is there a reason to not make this scriptable for JS-implemented channels?
Attachment #8678393 - Flags: feedback?(jonas) → feedback+
Attached patch Patch v3 (obsolete) — Splinter Review
This addresses Jonas' comments and appears to work in my testing. I still have to write an automated test for the websocket case.

The changes for redirects were tricky. I'm attaching them in a separate patch. Jonas tells me that there's a patch underway to have a LoadInfo per redirect and that'll make everything *so much better*, but until this lands, I had to do some crazy things to sync the state of "third-partyness" across the parent and the child as well as do the right thing if we failed the redirect (I haven't figured out how to test that case yet).

I'll request review once I have a green try run.

https://treeherder.mozilla.org/#/jobs?repo=try&revision=b5955a41df7e
Attachment #8678393 - Attachment is obsolete: true
Oh, and the changes to the control flow in ThirdPartyUtils::IsThirdPartyChannel could use a thorough double-checking. The interaction between forcing, whether a URI was passed and whether the window is in a third-party context makes for some exciting reading.
Attached patch Fixing tests (obsolete) — Splinter Review
I had to fix a few more callers of WebSocket.initLoadInfo.
Attachment #8679675 - Flags: review?(mozilla)
Attachment #8679675 - Flags: review?(jonas)
Attachment #8679195 - Attachment is obsolete: true
Attachment #8679196 - Attachment is obsolete: true
Attached patch Changes for redirects (obsolete) — Splinter Review
I had a couple of bugs in the last patch dealing with the failure case (in particular, if we failed, the child should only reset the third-partyness of its loadinfo and do nothing more; also, the parent should unconditionally set mOldIsThirdPartyRequest).
Attachment #8679676 - Flags: review?(mozilla)
Attachment #8679676 - Flags: review?(jonas)
Comment on attachment 8679676 [details] [diff] [review]
Changes for redirects

Review of attachment 8679676 [details] [diff] [review]:
-----------------------------------------------------------------

Bug 1194052 just landed, which should make your life and this patch a whole lot easier, right? No need to reset the thirdpartyness and such.
Attachment #8679676 - Flags: review?(mozilla)
Hi Christoph,

Reading through the patch that landed, there's one thing that I don't understand: why do we pass the current loadinfo to create the redirect channel and clone it after we create the channel? The way things are structured, that means that my patch here will overwrite the result of "mIsThirdPartyRequest" on both the original loadinfo and its clone. It's hard to work around this since there isn't really a better place to call NotifyLoadingURI (I really want to compute this on all new channels). Wouldn't it make sense to clone the loadinfo before creating the new channel?
Flags: needinfo?(mozilla)
Attached patch Rebased first patch (obsolete) — Splinter Review
Attachment #8682348 - Flags: review?(mozilla)
Attachment #8682348 - Flags: review?(jonas)
Attachment #8679675 - Attachment is obsolete: true
Attachment #8679676 - Attachment is obsolete: true
Attachment #8679675 - Flags: review?(mozilla)
Attachment #8679675 - Flags: review?(jonas)
Attachment #8679676 - Flags: review?(jonas)
Attached patch Updated redirect patch (obsolete) — Splinter Review
This implements what I suggest in comment 28. I left the nsBaseChannel as it was and just made the HTTP channels pre-clone the loadinfo.
Attachment #8682349 - Flags: review?(mozilla)
Attachment #8682349 - Flags: review?(jonas)
Flags: needinfo?(mozilla)
The try push in comment 32 was green but I don't understand why. I think the one from comment 33 is the right one!
Attachment #8682348 - Attachment is obsolete: true
Attachment #8682348 - Flags: review?(mozilla)
Attachment #8682348 - Flags: review?(jonas)
Attachment #8682349 - Attachment is obsolete: true
Attachment #8682349 - Flags: review?(mozilla)
Attachment #8682349 - Flags: review?(jonas)
Attached patch Base patch (obsolete) — Splinter Review
Attachment #8683995 - Flags: review?(mozilla)
Attachment #8683995 - Flags: review?(jonas)
Attached patch Fix redirects (obsolete) — Splinter Review
Attachment #8683997 - Flags: review?(mozilla)
Attachment #8683997 - Flags: review?(jonas)
I'm a little surprised that there wasn't something like this already in the tree.
Attachment #8683998 - Flags: review?(mozilla)
Attachment #8683998 - Flags: review?(jonas)
Comment on attachment 8683995 [details] [diff] [review]
Base patch

Review of attachment 8683995 [details] [diff] [review]:
-----------------------------------------------------------------

::: dom/base/ThirdPartyUtil.cpp
@@ +211,4 @@
>  
> +  if (!doForce) {
> +    nsCOMPtr<nsILoadInfo> loadInfo(aChannel->GetLoadInfo());
> +    if (MOZ_LIKELY(loadInfo)) {

nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
not sure if MOZ_LIKELY is really needed :-)

@@ +219,4 @@
>      }
> +  } else {
> +    parentIsThird = false;
> +  }

nit, you could initialize parentIsThird with false then you could safe that else branch, right?

::: dom/base/test/unit/test_thirdpartyutil.js
@@ +70,5 @@
>    // heirarchy. We leave that to mochitests.
>  
>    // Test isThirdPartyChannel. As above, we can't test the bits that require
> +  // a load context or window heirarchy. Because of that, the code assumes
> +  // that these are all third-party loads.

nit: s/heirarchy/hierarchy

::: netwerk/base/LoadInfo.cpp
@@ +256,5 @@
> +  }
> +
> +  // This is a subdocument load or a resource load.
> +  uint64_t windowID = 0;
> +  if (mInternalContentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {

I think it doesn't really make a difference at this point because TYPE_DOCUMENT and TYPE_SUBDOCUMENT do not have any different internal types, but maybe they have in the furture. Potentially it's safer to call
nsContentUtils::InternalContentPolicyTypeToExternal(mInternalContentPolicyType)
before doing the comparison - not completely sure though - just a thought.

::: netwerk/base/nsIOService.cpp
@@ +411,5 @@
>      // are in a captive portal, so we trigger a recheck.
>      RecheckCaptivePortalIfLocalRedirect(newChan);
>  
> +    nsCOMPtr<nsILoadInfo> loadInfo;
> +    newChan->GetLoadInfo(getter_AddRefs(loadInfo));

nit: simplify to:
nsCOMPtr<nsILoadInfo> loadInfo = newChan->GetLoadInfo();
Attachment #8683995 - Flags: review?(mozilla) → review+
Comment on attachment 8683997 [details] [diff] [review]
Fix redirects

Review of attachment 8683997 [details] [diff] [review]:
-----------------------------------------------------------------

::: netwerk/base/LoadInfo.cpp
@@ +271,5 @@
> +    // We can get here for redirects, but we don't have enough information to do
> +    // anything about it. Wait for the child to let us know.
> +
> +    return NS_OK;
> +  }

As discussed over IRC, it's slightly unfortunate for this bug that we clone the loadInfo in SetupReplacementChannel which happens after we need to update those flags. However, SetupReplacementChannel is the only central point where all redirects go through. Alternatively we would have to update all the callsites of SetupReplacementChannel and make sure the loadInfo is already cloned. Unfrotunately, we also can't clone it in the ioservice, because we potentially also have to deal with inner channels. 

To sum it up, I suppose this tri-state seems like the best option to me as well.

@@ +282,5 @@
>  
>    // This is a subdocument load or a resource load.
>    uint64_t windowID = 0;
> +  if (mInternalContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME ||
> +      mInternalContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME) {

Ok, I suppose that answers my question form the previous review.

::: netwerk/base/LoadInfo.h
@@ +109,5 @@
>    bool                             mInitialSecurityCheckDone;
> +  enum {
> +    SAME_ORIGIN,
> +    THIRD_PARTY,
> +    UNKNOWN

nit: can we rename that to UNKNOWN_THIRD_PARTY_STATE or something. UNKNOWN by itself seems rather confusing to me.

::: netwerk/protocol/http/HttpChannelParent.cpp
@@ +731,5 @@
>         this, result));
> +
> +  // Update our new channel's loadinfo's mIsThirdParty with the new value.
> +  nsCOMPtr<nsILoadInfo> loadInfo;
> +  mRedirectChannel->GetLoadInfo(getter_AddRefs(loadInfo));

nsCOMPtr<nsILoadInfo> loadInfo = mRedirectChannel->GetLoadInfo();
Attachment #8683997 - Flags: review?(mozilla) → review+
Comment on attachment 8683998 [details] [diff] [review]
Add a test for cookies in frames with redirection

Review of attachment 8683998 [details] [diff] [review]:
-----------------------------------------------------------------

::: dom/tests/mochitest/bugs/test_bug1171215.html
@@ +17,5 @@
> +  SimpleTest.waitForExplicitFinish();
> +
> +  /** Test for Bug 1022869 **/
> +  function startTest() {
> +    Math.sin(Math.random());

Why is that needed?
(In reply to Christoph Kerschbaumer [:ckerschb] from comment #41)
> > +    Math.sin(Math.random());
> 
> Why is that needed?

That was leftover from debugging. I've removed it locally (and added public domain notices to all of the tests).
Comment on attachment 8683998 [details] [diff] [review]
Add a test for cookies in frames with redirection

Review of attachment 8683998 [details] [diff] [review]:
-----------------------------------------------------------------

(In reply to Blake Kaplan (:mrbkap) (please use needinfo!) from comment #42)
> That was leftover from debugging. I've removed it locally (and added public
> domain notices to all of the tests).

Great, thanks!
Attachment #8683998 - Flags: review?(mozilla) → review+
Comment on attachment 8683995 [details] [diff] [review]
Base patch

Review of attachment 8683995 [details] [diff] [review]:
-----------------------------------------------------------------

What code handles redirects of websockets?

r- for now based on the comment in ThirdPartyUtil.cpp

::: dom/base/ThirdPartyUtil.cpp
@@ +230,5 @@
>    }
>  
>    // Obtain the URI from the channel, and its base domain.
>    nsCOMPtr<nsIURI> channelURI;
>    aChannel->GetURI(getter_AddRefs(channelURI));

I think we want to call NS_GetFinalChannelURI here. But see below.

@@ +239,5 @@
>    if (NS_FAILED(rv))
>      return rv;
>  
> +  // Determine whether aURI is foreign with respect to channelURI.
> +  return IsThirdPartyInternal(channelDomain, aURI, aResult);

Does this work at all?

It's really hard to trace the cookie code calling  ThirdPartyUtil::IsThirdPartyChannel in mxr, there's just too much indirection. But I *think* that we originally come from here:

http://mxr.mozilla.org/mozilla-central/source/netwerk/protocol/http/HttpBaseChannel.cpp#1922

Which means that the passed in URI is the URI of the channel. I.e. this call will return false any time we're loading a resource (say an image).

I think what you want to compare, is aURI and the URI of the loadingPrincipal.

If I'm right though, then I'm surprised that no tests would catch this?

::: dom/simplepush/PushService.jsm
@@ +829,5 @@
>                              Services.scriptSecurityManager.getSystemPrincipal(),
>                              null, // aTriggeringPrincipal
>                              Ci.nsILoadInfo.SEC_NORMAL,
> +                            Ci.nsIContentPolicy.TYPE_WEBSOCKET,
> +                            uri);

It's really unfortunate to have to do this. Can't the websocket code call loadinfo.notifyLoadingURI when the socket is opened?

::: netwerk/base/LoadInfo.cpp
@@ +256,5 @@
> +  }
> +
> +  // This is a subdocument load or a resource load.
> +  uint64_t windowID = 0;
> +  if (mInternalContentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {

Actually, I think TYPE_SUBDOCUMENT is not used for internal types. We use TYPE_INTERNAL_FRAME and TYPE_INTERNAL_IFRAME.

I think calling nsContentUtils::InternalContentPolicyTypeToExternal is the safer thing to do here. That should generate more stable values in the face of future changes.

Both here and for the TYPE_DOCUMENT test above.

::: netwerk/base/nsIOService.cpp
@@ +419,5 @@
> +        nsCOMPtr<nsIURI> uri;
> +        newChan->GetURI(getter_AddRefs(uri));
> +
> +        loadInfo->NotifyLoadingURI(uri);
> +    }

Why is this needed? Won't the call in nsIOService::NewChannelFromURIWithProxyFlagsInternal take care of this?
Attachment #8683995 - Flags: review?(jonas) → review-
Comment on attachment 8683997 [details] [diff] [review]
Fix redirects

Review of attachment 8683997 [details] [diff] [review]:
-----------------------------------------------------------------

::: netwerk/base/LoadInfo.cpp
@@ +257,5 @@
> +  // problems:
> +  //   * We call this function in the parent, where we can't access the window
> +  //     chain. In that case, the child either computes this and we're created
> +  //     with the right value or, for redirects, updates our state via
> +  //     ResetIsThirdPartyRequest.

I'm not sure it needs to be this complex.

If mIsThirdPartyRequest indicates that it's not third party when we first get into the parent, then it won't be thirdparty until we get a mismatch between the loadingPrincipal and the requested URI.

I.e. the window chain only matters for the initial value of mIsThirdPartyRequest. After that, any checks against the window chain will yield the same result.

No?
1. Get rid of the current redirectChain concept
2. Clone the loadinfo before calling NS_NewChannel
3. Keep your loadInfo.notifyUri function
4. Only call it in IOService.newChannel
5. Make loadInfo.notifyUri do something like

if (type == TYPE_DOCUMENT) {
  mIsThirdParty = false;
  return;
}
if (!mLoadingNode)
  mIsThirdParty = true;
  return;
}
mIsThirdParty = mIsThirdParty ||
                ThirdPartyUtils.CompareUrls(mLoadingPrincipal, uri) ||
                (mUriChain.IsEmpty() && mLoadingNode &&
                 ThirdPartyUtils.WindowHasThirdPartyAncestor(loadingNode.document.window)
mUriChain.AppendElement(uri);


6. In places where we look at the redirectChain, instead look at mUriChain.

mUriChain is effectively the same as the redirectChain. Only difference is that the urichain also include the current channels uri.


One tricky aspect is how we track internal redirects vs. non-internal ones. But that's solvable.
Attached patch Patch v2Splinter Review
This is the entire patch rolled up. Jonas, Christoph, and I met up a couple of days ago to discuss the end result. The solution we came up with in order to make things simpler was to avoid doing the final "is the load third party to its loading context" in the child and keeping that updated and only store the "is the load in a context that's already third party" on the loadinfo (since that doesn't ever change, even with redirects).
Attachment #8689859 - Flags: review?(mozilla)
Attachment #8689859 - Flags: review?(jonas)
Attachment #8683995 - Attachment is obsolete: true
Attachment #8683997 - Attachment is obsolete: true
Attachment #8683998 - Attachment is obsolete: true
Attachment #8683997 - Flags: review?(jonas)
Attachment #8683998 - Flags: review?(jonas)
Comment on attachment 8689859 [details] [diff] [review]
Patch v2

Review of attachment 8689859 [details] [diff] [review]:
-----------------------------------------------------------------

r=me with the below fix.

::: dom/base/ThirdPartyUtil.cpp
@@ +212,3 @@
>    // Obtain the URI from the channel, and its base domain.
>    nsCOMPtr<nsIURI> channelURI;
>    aChannel->GetURI(getter_AddRefs(channelURI));

I think you should change this to NS_GetFinalChannelURI. It shouldn't affect behavior of http requests, but it's the right thing to do.
Attachment #8689859 - Flags: review?(jonas) → review+
Comment on attachment 8689859 [details] [diff] [review]
Patch v2

Review of attachment 8689859 [details] [diff] [review]:
-----------------------------------------------------------------

Sorry that review took so long. Looks great!

::: dom/base/ThirdPartyUtil.cpp
@@ +186,5 @@
>    NS_ASSERTION(aResult, "null outparam pointer");
>  
>    nsresult rv;
>    bool doForce = false;
> +

Nit: is there a trailing whitespace left behind?

@@ +216,5 @@
>  
>    nsCString channelDomain;
>    rv = GetBaseDomain(channelURI, channelDomain);
>    if (NS_FAILED(rv))
>      return rv;

Just asking: would it make sense to assign a default-return-value to *aResult at the beginning of the function? Here we return an error but we do not assign a value to *aResult.
Attachment #8689859 - Flags: review?(mozilla) → review+
(In reply to Christoph Kerschbaumer [:ckerschb] from comment #50)
> Just asking: would it make sense to assign a default-return-value to
> *aResult at the beginning of the function? Here we return an error but we do
> not assign a value to *aResult.

XPCOM rules say that if a function returns error, the value of any outparams can't be trusted (and might not be set). I'm going to leave this the way it is.
https://hg.mozilla.org/integration/mozilla-inbound/rev/bc44c9e55ef0e042c5a6c88bd241c08282b27f55
Bug 1171215 - Compute third-partyness in the loadinfo instead of nsIHttpChannelInternal so that other protocols correctly respect the third-party cookie pref. r=sicking/ckerschb
https://hg.mozilla.org/mozilla-central/rev/bc44c9e55ef0
Status: ASSIGNED → RESOLVED
Closed: 9 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla45
Depends on: 1254856
Depends on: 1257861
Depends on: 1249083
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: