Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion MonacoRoslynCompletionProvider/Api/CodeRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ private static SyntaxTree CreateConsoleReadKeyShim(CSharpParseOptions parseOptio
return CSharpSyntaxTree.ParseText(
ConsoleReadKeyShimSource,
parseOptions,
path: "__ConsoleReadKeyShim.cs");
path: "__ConsoleReadKeyShim.cs",
encoding: Encoding.UTF8);
}

private static Task RunEntryPointAsync(Func<Task> executeAsync, bool requiresStaThread)
Expand Down
58 changes: 57 additions & 1 deletion SharpPad.Desktop/Interop/WebViewBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,15 @@ private async void OnWebMessageReceived(object? sender, WebViewMessageReceivedEv
case "pick-and-upload":
await HandlePickAndUploadAsync(root);
break;

case "download-file":
await HandleDownloadFileAsync(root);
break;

case "open-external-url":
HandleOpenExternalUrl(root);
break;

default:
Send(new
{
Expand Down Expand Up @@ -215,6 +219,58 @@ private async Task HandleDownloadFileAsync(JsonElement root)
});
}

private void HandleOpenExternalUrl(JsonElement root)
{
if (!root.TryGetProperty("url", out var urlProperty) ||
urlProperty.ValueKind != JsonValueKind.String)
{
Send(new
{
type = "open-external-url-result",
success = false,
message = "URL无效。"
});
return;
}

var url = urlProperty.GetString();
if (string.IsNullOrWhiteSpace(url))
{
Send(new
{
type = "open-external-url-result",
success = false,
message = "URL为空。"
});
return;
}

try
{
var psi = new System.Diagnostics.ProcessStartInfo
{
FileName = url,
UseShellExecute = true
};
System.Diagnostics.Process.Start(psi);

Send(new
{
type = "open-external-url-result",
success = true
});
}
catch (Exception ex)
{
Send(new
{
type = "open-external-url-result",
success = false,
message = $"打开URL失败: {ex.Message}"
});
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Unvalidated URL Handling in External Process Start

The HandleOpenExternalUrl method passes unvalidated, user-provided URLs directly to Process.Start with UseShellExecute. This creates a security vulnerability, allowing malicious web content to execute arbitrary protocols, open local files, or run system commands.

Fix in Cursor Fix in Web


private object? ExtractContext(JsonElement root)
{
if (!root.TryGetProperty("context", out var contextProperty))
Expand Down
2 changes: 1 addition & 1 deletion SharpPad/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<div id="container">
<button class="editor-control-button theme-button" id="themeButton" title="切换主题"></button>
<div class="button-container">
<a href="https://github.com/gaoconggit/SharpPad" target="_blank" rel="noopener noreferrer" title="Visit SharpPad on GitHub" style="display: inline-flex; align-items: center; justify-content: center; margin-left: 5px; text-decoration: none; color: inherit; padding: 5px 10px; border-radius: 4px; transition: background-color 0.2s ease; font-weight: 500;">
<a href="https://github.com/gaoconggit/SharpPad" id="githubLink" title="Visit SharpPad on GitHub" style="display: inline-flex; align-items: center; justify-content: center; margin-left: 5px; text-decoration: none; color: inherit; padding: 5px 10px; border-radius: 4px; transition: background-color 0.2s ease; font-weight: 500;">
<svg height="22" aria-hidden="true" viewBox="0 0 24 24" version="1.1" width="22" data-view-component="true" class="octicon octicon-mark-github v-align-middle" style="margin-right: 6px;">
<path fill="currentColor" d="M12.5.75C6.146.75 1 5.896 1 12.25c0 5.089 3.292 9.387 7.863 10.91.575.101.79-.244.79-.546 0-.273-.014-1.178-.014-2.142-2.889.532-3.636-.704-3.866-1.35-.13-.331-.69-1.352-1.18-1.625-.402-.216-.977-.748-.014-.762.906-.014 1.553.834 1.769 1.179 1.035 1.74 2.688 1.25 3.349.948.1-.747.402-1.25.733-1.538-2.559-.287-5.232-1.279-5.232-5.678 0-1.25.445-2.285 1.178-3.09-.115-.288-.517-1.467.115-3.048 0 0 .963-.302 3.163 1.179.92-.259 1.897-.388 2.875-.388.977 0 1.955.13 2.875.388 2.2-1.495 3.162-1.179 3.162-1.179.633 1.581.23 2.76.115 3.048.733.805 1.179 1.825 1.179 3.09 0 4.413-2.688 5.39-5.247 5.678.417.36.776 1.05.776 2.128 0 1.538-.014 2.774-.014 3.162 0 .302.216.662.79.547C20.709 21.637 24 17.324 24 12.25 24 5.896 18.854.75 12.5.75Z"></path>
</svg>
Expand Down
197 changes: 108 additions & 89 deletions SharpPad/wwwroot/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ const monacoConfig = {
};

// 系统设置配置
const systemSettings = {
// 禁用GPT补全
disableGptComplete: false
};

const systemSettings = {
// 禁用GPT补全
disableGptComplete: false
};
// 获取系统设置
export function getSystemSettings() {
return systemSettings;
Expand Down Expand Up @@ -58,87 +58,90 @@ import { FileManager } from './fileSystem/fileManager.js';
import { registerCsharpProvider } from './csharpLanguageProvider.js';
import { CodeRunner } from './execution/runner.js';
import { OutputPanel } from './execution/outputPanel.js';
import { sendRequest } from './utils/apiService.js';
import { showNotification } from './utils/common.js';
import { NugetManager } from './components/nuget/nugetManager.js';
import { setupSemanticColoring } from './semanticColoring.js';
import desktopBridge from './utils/desktopBridge.js';
import { sendRequest } from './utils/apiService.js';
import { showNotification } from './utils/common.js';
import { NugetManager } from './components/nuget/nugetManager.js';
import { setupSemanticColoring } from './semanticColoring.js';
import desktopBridge from './utils/desktopBridge.js';

// 初始化应用
async function initializeApp() {
// 初始化系统设置
loadSystemSettings();

if (desktopBridge.isAvailable) {
desktopBridge.onceHostReady(() => {
console.log('Desktop bridge ready');
desktopBridge.send({ type: 'ping' });
});

desktopBridge.onMessage(message => {
if (!message?.type) {
return;
}

switch (message.type) {
case 'bridge-error':
if (message.message) {
showNotification(`桌面通信错误: ${message.message}`, 'error');
}
console.error('Desktop bridge error:', message);
break;
case 'bridge-warning':
console.warn('Desktop bridge warning:', message.message);
break;
case 'pick-and-upload-progress':
console.log('Desktop upload in progress...', message.status);
break;
case 'pick-and-upload-completed': {
const handled = typeof window.fileManager?.handleDesktopUpload === 'function'
? window.fileManager.handleDesktopUpload(message)
: false;

if (!handled) {
if (message.success) {
showNotification('文件上传成功', 'success');
} else if (message.cancelled) {
showNotification('已取消上传', 'info');
} else {
const error = message.message || '上传失败,请重试';
showNotification(error, 'error');
}
}
break;
}
case 'download-file-completed': {
const handled = typeof window.fileManager?.handleDesktopDownload === 'function'
? window.fileManager.handleDesktopDownload(message)
: false;

if (!handled) {
if (message.success) {
showNotification('文件导出成功', 'success');
} else if (message.cancelled) {
showNotification('已取消导出', 'info');
} else {
const error = message.message || '导出失败,请重试';
showNotification(error, 'error');
}
}
break;
}
case 'pong':
console.log('Desktop bridge handshake completed.');
break;
default:
break;
}
});

window.requestDesktopUpload = (endpoint, context) => desktopBridge.requestPickAndUpload(endpoint, context);
} else {
console.log('Desktop bridge unavailable - running in browser mode.');
}
async function initializeApp() {
// 初始化系统设置
loadSystemSettings();

if (desktopBridge.isAvailable) {
desktopBridge.onceHostReady(() => {
console.log('Desktop bridge ready');
desktopBridge.send({ type: 'ping' });

// Setup GitHub link to open in system browser
setupGitHubLinkHandler();
});

desktopBridge.onMessage(message => {
if (!message?.type) {
return;
}

switch (message.type) {
case 'bridge-error':
if (message.message) {
showNotification(`桌面通信错误: ${message.message}`, 'error');
}
console.error('Desktop bridge error:', message);
break;
case 'bridge-warning':
console.warn('Desktop bridge warning:', message.message);
break;
case 'pick-and-upload-progress':
console.log('Desktop upload in progress...', message.status);
break;
case 'pick-and-upload-completed': {
const handled = typeof window.fileManager?.handleDesktopUpload === 'function'
? window.fileManager.handleDesktopUpload(message)
: false;

if (!handled) {
if (message.success) {
showNotification('文件上传成功', 'success');
} else if (message.cancelled) {
showNotification('已取消上传', 'info');
} else {
const error = message.message || '上传失败,请重试';
showNotification(error, 'error');
}
}
break;
}
case 'download-file-completed': {
const handled = typeof window.fileManager?.handleDesktopDownload === 'function'
? window.fileManager.handleDesktopDownload(message)
: false;

if (!handled) {
if (message.success) {
showNotification('文件导出成功', 'success');
} else if (message.cancelled) {
showNotification('已取消导出', 'info');
} else {
const error = message.message || '导出失败,请重试';
showNotification(error, 'error');
}
}
break;
}
case 'pong':
console.log('Desktop bridge handshake completed.');
break;
default:
break;
}
});

window.requestDesktopUpload = (endpoint, context) => desktopBridge.requestPickAndUpload(endpoint, context);
} else {
console.log('Desktop bridge unavailable - running in browser mode.');
}

// 初始化文件系统
const fileManager = new FileManager();
Expand Down Expand Up @@ -294,11 +297,11 @@ async function initializeApp() {
});

// 将编辑器实例暴露给全局,以便其他模块使用
window.editor = editor;
window.editorInstance = editorInstance;
// 暴露 CodeActionProvider 用于调试
window.editor.codeActionProvider = editorInstance.codeActionProvider;
}
window.editor = editor;
window.editorInstance = editorInstance;
// 暴露 CodeActionProvider 用于调试
window.editor.codeActionProvider = editorInstance.codeActionProvider;
}

// 从 localStorage 加载系统设置
function loadSystemSettings() {
Expand All @@ -313,6 +316,22 @@ function loadSystemSettings() {
}
}

// 设置GitHub链接在桌面应用中使用系统浏览器打开
function setupGitHubLinkHandler() {
const githubLink = document.getElementById('githubLink');
if (!githubLink) {
return;
}

githubLink.addEventListener('click', (event) => {
event.preventDefault();
const url = githubLink.getAttribute('href');
if (url && desktopBridge.isAvailable) {
desktopBridge.openExternalUrl(url);
}
});
}

// 确保在DOM完全加载后再启动应用
function ensureDOMReady() {
return new Promise((resolve) => {
Expand Down
17 changes: 17 additions & 0 deletions SharpPad/wwwroot/utils/desktopBridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,23 @@ const desktopBridge = {
return false;
}

const posted = sendToHost(payload);
if (!posted) {
console.warn('Desktop bridge: 当前环境不支持宿主消息通道。');
}
return posted;
},
openExternalUrl(url) {
if (typeof url !== 'string' || url.trim().length === 0) {
console.warn('Desktop bridge: 缺少有效的URL。');
return false;
}

const payload = {
type: 'open-external-url',
url: url.trim()
};

const posted = sendToHost(payload);
if (!posted) {
console.warn('Desktop bridge: 当前环境不支持宿主消息通道。');
Expand Down