Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98f380b254 | ||
|
|
829971f0ef | ||
|
|
bfe0bde83d | ||
|
|
9008c4ef0f | ||
|
|
f50fcac107 |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: :green_heart: Failing service test
|
||||
name: 💚 Failing service test
|
||||
about: Note failing service tests
|
||||
---
|
||||
|
||||
|
||||
@@ -4,6 +4,15 @@ describe('Main page', function() {
|
||||
const backendUrl = Cypress.env('backend_url')
|
||||
const SEARCH_INPUT = 'input[placeholder="search / project URL"]'
|
||||
|
||||
function expectBadgeExample(title, previewUrl, pattern) {
|
||||
cy.contains('tr', `${title}:`)
|
||||
.find('code')
|
||||
.should('have.text', pattern)
|
||||
cy.contains('tr', `${title}:`)
|
||||
.find('img')
|
||||
.should('have.attr', 'src', previewUrl)
|
||||
}
|
||||
|
||||
it('Search for badges', function() {
|
||||
cy.visit('/')
|
||||
|
||||
@@ -12,6 +21,16 @@ describe('Main page', function() {
|
||||
cy.contains('PyPI - License')
|
||||
})
|
||||
|
||||
it('Shows badge from category', function() {
|
||||
cy.visit('/category/chat')
|
||||
|
||||
expectBadgeExample(
|
||||
'Discourse status',
|
||||
'http://localhost:8080/badge/discourse-online-brightgreen.svg',
|
||||
'/discourse/:scheme/:host/status.svg'
|
||||
)
|
||||
})
|
||||
|
||||
it('Suggest badges', function() {
|
||||
const badgeUrl = `${backendUrl}/github/issues/badges/shields.svg`
|
||||
cy.visit('/')
|
||||
@@ -19,9 +38,7 @@ describe('Main page', function() {
|
||||
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
|
||||
cy.contains('Suggest badges').click()
|
||||
|
||||
cy.contains('GitHub issues')
|
||||
cy.get(`img[src='${badgeUrl}']`)
|
||||
cy.contains(badgeUrl)
|
||||
expectBadgeExample('GitHub issues', badgeUrl, badgeUrl)
|
||||
})
|
||||
|
||||
it('Customization form is filled with suggested badge details', function() {
|
||||
|
||||
@@ -2,10 +2,12 @@ import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import {
|
||||
badgeUrlFromPath,
|
||||
badgeUrlFromPattern,
|
||||
staticBadgeUrl,
|
||||
} from '../../core/badge-urls/make-badge-url'
|
||||
import { examplePropType } from '../lib/service-definitions/example-prop-types'
|
||||
import { removeRegexpFromPattern } from '../lib/pattern-helpers'
|
||||
import { Badge } from './common'
|
||||
import { StyledCode } from './snippet'
|
||||
|
||||
@@ -28,18 +30,18 @@ const ClickableCode = styled(StyledCode)`
|
||||
`
|
||||
|
||||
function Example({ baseUrl, onClick, exampleData }) {
|
||||
const { title, example, preview } = exampleData
|
||||
|
||||
const { title, example, preview, isBadgeSuggestion } = exampleData
|
||||
const { pattern, namedParams, queryParams } = example
|
||||
const exampleUrl = badgeUrlFromPattern({
|
||||
baseUrl,
|
||||
pattern,
|
||||
namedParams,
|
||||
queryParams,
|
||||
})
|
||||
|
||||
let exampleUrl
|
||||
let previewUrl
|
||||
if (preview.buildFromExample) {
|
||||
|
||||
if (isBadgeSuggestion) {
|
||||
exampleUrl = badgeUrlFromPattern({
|
||||
baseUrl,
|
||||
pattern,
|
||||
namedParams,
|
||||
queryParams,
|
||||
})
|
||||
previewUrl = exampleUrl
|
||||
} else {
|
||||
const { label, message, color, style, namedLogo } = preview
|
||||
@@ -51,6 +53,11 @@ function Example({ baseUrl, onClick, exampleData }) {
|
||||
style,
|
||||
namedLogo,
|
||||
})
|
||||
exampleUrl = badgeUrlFromPath({
|
||||
path: removeRegexpFromPattern(pattern),
|
||||
namedParams,
|
||||
queryParams,
|
||||
})
|
||||
}
|
||||
|
||||
const handleClick = () => onClick(exampleData)
|
||||
|
||||
@@ -16,7 +16,8 @@ export default function MarkupModalContent({ example, baseUrl }) {
|
||||
documentation,
|
||||
example: { pattern, namedParams, queryParams },
|
||||
link,
|
||||
preview: { style: initialStyle, buildFromExample } = {},
|
||||
preview: { style: initialStyle } = {},
|
||||
isBadgeSuggestion,
|
||||
} = example
|
||||
|
||||
return (
|
||||
@@ -30,7 +31,7 @@ export default function MarkupModalContent({ example, baseUrl }) {
|
||||
exampleNamedParams={namedParams}
|
||||
exampleQueryParams={queryParams}
|
||||
initialStyle={initialStyle}
|
||||
isPrefilled={buildFromExample}
|
||||
isPrefilled={isBadgeSuggestion}
|
||||
link={link}
|
||||
pattern={pattern}
|
||||
title={title}
|
||||
|
||||
@@ -72,7 +72,8 @@ export default class SuggestionAndSearch extends React.Component {
|
||||
title,
|
||||
link,
|
||||
example,
|
||||
preview: { ...preview, buildFromExample: true },
|
||||
preview,
|
||||
isBadgeSuggestion: true,
|
||||
documentation,
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
|
||||
// Given a patternToRegex `pattern` with multiple-choice options like
|
||||
// `foo|bar|baz`, return an array with the options. If it can't be described
|
||||
// as multiple-choice options, return `undefined`.
|
||||
const basicChars = /^[A-za-z0-9-]+$/
|
||||
function patternToOptions(pattern) {
|
||||
export function patternToOptions(pattern) {
|
||||
const split = pattern.split('|')
|
||||
if (split.some(part => !part.match(basicChars))) {
|
||||
return undefined
|
||||
@@ -11,4 +13,30 @@ function patternToOptions(pattern) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { patternToOptions }
|
||||
// Removes regexp for named parameters.
|
||||
export function removeRegexpFromPattern(pattern) {
|
||||
const tokens = pathToRegexp.parse(pattern)
|
||||
const simplePattern = tokens
|
||||
.map(token => {
|
||||
if (typeof token === 'string') {
|
||||
return token
|
||||
} else {
|
||||
const { delimiter, optional, repeat, name, pattern } = token
|
||||
if (typeof name === 'number') {
|
||||
return `${delimiter}(${pattern})`
|
||||
} else {
|
||||
let modifier = ''
|
||||
if (optional && !repeat) {
|
||||
modifier = '?'
|
||||
} else if (!optional && repeat) {
|
||||
modifier = '+'
|
||||
} else if (optional && repeat) {
|
||||
modifier = '*'
|
||||
}
|
||||
return `${delimiter}:${name}${modifier}`
|
||||
}
|
||||
}
|
||||
})
|
||||
.join('')
|
||||
return simplePattern
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { test, given } from 'sazerac'
|
||||
import { patternToOptions } from './pattern-helpers'
|
||||
import { patternToOptions, removeRegexpFromPattern } from './pattern-helpers'
|
||||
|
||||
describe('Badge URL functions', function() {
|
||||
test(patternToOptions, () => {
|
||||
@@ -7,4 +7,22 @@ describe('Badge URL functions', function() {
|
||||
given('abc|[^\\/]+').expect(undefined)
|
||||
given('abc|def|ghi').expect(['abc', 'def', 'ghi'])
|
||||
})
|
||||
|
||||
test(removeRegexpFromPattern, () => {
|
||||
given('/appveyor/ci/:user/:repo').expect('/appveyor/ci/:user/:repo')
|
||||
given('/discourse/:scheme(http|https)/:host/topics').expect(
|
||||
'/discourse/:scheme/:host/topics'
|
||||
)
|
||||
given('/github/size/:user/:repo/:path*').expect(
|
||||
'/github/size/:user/:repo/:path*'
|
||||
)
|
||||
given('/microbadger/image-size/image-size/:imageId+').expect(
|
||||
'/microbadger/image-size/image-size/:imageId+'
|
||||
)
|
||||
given('/node/v/@:scope/:packageName').expect('/node/v/@:scope/:packageName')
|
||||
given('/ubuntu/v/:packageName/:series?').expect(
|
||||
'/ubuntu/v/:packageName/:series?'
|
||||
)
|
||||
given('/:foo/(.*)').expect('/:foo/(.*)')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -17,8 +17,8 @@ const examplePropType = PropTypes.exact({
|
||||
color: PropTypes.string,
|
||||
namedLogo: PropTypes.string,
|
||||
style: PropTypes.string,
|
||||
buildFromExample: PropTypes.bool,
|
||||
}),
|
||||
isBadgeSuggestion: PropTypes.bool,
|
||||
documentation: PropTypes.exact({
|
||||
__html: PropTypes.string.isRequired,
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 2.2.0
|
||||
## 2.2.1
|
||||
|
||||
### Fixes
|
||||
|
||||
- Escape logos to prevent XSS vulnerability
|
||||
- Update docblock for BadgeFactory.create()
|
||||
|
||||
## 2.2.0 - 2019-05-29
|
||||
|
||||
### Deprecations
|
||||
|
||||
|
||||
@@ -16,9 +16,11 @@ class BadgeFactory {
|
||||
*
|
||||
* @param {object} format - Object specifying badge data
|
||||
* @param {string[]} format.text
|
||||
* @param {string} format.colorscheme
|
||||
* @param {string} format.colorA
|
||||
* @param {string} format.colorB
|
||||
* @param {string} format.labelColor - label color
|
||||
* @param {string} format.color - message color
|
||||
* @param {string} format.colorA - deprecated: alias for `labelColor`
|
||||
* @param {string} format.colorscheme - deprecated: alias for `color`
|
||||
* @param {string} format.colorB - deprecated: alias for `color`
|
||||
* @param {string} format.format
|
||||
* @param {string} format.template
|
||||
* @return {string} Badge in SVG or JSON format
|
||||
|
||||
@@ -90,6 +90,10 @@ function capitalize(s) {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1)
|
||||
}
|
||||
|
||||
/*
|
||||
note: makeBadge() is fairly thinly wrapped so if we are making changes here
|
||||
it is likely this will impact on the package's public interface in index.js
|
||||
*/
|
||||
module.exports = function makeBadge({
|
||||
format,
|
||||
template,
|
||||
@@ -169,7 +173,7 @@ module.exports = function makeBadge({
|
||||
escapedText: text.map(escapeXml),
|
||||
widths: [leftWidth + 10 + logoWidth + logoPadding, rightWidth + 10],
|
||||
links: links.map(escapeXml),
|
||||
logo,
|
||||
logo: escapeXml(logo),
|
||||
logoPosition,
|
||||
logoWidth,
|
||||
logoPadding,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gh-badges",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.1",
|
||||
"description": "Shields.io badge library",
|
||||
"keywords": [
|
||||
"GitHub",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { toSvgColor } = require('../gh-badges/lib/color')
|
||||
const coalesce = require('../core/base-service/coalesce')
|
||||
const { svg2base64 } = require('./svg-helpers')
|
||||
@@ -31,7 +32,12 @@ function prependPrefix(s, prefix) {
|
||||
}
|
||||
|
||||
function isDataUrl(s) {
|
||||
return s !== undefined && /^(data:)([^;]+);([^,]+),(.+)$/.test(s)
|
||||
try {
|
||||
Joi.assert(s, Joi.string().dataUri())
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// +'s are replaced with spaces when used in query params, this returns them
|
||||
|
||||
@@ -19,8 +19,16 @@ describe('Logo helpers', function() {
|
||||
})
|
||||
|
||||
test(isDataUrl, () => {
|
||||
//valid input
|
||||
given('data:image/svg+xml;base64,PHN2ZyB4bWxu').expect(true)
|
||||
|
||||
// invalid inputs
|
||||
forCases([given('data:foobar'), given('foobar')]).expect(false)
|
||||
|
||||
// attempted XSS attack
|
||||
given(
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg=="/><script>alert()</script>'
|
||||
).expect(false)
|
||||
})
|
||||
|
||||
test(prepareNamedLogo, () => {
|
||||
|
||||
@@ -44,7 +44,7 @@ class PowershellGalleryPlatformSupport extends BaseXmlService {
|
||||
return [
|
||||
{
|
||||
title: 'PowerShell Gallery',
|
||||
namedParams: { packageName: 'Az.Storage' },
|
||||
namedParams: { packageName: 'DNS.1.1.1.1' },
|
||||
staticPreview: this.render({
|
||||
platforms: ['windows', 'macos', 'linux'],
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user