@@ -6,11 +6,18 @@ var hljs = require('highlight.js');
66var server = require(' http' ).createServer(httpHandler),
77 exec = require(' child_process' ).exec,
88 io = require(' socket.io' ).listen(server),
9- send = require(' send' ),
10- server,
11- socket;
9+ send = require(' send' );
1210
13- server.listen(8090);
11+ // WARNING: By setting this environment variable, anyone on your network may
12+ // run arbitrary code in your browser and read arbitrary files in the working
13+ // directory of the open file!
14+ if (process.env.INSTANT_MARKDOWN_OPEN_TO_THE_WORLD) {
15+ // Listen on any interface.
16+ server.listen(8090, onListening).once(' error' , onServerError);
17+ } else {
18+ // Listen locally.
19+ server.listen(8090, ' 127.0.0.1' , onListening).once(' error' , onServerError);
20+ }
1421
1522var md = new MarkdownIt({
1623 html: true,
@@ -28,7 +35,13 @@ var md = new MarkdownIt({
2835 }
2936});
3037
31- function writeMarkdown(input, output) {
38+ var lastWrittenMarkdown = ' ' ;
39+ function writeMarkdown(body) {
40+ lastWrittenMarkdown = md.render(body);
41+ io.sockets.emit(' newContent' , lastWrittenMarkdown);
42+ }
43+
44+ function readAllInput(input, callback) {
3245 var body = ' ' ;
3346 input.on(' data' , function(data) {
3447 body += data;
@@ -37,24 +50,59 @@ function writeMarkdown(input, output) {
3750 }
3851 });
3952 input.on(' end' , function () {
40- output.emit( ' newContent ' , md.render( body) );
53+ callback( body);
4154 });
4255}
4356
57+ function addSecurityHeaders(req, res, isIndexFile) {
58+ var csp = [];
59+
60+ // Cannot use ' self' because Chrome does not treat ' self' as http://host
61+ // when the sandbox directive is set.
62+ var HTTP_HOST = req.headers.host || ' localhost:8090' ;
63+ var CSP_SELF = ' http://' + HTTP_HOST;
64+
65+ if (! process.env.INSTANT_MARKDOWN_ALLOW_UNSAFE_CONTENT) {
66+ if (isIndexFile) {
67+ // index.html will drop the scripting capabilities upon load.
68+ csp.push(' script-src ' + CSP_SELF + " 'unsafe-inline'" );
69+ csp.push(' sandbox allow-scripts allow-modals allow-forms' );
70+ } else {
71+ csp.push(' script-src ' );
72+ }
73+ }
74+ if (process.env.INSTANT_MARKDOWN_BLOCK_EXTERNAL) {
75+ csp.push(' default-src data: ' + CSP_SELF);
76+ csp.push(" style-src data: 'unsafe-inline' " + CSP_SELF);
77+ csp.push(' connect-src ' + CSP_SELF + ' ws://' + HTTP_HOST);
78+ }
79+ res.setHeader(' X-Content-Type-Options' , ' nosniff' );
80+ res.setHeader(' Content-Security-Policy' , csp.join(' ; ' ));
81+ if (isIndexFile) {
82+ // Never cache the index file, to make sure that changes to the CSP are
83+ // picked up across soft reloads.
84+ res.setHeader(' Cache-Control' , ' no-store' );
85+ }
86+ }
87+
4488function httpHandler(req, res) {
4589 switch(req.method)
4690 {
4791 case ' GET' :
4892 // Example: /my-repo/raw/master/sub-dir/some.png
4993 var githubUrl = req.url.match(/\/ [^\/ ]+\/ raw\/ [^\/ ]+\/ (.+)/);
5094 if (githubUrl) {
95+ addSecurityHeaders(req, res, false);
5196 // Serve the file out of the current working directory
5297 send(req, githubUrl[1])
5398 .root(process.cwd ())
5499 .pipe(res);
55100 return ;
56101 }
57102
103+ var isIndexFile = /^\/ (index\. html)? (\? | $)/.test(req.url);
104+ addSecurityHeaders(req, res, isIndexFile);
105+
58106 // Otherwise serve the file from the directory this module is in
59107 send(req, req.url)
60108 .root(__dirname)
@@ -70,12 +118,12 @@ function httpHandler(req, res) {
70118 // break ;
71119
72120 case ' DELETE' :
73- socket .emit(' die' );
121+ io.sockets .emit(' die' );
74122 process.exit ();
75123 break ;
76124
77125 case ' PUT' :
78- writeMarkdown (req, socket );
126+ readAllInput (req, writeMarkdown );
79127 res.writeHead(200);
80128 res.end ();
81129 break ;
@@ -84,18 +132,42 @@ function httpHandler(req, res) {
84132 }
85133}
86134
87- io.set(' log level' , 1);
88135io.sockets.on(' connection' , function(sock){
89- socket = sock;
90136 process.stdout.write(' connection established!' );
91- writeMarkdown(process.stdin, socket);
92- process.stdin.resume ();
137+ if (lastWrittenMarkdown) {
138+ sock.emit(' newContent' , lastWrittenMarkdown);
139+ }
93140});
94141
95142
96- if (process.platform.toLowerCase().indexOf(' darwin' ) > = 0){
97- exec(' open -g http://localhost:8090' , function(error, stdout, stderr){});
143+ function onListening() {
144+ if (process.platform.toLowerCase().indexOf(' darwin' ) > = 0){
145+ exec(' open -g http://localhost:8090' );
146+ }
147+ else { // assume unix/linux
148+ exec(' xdg-open http://localhost:8090' );
149+ }
150+ readAllInput(process.stdin, function(body) {
151+ writeMarkdown(body);
152+ });
153+ process.stdin.resume ();
98154}
99- else { // assume unix/linux
100- exec(' xdg-open http://localhost:8090' , function(error, stdout, stderr){});
155+
156+ function onServerError(e) {
157+ if (e.code === ' EADDRINUSE' ) {
158+ readAllInput(process.stdin, function(body) {
159+ // Forward to existing instant-markdown-d server.
160+ require(' http' ).request({
161+ hostname: ' localhost' ,
162+ port: 8090,
163+ path: ' /' ,
164+ method: ' PUT' ,
165+ }).end(body);
166+ });
167+ process.stdin.resume ();
168+ return ;
169+ }
170+
171+ // Another unexpected error. Raise it again.
172+ throw e;
101173}
0 commit comments