@electerm/electerm-react 1.100.46 → 1.100.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/common/file-drop-utils.js +61 -0
- package/client/components/ai/ai-config.jsx +3 -3
- package/client/components/bookmark-form/proxy.jsx +1 -1
- package/client/components/bookmark-form/render-ssh-tunnel.jsx +4 -4
- package/client/components/setting-panel/setting-common.jsx +1 -1
- package/client/components/sftp/file-item.jsx +3 -20
- package/client/components/tabs/tab.jsx +2 -2
- package/client/components/terminal/terminal.jsx +8 -8
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common utilities for handling file drops
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { getFolderFromFilePath } from '../components/sftp/file-read'
|
|
6
|
+
import { typeMap } from './constants'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Safely get file path from dropped file
|
|
10
|
+
* @param {File} file - File object from drop event
|
|
11
|
+
* @returns {string} - File path
|
|
12
|
+
*/
|
|
13
|
+
export const getFilePath = (file) => {
|
|
14
|
+
if (file.path) {
|
|
15
|
+
return file.path
|
|
16
|
+
}
|
|
17
|
+
// Try the official Electron 32+ method first if available
|
|
18
|
+
if (window.api && window.api.getPathForFile) {
|
|
19
|
+
return window.api.getPathForFile(file)
|
|
20
|
+
}
|
|
21
|
+
return file.name
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Process dropped files and return file list
|
|
26
|
+
* @param {DataTransfer} dataTransfer - DataTransfer object from drop event
|
|
27
|
+
* @returns {Array} - Array of file objects
|
|
28
|
+
*/
|
|
29
|
+
export const getDropFileList = (dataTransfer) => {
|
|
30
|
+
const fromFile = dataTransfer.getData('fromFile')
|
|
31
|
+
if (fromFile) {
|
|
32
|
+
return [JSON.parse(fromFile)]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { files } = dataTransfer
|
|
36
|
+
const res = []
|
|
37
|
+
for (let i = 0, len = files.length; i < len; i++) {
|
|
38
|
+
const item = files[i]
|
|
39
|
+
if (!item) {
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const filePath = getFilePath(item)
|
|
44
|
+
const isRemote = false
|
|
45
|
+
const fileObj = getFolderFromFilePath(filePath, isRemote)
|
|
46
|
+
res.push({
|
|
47
|
+
...fileObj,
|
|
48
|
+
type: typeMap.local
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
return res
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if filename contains unsafe characters
|
|
56
|
+
* @param {string} filename - Filename to check
|
|
57
|
+
* @returns {boolean} - True if unsafe
|
|
58
|
+
*/
|
|
59
|
+
export const isUnsafeFilename = (filename) => {
|
|
60
|
+
return /["'\n\r]/.test(filename)
|
|
61
|
+
}
|
|
@@ -27,8 +27,8 @@ const defaultRoles = [
|
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
const proxyOptions = [
|
|
30
|
-
{ value: 'socks5://
|
|
31
|
-
{ value: 'http://
|
|
30
|
+
{ value: 'socks5://127.0.0.1:1080' },
|
|
31
|
+
{ value: 'http://127.0.0.1:8080' },
|
|
32
32
|
{ value: 'https://proxy.example.com:3128' }
|
|
33
33
|
]
|
|
34
34
|
|
|
@@ -180,7 +180,7 @@ export default function AIConfigForm ({ initialValues, onSubmit, showAIConfig })
|
|
|
180
180
|
<Form.Item
|
|
181
181
|
label={e('proxy')}
|
|
182
182
|
name='proxyAI'
|
|
183
|
-
tooltip='Proxy for AI API requests (e.g., socks5://
|
|
183
|
+
tooltip='Proxy for AI API requests (e.g., socks5://127.0.0.1:1080)'
|
|
184
184
|
>
|
|
185
185
|
<AutoComplete
|
|
186
186
|
options={proxyOptions}
|
|
@@ -29,9 +29,9 @@ export default function renderSshTunnels (props) {
|
|
|
29
29
|
const [initialValues] = useState({
|
|
30
30
|
sshTunnel: 'forwardRemoteToLocal',
|
|
31
31
|
sshTunnelLocalPort: 12200,
|
|
32
|
-
sshTunnelLocalHost: '
|
|
32
|
+
sshTunnelLocalHost: '127.0.0.1',
|
|
33
33
|
sshTunnelRemotePort: 12300,
|
|
34
|
-
sshTunnelRemoteHost: '
|
|
34
|
+
sshTunnelRemoteHost: '127.0.0.1'
|
|
35
35
|
})
|
|
36
36
|
const [isDynamic, setter] = useState(formData.sshTunnel === 'dynamicForward')
|
|
37
37
|
const [list, setList] = useState(formData.sshTunnels || [])
|
|
@@ -85,9 +85,9 @@ export default function renderSshTunnels (props) {
|
|
|
85
85
|
// sshTunnel is forwardRemoteToLocal or forwardLocalToRemote or dynamicForward
|
|
86
86
|
const {
|
|
87
87
|
sshTunnel,
|
|
88
|
-
sshTunnelRemoteHost = '
|
|
88
|
+
sshTunnelRemoteHost = '127.0.0.1',
|
|
89
89
|
sshTunnelRemotePort = '',
|
|
90
|
-
sshTunnelLocalHost = '
|
|
90
|
+
sshTunnelLocalHost = '127.0.0.1',
|
|
91
91
|
sshTunnelLocalPort = '',
|
|
92
92
|
name
|
|
93
93
|
} = item
|
|
@@ -30,6 +30,7 @@ import findParent from '../../common/find-parent'
|
|
|
30
30
|
import sorter from '../../common/index-sorter'
|
|
31
31
|
import { getFolderFromFilePath, getLocalFileInfo } from './file-read'
|
|
32
32
|
import { readClipboard, copy as copyToClipboard, hasFileInClipboardText } from '../../common/clipboard'
|
|
33
|
+
import { getDropFileList } from '../../common/file-drop-utils'
|
|
33
34
|
import fs from '../../common/fs'
|
|
34
35
|
import time from '../../common/time'
|
|
35
36
|
import { filesize } from 'filesize'
|
|
@@ -228,26 +229,7 @@ export default class FileSection extends React.Component {
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
getDropFileList = data => {
|
|
231
|
-
|
|
232
|
-
if (fromFile) {
|
|
233
|
-
return [JSON.parse(fromFile)]
|
|
234
|
-
}
|
|
235
|
-
const { files } = data
|
|
236
|
-
const res = []
|
|
237
|
-
for (let i = 0, len = files.length; i < len; i++) {
|
|
238
|
-
const item = files[i]
|
|
239
|
-
if (!item) {
|
|
240
|
-
continue
|
|
241
|
-
}
|
|
242
|
-
// let file = item.getAsFile()
|
|
243
|
-
const isRemote = false
|
|
244
|
-
const fileObj = getFolderFromFilePath(item.path, isRemote)
|
|
245
|
-
res.push({
|
|
246
|
-
...fileObj,
|
|
247
|
-
type: typeMap.local
|
|
248
|
-
})
|
|
249
|
-
}
|
|
250
|
-
return res
|
|
232
|
+
return getDropFileList(data)
|
|
251
233
|
}
|
|
252
234
|
|
|
253
235
|
onDrop = async e => {
|
|
@@ -261,6 +243,7 @@ export default class FileSection extends React.Component {
|
|
|
261
243
|
if (!fromFiles) {
|
|
262
244
|
return
|
|
263
245
|
}
|
|
246
|
+
|
|
264
247
|
while (!target.className.includes(fileItemCls)) {
|
|
265
248
|
target = target.parentNode
|
|
266
249
|
}
|
|
@@ -350,10 +350,10 @@ class Tab extends Component {
|
|
|
350
350
|
const list = sshTunnelResults.map(({ sshTunnel: obj, error }, i) => {
|
|
351
351
|
const {
|
|
352
352
|
sshTunnelLocalPort,
|
|
353
|
-
sshTunnelRemoteHost = '
|
|
353
|
+
sshTunnelRemoteHost = '127.0.0.1',
|
|
354
354
|
sshTunnelRemotePort,
|
|
355
355
|
sshTunnel,
|
|
356
|
-
sshTunnelLocalHost = '
|
|
356
|
+
sshTunnelLocalHost = '127.0.0.1',
|
|
357
357
|
name
|
|
358
358
|
} = obj
|
|
359
359
|
let tunnel
|
|
@@ -47,6 +47,7 @@ import { createTerm, resizeTerm } from './terminal-apis.js'
|
|
|
47
47
|
import { shortcutExtend, shortcutDescExtend } from '../shortcuts/shortcut-handler.js'
|
|
48
48
|
import { KeywordHighlighterAddon } from './highlight-addon.js'
|
|
49
49
|
import { getLocalFileInfo } from '../sftp/file-read.js'
|
|
50
|
+
import { getFilePath, isUnsafeFilename } from '../../common/file-drop-utils.js'
|
|
50
51
|
import { CommandTrackerAddon } from './command-tracker-addon.js'
|
|
51
52
|
import AIIcon from '../icons/ai-icon.jsx'
|
|
52
53
|
import { formatBytes } from '../../common/byte-format.js'
|
|
@@ -356,12 +357,8 @@ class Term extends Component {
|
|
|
356
357
|
this.term.focus()
|
|
357
358
|
}
|
|
358
359
|
|
|
359
|
-
isUnsafeFilename = (filename) => {
|
|
360
|
-
return /["'\n\r]/.test(filename)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
360
|
cd = (p) => {
|
|
364
|
-
if (
|
|
361
|
+
if (isUnsafeFilename(p)) {
|
|
365
362
|
return message.error('File name contains unsafe characters')
|
|
366
363
|
}
|
|
367
364
|
this.runQuickCommand(`cd "${p}"`)
|
|
@@ -377,7 +374,7 @@ class Term extends Component {
|
|
|
377
374
|
try {
|
|
378
375
|
const fileData = JSON.parse(fromFile)
|
|
379
376
|
const filePath = resolve(fileData.path, fileData.name)
|
|
380
|
-
if (
|
|
377
|
+
if (isUnsafeFilename(filePath)) {
|
|
381
378
|
message.error(notSafeMsg)
|
|
382
379
|
return
|
|
383
380
|
}
|
|
@@ -392,13 +389,16 @@ class Term extends Component {
|
|
|
392
389
|
const files = dt.files
|
|
393
390
|
if (files && files.length) {
|
|
394
391
|
const arr = Array.from(files)
|
|
392
|
+
const filePaths = arr.map(f => getFilePath(f))
|
|
393
|
+
|
|
395
394
|
// Check each file path individually
|
|
396
|
-
const hasUnsafeFilename =
|
|
395
|
+
const hasUnsafeFilename = filePaths.some(path => isUnsafeFilename(path))
|
|
397
396
|
if (hasUnsafeFilename) {
|
|
398
397
|
message.error(notSafeMsg)
|
|
399
398
|
return
|
|
400
399
|
}
|
|
401
|
-
|
|
400
|
+
|
|
401
|
+
const filesAll = filePaths.map(path => `"${path}"`).join(' ')
|
|
402
402
|
this.attachAddon._sendData(filesAll)
|
|
403
403
|
}
|
|
404
404
|
}
|