1use std::borrow::Cow;
2use std::fmt::Write;
3
4use cabin_macros::Attribute;
5
6use super::anchor::ReferrerPolicy;
7use super::common::Common;
8use super::global::Global;
9use super::link::{Blocking, CrossOrigin, FetchPriority, Type};
10use crate::html::attributes::{Attributes, WithAttribute};
11use crate::html::{Aria, Html};
12use crate::render::{Escape, Renderer};
13use crate::view::RenderFuture;
14use crate::View;
15
16pub fn script(content: impl Into<Cow<'static, str>>) -> Html<marker::Script, (), impl View> {
18 Html::new("script", (), ScriptEscape(content.into()))
19}
20
21pub mod marker {
22 pub struct Script;
23}
24
25impl<A: Attributes, V: 'static> Script for Html<marker::Script, A, V> {}
26impl<A: Attributes, V: 'static> Common for Html<marker::Script, A, V> {}
27impl<A: Attributes, V: 'static> Global for Html<marker::Script, A, V> {}
28impl<A: Attributes, V: 'static> Aria for Html<marker::Script, A, V> {}
29
30pub trait Script: WithAttribute {
32 fn src(self, src: impl Into<Cow<'static, str>>) -> Self::Output<Src> {
34 self.with_attribute(Src(src.into()))
35 }
36
37 fn r#type(self, r#type: impl Into<Cow<'static, str>>) -> Self::Output<Type> {
39 self.with_attribute(Type(r#type.into()))
40 }
41
42 fn no_module(self) -> Self::Output<NoModule> {
44 self.with_no_module(true)
45 }
46
47 fn with_no_module(self, no_module: bool) -> Self::Output<NoModule> {
49 self.with_attribute(NoModule(no_module))
50 }
51
52 fn r#async(self) -> Self::Output<Async> {
54 self.with_async(true)
55 }
56
57 fn with_async(self, r#async: bool) -> Self::Output<Async> {
59 self.with_attribute(Async(r#async))
60 }
61
62 fn defer(self) -> Self::Output<Defer> {
64 self.with_defer(true)
65 }
66
67 fn with_defer(self, defer: bool) -> Self::Output<Defer> {
69 self.with_attribute(Defer(defer))
70 }
71
72 fn cross_origin(self, cross_origin: CrossOrigin) -> Self::Output<CrossOrigin> {
74 self.with_attribute(cross_origin)
75 }
76
77 fn integrity(self, integrity: impl Into<Cow<'static, str>>) -> Self::Output<Integrity> {
79 self.with_attribute(Integrity(integrity.into()))
80 }
81
82 fn referrer_policy(self, referrer_policy: ReferrerPolicy) -> Self::Output<ReferrerPolicy> {
84 self.with_attribute(referrer_policy)
85 }
86
87 fn blocking(self) -> Self::Output<Blocking> {
88 self.with_blocking(true)
89 }
90
91 fn with_blocking(self, blocking: bool) -> Self::Output<Blocking> {
92 self.with_attribute(Blocking(blocking))
93 }
94
95 fn fetch_priority(self, fetch_priority: FetchPriority) -> Self::Output<FetchPriority> {
97 self.with_attribute(fetch_priority)
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
103pub struct Src(pub Cow<'static, str>);
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
107pub struct NoModule(pub bool);
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
111pub struct Async(pub bool);
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
115pub struct Defer(pub bool);
116
117#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Attribute)]
119pub struct Integrity(pub Cow<'static, str>);
120
121pub struct ScriptEscape(pub Cow<'static, str>);
122
123impl View for ScriptEscape {
124 fn render(self, r: Renderer, _include_hash: bool) -> RenderFuture {
125 let mut txt = r.text();
126 RenderFuture::ready(
127 Escape::script(&mut txt)
128 .write_str(&self.0)
129 .map_err(crate::error::InternalError::from)
130 .map_err(crate::error::Error::from)
131 .and_then(|_| txt.end()),
132 )
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[tokio::test]
141 async fn test_script_escape() {
142 assert_eq!(
143 script("asd</script>")
144 .render(Renderer::new(), false)
145 .await
146 .unwrap()
147 .end()
148 .html,
149 r"<script>asd<\/script></script>"
150 );
151 assert_eq!(
152 script("asd<!--")
153 .render(Renderer::new(), false)
154 .await
155 .unwrap()
156 .end()
157 .html,
158 r"<script>asd<\!--</script>"
159 );
160 assert_eq!(
161 script(r#"if (1<2) alert("</script>")"#)
162 .render(Renderer::new(), false)
163 .await
164 .unwrap()
165 .end()
166 .html,
167 r#"<script>if (1<2) alert("<\/script>")</script>"#
168 );
169 }
170}