Skip to content
Closed
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
70 changes: 35 additions & 35 deletions cypress/e2e/copyCurl.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,45 +702,45 @@ describe('cURL functionality', () => {

// Check if element has text content (this gets all text including from child elements)
const textContent = $pre[0].textContent || $pre[0].innerText || ''
cy.log('Pre element textContent length:', textContent.length)
cy.log('Pre element textContent (first 100 chars):', textContent.substring(0, 100))
// cy.log('Pre element textContent length:', textContent.length)
// cy.log('Pre element textContent (first 100 chars):', textContent.substring(0, 100))

// Find the code element inside pre (transform wraps content in <code>)
const codeElement = $pre[0].querySelector('code')
cy.log('Code element found:', !!codeElement)
// cy.log('Code element found:', !!codeElement)
if (codeElement) {
cy.log('Code element textContent length:', codeElement.textContent?.length || 0)
// cy.log('Code element textContent length:', codeElement.textContent?.length || 0)
}

// Check computed styles for user-select on pre
const preStyles = window.getComputedStyle($pre[0])
cy.log('Pre computed user-select:', preStyles.userSelect)
cy.log('Pre computed pointer-events:', preStyles.pointerEvents)
cy.log('Pre computed cursor:', preStyles.cursor)
// cy.log('Pre computed user-select:', preStyles.userSelect)
// cy.log('Pre computed pointer-events:', preStyles.pointerEvents)
// cy.log('Pre computed cursor:', preStyles.cursor)

// Check computed styles on code element if it exists
if (codeElement) {
const codeStyles = window.getComputedStyle(codeElement)
cy.log('Code computed user-select:', codeStyles.userSelect)
cy.log('Code computed pointer-events:', codeStyles.pointerEvents)
// cy.log('Code computed user-select:', codeStyles.userSelect)
// cy.log('Code computed pointer-events:', codeStyles.pointerEvents)
}

// Check parent element styles
const parent = $pre[0].parentElement
if (parent) {
const parentStyles = window.getComputedStyle(parent)
cy.log('Parent user-select:', parentStyles.userSelect)
cy.log('Parent pointer-events:', parentStyles.pointerEvents)
cy.log('Parent cursor:', parentStyles.cursor)
// cy.log('Parent user-select:', parentStyles.userSelect)
// cy.log('Parent pointer-events:', parentStyles.pointerEvents)
// cy.log('Parent cursor:', parentStyles.cursor)
}

// Check if section is highlighted
const section = $pre[0].closest('section')
if (section) {
cy.log('Section has __cypress-highlight:', section.classList.contains('__cypress-highlight'))
// cy.log('Section has __cypress-highlight:', section.classList.contains('__cypress-highlight'))
const sectionStyles = window.getComputedStyle(section)
cy.log('Section cursor:', sectionStyles.cursor)
cy.log('Section pointer-events:', sectionStyles.pointerEvents)
// cy.log('Section cursor:', sectionStyles.cursor)
// cy.log('Section pointer-events:', sectionStyles.pointerEvents)
}

// Verify element has text before trying to select
Expand All @@ -765,13 +765,13 @@ describe('cURL functionality', () => {

// Verify text was selected
const selectedText = selection.toString()
cy.log('Selected text length:', selectedText.length)
cy.log('Selected text (first 100 chars):', selectedText.substring(0, 100))
cy.log('Selection range count:', selection.rangeCount)
// cy.log('Selected text length:', selectedText.length)
// cy.log('Selected text (first 100 chars):', selectedText.substring(0, 100))
// cy.log('Selection range count:', selection.rangeCount)

// If selection is empty, try selecting all text nodes
if (selectedText.length === 0) {
cy.log('Selection is empty, trying to select all text nodes...')
// cy.log('Selection is empty, trying to select all text nodes...')
const walker = document.createTreeWalker(
targetElement,
NodeFilter.SHOW_TEXT,
Expand All @@ -787,31 +787,31 @@ describe('cURL functionality', () => {
}

if (firstNode && lastNode) {
cy.log('Found text nodes - first:', firstNode.textContent?.substring(0, 20), 'last:', lastNode.textContent?.substring(0, 20))
// cy.log('Found text nodes - first:', firstNode.textContent?.substring(0, 20), 'last:', lastNode.textContent?.substring(0, 20))

// Try selecting the entire pre element first (simpler approach)
cy.log('Trying to select entire pre element contents...')
// cy.log('Trying to select entire pre element contents...')
const preRange = document.createRange()
preRange.selectNodeContents($pre[0])
selection.removeAllRanges()

try {
selection.addRange(preRange)
cy.log('Pre range added, rangeCount:', selection.rangeCount)
// cy.log('Pre range added, rangeCount:', selection.rangeCount)

// Wait a tick for selection to settle
cy.wait(10).then(() => {
const preSelectedText = selection.toString()
cy.log('After selecting pre contents, length:', preSelectedText.length)
cy.log('Selected text (first 100 chars):', preSelectedText.substring(0, 100))
// cy.log('After selecting pre contents, length:', preSelectedText.length)
// cy.log('Selected text (first 100 chars):', preSelectedText.substring(0, 100))

if (preSelectedText.length > 0) {
expect(preSelectedText).to.contain('curl', 'Selected text should contain curl command')
return // Success - exit early
}

// If pre selection failed, try text nodes
cy.log('Pre selection failed, trying text nodes...')
// cy.log('Pre selection failed, trying text nodes...')
const textRange = document.createRange()
textRange.setStart(firstNode, 0)
textRange.setEnd(lastNode, lastNode.textContent?.length || 0)
Expand All @@ -820,8 +820,8 @@ describe('cURL functionality', () => {

cy.wait(10).then(() => {
const newSelectedText = selection.toString()
cy.log('After text node selection, length:', newSelectedText.length)
cy.log('Selected text (first 100 chars):', newSelectedText.substring(0, 100))
// cy.log('After text node selection, length:', newSelectedText.length)
// cy.log('Selected text (first 100 chars):', newSelectedText.substring(0, 100))

if (newSelectedText.length > 0) {
expect(newSelectedText).to.contain('curl', 'Selected text should contain curl command')
Expand All @@ -830,23 +830,23 @@ describe('cURL functionality', () => {

// Last resort: verify text is accessible even if selection doesn't work
const accessibleText = $pre[0].textContent || $pre[0].innerText || ''
cy.log('Selection API not working, but text is accessible:', accessibleText.length, 'chars')
// cy.log('Selection API not working, but text is accessible:', accessibleText.length, 'chars')
expect(accessibleText.length).to.be.greaterThan(0, 'Text should be accessible even if selection fails')
expect(accessibleText).to.contain('curl', 'Text should contain curl command')
})
})
} catch (rangeError) {
cy.log('Error adding range:', rangeError)
// cy.log('Error adding range:', rangeError)
// Fallback: verify text is accessible
const accessibleText = $pre[0].textContent || $pre[0].innerText || ''
cy.log('Range error, but text is accessible:', accessibleText.length, 'chars')
// cy.log('Range error, but text is accessible:', accessibleText.length, 'chars')
expect(accessibleText.length).to.be.greaterThan(0, 'Text should be accessible even if selection API fails')
expect(accessibleText).to.contain('curl', 'Text should contain curl command')
}
} else {
cy.log('No text nodes found - checking element structure...')
cy.log('Pre element HTML length:', $pre[0].innerHTML.length)
cy.log('Pre element children count:', $pre[0].children.length)
// cy.log('No text nodes found - checking element structure...')
// cy.log('Pre element HTML length:', $pre[0].innerHTML.length)
// cy.log('Pre element children count:', $pre[0].children.length)

// Fallback: try selecting pre element directly
const preRange = document.createRange()
Expand All @@ -870,7 +870,7 @@ describe('cURL functionality', () => {
expect(selectedText).to.contain('curl', 'Selected text should contain curl command')
}
} catch (error) {
cy.log('Error adding range:', error)
// cy.log('Error adding range:', error)
throw error
}
})
Expand All @@ -884,7 +884,7 @@ describe('cURL functionality', () => {
const selection = window.getSelection()
if (selection) {
const selectedText = selection.toString()
cy.log('After drag selection, text length:', selectedText.length)
// cy.log('After drag selection, text length:', selectedText.length)
}
})
})
Expand Down
15 changes: 15 additions & 0 deletions cypress/e2e/transformXss.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { transform } from '../../src/modules/transform'

describe('transform() plaintext escaping', () => {
it('escapes HTML when Prism language is missing (plaintext)', () => {
const payload = '"</code><script>alert(1)</script>"'

const output = transform(payload, 'plaintext')

expect(output).to.contain('<code')
expect(output).to.contain('&lt;/code&gt;')
expect(output).to.contain('&lt;script&gt;alert(1)&lt;/script&gt;')
expect(output).to.not.contain('<script>')
expect(output).to.not.contain('</code><script>')
})
})
11 changes: 3 additions & 8 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,10 @@ app.get('/nested-empty', (req, res) => {

app.listen(port, () => console.log(`Example app listening on port ${port}!`))
.on('error', (err) => {
// Avoid additional logging in this repo; exit with non-zero status on server startup errors.
if (err.code === 'EADDRINUSE') {
console.error(`\n❌ Port ${port} is already in use.`)
console.error(` Please stop the process using port ${port} or use a different port.\n`)
console.error(` To find and kill the process on Windows:`)
console.error(` netstat -ano | findstr :${port}`)
console.error(` taskkill /PID <PID> /F\n`)
process.exit(1)
} else {
console.error('Server error:', err)
process.exit(1)
}

process.exit(1)
})
20 changes: 2 additions & 18 deletions src/modules/handleResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getPluginConfig } from '@utils/pluginConfig';
import { App } from 'vue';
import { getFormat } from '@utils/getFormat';
import { isValidUrlOrIp } from '@utils/isValidUrlOrIp';
import { generateCurl } from '@utils/generateCurl';

export const handleResponse = (res: ApiResponseBody, options: ApiRequestOptions, props: RequestProps[], index: number, app: App<Element>) => {

Expand Down Expand Up @@ -189,28 +190,11 @@ export const handleResponse = (res: ApiResponseBody, options: ApiRequestOptions,
.then(findSnapshotElement)
.then(($el) => {

const generateCurl = () => {
let curl = `curl -X ${options.method || 'GET'} "${options.url}"`;
if (options.headers) {
Object.entries(options.headers).forEach(([key, value]) => {
curl += ` -H "${key}: ${value}"`;
});
}
if (options.body) {
if (typeof options.body === 'object') {
curl += ` -d '${JSON.stringify(options.body)}'`;
} else {
curl += ` -d '${options.body}'`;
}
}
return curl;
};

log.set({
consoleProps() {
return {
yielded,
cURL: generateCurl()
cURL: generateCurl(props[index])
}
}
})
Expand Down
8 changes: 7 additions & 1 deletion src/modules/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,13 @@ export const transform = (body: any, language: 'json' | 'html' | 'xml' | 'blob'

const prismLanguage = Prism.languages[prismLanguageName]
if (!prismLanguage) {
return `<code class="language-plaintext">${content}</code>`
const escaped = content
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
return `<code class="language-plaintext">${escaped}</code>`
}
const formatted = Prism.highlight(content, prismLanguage, prismLanguageName)

Expand Down
15 changes: 8 additions & 7 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
}

.__cypress-highlight {
outline: 3px solid rgba(59, 130, 246, 0.6) !important;
@apply text-cy-blue;
outline: 3px solid currentColor !important;
outline-offset: 2px !important;
opacity: 1 !important;
pointer-events: auto !important;
Expand All @@ -34,7 +35,7 @@
section.__cypress-highlight {
pointer-events: auto !important;
cursor: pointer;
background-color: rgba(255, 255, 255, 0.1) !important;
@apply bg-white/10;
user-select: auto !important;
-webkit-user-select: auto !important;
-moz-user-select: auto !important;
Expand Down Expand Up @@ -301,17 +302,17 @@ section.__cypress-highlight [data-cy="copyCurl"] *,
}

.copy-curl-pre::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
@apply bg-black/10;
border-radius: 4px;
}

.copy-curl-pre::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
@apply bg-white/30;
border-radius: 4px;
}

.copy-curl-pre::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
@apply bg-white/50;
}

[data-cy="copyCurl"] pre,
Expand Down Expand Up @@ -342,7 +343,7 @@ code.language-xml .token.tag .token.tag-name,
.language-xml .token.tag,
.language-xml .token.tag-name,
.language-xml .token.tag .token.tag-name {
color: rgb(100, 112, 243) !important;
@apply text-cy-blue;
}

code.language-html .tag,
Expand All @@ -356,7 +357,7 @@ code.language-html .token.tag .token.tag-name,
.language-html .token.tag,
.language-html .token.tag-name,
.language-html .token.tag .token.tag-name {
color: rgb(255, 87, 112) !important;
@apply text-cy-red;
}
.operator, .punctuation, .subst{
@apply text-cy-gray
Expand Down
Loading