diff --git a/src/core/components/info.jsx b/src/core/components/info.jsx
index 0ebf8811..2926bd77 100644
--- a/src/core/components/info.jsx
+++ b/src/core/components/info.jsx
@@ -1,8 +1,8 @@
import React from "react"
import PropTypes from "prop-types"
-import { fromJS } from "immutable"
import ImPropTypes from "react-immutable-proptypes"
import { sanitizeUrl } from "core/utils"
+import { buildUrl } from "core/utils/url"
export class InfoBasePath extends React.Component {
@@ -26,13 +26,16 @@ export class InfoBasePath extends React.Component {
class Contact extends React.Component {
static propTypes = {
data: PropTypes.object,
- getComponent: PropTypes.func.isRequired
+ getComponent: PropTypes.func.isRequired,
+ specSelectors: PropTypes.object.isRequired,
+ selectedServer: PropTypes.string,
+ url: PropTypes.string.isRequired,
}
render(){
- let { data, getComponent } = this.props
+ let { data, getComponent, selectedServer, url: specUrl} = this.props
let name = data.get("name") || "the developer"
- let url = data.get("url")
+ let url = buildUrl(data.get("url"), specUrl, {selectedServer})
let email = data.get("email")
const Link = getComponent("Link")
@@ -53,17 +56,18 @@ class Contact extends React.Component {
class License extends React.Component {
static propTypes = {
license: PropTypes.object,
- getComponent: PropTypes.func.isRequired
-
+ getComponent: PropTypes.func.isRequired,
+ specSelectors: PropTypes.object.isRequired,
+ selectedServer: PropTypes.string,
+ url: PropTypes.string.isRequired,
}
render(){
- let { license, getComponent } = this.props
+ let { license, getComponent, selectedServer, url: specUrl } = this.props
const Link = getComponent("Link")
-
- let name = license.get("name") || "License"
- let url = license.get("url")
+ let name = license.get("name") || "License"
+ let url = buildUrl(license.get("url"), specUrl, {selectedServer})
return (
@@ -88,7 +92,7 @@ export class InfoUrl extends React.PureComponent {
const Link = getComponent("Link")
- return { url }
+ return { url }
}
}
@@ -100,17 +104,21 @@ export default class Info extends React.Component {
basePath: PropTypes.string,
externalDocs: ImPropTypes.map,
getComponent: PropTypes.func.isRequired,
+ oas3selectors: PropTypes.func,
+ selectedServer: PropTypes.string,
}
render() {
- let { info, url, host, basePath, getComponent, externalDocs } = this.props
+ let { info, url, host, basePath, getComponent, externalDocs, selectedServer, url: specUrl } = this.props
let version = info.get("version")
let description = info.get("description")
let title = info.get("title")
- let termsOfService = info.get("termsOfService")
+ let termsOfServiceUrl = buildUrl(info.get("termsOfService"), specUrl, {selectedServer})
let contact = info.get("contact")
let license = info.get("license")
- const { url:externalDocsUrl, description:externalDocsDescription } = (externalDocs || fromJS({})).toJS()
+ let rawExternalDocsUrl = externalDocs && externalDocs.get("url")
+ let externalDocsUrl = buildUrl(rawExternalDocsUrl, specUrl, {selectedServer})
+ let externalDocsDescription = externalDocs && externalDocs.get("description")
const Markdown = getComponent("Markdown", true)
const Link = getComponent("Link")
@@ -133,14 +141,14 @@ export default class Info extends React.Component {
{
- termsOfService &&
-
Terms of service
+ termsOfServiceUrl &&
+ Terms of service
}
- {contact && contact.size ?
: null }
- {license && license.size ?
: null }
- { externalDocsUrl ?
+ {contact && contact.size ?
: null }
+ {license && license.size ?
: null }
+ { externalDocs ?
{externalDocsDescription || externalDocsUrl}
: null }
diff --git a/src/core/components/operation-tag.jsx b/src/core/components/operation-tag.jsx
index d2e3f31d..1e71c587 100644
--- a/src/core/components/operation-tag.jsx
+++ b/src/core/components/operation-tag.jsx
@@ -3,6 +3,7 @@ import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import Im from "immutable"
import { createDeepLinkPath, escapeDeepLinkPath, sanitizeUrl } from "core/utils"
+import { buildUrl } from "core/utils/url"
export default class OperationTag extends React.Component {
@@ -15,12 +16,15 @@ export default class OperationTag extends React.Component {
tagObj: ImPropTypes.map.isRequired,
tag: PropTypes.string.isRequired,
+ oas3Selectors: PropTypes.func.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
getConfigs: PropTypes.func.isRequired,
getComponent: PropTypes.func.isRequired,
+ specUrl: PropTypes.string.isRequired,
+
children: PropTypes.element,
}
@@ -29,11 +33,12 @@ export default class OperationTag extends React.Component {
tagObj,
tag,
children,
-
+ oas3Selectors,
layoutSelectors,
layoutActions,
getConfigs,
getComponent,
+ specUrl,
} = this.props
let {
@@ -50,7 +55,8 @@ export default class OperationTag extends React.Component {
let tagDescription = tagObj.getIn(["tagDetails", "description"], null)
let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"])
- let tagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"])
+ let rawTagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"])
+ let tagExternalDocsUrl = buildUrl( rawTagExternalDocsUrl, specUrl, {selectedServer: oas3Selectors.selectedServer()} )
let isShownKey = ["operations-tag", tag]
let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list")
diff --git a/src/core/components/operation.jsx b/src/core/components/operation.jsx
index a4a79977..117d8227 100644
--- a/src/core/components/operation.jsx
+++ b/src/core/components/operation.jsx
@@ -2,6 +2,7 @@ import React, { PureComponent } from "react"
import PropTypes from "prop-types"
import { getList } from "core/utils"
import { getExtensions, sanitizeUrl, escapeDeepLinkPath } from "core/utils"
+import { buildUrl } from "core/utils/url"
import { Iterable, List } from "immutable"
import ImPropTypes from "react-immutable-proptypes"
@@ -81,6 +82,7 @@ export default class Operation extends PureComponent {
schemes
} = op
+ const externalDocsUrl = externalDocs ? buildUrl(externalDocs.url, specSelectors.url(), { selectedServer: oas3Selectors.selectedServer() }) : ""
let operation = operationProps.getIn(["op"])
let responses = operation.get("responses")
let parameters = getList(operation, ["parameters"])
@@ -127,14 +129,14 @@ export default class Operation extends PureComponent {
}
{
- externalDocs && externalDocs.url ?
+ externalDocsUrl ?
Find more details
- {externalDocs.url}
+ {externalDocsUrl}
: null
}
diff --git a/src/core/components/operations.jsx b/src/core/components/operations.jsx
index 68244177..505217ff 100644
--- a/src/core/components/operations.jsx
+++ b/src/core/components/operations.jsx
@@ -16,6 +16,7 @@ export default class Operations extends React.Component {
specActions: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
+ oas3Selectors: PropTypes.func.isRequired,
layoutSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
@@ -28,6 +29,7 @@ export default class Operations extends React.Component {
let {
specSelectors,
getComponent,
+ oas3Selectors,
layoutSelectors,
layoutActions,
getConfigs,
@@ -65,10 +67,12 @@ export default class Operations extends React.Component {
key={"operation-" + tag}
tagObj={tagObj}
tag={tag}
+ oas3Selectors={oas3Selectors}
layoutSelectors={layoutSelectors}
layoutActions={layoutActions}
getConfigs={getConfigs}
- getComponent={getComponent}>
+ getComponent={getComponent}
+ specUrl={specSelectors.url()}>
{
operations.map( op => {
const path = op.get("path")
diff --git a/src/core/containers/info.jsx b/src/core/containers/info.jsx
index 4ae558c7..ebf01a1f 100644
--- a/src/core/containers/info.jsx
+++ b/src/core/containers/info.jsx
@@ -7,16 +7,18 @@ export default class InfoContainer extends React.Component {
specActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
+ oas3Selectors: PropTypes.func.isRequired,
}
render () {
- const {specSelectors, getComponent} = this.props
+ const {specSelectors, getComponent, oas3Selectors} = this.props
const info = specSelectors.info()
const url = specSelectors.url()
const basePath = specSelectors.basePath()
const host = specSelectors.host()
const externalDocs = specSelectors.externalDocs()
+ const selectedServer = oas3Selectors.selectedServer()
const Info = getComponent("info")
@@ -24,7 +26,7 @@ export default class InfoContainer extends React.Component {
{info && info.count() ? (
+ getComponent={getComponent} selectedServer={selectedServer} />
) : null}
)
diff --git a/src/core/utils.js b/src/core/utils.js
index 00078cec..30b3f5fd 100644
--- a/src/core/utils.js
+++ b/src/core/utils.js
@@ -399,7 +399,7 @@ export const validatePattern = (val, rxPattern) => {
// validation of parameters before execute
export const validateParam = (param, value, { isOAS3 = false, bypassRequiredCheck = false } = {}) => {
-
+
let errors = []
let paramRequired = param.get("required")
@@ -436,7 +436,7 @@ export const validateParam = (param, value, { isOAS3 = false, bypassRequiredChec
let objectStringCheck = type === "object" && typeof value === "string" && value
const allChecks = [
- stringCheck, arrayCheck, arrayListCheck, arrayStringCheck, fileCheck,
+ stringCheck, arrayCheck, arrayListCheck, arrayStringCheck, fileCheck,
booleanCheck, numberCheck, integerCheck, objectCheck, objectStringCheck,
]
@@ -640,7 +640,6 @@ export function sanitizeUrl(url) {
return braintreeSanitizeUrl(url)
}
-
export function requiresValidationURL(uri) {
if (!uri || uri.indexOf("localhost") >= 0 || uri.indexOf("127.0.0.1") >= 0 || uri === "none") {
return false
diff --git a/src/core/utils/url.js b/src/core/utils/url.js
new file mode 100644
index 00000000..2f063445
--- /dev/null
+++ b/src/core/utils/url.js
@@ -0,0 +1,23 @@
+export function isAbsoluteUrl(url) {
+ return url.match(/^(?:[a-z]+:)?\/\//i) // Matches http://, HTTP://, https://, ftp://, //example.com,
+}
+
+export function addProtocol(url) {
+ if(!url.match(/^\/\//i)) return url // Checks if protocol is missing e.g. //example.com
+ return `${window.location.protocol}${url}`
+}
+
+export function buildBaseUrl(selectedServer, specUrl) {
+ if(!selectedServer) return specUrl
+ if(isAbsoluteUrl(selectedServer)) return addProtocol(selectedServer)
+
+ return new URL(selectedServer, specUrl).href
+}
+
+export function buildUrl(url, specUrl, { selectedServer="" } = {}) {
+ if(!url) return
+ if(isAbsoluteUrl(url)) return url
+
+ const baseUrl = buildBaseUrl(selectedServer, specUrl)
+ return new URL(url, baseUrl).href
+}
diff --git a/test/mocha/components/info-wrapper.jsx b/test/mocha/components/info-wrapper.jsx
index fcb98d6f..ba8be696 100644
--- a/test/mocha/components/info-wrapper.jsx
+++ b/test/mocha/components/info-wrapper.jsx
@@ -17,7 +17,10 @@ describe("", function () {
url () {},
basePath () {},
host () {},
- externalDocs () {}
+ externalDocs () {},
+ },
+ oas3Selectors: {
+ selectedServer () {},
},
getComponent: c => components[c]
}
diff --git a/test/mocha/components/operations.jsx b/test/mocha/components/operations.jsx
index 844a4931..4da89427 100644
--- a/test/mocha/components/operations.jsx
+++ b/test/mocha/components/operations.jsx
@@ -29,6 +29,7 @@ describe("", function(){
},
specSelectors: {
isOAS3() { return false },
+ url() { return "https://petstore.swagger.io/v2/swagger.json" },
taggedOperations() {
return fromJS({
"default": {
@@ -83,6 +84,7 @@ describe("", function(){
},
specSelectors: {
isOAS3() { return true },
+ url() { return "https://petstore.swagger.io/v2/swagger.json" },
taggedOperations() {
return fromJS({
"default": {
diff --git a/test/mocha/core/utils.js b/test/mocha/core/utils.js
index b7cbe8f9..e55c4cbb 100644
--- a/test/mocha/core/utils.js
+++ b/test/mocha/core/utils.js
@@ -32,6 +32,13 @@ import {
generateCodeVerifier,
createCodeChallenge,
} from "core/utils"
+
+import {
+ isAbsoluteUrl,
+ buildBaseUrl,
+ buildUrl,
+} from "core/utils/url"
+
import win from "core/window"
describe("utils", function() {
@@ -1334,6 +1341,92 @@ describe("utils", function() {
})
})
+ describe("isAbsoluteUrl", function() {
+
+ it("check if url is absolute", function() {
+ expect(!!isAbsoluteUrl("http://example.com")).toEqual(true)
+ expect(!!isAbsoluteUrl("https://secure-example.com")).toEqual(true)
+ expect(!!isAbsoluteUrl("HTTP://uppercase-example.com")).toEqual(true)
+ expect(!!isAbsoluteUrl("HTTP://uppercase-secure-example.com")).toEqual(true)
+ expect(!!isAbsoluteUrl("http://trailing-slash.com/")).toEqual(true)
+ expect(!!isAbsoluteUrl("ftp://file-transfer-protocol.com")).toEqual(true)
+ expect(!!isAbsoluteUrl("//no-protocol.com")).toEqual(true)
+ })
+
+ it("check if url is not absolute", function() {
+ expect(!!isAbsoluteUrl("/url-relative-to-host/base-path/path")).toEqual(false)
+ expect(!!isAbsoluteUrl("url-relative-to-base/base-path/path")).toEqual(false)
+ })
+ })
+
+ describe("buildBaseUrl", function() {
+ const specUrl = "https://petstore.swagger.io/v2/swagger.json"
+
+ const noServerSelected = ""
+ const absoluteServerUrl = "https://server-example.com/base-path/path"
+ const serverUrlRelativeToBase = "server-example/base-path/path"
+ const serverUrlRelativeToHost = "/server-example/base-path/path"
+
+ it("build base url with no server selected", function() {
+ expect(buildBaseUrl(noServerSelected, specUrl)).toBe("https://petstore.swagger.io/v2/swagger.json")
+ })
+
+ it("build base url from absolute server url", function() {
+ expect(buildBaseUrl(absoluteServerUrl, specUrl)).toBe("https://server-example.com/base-path/path")
+ })
+
+ it("build base url from relative server url", function() {
+ expect(buildBaseUrl(serverUrlRelativeToBase, specUrl)).toBe("https://petstore.swagger.io/v2/server-example/base-path/path")
+ expect(buildBaseUrl(serverUrlRelativeToHost, specUrl)).toBe("https://petstore.swagger.io/server-example/base-path/path")
+ })
+ })
+
+ describe("buildUrl", function() {
+ const specUrl = "https://petstore.swagger.io/v2/swagger.json"
+
+ const noUrl = ""
+ const absoluteUrl = "https://example.com/base-path/path"
+ const urlRelativeToBase = "relative-url/base-path/path"
+ const urlRelativeToHost = "/relative-url/base-path/path"
+
+ const noServerSelected = ""
+ const absoluteServerUrl = "https://server-example.com/base-path/path"
+ const serverUrlRelativeToBase = "server-example/base-path/path"
+ const serverUrlRelativeToHost = "/server-example/base-path/path"
+
+ it("build no url", function() {
+ expect(buildUrl(noUrl, specUrl, { selectedServer: absoluteServerUrl })).toBe(undefined)
+ expect(buildUrl(noUrl, specUrl, { selectedServer: serverUrlRelativeToBase })).toBe(undefined)
+ expect(buildUrl(noUrl, specUrl, { selectedServer: serverUrlRelativeToHost })).toBe(undefined)
+ })
+
+ it("build absolute url", function() {
+ expect(buildUrl(absoluteUrl, specUrl, { selectedServer: absoluteServerUrl })).toBe("https://example.com/base-path/path")
+ expect(buildUrl(absoluteUrl, specUrl, { selectedServer: serverUrlRelativeToBase })).toBe("https://example.com/base-path/path")
+ expect(buildUrl(absoluteUrl, specUrl, { selectedServer: serverUrlRelativeToHost })).toBe("https://example.com/base-path/path")
+ })
+
+ it("build relative url with no server selected", function() {
+ expect(buildUrl(urlRelativeToBase, specUrl, { selectedServer: noServerSelected })).toBe("https://petstore.swagger.io/v2/relative-url/base-path/path")
+ expect(buildUrl(urlRelativeToHost, specUrl, { selectedServer: noServerSelected })).toBe("https://petstore.swagger.io/relative-url/base-path/path")
+ })
+
+ it("build relative url with absolute server url", function() {
+ expect(buildUrl(urlRelativeToBase, specUrl, { selectedServer: absoluteServerUrl })).toBe("https://server-example.com/base-path/relative-url/base-path/path")
+ expect(buildUrl(urlRelativeToHost, specUrl, { selectedServer: absoluteServerUrl })).toBe("https://server-example.com/relative-url/base-path/path")
+ })
+
+ it("build relative url with server url relative to base", function() {
+ expect(buildUrl(urlRelativeToBase, specUrl, { selectedServer: serverUrlRelativeToBase })).toBe("https://petstore.swagger.io/v2/server-example/base-path/relative-url/base-path/path")
+ expect(buildUrl(urlRelativeToHost, specUrl, { selectedServer: serverUrlRelativeToBase })).toBe("https://petstore.swagger.io/relative-url/base-path/path")
+ })
+
+ it("build relative url with server url relative to host", function() {
+ expect(buildUrl(urlRelativeToBase, specUrl, { selectedServer: serverUrlRelativeToHost })).toBe("https://petstore.swagger.io/server-example/base-path/relative-url/base-path/path")
+ expect(buildUrl(urlRelativeToHost, specUrl, { selectedServer: serverUrlRelativeToHost })).toBe("https://petstore.swagger.io/relative-url/base-path/path")
+ })
+ })
+
describe("requiresValidationURL", function() {
it("Should tell us if we require a ValidationURL", function() {
const res = requiresValidationURL("https://example.com")
diff --git a/test/mocha/xss/info-sanitization.jsx b/test/mocha/xss/info-sanitization.jsx
index e868fe9f..f73b770a 100644
--- a/test/mocha/xss/info-sanitization.jsx
+++ b/test/mocha/xss/info-sanitization.jsx
@@ -18,7 +18,8 @@ describe(" Sanitization", function(){
description: "Description *with* "
}),
host: "example.test",
- basePath: "/api"
+ basePath: "/api",
+ selectedServer: "https://example.test",
}
it("renders sanitized .title content", function(){