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 /*
8  * A base class which implements nsIStyleSheetLinkingElement and can
9  * be subclassed by various content nodes that want to load
10  * stylesheets (<style>, <link>, processing instructions, etc).
11  */
12 
13 #include "nsStyleLinkElement.h"
14 
15 #include "mozilla/StyleSheet.h"
16 #include "mozilla/StyleSheetInlines.h"
17 #include "mozilla/css/Loader.h"
18 #include "mozilla/dom/Element.h"
19 #include "mozilla/dom/FragmentOrElement.h"
20 #include "mozilla/dom/HTMLLinkElement.h"
21 #include "mozilla/dom/ShadowRoot.h"
22 #include "mozilla/dom/SRILogHelper.h"
23 #include "mozilla/Preferences.h"
24 #include "nsIContent.h"
25 #include "nsIDocument.h"
26 #include "nsIDOMComment.h"
27 #include "nsIDOMNode.h"
28 #include "nsUnicharUtils.h"
29 #include "nsCRT.h"
30 #include "nsXPCOMCIDInternal.h"
31 #include "nsUnicharInputStream.h"
32 #include "nsContentUtils.h"
33 #include "nsStyleUtil.h"
34 #include "nsQueryObject.h"
35 #include "nsIContentPolicy.h"
36 #include "nsMimeTypes.h"
37 #include "imgLoader.h"
38 #include "MediaContainerType.h"
39 #include "DecoderDoctorDiagnostics.h"
40 #include "DecoderTraits.h"
41 #include "MediaList.h"
42 #include "nsAttrValueInlines.h"
43 
44 using namespace mozilla;
45 using namespace mozilla::dom;
46 
nsStyleLinkElement()47 nsStyleLinkElement::nsStyleLinkElement()
48     : mDontLoadStyle(false), mUpdatesEnabled(true), mLineNumber(1) {}
49 
~nsStyleLinkElement()50 nsStyleLinkElement::~nsStyleLinkElement() {
51   nsStyleLinkElement::SetStyleSheet(nullptr);
52 }
53 
Unlink()54 void nsStyleLinkElement::Unlink() {
55   nsStyleLinkElement::SetStyleSheet(nullptr);
56 }
57 
Traverse(nsCycleCollectionTraversalCallback & cb)58 void nsStyleLinkElement::Traverse(nsCycleCollectionTraversalCallback& cb) {
59   nsStyleLinkElement* tmp = this;
60   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheet);
61 }
62 
SetStyleSheet(StyleSheet * aStyleSheet)63 void nsStyleLinkElement::SetStyleSheet(StyleSheet* aStyleSheet) {
64   if (mStyleSheet) {
65     mStyleSheet->SetOwningNode(nullptr);
66   }
67 
68   mStyleSheet = aStyleSheet;
69   if (mStyleSheet) {
70     nsCOMPtr<nsINode> node = do_QueryObject(this);
71     if (node) {
72       mStyleSheet->SetOwningNode(node);
73     }
74   }
75 }
76 
GetStyleSheet()77 StyleSheet* nsStyleLinkElement::GetStyleSheet() { return mStyleSheet; }
78 
InitStyleLinkElement(bool aDontLoadStyle)79 void nsStyleLinkElement::InitStyleLinkElement(bool aDontLoadStyle) {
80   mDontLoadStyle = aDontLoadStyle;
81 }
82 
SetEnableUpdates(bool aEnableUpdates)83 void nsStyleLinkElement::SetEnableUpdates(bool aEnableUpdates) {
84   mUpdatesEnabled = aEnableUpdates;
85 }
86 
GetCharset(nsAString & aCharset)87 void nsStyleLinkElement::GetCharset(nsAString& aCharset) {
88   aCharset.Truncate();
89 }
90 
OverrideBaseURI(nsIURI * aNewBaseURI)91 /* virtual */ void nsStyleLinkElement::OverrideBaseURI(nsIURI* aNewBaseURI) {
92   NS_NOTREACHED(
93       "Base URI can't be overriden in this implementation "
94       "of nsIStyleSheetLinkingElement.");
95 }
96 
SetLineNumber(uint32_t aLineNumber)97 /* virtual */ void nsStyleLinkElement::SetLineNumber(uint32_t aLineNumber) {
98   mLineNumber = aLineNumber;
99 }
100 
GetLineNumber()101 /* virtual */ uint32_t nsStyleLinkElement::GetLineNumber() {
102   return mLineNumber;
103 }
104 
ToLinkMask(const nsAString & aLink)105 static uint32_t ToLinkMask(const nsAString& aLink) {
106   // Keep this in sync with sRelValues in HTMLLinkElement.cpp
107   if (aLink.EqualsLiteral("prefetch"))
108     return nsStyleLinkElement::ePREFETCH;
109   else if (aLink.EqualsLiteral("dns-prefetch"))
110     return nsStyleLinkElement::eDNS_PREFETCH;
111   else if (aLink.EqualsLiteral("stylesheet"))
112     return nsStyleLinkElement::eSTYLESHEET;
113   else if (aLink.EqualsLiteral("next"))
114     return nsStyleLinkElement::eNEXT;
115   else if (aLink.EqualsLiteral("alternate"))
116     return nsStyleLinkElement::eALTERNATE;
117   else if (aLink.EqualsLiteral("preconnect"))
118     return nsStyleLinkElement::ePRECONNECT;
119   else if (aLink.EqualsLiteral("preload"))
120     return nsStyleLinkElement::ePRELOAD;
121   else
122     return 0;
123 }
124 
ParseLinkTypes(const nsAString & aTypes)125 uint32_t nsStyleLinkElement::ParseLinkTypes(const nsAString& aTypes) {
126   uint32_t linkMask = 0;
127   nsAString::const_iterator start, done;
128   aTypes.BeginReading(start);
129   aTypes.EndReading(done);
130   if (start == done) return linkMask;
131 
132   nsAString::const_iterator current(start);
133   bool inString = !nsContentUtils::IsHTMLWhitespace(*current);
134   nsAutoString subString;
135 
136   while (current != done) {
137     if (nsContentUtils::IsHTMLWhitespace(*current)) {
138       if (inString) {
139         nsContentUtils::ASCIIToLower(Substring(start, current), subString);
140         linkMask |= ToLinkMask(subString);
141         inString = false;
142       }
143     } else {
144       if (!inString) {
145         start = current;
146         inString = true;
147       }
148     }
149     ++current;
150   }
151   if (inString) {
152     nsContentUtils::ASCIIToLower(Substring(start, current), subString);
153     linkMask |= ToLinkMask(subString);
154   }
155   return linkMask;
156 }
157 
158 // We will use official mime-types from:
159 // https://www.iana.org/assignments/media-types/media-types.xhtml#font
160 // We do not support old deprecated mime-types for preload feature.
161 // (We currectly do not support font/collection)
162 static uint32_t StyleLinkElementFontMimeTypesNum = 5;
163 static const char* StyleLinkElementFontMimeTypes[] = {
164     "font/otf", "font/sfnt", "font/ttf", "font/woff", "font/woff2"};
165 
IsFontMimeType(const nsAString & aType)166 bool IsFontMimeType(const nsAString& aType) {
167   if (aType.IsEmpty()) {
168     return true;
169   }
170   for (uint32_t i = 0; i < StyleLinkElementFontMimeTypesNum; i++) {
171     if (aType.EqualsASCII(StyleLinkElementFontMimeTypes[i])) {
172       return true;
173     }
174   }
175   return false;
176 }
177 
CheckPreloadAttrs(const nsAttrValue & aAs,const nsAString & aType,const nsAString & aMedia,nsIDocument * aDocument)178 bool nsStyleLinkElement::CheckPreloadAttrs(const nsAttrValue& aAs,
179                                            const nsAString& aType,
180                                            const nsAString& aMedia,
181                                            nsIDocument* aDocument) {
182   nsContentPolicyType policyType = Link::AsValueToContentPolicy(aAs);
183   if (policyType == nsIContentPolicy::TYPE_INVALID) {
184     return false;
185   }
186 
187   // Check if media attribute is valid.
188   if (!aMedia.IsEmpty()) {
189     RefPtr<MediaList> mediaList =
190         MediaList::Create(aDocument->GetStyleBackendType(), aMedia);
191     nsPresContext* presContext = aDocument->GetPresContext();
192     if (!presContext) {
193       return false;
194     }
195     if (!mediaList->Matches(presContext)) {
196       return false;
197     }
198   }
199 
200   if (aType.IsEmpty()) {
201     return true;
202   }
203 
204   nsString type = nsString(aType);
205   ToLowerCase(type);
206 
207   if (policyType == nsIContentPolicy::TYPE_OTHER) {
208     return true;
209 
210   } else if (policyType == nsIContentPolicy::TYPE_MEDIA) {
211     if (aAs.GetEnumValue() == DESTINATION_TRACK) {
212       if (type.EqualsASCII("text/vtt")) {
213         return true;
214       } else {
215         return false;
216       }
217     }
218     Maybe<MediaContainerType> mimeType = MakeMediaContainerType(aType);
219     if (!mimeType) {
220       return false;
221     }
222     DecoderDoctorDiagnostics diagnostics;
223     CanPlayStatus status =
224         DecoderTraits::CanHandleContainerType(*mimeType, &diagnostics);
225     // Preload if this return CANPLAY_YES and CANPLAY_MAYBE.
226     if (status == CANPLAY_NO) {
227       return false;
228     } else {
229       return true;
230     }
231 
232   } else if (policyType == nsIContentPolicy::TYPE_FONT) {
233     if (IsFontMimeType(type)) {
234       return true;
235     } else {
236       return false;
237     }
238 
239   } else if (policyType == nsIContentPolicy::TYPE_IMAGE) {
240     if (imgLoader::SupportImageWithMimeType(
241             NS_ConvertUTF16toUTF8(type).get(),
242             AcceptedMimeTypes::IMAGES_AND_DOCUMENTS)) {
243       return true;
244     } else {
245       return false;
246     }
247 
248   } else if (policyType == nsIContentPolicy::TYPE_SCRIPT) {
249     if (nsContentUtils::IsJavascriptMIMEType(type)) {
250       return true;
251     } else {
252       return false;
253     }
254 
255   } else if (policyType == nsIContentPolicy::TYPE_STYLESHEET) {
256     if (type.EqualsASCII("text/css")) {
257       return true;
258     } else {
259       return false;
260     }
261   }
262   return false;
263 }
264 
UpdateStyleSheet(nsICSSLoaderObserver * aObserver,bool * aWillNotify,bool * aIsAlternate,bool aForceReload)265 nsresult nsStyleLinkElement::UpdateStyleSheet(nsICSSLoaderObserver* aObserver,
266                                               bool* aWillNotify,
267                                               bool* aIsAlternate,
268                                               bool aForceReload) {
269   if (aForceReload) {
270     // We remove this stylesheet from the cache to load a new version.
271     nsCOMPtr<nsIContent> thisContent = do_QueryInterface(this);
272     nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree()
273                                     ? thisContent->OwnerDoc()
274                                     : thisContent->GetUncomposedDoc();
275     if (doc && doc->CSSLoader()->GetEnabled() && mStyleSheet &&
276         !mStyleSheet->IsInline()) {
277       doc->CSSLoader()->ObsoleteSheet(mStyleSheet->GetOriginalURI());
278     }
279   }
280   return DoUpdateStyleSheet(nullptr, nullptr, aObserver, aWillNotify,
281                             aIsAlternate, aForceReload);
282 }
283 
UpdateStyleSheetInternal(nsIDocument * aOldDocument,ShadowRoot * aOldShadowRoot,bool aForceUpdate)284 nsresult nsStyleLinkElement::UpdateStyleSheetInternal(
285     nsIDocument* aOldDocument, ShadowRoot* aOldShadowRoot, bool aForceUpdate) {
286   bool notify, alternate;
287   return DoUpdateStyleSheet(aOldDocument, aOldShadowRoot, nullptr, &notify,
288                             &alternate, aForceUpdate);
289 }
290 
IsScopedStyleElement(nsIContent * aContent)291 static bool IsScopedStyleElement(nsIContent* aContent) {
292   // This is quicker than, say, QIing aContent to nsStyleLinkElement
293   // and then calling its virtual GetStyleSheetInfo method to find out
294   // if it is scoped.
295   return (aContent->IsHTMLElement(nsGkAtoms::style) ||
296           aContent->IsSVGElement(nsGkAtoms::style)) &&
297          aContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::scoped) &&
298          aContent->OwnerDoc()->IsScopedStyleEnabled();
299 }
300 
HasScopedStyleSheetChild(nsIContent * aContent)301 static bool HasScopedStyleSheetChild(nsIContent* aContent) {
302   for (nsIContent* n = aContent->GetFirstChild(); n; n = n->GetNextSibling()) {
303     if (IsScopedStyleElement(n)) {
304       return true;
305     }
306   }
307   return false;
308 }
309 
310 // Called when aElement has had a <style scoped> child removed.
UpdateIsElementInStyleScopeFlagOnSubtree(Element * aElement)311 static void UpdateIsElementInStyleScopeFlagOnSubtree(Element* aElement) {
312   NS_ASSERTION(aElement->IsElementInStyleScope(),
313                "only call UpdateIsElementInStyleScopeFlagOnSubtree on a "
314                "subtree that has IsElementInStyleScope boolean flag set");
315 
316   if (HasScopedStyleSheetChild(aElement)) {
317     return;
318   }
319 
320   aElement->ClearIsElementInStyleScope();
321 
322   nsIContent* n = aElement->GetNextNode(aElement);
323   while (n) {
324     if (HasScopedStyleSheetChild(n)) {
325       n = n->GetNextNonChildNode(aElement);
326     } else {
327       if (n->IsElement()) {
328         n->ClearIsElementInStyleScope();
329       }
330       n = n->GetNextNode(aElement);
331     }
332   }
333 }
334 
DoUpdateStyleSheet(nsIDocument * aOldDocument,ShadowRoot * aOldShadowRoot,nsICSSLoaderObserver * aObserver,bool * aWillNotify,bool * aIsAlternate,bool aForceUpdate)335 nsresult nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument,
336                                                 ShadowRoot* aOldShadowRoot,
337                                                 nsICSSLoaderObserver* aObserver,
338                                                 bool* aWillNotify,
339                                                 bool* aIsAlternate,
340                                                 bool aForceUpdate) {
341   *aWillNotify = false;
342 
343   nsCOMPtr<nsIContent> thisContent = do_QueryInterface(this);
344   // All instances of nsStyleLinkElement should implement nsIContent.
345   MOZ_ASSERT(thisContent);
346 
347   if (thisContent->IsInAnonymousSubtree() &&
348       thisContent->IsAnonymousContentInSVGUseSubtree()) {
349     // Stylesheets in <use>-cloned subtrees are disabled until we figure out
350     // how they should behave.
351     return NS_OK;
352   }
353 
354   // Check for a ShadowRoot because link elements are inert in a
355   // ShadowRoot.
356   ShadowRoot* containingShadow = thisContent->GetContainingShadow();
357   if (thisContent->IsHTMLElement(nsGkAtoms::link) &&
358       (aOldShadowRoot || containingShadow)) {
359     return NS_OK;
360   }
361 
362   // XXXheycam ServoStyleSheets do not support <style scoped>.
363   Element* oldScopeElement = nullptr;
364   if (mStyleSheet) {
365     if (mStyleSheet->IsServo()) {
366       // XXXheycam ServoStyleSheets don't support <style scoped>.
367     } else {
368 #ifdef MOZ_OLD_STYLE
369       oldScopeElement = mStyleSheet->AsGecko()->GetScopeElement();
370 #else
371       MOZ_CRASH("old style system disabled");
372 #endif
373     }
374   }
375 
376   if (mStyleSheet && (aOldDocument || aOldShadowRoot)) {
377     MOZ_ASSERT(!(aOldDocument && aOldShadowRoot),
378                "ShadowRoot content is never in document, thus "
379                "there should not be a old document and old "
380                "ShadowRoot simultaneously.");
381 
382     // We're removing the link element from the document or shadow tree,
383     // unload the stylesheet.  We want to do this even if updates are
384     // disabled, since otherwise a sheet with a stale linking element pointer
385     // will be hanging around -- not good!
386     if (aOldShadowRoot) {
387       aOldShadowRoot->RemoveSheet(mStyleSheet);
388     } else {
389       aOldDocument->BeginUpdate(UPDATE_STYLE);
390       aOldDocument->RemoveStyleSheet(mStyleSheet);
391       aOldDocument->EndUpdate(UPDATE_STYLE);
392     }
393 
394     nsStyleLinkElement::SetStyleSheet(nullptr);
395     if (oldScopeElement) {
396       UpdateIsElementInStyleScopeFlagOnSubtree(oldScopeElement);
397     }
398   }
399 
400   // When static documents are created, stylesheets are cloned manually.
401   if (mDontLoadStyle || !mUpdatesEnabled ||
402       thisContent->OwnerDoc()->IsStaticDocument()) {
403     return NS_OK;
404   }
405 
406   nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree()
407                                   ? thisContent->OwnerDoc()
408                                   : thisContent->GetUncomposedDoc();
409   if (!doc || !doc->CSSLoader()->GetEnabled()) {
410     return NS_OK;
411   }
412 
413   bool isInline;
414   nsCOMPtr<nsIPrincipal> triggeringPrincipal;
415   nsCOMPtr<nsIURI> uri =
416       GetStyleSheetURL(&isInline, getter_AddRefs(triggeringPrincipal));
417 
418   if (!aForceUpdate && mStyleSheet && !isInline && uri) {
419     nsIURI* oldURI = mStyleSheet->GetSheetURI();
420     if (oldURI) {
421       bool equal;
422       nsresult rv = oldURI->Equals(uri, &equal);
423       if (NS_SUCCEEDED(rv) && equal) {
424         return NS_OK;  // We already loaded this stylesheet
425       }
426     }
427   }
428 
429   if (mStyleSheet) {
430     if (thisContent->IsInShadowTree()) {
431       ShadowRoot* containingShadow = thisContent->GetContainingShadow();
432       containingShadow->RemoveSheet(mStyleSheet);
433     } else {
434       doc->BeginUpdate(UPDATE_STYLE);
435       doc->RemoveStyleSheet(mStyleSheet);
436       doc->EndUpdate(UPDATE_STYLE);
437     }
438 
439     nsStyleLinkElement::SetStyleSheet(nullptr);
440   }
441 
442   if (!uri && !isInline) {
443     return NS_OK;  // If href is empty and this is not inline style then just
444                    // bail
445   }
446 
447   nsAutoString title, type, media;
448   bool isScoped;
449   bool isAlternate;
450 
451   GetStyleSheetInfo(title, type, media, &isScoped, &isAlternate);
452 
453   if (!type.LowerCaseEqualsLiteral("text/css")) {
454     return NS_OK;
455   }
456 
457   Element* scopeElement = isScoped ? thisContent->GetParentElement() : nullptr;
458   if (scopeElement) {
459     NS_ASSERTION(isInline, "non-inline style must not have scope element");
460     scopeElement->SetIsElementInStyleScopeFlagOnSubtree(true);
461   }
462 
463   bool doneLoading = false;
464   nsresult rv = NS_OK;
465 
466   // Load the link's referrerpolicy attribute. If the link does not provide a
467   // referrerpolicy attribute, ignore this and use the document's referrer
468   // policy
469 
470   net::ReferrerPolicy referrerPolicy = GetLinkReferrerPolicy();
471   if (referrerPolicy == net::RP_Unset) {
472     referrerPolicy = doc->GetReferrerPolicy();
473   }
474 
475   if (isInline) {
476     nsAutoString text;
477     if (!nsContentUtils::GetNodeTextContent(thisContent, false, text,
478                                             fallible)) {
479       return NS_ERROR_OUT_OF_MEMORY;
480     }
481 
482     MOZ_ASSERT(thisContent->NodeInfo()->NameAtom() != nsGkAtoms::link,
483                "<link> is not 'inline', and needs different CSP checks");
484     MOZ_ASSERT(thisContent->IsElement());
485     if (!nsStyleUtil::CSPAllowsInlineStyle(
486             thisContent->AsElement(), thisContent->NodePrincipal(),
487             triggeringPrincipal, doc->GetDocumentURI(), mLineNumber, text, &rv))
488       return rv;
489 
490     // Parse the style sheet.
491     rv = doc->CSSLoader()->LoadInlineStyle(
492         thisContent, text, triggeringPrincipal, mLineNumber, title, media,
493         referrerPolicy, scopeElement, aObserver, &doneLoading, &isAlternate);
494   } else {
495     nsAutoString integrity;
496     if (thisContent->IsElement()) {
497       thisContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity,
498                                         integrity);
499     }
500     if (!integrity.IsEmpty()) {
501       MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
502               ("nsStyleLinkElement::DoUpdateStyleSheet, integrity=%s",
503                NS_ConvertUTF16toUTF8(integrity).get()));
504     }
505 
506     // XXXbz clone the URI here to work around content policies modifying URIs.
507     nsCOMPtr<nsIURI> clonedURI;
508     uri->Clone(getter_AddRefs(clonedURI));
509     NS_ENSURE_TRUE(clonedURI, NS_ERROR_OUT_OF_MEMORY);
510     rv = doc->CSSLoader()->LoadStyleLink(
511         thisContent, clonedURI, triggeringPrincipal, title, media, isAlternate,
512         GetCORSMode(), referrerPolicy, integrity, aObserver, &isAlternate);
513     if (NS_FAILED(rv)) {
514       // Don't propagate LoadStyleLink() errors further than this, since some
515       // consumers (e.g. nsXMLContentSink) will completely abort on innocuous
516       // things like a stylesheet load being blocked by the security system.
517       doneLoading = true;
518       isAlternate = false;
519       rv = NS_OK;
520     }
521   }
522 
523   NS_ENSURE_SUCCESS(rv, rv);
524 
525   *aWillNotify = !doneLoading;
526   *aIsAlternate = isAlternate;
527 
528   return NS_OK;
529 }
530 
UpdateStyleSheetScopedness(bool aIsNowScoped)531 void nsStyleLinkElement::UpdateStyleSheetScopedness(bool aIsNowScoped) {
532   if (!mStyleSheet) {
533     return;
534   }
535 
536   if (mStyleSheet->IsServo()) {
537     // XXXheycam ServoStyleSheets don't support <style scoped>.
538     NS_ERROR("stylo: ServoStyleSheets don't support <style scoped>");
539     return;
540   }
541 
542 #ifdef MOZ_OLD_STYLE
543   CSSStyleSheet* sheet = mStyleSheet->AsGecko();
544 
545   nsCOMPtr<nsIContent> thisContent = do_QueryInterface(this);
546 
547   Element* oldScopeElement = sheet->GetScopeElement();
548   Element* newScopeElement =
549       aIsNowScoped ? thisContent->GetParentElement() : nullptr;
550 
551   if (oldScopeElement == newScopeElement) {
552     return;
553   }
554 
555   nsIDocument* document = thisContent->GetOwnerDocument();
556 
557   if (thisContent->IsInShadowTree()) {
558     ShadowRoot* containingShadow = thisContent->GetContainingShadow();
559     containingShadow->RemoveSheet(mStyleSheet);
560 
561     sheet->SetScopeElement(newScopeElement);
562 
563     containingShadow->InsertSheet(mStyleSheet, thisContent);
564   } else {
565     document->BeginUpdate(UPDATE_STYLE);
566     document->RemoveStyleSheet(mStyleSheet);
567 
568     sheet->SetScopeElement(newScopeElement);
569 
570     document->AddStyleSheet(mStyleSheet);
571     document->EndUpdate(UPDATE_STYLE);
572   }
573 
574   if (oldScopeElement) {
575     UpdateIsElementInStyleScopeFlagOnSubtree(oldScopeElement);
576   }
577   if (newScopeElement) {
578     newScopeElement->SetIsElementInStyleScopeFlagOnSubtree(true);
579   }
580 #else
581   MOZ_CRASH("old style system disabled");
582 #endif
583 }
584