Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No encode, decode in createLocation #465

Merged
merged 6 commits into from
Jun 10, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 14 additions & 1 deletion modules/LocationUtils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import resolvePathname from 'resolve-pathname'
import valueEqual from 'value-equal'
import warning from 'warning'
import { parsePath } from './PathUtils'

export const createLocation = (path, state, key, currentLocation) => {
Expand Down Expand Up @@ -33,7 +34,19 @@ export const createLocation = (path, state, key, currentLocation) => {
location.state = state
}

location.pathname = decodeURI(location.pathname)
try {
location.pathname = decodeURI(location.pathname)
} catch (e) {
if (e instanceof URIError) {
warning(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we try to create a location with a bad percent-encoding, we swallow the error, keep location.pathname encoded, and log a warning.

false,
'Pathname "' + location.pathname + '" could not be decoded. ' +
'Location\'s pathname will be encoded.'
)
} else {
throw e
}
}

if (key)
location.key = key
Expand Down
16 changes: 16 additions & 0 deletions modules/PathUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,32 @@ export const parsePath = (path) => {
}
}

const validatePath = (path) => {
try {
decodeURI(path)
} catch(e) {
if (e instanceof URIError) {
throw new Error(
'You are attempting to create a path that cannot be decoded. ' +
'Please call encodeURI on the pathname.'
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A slightly more useful error than the default Uncaught URIError: URI malformed

}
throw e
}
}

export const createPath = (location) => {
const { pathname, search, hash } = location

let path = pathname || '/'
validatePath(path)

if (search && search !== '?')
path += (search.charAt(0) === '?' ? search : `?${search}`)

if (hash && hash !== '#')
path += (hash.charAt(0) === '#' ? hash : `#${hash}`)


return path
}
12 changes: 12 additions & 0 deletions modules/__tests__/BrowserHistory-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ describeHistory('a browser history', () => {
})
})

describe('push with an invalid path string (bad percent-encoding)', () => {
it('throws an error', (done) => {
TestSequences.PushInvalidPathname(history, done)
})
})

describe('replace a new path', () => {
it('calls change listeners with the new location', (done) => {
TestSequences.ReplaceNewLocation(history, done)
Expand All @@ -89,6 +95,12 @@ describeHistory('a browser history', () => {
})
})

describe('replace with an invalid path string (bad percent-encoding)', () => {
it('throws an error', (done) => {
TestSequences.ReplaceInvalidPathname(history, done)
})
})

describe('location created by encoded and unencoded pathname', () => {
it('produces the same location.pathname', (done) => {
TestSequences.LocationPathnameAlwaysDecoded(history, done)
Expand Down
12 changes: 12 additions & 0 deletions modules/__tests__/HashHistory-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ describeHistory('a hash history', () => {
})
})

describe('push with an invalid path string (bad percent-encoding)', () => {
it('throws an error', (done) => {
TestSequences.PushInvalidPathname(history, done)
})
})

describe('replace a new path', () => {
it('calls change listeners with the new location', (done) => {
TestSequences.ReplaceNewLocation(history, done)
Expand All @@ -92,6 +98,12 @@ describeHistory('a hash history', () => {
})
})

describe('replace with an invalid path string (bad percent-encoding)', () => {
it('throws an error', (done) => {
TestSequences.ReplaceInvalidPathname(history, done)
})
})

describe('location created by encoded and unencoded pathname', () => {
it('produces the same location.pathname', (done) => {
TestSequences.LocationPathnameAlwaysDecoded(history, done)
Expand Down
49 changes: 49 additions & 0 deletions modules/__tests__/LocationUtils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,55 @@ describe('createLocation', () => {
})
})

describe('with a path that cannot be decoded', () => {
let consoleError = console.error // eslint-disable-line no-console
let warningMessage

beforeEach(() => {
console.error = (message) => { // eslint-disable-line no-console
warningMessage = message
}
})

afterEach(() => {
console.error = consoleError // eslint-disable-line no-console
})

describe('given as a string', () => {
it('does not throw when decodeURI throws a URIError, but logs warning', () => {
expect(() => {
createLocation('/test%')
expect(warningMessage).toBe(
'Warning: Pathname "/test%" could not be decoded. ' +
'Location\'s pathname will be encoded.'
)
}).toNotThrow()
})

it('sets pathname to be encoded string', () => {
const location = createLocation('/test%')
expect(location).toMatch({ pathname: '/test%' })
})
})

describe('given as an object', () => {
it('does not throw when decodeURI throws a URIError, but logs warning', () => {
expect(() => {
createLocation({ pathname: '/test%' })
expect(warningMessage).toBe(
'Warning: Pathname "/test%" could not be decoded. ' +
'Location\'s pathname will be encoded.'
)
}).toNotThrow()
})

it('sets pathname to be encoded string', () => {
const location = createLocation({ pathname: '/test%' })
expect(location).toMatch({ pathname: '/test%' })
})
})
})

describe('key', () => {
it('has a key property if a key is provided', () => {
const location = createLocation('/the/path', undefined, 'key')
Expand Down
26 changes: 26 additions & 0 deletions modules/__tests__/TestSequences/PushInvalidPathname.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import expect from 'expect'
import execSteps from './execSteps'

export default (history, done) => {
const steps = [
() => {
// encoded string
const pathname = '/hello%'
expect(() => {
history.push(pathname)
}).toThrow(
'You are attempting to create a path that cannot be decoded. ' +
'Please call encodeURI on the pathname.'
)
}
]

let consoleError = console.error // eslint-disable-line no-console

console.error = () => {} // eslint-disable-line no-console

execSteps(steps, history, (...args) => {
console.error = consoleError // eslint-disable-line no-console
done(...args)
})
}
26 changes: 26 additions & 0 deletions modules/__tests__/TestSequences/ReplaceInvalidPathname.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import expect from 'expect'
import execSteps from './execSteps'

export default (history, done) => {
const steps = [
() => {
// encoded string
const pathname = '/hello%'
expect(() => {
history.replace(pathname)
}).toThrow(
'You are attempting to create a path that cannot be decoded. ' +
'Please call encodeURI on the pathname.'
)
}
]

let consoleError = console.error // eslint-disable-line no-console

console.error = () => {} // eslint-disable-line no-console

execSteps(steps, history, (...args) => {
console.error = consoleError // eslint-disable-line no-console
done(...args)
})
}
2 changes: 2 additions & 0 deletions modules/__tests__/TestSequences/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export Listen from './Listen'
export LocationPathnameAlwaysDecoded from './LocationPathnameAlwaysDecoded'
export NoslashHashPathCoding from './NoslashHashPathCoding'
export PushEncodedLocation from './PushEncodedLocation'
export PushInvalidPathname from './PushInvalidPathname'
export PushNewLocation from './PushNewLocation'
export PushMissingPathname from './PushMissingPathname'
export PushSamePath from './PushSamePath'
Expand All @@ -23,6 +24,7 @@ export PushState from './PushState'
export PushStateWarning from './PushStateWarning'
export PushRelativePathname from './PushRelativePathname'
export PushUnicodeLocation from './PushUnicodeLocation'
export ReplaceInvalidPathname from './ReplaceInvalidPathname'
export ReplaceNewLocation from './ReplaceNewLocation'
export ReplaceSamePath from './ReplaceSamePath'
export ReplaceState from './ReplaceState'
Expand Down