From 3a58b2a1f49f34309b629501f3a613a22ff67b9c Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Sat, 20 Apr 2024 19:36:14 +0200 Subject: [PATCH] webmail: show all images (inline and attachment) below the text part (for the text view, not for html view) the attachment buttons for images get some opacity for the text view, to indicate you don't have to open them explicitly. --- webmail/lib.ts | 12 +++++++++++ webmail/msg.js | 11 ++++++++++ webmail/text.html | 5 ++++- webmail/text.js | 17 +++++++++++++++- webmail/text.ts | 19 +++++++++++++++-- webmail/webmail.go | 19 +++++++++-------- webmail/webmail.html | 5 ++++- webmail/webmail.js | 44 +++++++++++++++++++++++----------------- webmail/webmail.ts | 45 ++++++++++++++++++++++++----------------- webmail/webmail_test.go | 7 ++++++- 10 files changed, 133 insertions(+), 51 deletions(-) diff --git a/webmail/lib.ts b/webmail/lib.ts index 8c1abf3..751f754 100644 --- a/webmail/lib.ts +++ b/webmail/lib.ts @@ -22,6 +22,18 @@ const join = (l: any, efn: () => any): any[] => { return r } +// From https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types +const imageTypes = [ + 'image/avif', + 'image/webp', + 'image/gif', + 'image/png', + 'image/jpeg', + 'image/apng', + 'image/svg+xml', +] +const isImage = (a: api.Attachment) => imageTypes.includes((a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase()) + // addLinks turns a line of text into alternating strings and links. Links that // would end with interpunction followed by whitespace are returned with that // interpunction moved to the next string instead. diff --git a/webmail/msg.js b/webmail/msg.js index 71d77f1..14757c1 100644 --- a/webmail/msg.js +++ b/webmail/msg.js @@ -1009,6 +1009,17 @@ const join = (l, efn) => { } return r; }; +// From https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types +const imageTypes = [ + 'image/avif', + 'image/webp', + 'image/gif', + 'image/png', + 'image/jpeg', + 'image/apng', + 'image/svg+xml', +]; +const isImage = (a) => imageTypes.includes((a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase()); // addLinks turns a line of text into alternating strings and links. Links that // would end with interpunction followed by whitespace are returned with that // interpunction moved to the next string instead. diff --git a/webmail/text.html b/webmail/text.html index ea6104c..d5fa00f 100644 --- a/webmail/text.html +++ b/webmail/text.html @@ -7,11 +7,14 @@ diff --git a/webmail/text.js b/webmail/text.js index c248803..68605bf 100644 --- a/webmail/text.js +++ b/webmail/text.js @@ -1009,6 +1009,17 @@ const join = (l, efn) => { } return r; }; +// From https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types +const imageTypes = [ + 'image/avif', + 'image/webp', + 'image/gif', + 'image/png', + 'image/jpeg', + 'image/apng', + 'image/svg+xml', +]; +const isImage = (a) => imageTypes.includes((a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase()); // addLinks turns a line of text into alternating strings and links. Links that // would end with interpunction followed by whitespace are returned with that // interpunction moved to the next string instead. @@ -1186,7 +1197,11 @@ const loadMsgheaderView = (msgheaderelem, mi, moreHeaders, refineKeyword, allAdd // Javascript is generated from typescript, do not modify generated javascript because changes will be overwritten. const init = async () => { const pm = api.parser.ParsedMessage(parsedMessage); - dom._kids(document.body, dom.div(dom._class('pad', 'mono'), style({ whiteSpace: 'pre-wrap' }), join((pm.Texts || []).map(t => renderText(t)), () => dom.hr(style({ margin: '2ex 0' }))))); + const mi = api.parser.MessageItem(messageItem); + dom._kids(document.body, dom.div(dom._class('pad', 'mono', 'textmulti'), style({ whiteSpace: 'pre-wrap' }), (pm.Texts || []).map(t => renderText(t.replace(/\r\n/g, '\n'))), (mi.Attachments || []).filter(f => isImage(f)).map(f => { + const pathStr = [0].concat(f.Path || []).join('.'); + return dom.div(dom.div(style({ flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', maxHeight: 'calc(100% - 50px)' }), dom.img(attr.src('view/' + pathStr), attr.title(f.Filename), style({ backgroundColor: 'white', maxWidth: '100%', maxHeight: '100%', boxShadow: '0 0 20px rgba(0, 0, 0, 0.1)' })))); + }))); }; init() .catch((err) => { diff --git a/webmail/text.ts b/webmail/text.ts index c9e61fd..a4659bc 100644 --- a/webmail/text.ts +++ b/webmail/text.ts @@ -1,14 +1,29 @@ // Javascript is generated from typescript, do not modify generated javascript because changes will be overwritten. // Loaded from synchronous javascript. +declare let messageItem: api.MessageItem declare let parsedMessage: api.ParsedMessage const init = async () => { const pm = api.parser.ParsedMessage(parsedMessage) + const mi = api.parser.MessageItem(messageItem) dom._kids(document.body, - dom.div(dom._class('pad', 'mono'), + dom.div(dom._class('pad', 'mono', 'textmulti'), style({whiteSpace: 'pre-wrap'}), - join((pm.Texts || []).map(t => renderText(t)), () => dom.hr(style({margin: '2ex 0'}))), + (pm.Texts || []).map(t => renderText(t.replace(/\r\n/g, '\n'))), + (mi.Attachments || []).filter(f => isImage(f)).map(f => { + const pathStr = [0].concat(f.Path || []).join('.') + return dom.div( + dom.div( + style({flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', maxHeight: 'calc(100% - 50px)'}), + dom.img( + attr.src('view/'+pathStr), + attr.title(f.Filename), + style({backgroundColor: 'white', maxWidth: '100%', maxHeight: '100%', boxShadow: '0 0 20px rgba(0, 0, 0, 0.1)'}) + ), + ) + ) + }), ) ) } diff --git a/webmail/webmail.go b/webmail/webmail.go index caf7518..544860b 100644 --- a/webmail/webmail.go +++ b/webmail/webmail.go @@ -377,7 +377,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt // allowed. Used to display a message including header. The header is rendered with // javascript, the content is rendered in a separate iframe with a CSP that doesn't // have allowSelfScript. - headers := func(sameOrigin, allowExternal, allowSelfScript bool) { + headers := func(sameOrigin, allowExternal, allowSelfScript, allowSelfImg bool) { // allow-popups is needed to make opening links in new tabs work. sb := "sandbox allow-popups allow-popups-to-escape-sandbox; " if sameOrigin && allowSelfScript { @@ -394,6 +394,8 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt var csp string if allowExternal { csp = sb + "frame-ancestors 'self'; default-src 'none'; img-src data: http: https: 'unsafe-inline'; style-src 'unsafe-inline' data: http: https:; font-src data: http: https: 'unsafe-inline'; media-src 'unsafe-inline' data: http: https:" + script + } else if allowSelfImg { + csp = sb + "frame-ancestors 'self'; default-src 'none'; img-src data: 'self'; style-src 'unsafe-inline'" + script } else { csp = sb + "frame-ancestors 'self'; default-src 'none'; img-src data:; style-src 'unsafe-inline'" + script } @@ -416,7 +418,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt mi, err := messageItem(log, m, &state) xcheckf(ctx, err, "parsing message") - headers(false, false, false) + headers(false, false, false, false) h.Set("Content-Type", "application/zip") h.Set("Cache-Control", "no-store, max-age=0") var subjectSlug string @@ -536,7 +538,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt // browsers or users would think of executing. We do set the charset if available // on the outer part. If present, we assume it may be relevant for other parts. If // not, there is not much we could do better... - headers(false, false, false) + headers(false, false, false, false) ct := "text/plain" params := map[string]string{} if charset := p.ContentTypeParams["charset"]; charset != "" { @@ -571,7 +573,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt sameorigin := true loadExternal := t[1] == "msghtmlexternal" allowSelfScript := true - headers(sameorigin, loadExternal, allowSelfScript) + headers(sameorigin, loadExternal, allowSelfScript, false) h.Set("Content-Type", "text/html; charset=utf-8") h.Set("Cache-Control", "no-store, max-age=0") @@ -604,7 +606,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt mijson, err := json.Marshal(mi) xcheckf(ctx, err, "marshal messageitem") - headers(false, false, false) + headers(false, false, false, false) h.Set("Content-Type", "application/javascript; charset=utf-8") h.Set("Cache-Control", "no-store, max-age=0") @@ -636,7 +638,8 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt // Needed for inner document height for outer iframe height in separate message view. sameorigin := true allowSelfScript := true - headers(sameorigin, false, allowSelfScript) + allowSelfImg := true + headers(sameorigin, false, allowSelfScript, allowSelfImg) h.Set("Content-Type", "text/html; charset=utf-8") h.Set("Cache-Control", "no-store, max-age=0") @@ -662,7 +665,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt // inner height so we load it as different origin, which should be safer. sameorigin := r.URL.Query().Get("sameorigin") == "true" allowExternal := strings.HasSuffix(t[1], "external") - headers(sameorigin, allowExternal, false) + headers(sameorigin, allowExternal, false, false) h.Set("Content-Type", "text/html; charset=utf-8") h.Set("Cache-Control", "no-store, max-age=0") @@ -724,7 +727,7 @@ func handle(apiHandler http.Handler, isForwarded bool, accountPath string, w htt ap = ap.Parts[int(index)] } - headers(false, false, false) + headers(false, false, false, false) var ct string if t[1] == "viewtext" { ct = "text/plain" diff --git a/webmail/webmail.html b/webmail/webmail.html index 9154bba..7a5305c 100644 --- a/webmail/webmail.html +++ b/webmail/webmail.html @@ -27,9 +27,12 @@ button.keyword { cursor: pointer; } .btngroup button:first-child, .btngroup .button:first-child { border-radius: .15em 0 0 .15em; } .btngroup button:last-child, .btngroup .button:last-child { border-radius: 0 .15em .15em 0; border-right-width: 1px; } iframe { border: 0; } -.pad { padding: .5em; } .invert { filter: invert(100%); } .scriptswitch { text-decoration: underline #dca053 2px; } +.textmulti > *:nth-child(even) { background-color: #f4f4f4; } +.textmulti > * { padding: 2ex .5em; margin: -.5em; /* compensate pad */ } +.textmulti > *:first-child { padding: .5em; } +.pad { padding: .5em; } .msgitem { display: flex; user-select: none; } .msgitemcell { padding: 2px 4px; } diff --git a/webmail/webmail.js b/webmail/webmail.js index ec2325f..18d877a 100644 --- a/webmail/webmail.js +++ b/webmail/webmail.js @@ -1009,6 +1009,17 @@ const join = (l, efn) => { } return r; }; +// From https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types +const imageTypes = [ + 'image/avif', + 'image/webp', + 'image/gif', + 'image/png', + 'image/jpeg', + 'image/apng', + 'image/svg+xml', +]; +const isImage = (a) => imageTypes.includes((a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase()); // addLinks turns a line of text into alternating strings and links. Links that // would end with interpunction followed by whitespace are returned with that // interpunction moved to the next string instead. @@ -3565,18 +3576,7 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad msgheaderdetailsElem = dom.table(style({ marginBottom: '1ex', width: '100%' }), Object.entries(pm.Headers || {}).sort().map(t => (t[1] || []).map(v => dom.tr(dom.td(t[0] + ':', style({ textAlign: 'right', color: '#555' })), dom.td(v))))); msgattachmentElem.parentNode.insertBefore(msgheaderdetailsElem, msgattachmentElem); }; - // From https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types - const imageTypes = [ - 'image/avif', - 'image/webp', - 'image/gif', - 'image/png', - 'image/jpeg', - 'image/apng', - 'image/svg+xml', - ]; const isText = (a) => ['text', 'message'].includes(a.Part.MediaType.toLowerCase()); - const isImage = (a) => imageTypes.includes((a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase()); const isPDF = (a) => (a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase() === 'application/pdf'; const isViewable = (a) => isText(a) || isImage(a) || isPDF(a); const attachments = (mi.Attachments || []); @@ -3643,9 +3643,10 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad popupRoot.focus(); attachmentView = { key: keyHandler(attachShortcuts) }; }; - const renderAttachments = (all) => { + var filesAll = false; + const renderAttachments = () => { const l = mi.Attachments || []; - dom._kids(msgattachmentElem, (l && l.length === 0) ? [] : dom.div(style({ borderTop: '1px solid #ccc' }), dom.div(dom._class('pad'), 'Attachments: ', l.slice(0, all ? l.length : 4).map(a => { + dom._kids(msgattachmentElem, (l && l.length === 0) ? [] : dom.div(style({ borderTop: '1px solid #ccc' }), dom.div(dom._class('pad'), 'Attachments: ', l.slice(0, filesAll ? l.length : 4).map(a => { const name = a.Filename || '(unnamed)'; const viewable = isViewable(a); const size = formatSize(a.Part.DecodedSize); @@ -3657,14 +3658,15 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad }); const dlbtn = dom.a(dom._class('button'), attr.download(''), attr.href(dlurl), dl, viewable ? style({ padding: '0px 0.25em' }) : ' ' + name, attr.title('Download this file. Size: ' + size), style({ lineHeight: '1.5' })); if (viewable) { - return [dom.span(dom._class('btngroup'), viewbtn, dlbtn), ' ']; + return [dom.span(dom._class('btngroup'), urlType === 'text' && isImage(a) ? style({ opacity: '.6' }) : [], viewbtn, dlbtn), ' ']; } return [dom.span(dom._class('btngroup'), dlbtn, viewbtn), ' ']; - }), all || l.length < 6 ? [] : dom.clickbutton('More...', function click() { - renderAttachments(true); + }), filesAll || l.length < 6 ? [] : dom.clickbutton('More...', function click() { + filesAll = true; + renderAttachments(); }), ' ', dom.a('Download all as zip', attr.download(''), style({ color: 'inherit' }), attr.href('msg/' + m.ID + '/attachments.zip'))))); }; - renderAttachments(false); + renderAttachments(); const root = dom.div(style({ position: 'absolute', top: 0, right: 0, bottom: 0, left: 0, display: 'flex', flexDirection: 'column' })); dom._kids(root, msgmetaElem, msgcontentElem); const loadText = (pm) => { @@ -3672,18 +3674,24 @@ const newMsgView = (miv, msglistView, listMailboxes, possibleLabels, messageLoad // text to use when writing a reply. We still set url so the text content can be // opened in a separate tab, even though it will look differently. urlType = 'text'; - const elem = dom.div(dom._class('mono'), style({ whiteSpace: 'pre-wrap' }), join((pm.Texts || []).map(t => renderText(t.replace(/\r\n/g, '\n'))), () => dom.hr(style({ margin: '2ex 0' })))); + const elem = dom.div(dom._class('mono', 'textmulti'), style({ whiteSpace: 'pre-wrap' }), (pm.Texts || []).map(t => renderText(t.replace(/\r\n/g, '\n'))), (mi.Attachments || []).filter(f => isImage(f)).map(f => { + const pathStr = [0].concat(f.Path || []).join('.'); + return dom.div(dom.div(style({ flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', maxHeight: 'calc(100% - 50px)' }), dom.img(attr.src('msg/' + m.ID + '/view/' + pathStr), attr.title(f.Filename), style({ backgroundColor: 'white', maxWidth: '100%', maxHeight: '100%', boxShadow: '0 0 20px rgba(0, 0, 0, 0.1)' })))); + })); dom._kids(msgcontentElem); dom._kids(msgscrollElem, elem); dom._kids(msgcontentElem, msgscrollElem); + renderAttachments(); // Rerender opaciy on inline images. }; const loadHTML = () => { urlType = 'html'; dom._kids(msgcontentElem, dom.iframe(attr.tabindex('0'), attr.title('HTML version of message with images inlined, without external resources loaded.'), attr.src('msg/' + m.ID + '/' + urlType), style({ border: '0', position: 'absolute', width: '100%', height: '100%', backgroundColor: 'white' }))); + renderAttachments(); // Rerender opaciy on inline images. }; const loadHTMLexternal = () => { urlType = 'htmlexternal'; dom._kids(msgcontentElem, dom.iframe(attr.tabindex('0'), attr.title('HTML version of message with images inlined and with external resources loaded.'), attr.src('msg/' + m.ID + '/' + urlType), style({ border: '0', position: 'absolute', width: '100%', height: '100%', backgroundColor: 'white' }))); + renderAttachments(); // Rerender opaciy on inline images. }; const loadMoreHeaders = (pm) => { if (settings.showHeaders.length === 0) { diff --git a/webmail/webmail.ts b/webmail/webmail.ts index 26191e4..8265c3d 100644 --- a/webmail/webmail.ts +++ b/webmail/webmail.ts @@ -3065,18 +3065,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l msgattachmentElem.parentNode!.insertBefore(msgheaderdetailsElem, msgattachmentElem) } - // From https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types - const imageTypes = [ - 'image/avif', - 'image/webp', - 'image/gif', - 'image/png', - 'image/jpeg', - 'image/apng', - 'image/svg+xml', - ] const isText = (a: api.Attachment) => ['text', 'message'].includes(a.Part.MediaType.toLowerCase()) - const isImage = (a: api.Attachment) => imageTypes.includes((a.Part.MediaType + '/' + a.Part.MediaSubType).toLowerCase()) const isPDF = (a: api.Attachment) => (a.Part.MediaType+'/'+a.Part.MediaSubType).toLowerCase() === 'application/pdf' const isViewable = (a: api.Attachment) => isText(a) || isImage(a) || isPDF(a) const attachments: api.Attachment[] = (mi.Attachments || []) @@ -3215,14 +3204,15 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l attachmentView = {key: keyHandler(attachShortcuts)} } - const renderAttachments = (all: boolean) => { + var filesAll = false + const renderAttachments = () => { const l = mi.Attachments || [] dom._kids(msgattachmentElem, (l && l.length === 0) ? [] : dom.div( style({borderTop: '1px solid #ccc'}), dom.div(dom._class('pad'), 'Attachments: ', - l.slice(0, all ? l.length : 4).map(a => { + l.slice(0, filesAll ? l.length : 4).map(a => { const name = a.Filename || '(unnamed)' const viewable = isViewable(a) const size = formatSize(a.Part.DecodedSize) @@ -3234,19 +3224,20 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l }) const dlbtn = dom.a(dom._class('button'), attr.download(''), attr.href(dlurl), dl, viewable ? style({padding: '0px 0.25em'}) : ' '+name, attr.title('Download this file. Size: '+size), style({lineHeight: '1.5'})) if (viewable) { - return [dom.span(dom._class('btngroup'), viewbtn, dlbtn), ' '] + return [dom.span(dom._class('btngroup'), urlType === 'text' && isImage(a) ? style({opacity: '.6'}) : [], viewbtn, dlbtn), ' '] } return [dom.span(dom._class('btngroup'), dlbtn, viewbtn), ' '] }), - all || l.length < 6 ? [] : dom.clickbutton('More...', function click() { - renderAttachments(true) + filesAll || l.length < 6 ? [] : dom.clickbutton('More...', function click() { + filesAll = true + renderAttachments() }), ' ', dom.a('Download all as zip', attr.download(''), style({color: 'inherit'}), attr.href('msg/'+m.ID+'/attachments.zip')), ), ) ) } - renderAttachments(false) + renderAttachments() const root = dom.div(style({position: 'absolute', top: 0, right: 0, bottom: 0, left: 0, display: 'flex', flexDirection: 'column'})) dom._kids(root, msgmetaElem, msgcontentElem) @@ -3256,13 +3247,27 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l // text to use when writing a reply. We still set url so the text content can be // opened in a separate tab, even though it will look differently. urlType = 'text' - const elem = dom.div(dom._class('mono'), + const elem = dom.div(dom._class('mono', 'textmulti'), style({whiteSpace: 'pre-wrap'}), - join((pm.Texts || []).map(t => renderText(t.replace(/\r\n/g, '\n'))), () => dom.hr(style({margin: '2ex 0'}))), + (pm.Texts || []).map(t => renderText(t.replace(/\r\n/g, '\n'))), + (mi.Attachments || []).filter(f => isImage(f)).map(f => { + const pathStr = [0].concat(f.Path || []).join('.') + return dom.div( + dom.div( + style({flexGrow: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', maxHeight: 'calc(100% - 50px)'}), + dom.img( + attr.src('msg/'+m.ID+'/view/'+pathStr), + attr.title(f.Filename), + style({backgroundColor: 'white', maxWidth: '100%', maxHeight: '100%', boxShadow: '0 0 20px rgba(0, 0, 0, 0.1)'}) + ), + ) + ) + }), ) dom._kids(msgcontentElem) dom._kids(msgscrollElem, elem) dom._kids(msgcontentElem, msgscrollElem) + renderAttachments() // Rerender opaciy on inline images. } const loadHTML = (): void => { urlType = 'html' @@ -3274,6 +3279,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l style({border: '0', position: 'absolute', width: '100%', height: '100%', backgroundColor: 'white'}), ) ) + renderAttachments() // Rerender opaciy on inline images. } const loadHTMLexternal = (): void => { urlType = 'htmlexternal' @@ -3285,6 +3291,7 @@ const newMsgView = (miv: MsgitemView, msglistView: MsglistView, listMailboxes: l style({border: '0', position: 'absolute', width: '100%', height: '100%', backgroundColor: 'white'}), ) ) + renderAttachments() // Rerender opaciy on inline images. } const loadMoreHeaders = (pm: api.ParsedMessage) => { diff --git a/webmail/webmail_test.go b/webmail/webmail_test.go index cfcb223..c92358f 100644 --- a/webmail/webmail_test.go +++ b/webmail/webmail_test.go @@ -530,6 +530,11 @@ func TestWebmail(t *testing.T) { "Content-Security-Policy", "frame-ancestors 'self'; default-src 'none'; img-src data:; style-src 'unsafe-inline'; script-src 'unsafe-inline' 'self'; frame-src 'self'; connect-src 'self'", } + // Text and img-src 'self', for viewing image files inline. + cspTextImg := [2]string{ + "Content-Security-Policy", + "frame-ancestors 'self'; default-src 'none'; img-src data: 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline' 'self'; frame-src 'self'; connect-src 'self'", + } // HTML as viewed in the regular viewer, not in a new tab. cspHTML := [2]string{ "Content-Security-Policy", @@ -560,7 +565,7 @@ func TestWebmail(t *testing.T) { "Content-Security-Policy", "frame-ancestors 'self'; default-src 'none'; img-src data: http: https: 'unsafe-inline'; style-src 'unsafe-inline' data: http: https:; font-src data: http: https: 'unsafe-inline'; media-src 'unsafe-inline' data: http: https:; script-src 'unsafe-inline' 'self'; frame-src 'self'; connect-src 'self'", } - testHTTPAuthREST("GET", pathInboxAltRel+"/text", http.StatusOK, httpHeaders{ctHTML, cspText}, nil) + testHTTPAuthREST("GET", pathInboxAltRel+"/text", http.StatusOK, httpHeaders{ctHTML, cspTextImg}, nil) testHTTPAuthREST("GET", pathInboxAltRel+"/html", http.StatusOK, httpHeaders{ctHTML, cspHTML}, nil) testHTTPAuthREST("GET", pathInboxAltRel+"/htmlexternal", http.StatusOK, httpHeaders{ctHTML, cspHTMLExternal}, nil) testHTTPAuthREST("GET", pathInboxAltRel+"/msgtext", http.StatusOK, httpHeaders{ctHTML, cspText}, nil)