Merge pull request #2 from rfc1437/alert-autofix-4

Potential fix for code scanning alert no. 4: Incomplete string escaping or encoding
This commit is contained in:
Georg Bauer
2026-02-16 18:06:49 +01:00
committed by GitHub
3 changed files with 83 additions and 22 deletions

View File

@@ -239,18 +239,79 @@ export class GitEngine {
];
}
/**
* Extracts the host portion from common Git URL formats.
*
* Supports:
* - HTTPS/HTTP: https://host/owner/repo.git
* - SSH: git@host:owner/repo.git
* - SSH URL: ssh://git@host/owner/repo.git
*/
private getHostFromGitUrl(value: string): string | null {
const trimmed = value.trim();
// Try standard URL parsing for HTTP(S) and ssh:// URLs
try {
const url = new URL(trimmed);
if (url.hostname) {
return url.hostname.toLowerCase();
}
} catch {
// Fall through to manual parsing for scp-like SSH syntax
}
// Match scp-like SSH syntax: [user@]host:owner/repo.git
// Examples:
// git@github.com:owner/repo.git
// git@gitlab.example.com:owner/repo.git
const sshLikeMatch = trimmed.match(/^[^@]+@([^:]+):.+$/);
if (sshLikeMatch && sshLikeMatch[1]) {
return sshLikeMatch[1].toLowerCase();
}
return null;
}
private isGitHubUrl(value: string): boolean {
const normalized = value.toLowerCase();
return normalized.includes('github.com') || normalized.includes('git@github.com:');
const host = this.getHostFromGitUrl(value);
if (host) {
// Accept github.com and common www-prefixed variant.
return host === 'github.com' || host === 'www.github.com';
}
// Fallback for non-standard patterns like "github.com:owner/repo"
const normalized = value.trim().toLowerCase();
return normalized.startsWith('github.com:') || normalized.startsWith('ssh://github.com/');
}
private isGitLabUrl(value: string): boolean {
const normalized = value.toLowerCase();
return normalized.includes('gitlab.com') || normalized.includes('git@gitlab.com:') || normalized.includes('gitlab');
const host = this.getHostFromGitUrl(value);
if (host) {
// Hosted GitLab
if (host === 'gitlab.com' || host === 'www.gitlab.com') {
return true;
}
// Self-hosted GitLab: many instances include "gitlab" in the hostname.
if (host.includes('gitlab')) {
return true;
}
}
// Fallback for non-standard patterns like "gitlab.com:owner/repo"
const normalized = value.trim().toLowerCase();
return normalized.startsWith('gitlab.com:') || normalized.startsWith('ssh://gitlab.com/');
}
private isGiteaForgejoUrl(value: string): boolean {
const normalized = value.toLowerCase();
const host = this.getHostFromGitUrl(value);
if (host) {
if (host.includes('gitea') || host.includes('forgejo')) {
return true;
}
}
// Fallback: if we cannot parse a host, fall back to substring detection.
const normalized = value.trim().toLowerCase();
return normalized.includes('gitea') || normalized.includes('forgejo');
}

View File

