1+ import React , { useState } from "react" ;
2+ import styled from "styled-components" ;
3+ import {
4+ lightGray ,
5+ vscBackground ,
6+ vscButtonBackground ,
7+ vscButtonForeground ,
8+ vscForeground ,
9+ vscInputBorder ,
10+ } from "../" ;
11+
12+ const ImagePlaceholder = styled . div `
13+ border: 1px solid ${ vscInputBorder } ;
14+ border-radius: 4px;
15+ padding: 12px;
16+ margin: 8px 0;
17+ background-color: ${ vscBackground } ;
18+ display: inline-block;
19+ max-width: 100%;
20+ ` ;
21+
22+ const WarningText = styled . div `
23+ color: ${ lightGray } ;
24+ font-size: 12px;
25+ margin-bottom: 8px;
26+ ` ;
27+
28+ const UrlDisplay = styled . div `
29+ font-family: var(--vscode-editor-font-family);
30+ font-size: 12px;
31+ color: ${ vscForeground } ;
32+ word-break: break-all;
33+ margin: 8px 0;
34+ padding: 8px;
35+ background-color: rgba(0, 0, 0, 0.1);
36+ border-radius: 3px;
37+ ` ;
38+
39+ const QueryParamsDisplay = styled . div `
40+ font-family: var(--vscode-editor-font-family);
41+ font-size: 11px;
42+ color: ${ vscForeground } ;
43+ margin: 8px 0;
44+ padding: 8px;
45+ background-color: rgba(128, 128, 128, 0.1);
46+ border-radius: 3px;
47+ border: 1px solid ${ lightGray } ;
48+ ` ;
49+
50+ const LoadButton = styled . button `
51+ background-color: ${ vscButtonBackground } ;
52+ color: ${ vscButtonForeground } ;
53+ border: 1px solid ${ vscInputBorder } ;
54+ padding: 6px 12px;
55+ border-radius: 3px;
56+ cursor: pointer;
57+ font-size: 12px;
58+ margin-top: 8px;
59+
60+ &:hover {
61+ opacity: 0.9;
62+ }
63+
64+ &:active {
65+ transform: translateY(1px);
66+ }
67+ ` ;
68+
69+ const ImageContainer = styled . div `
70+ max-width: 100%;
71+ display: inline-block;
72+
73+ img {
74+ max-width: 100%;
75+ height: auto;
76+ display: block;
77+ }
78+ ` ;
79+
80+ interface SecureImageComponentProps {
81+ src ?: string ;
82+ alt ?: string ;
83+ title ?: string ;
84+ className ?: string ;
85+ }
86+
87+ export const SecureImageComponent : React . FC < SecureImageComponentProps > = ( {
88+ src,
89+ alt,
90+ title,
91+ className,
92+ } ) => {
93+ const [ showImage , setShowImage ] = useState ( false ) ;
94+ const [ imageError , setImageError ] = useState ( false ) ;
95+
96+ if ( ! src ) {
97+ return < span > [Invalid image: no source]</ span > ;
98+ }
99+
100+ // Parse URL to check for query parameters
101+ let queryParams : Record < string , string > = { } ;
102+ let hasQueryParams = false ;
103+
104+ try {
105+ const url = new URL ( src , window . location . href ) ;
106+ const params = new URLSearchParams ( url . search ) ;
107+ params . forEach ( ( value , key ) => {
108+ queryParams [ key ] = value ;
109+ hasQueryParams = true ;
110+ } ) ;
111+ } catch ( e ) {
112+ // If URL parsing fails, treat src as a relative path
113+ const queryIndex = src . indexOf ( '?' ) ;
114+ if ( queryIndex > - 1 ) {
115+ hasQueryParams = true ;
116+ const params = new URLSearchParams ( src . substring ( queryIndex ) ) ;
117+ params . forEach ( ( value , key ) => {
118+ queryParams [ key ] = value ;
119+ } ) ;
120+ }
121+ }
122+
123+ if ( showImage && ! imageError ) {
124+ return (
125+ < ImageContainer className = { className } >
126+ < img
127+ src = { src }
128+ alt = { alt || "" }
129+ title = { title }
130+ onError = { ( ) => {
131+ setImageError ( true ) ;
132+ setShowImage ( false ) ;
133+ } }
134+ />
135+ </ ImageContainer >
136+ ) ;
137+ }
138+
139+ return (
140+ < ImagePlaceholder >
141+ < WarningText >
142+ Image blocked for security. Click to load if you trust the source.
143+ </ WarningText >
144+
145+ < UrlDisplay >
146+ < strong > URL:</ strong > { src }
147+ </ UrlDisplay >
148+
149+ { hasQueryParams && (
150+ < QueryParamsDisplay >
151+ < strong > Warning: URL contains query parameters:</ strong >
152+ < pre style = { { margin : "4px 0" , fontSize : "11px" } } >
153+ { JSON . stringify ( queryParams , null , 2 ) }
154+ </ pre >
155+ </ QueryParamsDisplay >
156+ ) }
157+
158+ { imageError && (
159+ < div style = { { color : lightGray , fontSize : "12px" , marginTop : "8px" } } >
160+ Failed to load image. The URL may be invalid or inaccessible.
161+ </ div >
162+ ) }
163+
164+ < LoadButton onClick = { ( ) => setShowImage ( true ) } >
165+ Load Image { hasQueryParams && "(Contains Parameters)" }
166+ </ LoadButton >
167+ </ ImagePlaceholder >
168+ ) ;
169+ } ;
0 commit comments