@@ -632,7 +632,8 @@ http.createServer(function(request, response) {
632632}).listen(8000);
633633
634634function urlToPath(url) {
635- return "." + require("url").parse(url).pathname;
635+ var path = require("url").parse(url).pathname;
636+ return "." + decodeURIComponent(path);
636637}
637638----
638639
@@ -650,8 +651,9 @@ or a string, and is passed directly to the response's `end` method.
650651
651652To get a path from the URL in the request, the `urlToPath` function
652653uses node's built-in `"url"` module to parse the URL. It takes its
653- pathname, which will be something like `/file.txt`, and prefixes a
654- single dot to produce a path relative to the current directory.
654+ pathname, which will be something like `/file.txt`, decodes that to
655+ get rid of the `%20`-style escape codes, and prefixes a single dot to
656+ produce a path relative to the current directory.
655657
656658(If you are worried about the security of the `urlToPath` function,
657659you are right. We will come back to it in the exercises.)
@@ -945,31 +947,193 @@ when the I/O you asked for is completed.
945947
946948== Exercises ==
947949
948- FIXME
949-
950950=== Content negotiation, again ===
951951
952- Repeat 17.1, with node
952+ In Chapter 17, the first exercise was to make several requests to
953+ _http://eloquentjavascript.net/author_, asking for different types of
954+ content by passing different `Accept` headers.
955+
956+ Do this again, using node's `http.request` function. Ask for at least
957+ the media types `text/plain`, `text/html`, and `application/json`.
958+ Remember that headers to a request can be given as an object, in the
959+ `headers` property of `http.request`’s first argument.
960+
961+ Write out the content of the responses to each request.
962+
963+ !!solution!!
964+
965+ Don't forget to call the `end` method on the object returned by
966+ `http.request`, in order to actually fire off the request.
967+
968+ The response object passed to `http.request`’s callback is a readable
969+ stream. This means that it is not entirely trivial to get the whole
970+ response body out of it. The following utility function reads a whole
971+ stream and calls a callback function with the result, using the usual
972+ pattern of passing any errors it encounters as first argument to the
973+ callback.
974+
975+ [source,text/javascript]
976+ ----
977+ function readStreamAsString(stream, callback) {
978+ var data = "";
979+ stream.on("data", function(chunk) {
980+ data += chunk;
981+ });
982+ stream.on("end", function() {
983+ callback(null, data);
984+ });
985+ stream.on("error", function(error) {
986+ callback(error);
987+ });
988+ }
989+ ----
990+
991+ !!solution!!
953992
954993=== Fixing a leak ===
955994
956- === Creating directories ===
995+ For easy remote access to some files, I might get into the habit of
996+ having the file server defined in this chapter running on my machine,
997+ in the `/home/marijn/public` directory. Then, one day, I find that
998+ someone has gained access to all the passwords I stored in my browser.
999+
1000+ What happened?
1001+
1002+ If it isn't clear to you yet, think back to the `urlToPath` function,
1003+ defined like this:
9571004
9581005[source,javascript]
9591006----
960- methods.MKCOL = function(path, request, response) {
961- fs.stat(path, function(error, stats) {
962- if (error && error.code == "ENOENT")
963- fs.mkdir(path, respondErrorOrNothing(response));
964- else if (error)
965- respond(500, error.toString(), response);
966- else if (stats.isDirectory())
967- respond(204, null, response);
968- else
969- respond(400, "File exists", response);
970- });
971- };
1007+ function urlToPath(url) {
1008+ var path = require("url").parse(url).pathname;
1009+ return "." + decodeURIComponent(path);
1010+ }
1011+ ----
1012+
1013+ Now consider the fact that paths passed to the `"fs"` functions can be
1014+ relative—they may contain `"../"` to go up a directory. What happens
1015+ when a client sends requests to URLs like the ones below?
1016+
1017+ ----
1018+ http://myhostname:8000/../.config/config/google-chrome/Default/Web%20Data
1019+ http://myhostname:8000/../.ssh/id_dsa
1020+ http://myhostname:8000/../../../etc/passwd
9721021----
9731022
974- === A file manager ===
1023+ Change `urlToPath` to fix this problem. Take into account the fact
1024+ that node on Windows allows both forward slashes and backslashes to
1025+ separate directories.
1026+
1027+ Also, meditate on the fact that as soon as you expose some half-baked
1028+ system on the Internet, the bugs in that system can often be used to
1029+ do bad things to the machine the it is running on.
1030+
1031+ !!solution!!
1032+
1033+ It is enough to strip out all occurrences of two dots which have a one
1034+ of a slash, backslash, or the end of the string on both sided. Using
1035+ the `replace` method with a regular expression is the easiest way to
1036+ do this. Do not forget the `g` flag on the expression, or `replace`
1037+ will only replace a single instance, and people could still get around
1038+ this safety measure by including additional double dots in their
1039+ paths! Also make sure you do the replace *after* decoding the string,
1040+ or it would be possible to foil the check by encoding a dot or a
1041+ slash.
1042+
1043+ Another potentially worrying case is paths starting with a slash,
1044+ which interpreted as absolute paths. But because `urlToPath` puts a
1045+ dot character in front of the path, it is impossible to create
1046+ requests that result in such a path. Multiple slashes in a row, inside
1047+ of the path, are odd, but will be treated as a single slash by the
1048+ file system.
1049+
1050+ !!solution!!
1051+
1052+ === Creating directories ===
9751053
1054+ Though the `DELETE` method is wired up to delete directories (using
1055+ `fs.rmdir`) when applied to one, the file server currently does not
1056+ provide any way to _create_ a directory.
1057+
1058+ Add support for a method `MKCOL`, which should create a directory by
1059+ calling `fs.mkdir`. `MKCOL` is not one of the basic HTTP methods, but
1060+ it does exist, for this same purpose, in the _WebDAV_ standard, which
1061+ specifies a set of extensions to HTTP that make it suitable for
1062+ writing resources, not just reading them.
1063+
1064+ !!solution!!
1065+
1066+ You can use the function that implements the `DELETE` method as a
1067+ blueprint for methods.`MKCOL`. When no file is found, try to create a
1068+ directory with `fs.mkdir`. When a directory exists at that path, you
1069+ can return a 204 response, so that directory creation requests are
1070+ idempotent. If a non-directory file exists here, return an error code.
1071+ 400 (“bad request”) would be appropriate here.
1072+
1073+ !!solution!!
1074+
1075+ === A public space on the web ===
1076+
1077+ Since the file server serves up any kind of files, and even includes
1078+ the right `Content-Type` header, you can use it to serve a web site.
1079+ Since it allows everybody to delete and replace files, it would be an
1080+ interesting kind of web site: one that can be modified, vandalized,
1081+ and destroyed by everybody who takes the time to create the right HTTP
1082+ request. Still, it would be a web site.
1083+
1084+ Write a simple HTML page, which includes a simple JavaScript file, put
1085+ them in a directory served by the file server, and open them in your
1086+ browser.
1087+
1088+ Next, as an advanced exercise, or even a weekend project, combine all
1089+ the knowledge you gained from this book to build a more user-friendly
1090+ interface for modifying the website, *inside* of the website itself.
1091+
1092+ Include HTML forms (Chapter 18) to edit the content of the files that
1093+ make up the website, allowing the user to update them on the server
1094+ (using HTTP request as described in Chapter 17).
1095+
1096+ Start by making only a single file editable. Then try to extend the
1097+ code to allow the user to select a file to edit, using the fact that
1098+ our file server returns lists of files when reading a directory.
1099+
1100+ Don't work directly in the code on the file server, since if you make
1101+ a mistake, you are likely to damage the files there. Instead, keep you
1102+ work _outside_ of the publicly accessible directory, and copy it in to
1103+ test it.
1104+
1105+ If your computer is directly connected to the internet, without a
1106+ firewall, router, or other interfering device in between, you might be
1107+ able to invite a friend to use your web site. To check, go to
1108+ http://www.whatismyip.com/[_whatismyip.com_], copy the IP address it
1109+ gives you into the address bar of your browser, and add `:8000` after
1110+ it to select the right port. If that brings you to your site, it is
1111+ online for everybody to see.
1112+
1113+ !!solution!!
1114+
1115+ You can create a `<textarea>` element to hold the content of the file
1116+ that is being edited. A `GET` request, using `XMLHttpRequest`, can be
1117+ used to get the current content of the file. You can use relative URLs
1118+ like _index.html_, instead of _http://localhost:8000/index.html_, to
1119+ refer to files on the same server as the running script.
1120+
1121+ Then, when the user clicks a button (you can use a `<form>` element
1122+ and `"submit"` event, or simply a `"click"` handler), make a `PUT`
1123+ request to the same URL, with the content of the `<textarea>` as
1124+ request body, to save the file.
1125+
1126+ You can then add a `<select>` element that contains all the files in
1127+ the server's root directory, by adding `<option>` elements containing
1128+ the lines returned by a `GET` request to the URL `/`. When the user
1129+ selects another file (a `"change"` event on the field), the script
1130+ must fetch and display that file. Also make sure that when saving a
1131+ file, you use the currently selected file name.
1132+
1133+ Unfortunately, the server is too simplistic to be able to reliably
1134+ read files from subdirectories, since it does not tell us whether the
1135+ thing we fetched with a `GET` request is a regular file or a
1136+ directory. Can you think of a way to extend the server to address
1137+ this?
1138+
1139+ !!solution!!
0 commit comments