@@ -32,7 +32,7 @@ | |||||
"test": "npm run lint-errors && npm run just-test-in-node", | "test": "npm run lint-errors && npm run just-test-in-node", | ||||
"test-in-node": "npm run lint-errors && npm run just-test-in-node", | "test-in-node": "npm run lint-errors && npm run just-test-in-node", | ||||
"just-test": "karma start --config karma.conf.js", | "just-test": "karma start --config karma.conf.js", | ||||
"just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package", | |||||
"just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package test/xss", | |||||
"test-e2e": "sleep 3 && nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json", | "test-e2e": "sleep 3 && nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json", | ||||
"e2e-initial-render": "nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json --group initial-render", | "e2e-initial-render": "nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json --group initial-render", | ||||
"mock-api": "json-server --watch test/e2e/db.json --port 3204", | "mock-api": "json-server --watch test/e2e/db.json --port 3204", | ||||
@@ -29,7 +29,10 @@ Markdown.propTypes = { | |||||
export default Markdown | export default Markdown | ||||
const sanitizeOptions = { | const sanitizeOptions = { | ||||
allowedTags: sanitize.defaults.allowedTags.concat([ "img" ]), | |||||
allowedTags: sanitize.defaults.allowedTags.concat([ "h1", "h2", "img" ]), | |||||
allowedAttributes: { | |||||
"img": sanitize.defaults.allowedAttributes.img.concat(["title"]) | |||||
}, | |||||
textFilter: function(text) { | textFilter: function(text) { | ||||
return text.replace(/"/g, "\"") | return text.replace(/"/g, "\"") | ||||
} | } | ||||
@@ -83,8 +83,12 @@ export default class ResponseBody extends React.Component { | |||||
// Anything else (CORS) | // Anything else (CORS) | ||||
} else if (typeof content === "string") { | } else if (typeof content === "string") { | ||||
bodyEl = <HighlightCode value={ content } /> | bodyEl = <HighlightCode value={ content } /> | ||||
} else { | |||||
} else if ( content.size > 0 ) { | |||||
// We don't know the contentType, but there was some content returned | |||||
bodyEl = <div>Unknown response type</div> | bodyEl = <div>Unknown response type</div> | ||||
} else { | |||||
// We don't know the contentType and there was no content returned | |||||
bodyEl = null | |||||
} | } | ||||
return ( !bodyEl ? null : <div> | return ( !bodyEl ? null : <div> | ||||
@@ -7,13 +7,16 @@ export default function downloadUrlPlugin (toolbox) { | |||||
let { fn } = toolbox | let { fn } = toolbox | ||||
const actions = { | const actions = { | ||||
download: (url)=> ({ errActions, specSelectors, specActions }) => { | |||||
download: (url)=> ({ errActions, specSelectors, specActions, getConfigs }) => { | |||||
let { fetch } = fn | let { fetch } = fn | ||||
const config = getConfigs() | |||||
url = url || specSelectors.url() | url = url || specSelectors.url() | ||||
specActions.updateLoadingStatus("loading") | specActions.updateLoadingStatus("loading") | ||||
fetch({ | fetch({ | ||||
url, | url, | ||||
loadSpec: true, | loadSpec: true, | ||||
requestInterceptor: config.requestInterceptor || (a => a), | |||||
responseInterceptor: config.responseInterceptor || (a => a), | |||||
credentials: "same-origin", | credentials: "same-origin", | ||||
headers: { | headers: { | ||||
"Accept": "application/json,*/*" | "Accept": "application/json,*/*" | ||||
@@ -1,10 +1,11 @@ | |||||
import React from "react" | import React from "react" | ||||
import PropTypes from "prop-types" | |||||
import ReactMarkdown from "react-markdown" | import ReactMarkdown from "react-markdown" | ||||
import { Parser, HtmlRenderer } from "commonmark" | import { Parser, HtmlRenderer } from "commonmark" | ||||
import { OAS3ComponentWrapFactory } from "../helpers" | import { OAS3ComponentWrapFactory } from "../helpers" | ||||
import { sanitizer } from "core/components/providers/markdown" | import { sanitizer } from "core/components/providers/markdown" | ||||
export default OAS3ComponentWrapFactory(({ source }) => { | |||||
export const Markdown = ({ source }) => { | |||||
if ( source ) { | if ( source ) { | ||||
const parser = new Parser() | const parser = new Parser() | ||||
const writer = new HtmlRenderer() | const writer = new HtmlRenderer() | ||||
@@ -23,4 +24,9 @@ export default OAS3ComponentWrapFactory(({ source }) => { | |||||
) | ) | ||||
} | } | ||||
return null | return null | ||||
}) | |||||
} | |||||
Markdown.propTypes = { | |||||
source: PropTypes.string | |||||
} | |||||
export default OAS3ComponentWrapFactory(Markdown) |
@@ -543,14 +543,14 @@ | |||||
.response-col_description__inner | .response-col_description__inner | ||||
{ | { | ||||
span | |||||
div.markdown, div.renderedMarkdown | |||||
{ | { | ||||
font-size: 12px; | font-size: 12px; | ||||
font-style: italic; | font-style: italic; | ||||
display: block; | display: block; | ||||
margin: 10px 0; | |||||
margin: 0; | |||||
padding: 10px; | padding: 10px; | ||||
border-radius: 4px; | border-radius: 4px; | ||||
@@ -0,0 +1,48 @@ | |||||
/* eslint-env mocha */ | |||||
import React from "react" | |||||
import expect from "expect" | |||||
import { render } from "enzyme" | |||||
import Markdown from "components/providers/markdown" | |||||
import { Markdown as OAS3Markdown } from "corePlugins/oas3/wrap-components/markdown.js" | |||||
describe("Markdown component", function() { | |||||
describe("Swagger 2.0", function() { | |||||
it("allows image elements", function() { | |||||
const str = `![Image alt text](http://image.source "Image title")` | |||||
const el = render(<Markdown source={str} />) | |||||
expect(el.html()).toEqual(`<div class="markdown"><p><img src="http://image.source" title="Image title"></p>\n</div>`) | |||||
}) | |||||
it("allows heading elements", function() { | |||||
const str = ` | |||||
# h1 | |||||
## h2 | |||||
### h3 | |||||
#### h4 | |||||
##### h5 | |||||
###### h6` | |||||
const el = render(<Markdown source={str} />) | |||||
expect(el.html()).toEqual(`<div class="markdown"><h1>h1</h1>\n<h2>h2</h2>\n<h3>h3</h3>\n<h4>h4</h4>\n<h5>h5</h5>\n<h6>h6</h6>\n</div>`) | |||||
}) | |||||
}) | |||||
describe("OAS 3", function() { | |||||
it("allows image elements", function() { | |||||
const str = `![Image alt text](http://image.source "Image title")` | |||||
const el = render(<OAS3Markdown source={str} />) | |||||
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img src="http://image.source" title="Image title"></p></div></div>`) | |||||
}) | |||||
it("allows heading elements", function() { | |||||
const str = ` | |||||
# h1 | |||||
## h2 | |||||
### h3 | |||||
#### h4 | |||||
##### h5 | |||||
###### h6` | |||||
const el = render(<OAS3Markdown source={str} />) | |||||
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><h1>h1</h1>\n<h2>h2</h2>\n<h3>h3</h3>\n<h4>h4</h4>\n<h5>h5</h5>\n<h6>h6</h6></div></div>`) | |||||
}) | |||||
}) | |||||
}) |
@@ -0,0 +1,33 @@ | |||||
/* eslint-env mocha */ | |||||
import React from "react" | |||||
import expect from "expect" | |||||
import { render } from "enzyme" | |||||
import { fromJS } from "immutable" | |||||
import Info from "components/info" | |||||
import Markdown from "components/providers/markdown" | |||||
describe("<Info/> Sanitization", function(){ | |||||
const dummyComponent = () => null | |||||
const components = { | |||||
Markdown | |||||
} | |||||
const props = { | |||||
getComponent: c => components[c] || dummyComponent, | |||||
info: fromJS({ | |||||
title: "Test Title **strong** <script>alert(1)</script>", | |||||
description: "Description *with* <script>Markdown</script>" | |||||
}), | |||||
host: "example.test", | |||||
basePath: "/api" | |||||
} | |||||
it("renders sanitized .title content", function(){ | |||||
let wrapper = render(<Info {...props}/>) | |||||
expect(wrapper.find(".title").html()).toEqual("Test Title **strong** <script>alert(1)</script>") | |||||
}) | |||||
it("renders sanitized .description content", function() { | |||||
let wrapper = render(<Info {...props}/>) | |||||
expect(wrapper.find(".description").html()).toEqual("<div class=\"markdown\"><p>Description <em>with</em> </p>\n</div>") | |||||
}) | |||||
}) |
@@ -0,0 +1,36 @@ | |||||
/* eslint-env mocha */ | |||||
import React from "react" | |||||
import expect from "expect" | |||||
import { render } from "enzyme" | |||||
import Markdown from "components/providers/markdown" | |||||
import { Markdown as OAS3Markdown } from "corePlugins/oas3/wrap-components/markdown.js" | |||||
describe("Markdown Script Sanitization", function() { | |||||
describe("Swagger 2.0", function() { | |||||
it("sanitizes <script> elements", function() { | |||||
const str = `script <script>alert(1)</script>` | |||||
const el = render(<Markdown source={str} />) | |||||
expect(el.html()).toEqual(`<div class="markdown"><p>script </p>\n</div>`) | |||||
}) | |||||
it("sanitizes <img> elements", function() { | |||||
const str = `<img src=x onerror="alert('img-in-description')">` | |||||
const el = render(<Markdown source={str} />) | |||||
expect(el.html()).toEqual(`<div class="markdown"><p><img src="x"></p>\n</div>`) | |||||
}) | |||||
}) | |||||
describe("OAS 3", function() { | |||||
it("sanitizes <script> elements", function() { | |||||
const str = `script <script>alert(1)</script>` | |||||
const el = render(<OAS3Markdown source={str} />) | |||||
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p>script </p></div></div>`) | |||||
}) | |||||
it("sanitizes <img> elements", function() { | |||||
const str = `<img src=x onerror="alert('img-in-description')">` | |||||
const el = render(<OAS3Markdown source={str} />) | |||||
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><img src="x"></div></div>`) | |||||
}) | |||||
}) | |||||
}) |