1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Implement circular JSON err.sh link (#6149)

* Implement circular JSON err.sh link

* Add test for getInitialProps returning circular json

* Make test warn less

* Fix tests

* Add reference to original tests
This commit is contained in:
Tim Neutkens 2019-01-27 16:12:17 +01:00 committed by GitHub
parent 125aaf8834
commit b3045cc7a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 105 additions and 10 deletions

View file

@ -0,0 +1,11 @@
# Circular structure in "getInitialProps" result
#### Why This Error Occurred
`getInitialProps` is serialized to JSON using `JSON.stringify` and sent to the client side for hydrating the page.
However, the result returned from `getInitialProps` can't be serialized when it has a circular structure.
#### Possible Ways to Fix It
Circular structures are not supported, so the way to fix this error is removing the circular structure from the object that is returned from `getInitialProps`.

View file

@ -66,7 +66,6 @@
"friendly-errors-webpack-plugin": "1.7.0",
"glob": "7.1.2",
"hoist-non-react-statics": "3.2.0",
"htmlescape": "1.1.1",
"http-status": "1.0.1",
"launch-editor": "2.2.1",
"loader-utils": "1.1.0",

View file

@ -1,7 +1,7 @@
/* eslint-disable */
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import htmlescape from 'htmlescape'
import {htmlEscapeJsonString} from '../server/htmlescape'
import flush from 'styled-jsx/server'
const Fragment = React.Fragment || function Fragment ({ children }) {
@ -193,8 +193,16 @@ export class NextScript extends Component {
}
static getInlineScriptSource (documentProps) {
const { __NEXT_DATA__ } = documentProps
return htmlescape(__NEXT_DATA__)
const {__NEXT_DATA__} = documentProps
try {
const data = JSON.stringify(__NEXT_DATA__)
return htmlEscapeJsonString(data)
} catch(err) {
if(err.message.indexOf('circular structure')) {
throw new Error(`Circular structure in "getInitialProps" result of page "${__NEXT_DATA__.page}". https://err.sh/zeit/next.js/circular-structure`)
}
throw err
}
}
render () {

View file

@ -0,0 +1,16 @@
// This utility is based on https://github.com/zertosh/htmlescape
// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE
const ESCAPE_LOOKUP: {[match: string]: string} = {
'&': '\\u0026',
'>': '\\u003e',
'<': '\\u003c',
'\u2028': '\\u2028',
'\u2029': '\\u2029',
}
const ESCAPE_REGEX = /[&><\u2028\u2029]/g
export function htmlEscapeJsonString(str: string) {
return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match])
}

View file

@ -6,7 +6,7 @@ module.exports = {
},
webpack (config) {
config.module.rules.push({
test: /pages[\\/]hmr/,
test: /pages[\\/]hmr[\\/]about/,
loader: path.join(__dirname, 'warning-loader.js')
})

View file

@ -0,0 +1,17 @@
function CircularJSONErrorPage () {
return <div>This won't render</div>
}
CircularJSONErrorPage.getInitialProps = () => {
// This creates a circular JSON object
const object = {}
object.arr = [
object, object
]
object.arr.push(object.arr)
object.obj = object
return object
}
export default CircularJSONErrorPage

View file

@ -120,6 +120,12 @@ export default function ({ app }, suiteName, render, fetch) {
expect(link.text()).toBe('About')
})
test('getInitialProps circular structure', async () => {
const $ = await get$('/circular-json-error')
const expectedErrorMessage = 'Circular structure in "getInitialProps" result of page "/circular-json-error".'
expect($('pre').text().includes(expectedErrorMessage)).toBeTruthy()
})
test('getInitialProps should be class method', async () => {
const $ = await get$('/instance-get-initial-props')
const expectedErrorMessage = '"InstanceInitialPropsPage.getInitialProps()" is defined as an instance method - visit https://err.sh/zeit/next.js/get-initial-props-as-an-instance-method for more information.'

View file

@ -0,0 +1,43 @@
/* eslint-env jest */
// These tests are based on https://github.com/zertosh/htmlescape/blob/3e6cf0614dd0f778fd0131e69070b77282150c15/test/htmlescape-test.js
// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE
import {htmlEscapeJsonString} from 'next/dist/server/htmlescape'
import vm from 'vm'
describe('htmlescape', () => {
test('with angle brackets should escape', () => {
const evilObj = {evil: '<script></script>'}
expect(htmlEscapeJsonString(JSON.stringify(evilObj))).toBe('{"evil":"\\u003cscript\\u003e\\u003c/script\\u003e"}')
})
test('with angle brackets should parse back', () => {
const evilObj = {evil: '<script></script>'}
expect(JSON.parse(htmlEscapeJsonString(JSON.stringify(evilObj)))).toMatchObject(evilObj)
})
test('with ampersands should escape', () => {
const evilObj = {evil: '&'}
expect(htmlEscapeJsonString(JSON.stringify(evilObj))).toBe('{"evil":"\\u0026"}')
})
test('with ampersands should parse back', () => {
const evilObj = {evil: '&'}
expect(JSON.parse(htmlEscapeJsonString(JSON.stringify(evilObj)))).toMatchObject(evilObj)
})
test('with "LINE SEPARATOR" and "PARAGRAPH SEPARATOR" should escape', () => {
const evilObj = {evil: '\u2028\u2029'}
expect(htmlEscapeJsonString(JSON.stringify(evilObj))).toBe('{"evil":"\\u2028\\u2029"}')
})
test('with "LINE SEPARATOR" and "PARAGRAPH SEPARATOR" should parse back', () => {
const evilObj = {evil: '\u2028\u2029'}
expect(JSON.parse(htmlEscapeJsonString(JSON.stringify(evilObj)))).toMatchObject(evilObj)
})
test('escaped line terminators should work', () => {
expect(() => {
vm.runInNewContext('(' + htmlEscapeJsonString(JSON.stringify({evil: '\u2028\u2029'})) + ')')
}).not.toThrow()
})
})

View file

@ -5898,11 +5898,6 @@ html-entities@^1.2.0:
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=
htmlescape@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351"
integrity sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=
htmlparser2@^3.9.1:
version "3.10.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464"