@@ -1630,7 +1630,7 @@ describe('ImportExecutionEngine E2E Tests', () => {
/**
* Creates a custom post with specific content for URL conversion testing
*/
function createPostWithContent(content: string, siteUrl: string = 'https://testblog.example.com'): ImportAnalysisReport {
function createPostWithContent(content: string, siteUrl: string = 'https://testblog'): ImportAnalysisReport {
const customPost: WxrPost = {
wpId: 9001,
title: 'URL Conversion Test Post',
@@ -1673,7 +1673,7 @@ describe('ImportExecutionEngine E2E Tests', () => {
it('should convert absolute media URLs from site domain to relative paths', async () => {
// Post with image URL pointing to the site's own media
const content = `<p>Check out this image:</p>
<img src="https://testblog.example.com/wp-content/uploads/2022/11/P1010853_01.jpg" alt="My Photo" />
<img src="https://testblog/wp-content/uploads/2022/11/P1010853_01.jpg" alt="My Photo" />
<p>Nice, right?</p>`;
const report = createPostWithContent(content);
@@ -1687,13 +1687,13 @@ describe('ImportExecutionEngine E2E Tests', () => {
// Should convert to relative media URL
expect(fileContent).toContain('![My Photo](media/2022/11/P1010853_01.jpg)');
// Should NOT contain the absolute URL
expect(fileContent).not.toContain('https://testblog.example.com/wp-content/uploads');
expect(fileContent).not.toContain('https://testblog/wp-content/uploads');
});
it('should convert linked images with absolute media URLs to relative paths', async () => {
// Linked image pattern common in WordPress - thumbnail links to full-size
const content = `<a href="https://testblog.example.com/wp-content/uploads/2022/11/full-size.jpg">
<img src="https://testblog.example.com/wp-content/uploads/2022/11/thumb.jpg" alt="Gallery Image" />
const content = `<a href="https://testblog/wp-content/uploads/2022/11/full-size.jpg">
<img src="https://testblog/wp-content/uploads/2022/11/thumb.jpg" alt="Gallery Image" />
</a>`;
const report = createPostWithContent(content);
@@ -1707,13 +1707,13 @@ describe('ImportExecutionEngine E2E Tests', () => {
// The linked image rule uses the href (full-size) as the image URL
expect(fileContent).toContain('media/2022/11/full-size.jpg');
// Should NOT contain absolute URLs
expect(fileContent).not.toContain('https://testblog.example.com/wp-content/uploads');
expect(fileContent).not.toContain('https://testblog/wp-content/uploads');
});
it('should preserve external image URLs that are not from the site', async () => {
// Mix of site-owned and external images
const content = `<p>Own image:</p>
<img src="https://testblog.example.com/wp-content/uploads/2024/01/local.jpg" alt="Local" />
<img src="https://testblog/wp-content/uploads/2024/01/local.jpg" alt="Local" />
<p>External image:</p>
<img src="https://external-site.com/images/photo.jpg" alt="External" />`;
@@ -1760,7 +1760,7 @@ describe('ImportExecutionEngine E2E Tests', () => {
it('should convert media URLs in markdown image syntax after HTML conversion', async () => {
// Sometimes WordPress content already has markdown-like syntax in HTML
const content = `<p>Image with title:</p>
<img src="https://testblog.example.com/wp-content/uploads/2024/02/sunset.png" alt="Sunset" title="Beautiful Sunset" />`;
<img src="https://testblog/wp-content/uploads/2024/02/sunset.png" alt="Sunset" title="Beautiful Sunset" />`;
const report = createPostWithContent(content);
await engine.executeImport(report, {});
@@ -1776,9 +1776,9 @@ describe('ImportExecutionEngine E2E Tests', () => {
it('should handle multiple images in same post', async () => {
const content = `<p>Gallery:</p>
<img src="https://testblog.example.com/wp-content/uploads/2024/01/img1.jpg" alt="Image 1" />
<img src="https://testblog.example.com/wp-content/uploads/2024/01/img2.jpg" alt="Image 2" />
<img src="https://testblog.example.com/wp-content/uploads/2024/02/img3.jpg" alt="Image 3" />`;
<img src="https://testblog/wp-content/uploads/2024/01/img1.jpg" alt="Image 1" />
<img src="https://testblog/wp-content/uploads/2024/01/img2.jpg" alt="Image 2" />
<img src="https://testblog/wp-content/uploads/2024/02/img3.jpg" alt="Image 3" />`;
const report = createPostWithContent(content);
await engine.executeImport(report, {});
@@ -1793,7 +1793,7 @@ describe('ImportExecutionEngine E2E Tests', () => {
});
it('should handle deep nested upload paths', async () => {
const content = `<img src="https://testblog.example.com/wp-content/uploads/sites/2/2024/03/nested/deep/image.jpg" alt="Deep" />`;
const content = `<img src="https://testblog/wp-content/uploads/sites/2/2024/03/nested/deep/image.jpg" alt="Deep" />`;
const report = createPostWithContent(content);
await engine.executeImport(report, {});
@@ -1808,8 +1808,8 @@ describe('ImportExecutionEngine E2E Tests', () => {
it('should NOT convert wp-content/themes or wp-content/plugins URLs', async () => {
// Assets from themes/plugins should stay absolute (they're not imported media)
const content = `<img src="https://testblog.example.com/wp-content/themes/mytheme/images/logo.png" alt="Theme Logo" />
<img src="https://testblog.example.com/wp-content/plugins/myplugin/assets/icon.png" alt="Plugin Icon" />`;
const content = `<img src="https://testblog/wp-content/themes/mytheme/images/logo.png" alt="Theme Logo" />
<img src="https://testblog/wp-content/plugins/myplugin/assets/icon.png" alt="Plugin Icon" />`;
const report = createPostWithContent(content);
await engine.executeImport(report, {});
@@ -1819,9 +1819,9 @@ describe('ImportExecutionEngine E2E Tests', () => {
const fileContent = writtenFile!.content;
// Theme assets should remain absolute
expect(fileContent).toContain('https://testblog.example.com/wp-content/themes/');
expect(fileContent).toContain('https://testblog/wp-content/themes/');
// Plugin assets should remain absolute
expect(fileContent).toContain('https://testblog.example.com/wp-content/plugins/');
expect(fileContent).toContain('https://testblog/wp-content/plugins/');
});
});
});

View File

@@ -79,7 +79,7 @@ vi.mock('@milkdown/kit/utils', () => ({
$remark: () => ({}),
$prose: () => ({}),
replaceAll: (content: string) => () => {
const normalized = content.replace('\n', '\n\n');
const normalized = content.replace(/\n/g, '\n\n');
markdownUpdatedHandler?.({}, normalized, '');
},
callCommand: () => () => {},