@@ -28,7 +28,14 @@ import { use_font_size_scaling } from "../frame-tree/hooks";
28
28
import { EditorState } from "../frame-tree/types" ;
29
29
import { useEffect , useRef , useState } from "react" ;
30
30
import { webapp_client } from "@cocalc/frontend/webapp-client" ;
31
- import { Spin } from "antd" ;
31
+ import { Switch , Spin , Tooltip } from "antd" ;
32
+ import {
33
+ set_local_storage ,
34
+ get_local_storage ,
35
+ delete_local_storage ,
36
+ } from "@cocalc/frontend/misc/local-storage" ;
37
+ import { appBasePath } from "@cocalc/frontend/customize/app-base-path" ;
38
+ import { join } from "path" ;
32
39
33
40
interface Props {
34
41
id : string ;
@@ -47,6 +54,20 @@ interface Props {
47
54
value ?: string ;
48
55
}
49
56
57
+ function getKey ( { project_id, path } ) {
58
+ return `${ project_id } :${ path } :trust` ;
59
+ }
60
+ function isTrusted ( props ) : boolean {
61
+ return ! ! get_local_storage ( getKey ( props ) ) ;
62
+ }
63
+ function setTrusted ( props , trust : boolean ) {
64
+ if ( trust ) {
65
+ set_local_storage ( getKey ( props ) , "true" ) ;
66
+ } else {
67
+ delete_local_storage ( getKey ( props ) ) ;
68
+ }
69
+ }
70
+
50
71
function should_memoize ( prev , next ) {
51
72
return ! is_different ( prev , next , [
52
73
"reload" ,
@@ -83,6 +104,11 @@ export const IFrameHTML: React.FC<Props> = React.memo((props: Props) => {
83
104
// is only needed for rmd mode where an aux file loaded from server.
84
105
const [ init , setInit ] = useState < boolean > ( mode == "rmd" ) ;
85
106
const [ srcDoc , setSrcDoc ] = useState < string | null > ( null ) ;
107
+ const [ trust , setTrust0 ] = useState < boolean > ( isTrusted ( props ) ) ;
108
+ const setTrust = ( trust ) => {
109
+ setTrusted ( props , trust ) ;
110
+ setTrust0 ( trust ) ;
111
+ } ;
86
112
87
113
useEffect ( ( ) => {
88
114
if ( mode != "rmd" ) {
@@ -115,9 +141,31 @@ export const IFrameHTML: React.FC<Props> = React.memo((props: Props) => {
115
141
// done -- we tried
116
142
setInit ( false ) ;
117
143
}
118
- setSrcDoc ( buf . toString ( "utf8" ) ) ;
144
+ let src = buf . toString ( "utf8" ) ;
145
+ if ( trust ) {
146
+ // extra security and pointed in the right direction.
147
+ // We can't just use <iframe src= since the backend 'raw files' server
148
+ // just always forces a download of that, for security reasons.
149
+ const extraHead = `<base href="${ join ( appBasePath , project_id ) } /files/">
150
+ <meta http-equiv="Content-Security-Policy" content="default-src * data: blob: 'unsafe-inline' 'unsafe-eval';
151
+ script-src * data: blob: 'unsafe-inline' 'unsafe-eval';
152
+ img-src * data: blob:;
153
+ style-src * data: blob: 'unsafe-inline';
154
+ font-src * data: blob: about:;
155
+ connect-src * data: blob:;
156
+ frame-src * data: blob:;
157
+ object-src * data: blob:;
158
+ media-src * data: blob:;">` ;
159
+ const newSrc = src . replace ( / < h e a d > / i, `<head>${ extraHead } ` ) ;
160
+ if ( src != newSrc ) {
161
+ src = newSrc ;
162
+ } else {
163
+ src = `<head>\n${ extraHead } \n</head>\n\n${ src } ` ;
164
+ }
165
+ }
166
+ setSrcDoc ( src ) ;
119
167
} ) ( ) ;
120
- } , [ reload , mode , path , derived_file_types ] ) ;
168
+ } , [ reload , mode , path , derived_file_types , trust ] ) ;
121
169
122
170
const rootEl = useRef ( null ) ;
123
171
const iframe = useRef ( null ) ;
@@ -211,15 +259,18 @@ export const IFrameHTML: React.FC<Props> = React.memo((props: Props) => {
211
259
if ( mode == "rmd" && srcDoc == null ) {
212
260
return render_no_html ( ) ;
213
261
}
262
+
214
263
return (
215
264
< iframe
216
265
ref = { iframe }
217
266
srcDoc = { mode != "rmd" ? value : ( srcDoc ?? "" ) }
218
- sandbox = "allow-forms allow-scripts allow-presentation"
219
267
width = { "100%" }
220
268
height = { "100%" }
221
269
style = { { border : 0 , ...style } }
222
270
onLoad = { iframe_loaded }
271
+ sandbox = {
272
+ ! trust ? "allow-forms allow-scripts allow-presentation" : undefined
273
+ }
223
274
/>
224
275
) ;
225
276
}
@@ -266,6 +317,21 @@ export const IFrameHTML: React.FC<Props> = React.memo((props: Props) => {
266
317
// the cocalc-editor-div is needed for a safari hack only
267
318
return (
268
319
< div style = { STYLE } className = { "cocalc-editor-div smc-vfill" } ref = { rootEl } >
320
+ < div >
321
+ < Tooltip
322
+ title = {
323
+ "Arbitrary HTML is potentially dangerous. If you trust this content, switch this to trusted."
324
+ }
325
+ >
326
+ < Switch
327
+ style = { { float : "right" , marginTop : "5px" , marginRight : "5px" } }
328
+ checked = { trust }
329
+ onChange = { ( checked ) => setTrust ( checked ) }
330
+ unCheckedChildren = { "Untrusted" }
331
+ checkedChildren = { "Trusted" }
332
+ />
333
+ </ Tooltip >
334
+ </ div >
269
335
{ render_iframe ( ) }
270
336
</ div >
271
337
) ;
0 commit comments