Compare commits

..

5 Commits
2.2.0 ... 2.2.1

Author SHA1 Message Date
chris48s
98f380b254 improve logo escaping (#3511)
* escape logo in make-badge

* 2.2.1 release notes

* tighten up validation of logo param
2019-05-30 18:28:37 +01:00
chris48s
829971f0ef Fix BadgeFactory.create() docblock (#3506)
* fix docblock

* add a reminder in make-badge.js
2019-05-30 12:28:17 +01:00
chris48s
bfe0bde83d switch [powershellgallery] platform example (#3504)
The example we were previously using
(Az.Storage) now renders

platform | not specified

This replaces the example with a
package that gives us a valid result
2019-05-29 21:21:35 +01:00
Marcin Mielnicki
9008c4ef0f Show a badge pattern instead of a badge url (#3438)
* Show patterns instead of real paths

* realBadge property instead of preview.buildFromExample

* E2e test for displaying a badge

* Check bagde url in badge wrapper

* expectBadgeExample helper function

* Remove a hostname from badge url

* Simpler Cypress assertions

* realBadge -> isBadgeSuggestion

* Do not show regexp in patterns
2019-05-29 21:17:27 +02:00
chris48s
f50fcac107 fix 'failing service test' template (#3503) 2019-05-29 16:30:16 +01:00
15 changed files with 128 additions and 29 deletions

View File

@@ -1,5 +1,5 @@
---
name: :green_heart: Failing service test
name: 💚 Failing service test
about: Note failing service tests
---

View File

@@ -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() {

View File

@@ -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)

View File

@@ -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}

View File

@@ -72,7 +72,8 @@ export default class SuggestionAndSearch extends React.Component {
title,
link,
example,
preview: { ...preview, buildFromExample: true },
preview,
isBadgeSuggestion: true,
documentation,
})
)

View File

@@ -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
}

View File

@@ -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/(.*)')
})
})

View File

@@ -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,
}),

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -1,6 +1,6 @@
{
"name": "gh-badges",
"version": "2.2.0",
"version": "2.2.1",
"description": "Shields.io badge library",
"keywords": [
"GitHub",

View File

@@ -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

View File

@@ -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, () => {

View File

@@ -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'],
}),