Skip to content

Commit edd0653

Browse files
authored
Merge pull request #39 from Rich-Harris/devalue-json
add a non-JavaScript mode
2 parents 3735b1f + 4b9b044 commit edd0653

File tree

11 files changed

+896
-287
lines changed

11 files changed

+896
-287
lines changed

README.md

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,32 +25,58 @@ Try it out [here](https://svelte.dev/repl/138d70def7a748ce9eda736ef1c71239?versi
2525

2626
## Usage
2727

28+
There are two ways to use `devalue`:
29+
30+
### `uneval`
31+
32+
This function takes a JavaScript value and returns the JavaScript code to create an equivalent value — sort of like `eval` in reverse:
33+
34+
```js
35+
import * as devalue from 'devalue';
36+
37+
let obj = { message: 'hello' };
38+
devalue.uneval(obj); // '{message:"hello"}'
39+
40+
obj.self = obj;
41+
devalue.uneval(obj); // '(function(a){a.message="hello";a.self=a;return a}({}))'
42+
```
43+
44+
Use `uneval` when you want the most compact possible output and don't want to include any code for parsing the serialized value.
45+
46+
### `stringify` and `parse`
47+
48+
These two functions are analogous to `JSON.stringify` and `JSON.parse`:
49+
2850
```js
29-
import { devalue } from 'devalue';
51+
import * as devalue from 'devalue';
3052

31-
let obj = { a: 1, b: 2 };
32-
obj.c = 3;
53+
let obj = { message: 'hello' };
3354

34-
devalue(obj); // '{a:1,b:2,c:3}'
55+
let stringified = devalue.stringify(obj); // '[{"message":1},"hello"]'
56+
devalue.parse(stringified); // { message: 'hello' }
3557

3658
obj.self = obj;
37-
devalue(obj); // '(function(a){a.a=1;a.b=2;a.c=3;a.self=a;return a}({}))'
59+
60+
stringified = devalue.stringify(obj); // '[{"message":1,"self":0},"hello"]'
61+
devalue.parse(stringified); // { message: 'hello', self: [Circular] }
3862
```
3963

64+
Use `stringify` and `parse` when evaluating JavaScript isn't an option.
65+
4066
## Error handling
4167

42-
If `devalue` encounters a function or a non-POJO, it will throw an error. You can find where in the input data the offending value lives by inspecting `error.path`:
68+
If `uneval` or `stringify` encounters a function or a non-POJO, it will throw an error. You can find where in the input data the offending value lives by inspecting `error.path`:
4369

4470
```js
4571
try {
4672
const map = new Map();
4773
map.set('key', function invalid() {});
4874

49-
devalue({
75+
uneval({
5076
object: {
5177
array: [map]
5278
}
53-
})
79+
});
5480
} catch (e) {
5581
console.log(e.path); // '.object.array[0].get("key")'
5682
}
@@ -84,12 +110,12 @@ Which would result in this:
84110
</script>
85111
```
86112
87-
Using `devalue`, we're protected against that attack:
113+
Using `uneval` or `stringify`, we're protected against that attack:
88114
89115
```js
90116
const template = `
91117
<script>
92-
var preloaded = ${devalue(state)};
118+
var preloaded = ${uneval(state)};
93119
</script>`;
94120
```
95121
@@ -102,15 +128,15 @@ const template = `
102128
</script>
103129
```
104130
105-
This, along with the fact that `devalue` bails on functions and non-POJOs, stops attackers from executing arbitrary code. Strings generated by `devalue` can be safely deserialized with `eval` or `new Function`:
131+
This, along with the fact that `uneval` and `stringify` bail on functions and non-POJOs, stops attackers from executing arbitrary code. Strings generated by `uneval` can be safely deserialized with `eval` or `new Function`:
106132
107133
```js
108134
const value = (0, eval)('(' + str + ')');
109135
```
110136
111137
## Other security considerations
112138
113-
While `devalue` prevents the XSS vulnerability shown above, meaning you can use it to send data from server to client, **you should not send user data from client to server** using the same method. Since it has to be evaluated, an attacker that successfully submitted data that bypassed `devalue` would have access to your system.
139+
While `uneval` prevents the XSS vulnerability shown above, meaning you can use it to send data from server to client, **you should not send user data from client to server** using the same method. Since it has to be evaluated, an attacker that successfully submitted data that bypassed `uneval` would have access to your system.
114140
115141
When using `eval`, ensure that you call it _indirectly_ so that the evaluated code doesn't have access to the surrounding scope:
116142
@@ -127,7 +153,7 @@ Using `new Function(code)` is akin to using indirect eval.
127153
## See also
128154
129155
- [lave](https://github.com/jed/lave) by Jed Schmidt
130-
- [arson](https://github.com/benjamn/arson) by Ben Newman
156+
- [arson](https://github.com/benjamn/arson) by Ben Newman. The `stringify`/`parse` approach in `devalue` was inspired by `arson`
131157
- [tosource](https://github.com/marcello3d/node-tosource) by Marcello Bastéa-Forte
132158
- [serialize-javascript](https://github.com/yahoo/serialize-javascript) by Eric Ferraiuolo
133159
- [jsesc](https://github.com/mathiasbynens/jsesc) by Mathias Bynens

devalue.test.js

Lines changed: 0 additions & 185 deletions
This file was deleted.

index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export { uneval } from './src/uneval.js';
2+
export { parse } from './src/parse.js';
3+
export { stringify } from './src/stringify.js';
4+
5+
export function devalue() {
6+
throw new Error(
7+
'The `devalue` export has been removed. Use `uneval` instead'
8+
);
9+
}

package.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,24 @@
55
"repository": "Rich-Harris/devalue",
66
"exports": {
77
".": {
8-
"import": "./devalue.js",
9-
"types": "./types/devalue.d.ts"
8+
"import": "./index.js",
9+
"types": "./types/index.d.ts"
1010
}
1111
},
12-
"main": "devalue.js",
12+
"main": "index.js",
1313
"files": [
14-
"devalue.js",
14+
"index.js",
15+
"src",
1516
"types"
1617
],
17-
"types": "./types/devalue.d.ts",
18+
"types": "./types/index.d.ts",
1819
"devDependencies": {
1920
"typescript": "^3.1.3",
2021
"uvu": "^0.5.6"
2122
},
2223
"scripts": {
2324
"build": "tsc",
24-
"test": "node devalue.test.js",
25+
"test": "uvu test",
2526
"prepublishOnly": "npm test && npm run build"
2627
},
2728
"license": "MIT",

src/constants.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const UNDEFINED = -1;
2+
export const HOLE = -2;
3+
export const NAN = -3;
4+
export const POSITIVE_INFINITY = -4;
5+
export const NEGATIVE_INFINITY = -5;
6+
export const NEGATIVE_ZERO = -6;

0 commit comments

Comments
 (0)