diff --git a/Cargo.lock b/Cargo.lock index a918f9f5c95..4958f50b1e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,17 @@ dependencies = [ "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ammonia" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "html5ever 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)", + "maplit 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tendril 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "antidote" version = "1.0.0" @@ -80,6 +91,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "cargo-registry" version = "0.1.0" dependencies = [ + "ammonia 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "bufstream 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "cargo-registry-s3 0.1.0", "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -109,13 +121,15 @@ dependencies = [ "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "oauth2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)", + "pulldown-cmark 0.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "r2d2 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "r2d2-diesel 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tar 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -123,11 +137,11 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -166,7 +180,7 @@ name = "clippy" version = "0.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cargo_metadata 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cargo_metadata 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "clippy_lints 0.0.142 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -181,8 +195,8 @@ dependencies = [ "quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -367,6 +381,14 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "debug_unreachable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "derive-error-chain" version = "0.10.1" @@ -466,6 +488,15 @@ name = "foreign-types" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "futf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "futures" version = "0.1.14" @@ -494,9 +525,21 @@ name = "hex" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "html5ever" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "markup5ever 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "idna" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -584,6 +627,29 @@ name = "log" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "maplit" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "markup5ever" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tendril 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "matches" version = "0.1.6" @@ -696,6 +762,40 @@ name = "percent-encoding" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "phf" +version = "0.7.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "phf_codegen" +version = "0.7.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "phf_generator" +version = "0.7.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "phf_shared" +version = "0.7.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pkg-config" version = "0.3.9" @@ -709,6 +809,19 @@ dependencies = [ "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "precomputed-hash" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pulldown-cmark" +version = "0.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quine-mc_cluskey" version = "0.2.4" @@ -769,7 +882,7 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -883,12 +996,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde_derive" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", @@ -913,9 +1026,14 @@ dependencies = [ "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "siphasher" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "socket2" version = "0.2.1" @@ -928,6 +1046,36 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "string_cache" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", + "precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "string_cache_codegen" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "string_cache_shared" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "syn" version = "0.11.11" @@ -946,6 +1094,26 @@ dependencies = [ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tar" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", + "xattr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tendril" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futf 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "utf-8 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread-id" version = "2.0.0" @@ -979,7 +1147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -988,7 +1156,7 @@ name = "toml" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1009,6 +1177,14 @@ name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unreachable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unreachable" version = "1.0.0" @@ -1027,11 +1203,19 @@ name = "url" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "idna 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "utf-8" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "utf8-ranges" version = "0.1.3" @@ -1071,9 +1255,18 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "xattr" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.26 (registry+https://github.com/rust-lang/crates.io-index)", +] + [metadata] "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" +"checksum ammonia 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a84d348f2d98bb52f7e8d896c3ce5b6bbf66a040dd334c9cfe1503b6d8e7d15e" "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" "checksum backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72f9b4182546f4b04ebc4ab7f84948953a118bd6021a1b6a6c909e3e94f6be76" "checksum backtrace-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3a0d842ea781ce92be2bf78a9b38883948542749640b8378b3b2f03d1fd9f1ff" @@ -1081,7 +1274,7 @@ dependencies = [ "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bufstream 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f2f382711e76b9de6c744cc00d0497baba02fb00a787f088c879f01d09468e32" "checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d" -"checksum cargo_metadata 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5d84cb53c78e573aa126a4b9f963fdb2629f8183b26e235da08bb36dc7381162" +"checksum cargo_metadata 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "be1057b8462184f634c3a208ee35b0f935cfd94b694b26deadccd98732088d7b" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" "checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9" "checksum civet 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6263e7af767a5bf9e4d3d0a6c3ceb5f3940ec85cf2fbfee59024b8a264be180f" @@ -1106,6 +1299,7 @@ dependencies = [ "checksum curl 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6689276ab61f97c660669a5ecc117c36875dfc1ba301c986b16c653415bdf9d7" "checksum curl-sys 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d5481162dc4f424d088581db2f979fa7d4c238fe9794595de61d8d7522e277de" "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850" +"checksum debug_unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9a032eac705ca39214d169f83e3d3da290af06d8d1d344d1baad2fd002dca4b3" "checksum derive-error-chain 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3c9ca9ade651388daad7c993f005d0d20c4f6fe78c1cdc93e95f161c6f5ede4a" "checksum diesel 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d7302a01c06bd94e619661c44528375a068e34717608d0d1c93bf75428da142a" "checksum diesel_codegen 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "044abc50a0ee67e195b0ae95c9ffe6903748434294636dd67808301b7df1902f" @@ -1118,11 +1312,13 @@ dependencies = [ "checksum filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5363ab8e4139b8568a6237db5248646e5a8a2f89bd5ccb02092182b11fd3e922" "checksum flate2 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)" = "36df0166e856739905cd3d7e0b210fe818592211a008862599845e012d8d304c" "checksum foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e4056b9bd47f8ac5ba12be771f77a0dae796d1bbaaf5fd0b9c2d38b69b8a29d" +"checksum futf 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "51f93f3de6ba1794dcd5810b3546d004600a59a98266487c8407bc4b24e398f3" "checksum futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4b63a4792d4f8f686defe3b39b92127fea6344de5d38202b2ee5a11bbbf29d6a" "checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a" "checksum git2 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa01936ac96555c083c0e8553f672616274408d9d3fc5b8696603fbf63ff43ee" "checksum hex 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" -"checksum idna 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2233d4940b1f19f0418c158509cd7396b8d70a5db5705ce410914dc8fa603b37" +"checksum html5ever 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a49d5001dd1bddf042ea41ed4e0a671d50b1bf187e66b349d7ec613bdce4ad90" +"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" "checksum itertools 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "772a0928a97246167d59a2a4729df5871f1327ab8b36fd24c4224b229cb47b99" "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" @@ -1133,6 +1329,9 @@ dependencies = [ "checksum libz-sys 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "3fdd64ef8ee652185674455c1d450b83cbc8ad895625d543b5324d923f82e4d8" "checksum license-exprs 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "20e170a9f8785c9bb07576397a605ac453332e833a5eb5686cd4dcbb39cc1a66" "checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" +"checksum mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +"checksum maplit 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "be384c560e0c3ad868b590ffb88d2c0a1effde6f59885234e4ea811c1202bfea" +"checksum markup5ever 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5050cc22c1d567a9b99bcbe9ffbd8f3127b3d146994105480240dc8ba4f4f2ef" "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" @@ -1147,8 +1346,14 @@ dependencies = [ "checksum openssl-probe 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d98df0270d404ccd3c050a41d579c52d1db15375168bb3471e04ec0f5f378daf" "checksum openssl-sys 0.9.14 (registry+https://github.com/rust-lang/crates.io-index)" = "236c718c2e2c2b58a546d86ffea5194400bb15dbe01ca85325ffd357b03cf66c" "checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356" +"checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc" +"checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f" +"checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03" +"checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2" "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" "checksum pq-sys 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4dfb5e575ef93a1b7b2a381d47ba7c5d4e4f73bff37cee932195de769aad9a54" +"checksum precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf1fc3616b3ef726a847f2cd2388c646ef6a1f1ba4835c2629004da48184150" +"checksum pulldown-cmark 0.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "378e941dbd392c101f2cb88097fa4d7167bc421d4b88de3ff7dbee503bc3233b" "checksum quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum r2d2 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6c665a538b218e1620093be6643b375d3321837bfc1b30aa18757b7c6546d2ca" @@ -1156,7 +1361,7 @@ dependencies = [ "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" "checksum rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a77c51c07654ddd93f6cb543c7a849863b03abc7e82591afda6dc8ad4ac3ac4a" "checksum rayon-core 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7febc28567082c345f10cddc3612c6ea020fc3297a1977d472cf9fdb73e6e493" -"checksum redox_syscall 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "9aa093607d28cfd65f317edeeefb6749be428eacc8decd1c5f8c0fbcc327aff5" +"checksum redox_syscall 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "9df6a71a1e67be2104410736b2389fb8e383c1d7e9e792d629ff13c02867147a" "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" "checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b" "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" @@ -1172,13 +1377,19 @@ dependencies = [ "checksum semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" "checksum semver-parser 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fff3c9c5a54636ab95acd8c1349926e04cb1eb8cd70b5adced8a1d1f703a67" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6a7c6b751a2e8d5df57a5ff71b5b4fc8aaee9ee28ff1341d640dd130bb5f4f7a" -"checksum serde_derive 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "2f6ca58905ebd3c3b285a8a6d4f3ac92b92c0d7951d5649b1bdd212549c06639" +"checksum serde 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "433d7d9f8530d5a939ad5e0e72a6243d2e42a24804f70bf592c679363dcacb2f" +"checksum serde_derive 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "7b707cf0d4cab852084f573058def08879bb467fda89d99052485e7d00edd624" "checksum serde_derive_internals 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37aee4e0da52d801acfbc0cc219eb1eda7142112339726e427926a6f6ee65d3a" "checksum serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "48b04779552e92037212c3615370f6bd57a40ebba7f20e554ff9f55e41a69a7b" +"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537" "checksum socket2 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "12cdbddbaa27bf94cc194b8e37f5811db6fe83cea96cf99cf1f8e92b65a41371" +"checksum string_cache 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2fa69b90c5398217fb0414706d1becea9325ad21ed5d87bd6dda82127911f324" +"checksum string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "479cde50c3539481f33906a387f2bd17c8e87cb848c35b6021d41fb81ff9b4d7" +"checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum tar 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "281285b717926caa919ad905ef89c63d75805c7d89437fb873100925a53f2b1b" +"checksum tendril 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1b72f8e2f5b73b65c315b1a70c730f24b9d7a25f39e98de8acbe2bb795caea" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" "checksum thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" @@ -1187,9 +1398,11 @@ dependencies = [ "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum untrusted 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6b65243989ef6aacd9c0d6bd2b822765c3361d8ed352185a6f3a41f3a718c673" "checksum url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb819346883532a271eb626deb43c4a1bb4c4dd47c519bd78137c3e72a4fe27" +"checksum utf-8 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6f923c601c7ac48ef1d66f7d5b5b2d9a7ba9c51333ab75a3ddf8d0309185a56" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" @@ -1197,3 +1410,4 @@ dependencies = [ "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +"checksum xattr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "5f04de8a1346489a2f9e9bd8526b73d135ec554227b17568456e86aa35b6f3fc" diff --git a/Cargo.toml b/Cargo.toml index e4f05043b3d..32bf330472e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ git2 = "0.6.4" flate2 = "0.2" semver = "0.5" url = "1.2.1" +tar = "0.4.13" r2d2 = "0.7.0" openssl = "0.9.9" @@ -47,6 +48,8 @@ serde_derive = "1.0.0" serde = "1.0.0" clippy = { version = "=0.0.142", optional = true } chrono = "0.4.0" +pulldown-cmark = { version = "0.0.15", default-features = false } +ammonia = "0.5.0" conduit = "0.8" conduit-conditional-get = "0.8" diff --git a/app/components/crate-readme.js b/app/components/crate-readme.js new file mode 100644 index 00000000000..eb18c70bc30 --- /dev/null +++ b/app/components/crate-readme.js @@ -0,0 +1,11 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + rendered: '', + didRender() { + this._super(...arguments); + this.$('pre > code').each(function() { + window.Prism.highlightElement(this); + }); + } +}); diff --git a/app/models/version.js b/app/models/version.js index fec6f6c3f39..3da95b4300d 100644 --- a/app/models/version.js +++ b/app/models/version.js @@ -4,6 +4,7 @@ import DS from 'ember-data'; export default DS.Model.extend({ num: DS.attr('string'), dl_path: DS.attr('string'), + readme_path: DS.attr('string'), created_at: DS.attr('date'), updated_at: DS.attr('date'), downloads: DS.attr('number'), diff --git a/app/routes/crate/version.js b/app/routes/crate/version.js index e6a0be8e299..e6c2f189859 100644 --- a/app/routes/crate/version.js +++ b/app/routes/crate/version.js @@ -99,9 +99,28 @@ export default Route.extend({ `Version '${params.version_num}' of crate '${crate.get('name')}' does not exist`); } - return version || + const result = version || versions.find(version => version.get('num') === maxVersion) || versions.objectAt(0); + if (result.get('readme_path')) { + this.get('ajax').request(result.get('readme_path')) + .then((r) => this.get('ajax').raw(r.url, { + method: 'GET', + dataType: 'html', + headers: { + // We need to force the Accept header, otherwise crates.io won't return + // the readme file when not using S3. + Accept: '*/*', + }, + })) + .then((r) => { + crate.set('readme', r.payload); + }) + .catch(() => { + crate.set('readme', null); + }); + } + return result; }); }, diff --git a/app/styles/crate.scss b/app/styles/crate.scss index 9a39f5c60ee..b590aef5cf3 100644 --- a/app/styles/crate.scss +++ b/app/styles/crate.scss @@ -208,6 +208,7 @@ .docs { @include flex(7); padding-right: 40px; + max-width: 600px; } .authorship { @include flex(3); @@ -302,6 +303,13 @@ color: red; } } + .crate-readme { + line-height: 1.5; + + pre { + overflow-x: scroll; + } + } .last-update { color: $main-color-light; font-size: 90%; diff --git a/app/templates/components/crate-readme.hbs b/app/templates/components/crate-readme.hbs new file mode 100644 index 00000000000..624ded4c91a --- /dev/null +++ b/app/templates/components/crate-readme.hbs @@ -0,0 +1 @@ +{{{rendered}}} diff --git a/app/templates/crate/version.hbs b/app/templates/crate/version.hbs index f3dcc984911..ea0a568db35 100644 --- a/app/templates/crate/version.hbs +++ b/app/templates/crate/version.hbs @@ -28,13 +28,28 @@ @@ -54,12 +69,6 @@ {{else}}
- {{#if crate.description}} -
-

About This Package

-

{{ crate.description }}

-
- {{/if}}
Cargo.toml
{{ crate.name }} = "{{ currentVersion.num }}" @@ -82,6 +91,11 @@ {{/if}}
+ {{#if crate.readme}} +
+ {{crate-readme rendered=crate.readme}} +
+ {{/if}}
@@ -166,88 +180,54 @@ {{/each}}
-
-
- - - {{/if}} - -
-

Versions

- - - {{#if hasMoreVersions}} - {{#link-to 'crate.versions' crate}} - show all {{ crate.versions.length }} versions - {{/link-to}} - {{/if}} - -
@@ -256,14 +236,14 @@
{{svg-jar "download"}} - {{ format-num downloadsContext.downloads }} + {{format-num downloadsContext.downloads}} Downloads all time
{{svg-jar "crate"}} - {{ crate.versions.length }} + {{crate.versions.length}} Versions published
diff --git a/ember-cli-build.js b/ember-cli-build.js index 427a8da2746..5e987af7ed1 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -5,10 +5,31 @@ const EmberApp = require('ember-cli/lib/broccoli/ember-app'); module.exports = function(defaults) { + const highlightedLanguages = [ + 'bash', + 'clike', + 'glsl', + 'go', + 'ini', + 'javascript', + 'json', + 'markup', + 'protobuf', + 'ruby', + 'rust', + 'scss', + 'sql', + 'yaml' + ]; + let app = new EmberApp(defaults, { babel6: { plugins: ['transform-object-rest-spread'], }, + 'ember-prism': { + theme: 'twilight', + components: highlightedLanguages, + } }); // Use `app.import` to add additional libraries to the generated diff --git a/package-lock.json b/package-lock.json index 88024e3203e..f6ccc2e7b01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6372,6 +6372,56 @@ "ember-cli-htmlbars": "2.0.2" } }, + "ember-prism": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ember-prism/-/ember-prism-0.1.0.tgz", + "integrity": "sha1-JWz25nDj/9B4OXa58nSuhLU8ExQ=", + "dev": true, + "requires": { + "ember-cli-babel": "6.6.0", + "ember-cli-htmlbars": "1.3.4", + "ember-cli-node-assets": "0.2.2", + "prismjs": "1.6.0" + }, + "dependencies": { + "ember-cli-htmlbars": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ember-cli-htmlbars/-/ember-cli-htmlbars-1.3.4.tgz", + "integrity": "sha512-5lycG6z35QHr3WZF1OkVvT+N/GGAVuemtM6m8NUgBWoeA2TqOgPFRcI0eRqoLA0HAfe0R2MReKmMI7y1LEM1+w==", + "dev": true, + "requires": { + "broccoli-persistent-filter": "1.4.2", + "ember-cli-version-checker": "1.3.1", + "hash-for-dep": "1.1.2", + "json-stable-stringify": "1.0.1", + "strip-bom": "2.0.0" + } + }, + "ember-cli-node-assets": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ember-cli-node-assets/-/ember-cli-node-assets-0.2.2.tgz", + "integrity": "sha1-0tVWJufMZhn4gtf+VXUfkmYCJwg=", + "dev": true, + "requires": { + "broccoli-funnel": "1.2.0", + "broccoli-merge-trees": "1.2.4", + "broccoli-source": "1.1.0", + "debug": "2.6.8", + "lodash": "4.17.4", + "resolve": "1.3.3" + } + }, + "ember-cli-version-checker": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ember-cli-version-checker/-/ember-cli-version-checker-1.3.1.tgz", + "integrity": "sha1-C8LRNMgwFC2mS/lieg7e0QthrnI=", + "dev": true, + "requires": { + "semver": "5.3.0" + } + } + } + }, "ember-qunit": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/ember-qunit/-/ember-qunit-2.1.4.tgz", @@ -11584,6 +11634,15 @@ "integrity": "sha1-xDjKLKM+OSdnHbSracDlL5NqTw8=", "dev": true }, + "prismjs": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.6.0.tgz", + "integrity": "sha1-EY2V+3pm26InLjQ7NF9SNmWds2U=", + "dev": true, + "requires": { + "clipboard": "1.7.1" + } + }, "private": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/private/-/private-0.1.7.tgz", diff --git a/package.json b/package.json index e164b315d03..85f2f1206a1 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "ember-moment": "^7.3.1", "ember-normalize": "^1.0.0", "ember-page-title": "^3.2.1", + "ember-prism": "^0.1.0", "ember-resolver": "^4.1.0", "ember-router-scroll": "^0.2.0", "ember-source": "~2.13.3", diff --git a/src/bin/render-readmes.rs b/src/bin/render-readmes.rs new file mode 100644 index 00000000000..00000dc3843 --- /dev/null +++ b/src/bin/render-readmes.rs @@ -0,0 +1,249 @@ +// Iterates over every crate versions ever uploaded and (re-)renders their +// readme using the Markdown renderer from the cargo_registry crate. +// +// Warning: this can take a lot of time. +// +// Usage: +// cargo run --bin render-readmes [page-size: optional = 25] +// The page-size argument dictate how much versions should be queried and processed at once. + +#![deny(warnings)] + +#[macro_use] +extern crate serde_derive; + +extern crate cargo_registry; +extern crate curl; +extern crate diesel; +extern crate flate2; +extern crate s3; +extern crate tar; +extern crate time; +extern crate toml; +extern crate url; + +use curl::easy::{Easy, List}; +use diesel::prelude::*; +use flate2::read::GzDecoder; +use std::env; +use std::io::{Cursor, Read}; +use std::path::Path; +use std::thread; +use tar::Archive; +use url::Url; + +use cargo_registry::{Config, Version}; +use cargo_registry::version::EncodableVersion; +use cargo_registry::schema::*; +use cargo_registry::render::markdown_to_html; + +const DEFAULT_PAGE_SIZE: i64 = 25; + +fn main() { + let config: Config = Default::default(); + let conn = cargo_registry::db::connect_now().unwrap(); + let versions_count = versions::table + .select(versions::all_columns) + .count() + .get_result::(&conn) + .expect("error counting versions"); + let page_size = match env::args().nth(1) { + None => DEFAULT_PAGE_SIZE, + Some(s) => s.parse::().unwrap_or(DEFAULT_PAGE_SIZE), + }; + let pages = if versions_count % page_size == 0 { + versions_count / page_size + } else { + versions_count / page_size + 1 + }; + for current_page in 0..pages { + let versions: Vec = versions::table + .inner_join(crates::table) + .select((versions::all_columns, crates::name)) + .limit(page_size) + .offset(current_page * page_size) + .load::<(Version, String)>(&conn) + .expect("error loading versions") + .into_iter() + .map(|(version, crate_name)| version.encodable(&crate_name)) + .collect(); + let mut tasks = Vec::with_capacity(page_size as usize); + for version in versions { + let config = config.clone(); + let handle = thread::spawn(move || { + println!("[{}-{}] Rendering README...", version.krate, version.num); + let readme = get_readme(&config, &version); + if readme.is_none() { + return; + } + let readme = readme.unwrap(); + let readme_path = format!( + "readmes/{}/{}-{}.html", + version.krate, + version.krate, + version.num + ); + let readme_len = readme.len(); + let mut body = Cursor::new(readme.into_bytes()); + config + .uploader + .upload( + Easy::new(), + &readme_path, + &mut body, + "text/html", + readme_len as u64, + ) + .expect(&format!( + "[{}-{}] Couldn't upload file to S3", + version.krate, + version.num + )); + }); + tasks.push(handle); + } + for handle in tasks { + if let Err(err) = handle.join() { + println!("Thead panicked: {:?}", err); + } + } + } +} + +/// Renders the readme of an uploaded crate version. +fn get_readme(config: &Config, version: &EncodableVersion) -> Option { + let mut handle = Easy::new(); + let location = match config.uploader.crate_location(&version.krate, &version.num) { + Some(l) => l, + None => return None, + }; + let date = time::now().rfc822z().to_string(); + let url = Url::parse(&location).expect(&format!( + "[{}-{}] Couldn't parse crate URL", + version.krate, + version.num + )); + + let mut headers = List::new(); + headers + .append(&format!("Host: {}", url.host().unwrap())) + .unwrap(); + headers.append(&format!("Date: {}", date)).unwrap(); + + handle.url(url.as_str()).unwrap(); + handle.get(true).unwrap(); + handle.http_headers(headers).unwrap(); + + let mut response = Vec::new(); + { + let mut req = handle.transfer(); + req.write_function(|data| { + response.extend(data); + Ok(data.len()) + }).unwrap(); + if let Err(err) = req.perform() { + println!( + "[{}-{}] Unable to fetch crate: {}", + version.krate, + version.num, + err + ); + return None; + } + } + if handle.response_code().unwrap() != 200 { + let response = String::from_utf8_lossy(&response); + println!( + "[{}-{}] Failed to get a 200 response: {}", + version.krate, + version.num, + response + ); + return None; + } + let reader = Cursor::new(response); + let reader = GzDecoder::new(reader).expect(&format!( + "[{}-{}] Invalid gzip header", + version.krate, + version.num + )); + let mut archive = Archive::new(reader); + let mut entries = archive.entries().expect(&format!( + "[{}-{}] Invalid tar archive entries", + version.krate, + version.num + )); + let manifest: Manifest = { + let path = format!("{}-{}/Cargo.toml", version.krate, version.num); + let contents = find_file_by_path(&mut entries, Path::new(&path), &version); + toml::from_str(&contents).expect(&format!( + "[{}-{}] Syntax error in manifest file", + version.krate, + version.num + )) + }; + if manifest.package.readme.is_none() { + return None; + } + let rendered = { + let path = format!( + "{}-{}/{}", + version.krate, + version.num, + manifest.package.readme.unwrap() + ); + let contents = find_file_by_path(&mut entries, Path::new(&path), &version); + markdown_to_html(&contents).expect(&format!( + "[{}-{}] Couldn't render README", + version.krate, + version.num + )) + }; + return Some(rendered); + #[derive(Deserialize)] + struct Package { + readme: Option, + } + #[derive(Deserialize)] + struct Manifest { + package: Package, + } +} + +/// Search an entry by its path in a Tar archive. +fn find_file_by_path( + mut entries: &mut tar::Entries, + path: &Path, + version: &EncodableVersion, +) -> String { + let mut file = entries + .find(|entry| match *entry { + Err(_) => false, + Ok(ref file) => { + let filepath = match file.path() { + Ok(p) => p, + Err(_) => return false, + }; + return filepath == path; + } + }) + .expect(&format!( + "[{}-{}] couldn't open file: {}", + version.krate, + version.num, + path.display() + )) + .expect(&format!( + "[{}-{}] file is not present: {}", + version.krate, + version.num, + path.display() + )); + let mut contents = String::new(); + file.read_to_string(&mut contents).expect(&format!( + "[{}-{}] Couldn't read file contents", + version.krate, + version.num + )); + contents +} diff --git a/src/bin/server.rs b/src/bin/server.rs index 9245acea5ec..306d543492c 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -7,11 +7,10 @@ extern crate git2; extern crate env_logger; extern crate s3; -use cargo_registry::{env, Env, Uploader, Replica}; +use cargo_registry::{env, Env}; use civet::Server; use std::env; use std::fs::{self, File}; -use std::path::PathBuf; use std::sync::Arc; use std::sync::mpsc::channel; @@ -19,25 +18,25 @@ use std::sync::mpsc::channel; fn main() { // Initialize logging env_logger::init().unwrap(); + let config: cargo_registry::Config = Default::default(); // If there isn't a git checkout containing the crate index repo at the path specified // by `GIT_REPO_CHECKOUT`, delete that directory and clone the repo specified by `GIT_REPO_URL` // into that directory instead. Uses the credentials specified in `GIT_HTTP_USER` and // `GIT_HTTP_PWD` via the `cargo_registry::git::credentials` function. let url = env("GIT_REPO_URL"); - let checkout = PathBuf::from(env("GIT_REPO_CHECKOUT")); - let repo = match git2::Repository::open(&checkout) { + let repo = match git2::Repository::open(&config.git_repo_checkout) { Ok(r) => r, Err(..) => { - let _ = fs::remove_dir_all(&checkout); - fs::create_dir_all(&checkout).unwrap(); + let _ = fs::remove_dir_all(&config.git_repo_checkout); + fs::create_dir_all(&config.git_repo_checkout).unwrap(); let mut cb = git2::RemoteCallbacks::new(); cb.credentials(cargo_registry::git::credentials); let mut opts = git2::FetchOptions::new(); opts.remote_callbacks(cb); git2::build::RepoBuilder::new() .fetch_options(opts) - .clone(&url, &checkout) + .clone(&url, &config.git_repo_checkout) .unwrap() } }; @@ -48,97 +47,6 @@ fn main() { cfg.set_str("user.name", "bors").unwrap(); cfg.set_str("user.email", "bors@rust-lang.org").unwrap(); - let api_protocol = String::from("https"); - - let mirror = if env::var("MIRROR").is_ok() { - Replica::ReadOnlyMirror - } else { - Replica::Primary - }; - - let heroku = env::var("HEROKU").is_ok(); - let cargo_env = if heroku { - Env::Production - } else { - Env::Development - }; - - let uploader = match (cargo_env, mirror) { - (Env::Production, Replica::Primary) => { - // `env` panics if these vars are not set, and in production for a primary instance, - // that's what we want since we don't want to be able to start the server if the server - // doesn't know where to upload crates. - Uploader::S3 { - bucket: s3::Bucket::new( - env("S3_BUCKET"), - env::var("S3_REGION").ok(), - env("S3_ACCESS_KEY"), - env("S3_SECRET_KEY"), - &api_protocol, - ), - proxy: None, - } - } - (Env::Production, Replica::ReadOnlyMirror) => { - // Read-only mirrors don't need access key or secret key since by definition, - // they'll only need to read from a bucket, not upload. - // - // Read-only mirrors might have access key or secret key, so use them if those - // environment variables are set. - // - // Read-only mirrors definitely need bucket though, so that they know where - // to serve crate files from. - Uploader::S3 { - bucket: s3::Bucket::new( - env("S3_BUCKET"), - env::var("S3_REGION").ok(), - env::var("S3_ACCESS_KEY").unwrap_or(String::new()), - env::var("S3_SECRET_KEY").unwrap_or(String::new()), - &api_protocol, - ), - proxy: None, - } - } - // In Development mode, either running as a primary instance or a read-only mirror - _ => { - if env::var("S3_BUCKET").is_ok() { - // If we've set the `S3_BUCKET` variable to any value, use all of the values - // for the related S3 environment variables and configure the app to upload to - // and read from S3 like production does. All values except for bucket are - // optional, like production read-only mirrors. - println!("Using S3 uploader"); - Uploader::S3 { - bucket: s3::Bucket::new( - env("S3_BUCKET"), - env::var("S3_REGION").ok(), - env::var("S3_ACCESS_KEY").unwrap_or(String::new()), - env::var("S3_SECRET_KEY").unwrap_or(String::new()), - &api_protocol, - ), - proxy: None, - } - } else { - // If we don't set the `S3_BUCKET` variable, we'll use a development-only - // uploader that makes it possible to run and publish to a locally-running - // crates.io instance without needing to set up an account and a bucket in S3. - println!("Using local uploader, crate files will be in the dist directory"); - Uploader::Local - } - } - }; - - let config = cargo_registry::Config { - uploader: uploader, - session_key: env("SESSION_KEY"), - git_repo_checkout: checkout, - gh_client_id: env("GH_CLIENT_ID"), - gh_client_secret: env("GH_CLIENT_SECRET"), - db_url: env("DATABASE_URL"), - env: cargo_env, - max_upload_size: 10 * 1024 * 1024, // 10 MB default file upload size limit - mirror: mirror, - api_protocol: api_protocol, - }; let app = cargo_registry::App::new(&config); let app = cargo_registry::middleware(Arc::new(app)); @@ -147,6 +55,7 @@ fn main() { let categories_toml = include_str!("../categories.toml"); cargo_registry::categories::sync(&categories_toml).unwrap(); + let heroku = env::var("HEROKU").is_ok(); let port = if heroku { 8888 } else { @@ -155,7 +64,11 @@ fn main() { .and_then(|s| s.parse().ok()) .unwrap_or(8888) }; - let threads = if cargo_env == Env::Development { 1 } else { 50 }; + let threads = if config.env == Env::Development { + 1 + } else { + 50 + }; let mut cfg = civet::Config::new(); cfg.port(port).threads(threads).keep_alive(true); let _a = Server::start(cfg, app); diff --git a/src/config.rs b/src/config.rs index 30890add680..40f91f5611b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,9 @@ +use s3; + +use std::env; use std::path::PathBuf; -use {Uploader, Replica}; + +use {Env, env, Uploader, Replica}; #[derive(Clone, Debug)] pub struct Config { @@ -14,3 +18,115 @@ pub struct Config { pub mirror: Replica, pub api_protocol: String, } + +impl Default for Config { + /// Returns a default value for the application's config + /// + /// Sets the following default values: + /// - `Config::max_upload_size`: 10MiB + /// - `Config::api_protocol`: `https` + /// + /// Pulls values from the following environment variables: + /// - `GIT_REPO_CHECKOUT`: The directory where the registry index was cloned. + /// - `MIRROR`: Is this instance of cargo_registry a mirror of crates.io. + /// - `HEROKU`: Is this instance of cargo_registry currently running on Heroku. + /// - `S3_BUCKET`: The S3 bucket used to store crate files. If not present during development, + /// cargo_registry will fall back to a local uploader. + /// - `S3_REGION`: The region in which the bucket was created. Optional if US standard. + /// - `S3_ACCESS_KEY`: The access key to interact with S3. Optional if running a mirror. + /// - `S3_SECRET_KEY`: The secret key to interact with S3. Optional if running a mirror. + /// - `SESSION_KEY`: The key used to sign and encrypt session cookies. + /// - `GH_CLIENT_ID`: The client ID of the associated GitHub application. + /// - `GH_CLIENT_SECRET`: The client secret of the associated GitHub application. + /// - `DATABASE_URL`: The URL of the postgres database to use. + fn default() -> Config { + let checkout = PathBuf::from(env("GIT_REPO_CHECKOUT")); + let api_protocol = String::from("https"); + let mirror = if env::var("MIRROR").is_ok() { + Replica::ReadOnlyMirror + } else { + Replica::Primary + }; + let heroku = env::var("HEROKU").is_ok(); + let cargo_env = if heroku { + Env::Production + } else { + Env::Development + }; + let uploader = match (cargo_env, mirror) { + (Env::Production, Replica::Primary) => { + // `env` panics if these vars are not set, and in production for a primary instance, + // that's what we want since we don't want to be able to start the server if the + // server doesn't know where to upload crates. + Uploader::S3 { + bucket: s3::Bucket::new( + env("S3_BUCKET"), + env::var("S3_REGION").ok(), + env("S3_ACCESS_KEY"), + env("S3_SECRET_KEY"), + &api_protocol, + ), + proxy: None, + } + } + (Env::Production, Replica::ReadOnlyMirror) => { + // Read-only mirrors don't need access key or secret key since by definition, + // they'll only need to read from a bucket, not upload. + // + // Read-only mirrors might have access key or secret key, so use them if those + // environment variables are set. + // + // Read-only mirrors definitely need bucket though, so that they know where + // to serve crate files from. + Uploader::S3 { + bucket: s3::Bucket::new( + env("S3_BUCKET"), + env::var("S3_REGION").ok(), + env::var("S3_ACCESS_KEY").unwrap_or_default(), + env::var("S3_SECRET_KEY").unwrap_or_default(), + &api_protocol, + ), + proxy: None, + } + } + // In Development mode, either running as a primary instance or a read-only mirror + _ => { + if env::var("S3_BUCKET").is_ok() { + // If we've set the `S3_BUCKET` variable to any value, use all of the values + // for the related S3 environment variables and configure the app to upload to + // and read from S3 like production does. All values except for bucket are + // optional, like production read-only mirrors. + println!("Using S3 uploader"); + Uploader::S3 { + bucket: s3::Bucket::new( + env("S3_BUCKET"), + env::var("S3_REGION").ok(), + env::var("S3_ACCESS_KEY").unwrap_or_default(), + env::var("S3_SECRET_KEY").unwrap_or_default(), + &api_protocol, + ), + proxy: None, + } + } else { + // If we don't set the `S3_BUCKET` variable, we'll use a development-only + // uploader that makes it possible to run and publish to a locally-running + // crates.io instance without needing to set up an account and a bucket in S3. + println!("Using local uploader, crate files will be in the dist directory"); + Uploader::Local + } + } + }; + Config { + uploader: uploader, + session_key: env("SESSION_KEY"), + git_repo_checkout: checkout, + gh_client_id: env("GH_CLIENT_ID"), + gh_client_secret: env("GH_CLIENT_SECRET"), + db_url: env("DATABASE_URL"), + env: cargo_env, + max_upload_size: 10 * 1024 * 1024, // 10 MB default file upload size limit + mirror: mirror, + api_protocol: api_protocol, + } + } +} diff --git a/src/http.rs b/src/http.rs index 552bca78913..13a7cc234c0 100644 --- a/src/http.rs +++ b/src/http.rs @@ -1,14 +1,21 @@ use conduit::{Request, Response}; use conduit_middleware::Middleware; + use curl; use curl::easy::{Easy, List}; + use oauth2::*; -use app::App; -use util::{CargoResult, internal, ChainError, human}; + use serde_json; use serde::Deserialize; + use std::str; use std::error::Error; +use std::collections::HashMap; + +use app::App; +use util::{CargoResult, internal, ChainError, human}; +use Uploader; /// Does all the nonsense for sending a GET to Github. Doesn't handle parsing /// because custom error-code handling may be desirable. Use @@ -91,8 +98,55 @@ pub fn token(token: String) -> Token { } } -#[derive(Clone, Copy, Debug)] -pub struct SecurityHeadersMiddleware; +#[derive(Clone, Debug)] +pub struct SecurityHeadersMiddleware { + headers: HashMap>, +} + +impl SecurityHeadersMiddleware { + pub fn new(uploader: &Uploader) -> Self { + let mut headers = HashMap::new(); + + headers.insert("X-Content-Type-Options".into(), vec!["nosniff".into()]); + + headers.insert("X-Frame-Options".into(), vec!["SAMEORIGIN".into()]); + + headers.insert("X-XSS-Protection".into(), vec!["1; mode=block".into()]); + + let s3_host = match *uploader { + Uploader::S3 { ref bucket, .. } => bucket.host(), + _ => { + unreachable!( + "This middleware should only be used in the production environment, \ + which should also require an S3 uploader, QED" + ) + } + }; + + // It would be better if we didn't have to have 'unsafe-eval' in the `script-src` + // policy, but google charts (used for the download graph on crate pages) uses `eval` + // to load scripts. Remove 'unsafe-eval' if google fixes the issue: + // https://github.com/google/google-visualization-issues/issues/1356 + // or if we switch to a different graph generation library. + headers.insert( + "Content-Security-Policy".into(), + vec![ + format!( + "default-src 'self'; \ + connect-src 'self' https://docs.rs https://{}; \ + script-src 'self' 'unsafe-eval' \ + https://www.google-analytics.com https://www.google.com; \ + style-src 'self' https://www.google.com https://ajax.googleapis.com; \ + img-src *; \ + object-src 'none'", + s3_host + ), + ], + ); + + SecurityHeadersMiddleware { headers } + } +} impl Middleware for SecurityHeadersMiddleware { fn after( @@ -101,36 +155,7 @@ impl Middleware for SecurityHeadersMiddleware { mut res: Result>, ) -> Result> { if let Ok(ref mut response) = res { - // It would be better if we didn't have to have 'unsafe-eval' in the `script-src` - // policy, but google charts (used for the download graph on crate pages) uses `eval` - // to load scripts. Remove 'unsafe-eval' if google fixes the issue: - // https://github.com/google/google-visualization-issues/issues/1356 - // or if we switch to a different graph generation library. - response.headers.insert( - "Content-Security-Policy".into(), - vec![ - "default-src 'self'; \ - connect-src 'self' https://docs.rs; \ - script-src 'self' 'unsafe-eval' \ - https://www.google-analytics.com https://www.google.com; \ - style-src 'self' https://www.google.com https://ajax.googleapis.com; \ - img-src *; \ - object-src 'none'" - .into(), - ], - ); - response.headers.insert( - "X-Content-Type-Options".into(), - vec!["nosniff".into()], - ); - response.headers.insert( - "X-Frame-Options".into(), - vec!["SAMEORIGIN".into()], - ); - response.headers.insert( - "X-XSS-Protection".into(), - vec!["1; mode=block".into()], - ); + response.headers.extend(self.headers.clone()); } res } diff --git a/src/krate.rs b/src/krate.rs index 7859b710e97..2da5fe23438 100644 --- a/src/krate.rs +++ b/src/krate.rs @@ -29,6 +29,7 @@ use git; use keyword::{EncodableKeyword, CrateKeyword}; use owner::{EncodableOwner, Owner, Rights, OwnerKind, Team, rights, CrateOwner}; use pagination::Paginate; +use render; use schema::*; use upload; use user::RequestUser; @@ -951,10 +952,22 @@ pub fn new(req: &mut Request) -> CargoResult { let ignored_invalid_badges = Badge::update_crate(&conn, &krate, new_crate.badges.as_ref())?; let max_version = krate.max_version(&conn)?; + // Render the README for this crate + let readme = match new_crate.readme.as_ref() { + Some(readme) => Some(render::markdown_to_html(&**readme)?), + None => None, + }; + // Upload the crate, return way to delete the crate from the server // If the git commands fail below, we shouldn't keep the crate on the // server. - let (cksum, mut bomb) = app.config.uploader.upload(req, &krate, max, vers)?; + let (cksum, mut crate_bomb, mut readme_bomb) = app.config.uploader.upload_crate( + req, + &krate, + readme, + max, + vers, + )?; // Register this crate in our local git repo. let git_crate = git::Crate { @@ -973,7 +986,8 @@ pub fn new(req: &mut Request) -> CargoResult { })?; // Now that we've come this far, we're committed! - bomb.path = None; + crate_bomb.path = None; + readme_bomb.path = None; #[derive(Serialize)] struct Warnings<'a> { @@ -1074,6 +1088,28 @@ pub fn download(req: &mut Request) -> CargoResult { } } +/// Handles the `GET /crates/:crate_id/:version/readme` route. +pub fn readme(req: &mut Request) -> CargoResult { + let crate_name = &req.params()["crate_id"]; + let version = &req.params()["version"]; + + let redirect_url = req.app() + .config + .uploader + .readme_location(crate_name, version) + .ok_or_else(|| human("crate readme not found"))?; + + if req.wants_json() { + #[derive(Serialize)] + struct R { + url: String, + } + Ok(req.json(&R { url: redirect_url })) + } else { + Ok(req.redirect(redirect_url)) + } +} + fn increment_download_counts(req: &Request, crate_name: &str, version: &str) -> CargoResult<()> { use self::versions::dsl::*; diff --git a/src/lib.rs b/src/lib.rs index 0f7f8bf8dac..f13cc6ae250 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ extern crate log; extern crate serde_json; #[macro_use] extern crate serde_derive; +extern crate ammonia; extern crate chrono; extern crate curl; extern crate diesel_full_text_search; @@ -29,6 +30,7 @@ extern crate hex; extern crate license_exprs; extern crate oauth2; extern crate openssl; +extern crate pulldown_cmark; extern crate r2d2; extern crate r2d2_diesel; extern crate rand; @@ -84,6 +86,7 @@ pub mod http; pub mod keyword; pub mod krate; pub mod owner; +pub mod render; pub mod schema; pub mod token; pub mod upload; @@ -135,6 +138,7 @@ pub fn middleware(app: Arc) -> MiddlewareBuilder { api_router.put("/crates/new", C(krate::new)); api_router.get("/crates/:crate_id/:version", C(version::show)); api_router.get("/crates/:crate_id/:version/download", C(krate::download)); + api_router.get("/crates/:crate_id/:version/readme", C(krate::readme)); api_router.get( "/crates/:crate_id/:version/dependencies", C(version::dependencies), @@ -224,7 +228,7 @@ pub fn middleware(app: Arc) -> MiddlewareBuilder { env == Env::Production, )); if env == Env::Production { - m.add(http::SecurityHeadersMiddleware); + m.add(http::SecurityHeadersMiddleware::new(&app.config.uploader)); } m.add(app::AppMiddleware::new(app)); diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 00000000000..96659be002e --- /dev/null +++ b/src/render.rs @@ -0,0 +1,170 @@ +use ammonia::Ammonia; +use pulldown_cmark::Parser; +use pulldown_cmark::html; + +use util::CargoResult; + +/// Context for markdown to HTML rendering. +#[allow(missing_debug_implementations)] +pub struct MarkdownRenderer<'a> { + html_sanitizer: Ammonia<'a>, +} + +impl<'a> MarkdownRenderer<'a> { + /// Creates a new renderer instance. + pub fn new() -> MarkdownRenderer<'a> { + let tags = [ + "a", + "b", + "blockquote", + "br", + "code", + "dd", + "del", + "dl", + "dt", + "em", + "i", + "h1", + "h2", + "h3", + "hr", + "img", + "kbd", + "li", + "ol", + "p", + "pre", + "s", + "strike", + "strong", + "sub", + "sup", + "table", + "tbody", + "td", + "th", + "thead", + "tr", + "ul", + "hr", + "span", + ].iter() + .cloned() + .collect(); + let tag_attributes = [ + ("a", ["href", "target"].iter().cloned().collect()), + ( + "img", + ["width", "height", "src", "alt", "align", "width"] + .iter() + .cloned() + .collect(), + ), + ("code", ["class"].iter().cloned().collect()), + ("span", ["style"].iter().cloned().collect()), + ].iter() + .cloned() + .collect(); + let html_sanitizer = Ammonia { + keep_cleaned_elements: true, + tags: tags, + tag_attributes: tag_attributes, + ..Ammonia::default() + }; + MarkdownRenderer { html_sanitizer: html_sanitizer } + } + + /// Renders the given markdown to HTML using the current settings. + pub fn to_html(&self, text: &str) -> CargoResult { + let mut rendered = String::with_capacity(text.len() * 3 / 2); + let parser = Parser::new(text); + html::push_html(&mut rendered, parser); + Ok(self.html_sanitizer.clean(&rendered)) + } +} + +impl<'a> Default for MarkdownRenderer<'a> { + fn default() -> Self { + Self::new() + } +} + +/// Renders a markdown text to sanitized HTML. +/// +/// The returned text should not contain any harmful HTML tag or attribute (such as iframe, +/// onclick, onmouseover, etc.). +/// +/// # Examples +/// +/// ``` +/// use render::markdown_to_html; +/// +/// let text = "[Rust](https://rust-lang.org/) is an awesome *systems programming* language!"; +/// let rendered = markdown_to_html(text)?; +/// ``` +pub fn markdown_to_html(text: &str) -> CargoResult { + let renderer = MarkdownRenderer::new(); + renderer.to_html(text) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_text() { + let text = ""; + let result = markdown_to_html(text); + assert_eq!(result.is_ok(), true); + let rendered = result.unwrap(); + assert_eq!(rendered, ""); + } + + #[test] + fn text_with_script_tag() { + let text = "foo_readme\n\n"; + let result = markdown_to_html(text); + assert_eq!(result.is_ok(), true); + let rendered = result.unwrap(); + assert_eq!(rendered.contains("foo_readme"), true); + assert_eq!(rendered.contains("script"), false); + assert_eq!(rendered.contains("alert('Hello World')"), true); + } + + #[test] + fn text_with_iframe_tag() { + let text = "foo_readme\n\n"; + let result = markdown_to_html(text); + assert_eq!(result.is_ok(), true); + let rendered = result.unwrap(); + assert_eq!(rendered.contains("foo_readme"), true); + assert_eq!(rendered.contains("iframe"), false); + assert_eq!(rendered.contains("alert('Hello World')"), true); + } + + #[test] + fn text_with_unknwon_tag() { + let text = "foo_readme\n\nalert('Hello World')"; + let result = markdown_to_html(text); + assert_eq!(result.is_ok(), true); + let rendered = result.unwrap(); + assert_eq!(rendered.contains("foo_readme"), true); + assert_eq!(rendered.contains("unknown"), false); + assert_eq!(rendered.contains("alert('Hello World')"), true); + } + + #[test] + fn text_with_inline_javascript() { + let text = r#"foo_readme\n\nCrate page"#; + let result = markdown_to_html(text); + assert_eq!(result.is_ok(), true); + let rendered = result.unwrap(); + assert_eq!(rendered.contains("foo_readme"), true); + assert_eq!(rendered.contains(" Option { match *self { Uploader::S3 { ref bucket, .. } => { @@ -54,35 +60,60 @@ impl Uploader { } } + /// Returns the URL of an uploaded crate's version readme. + /// + /// The function doesn't check for the existence of the file. + /// It returns `None` if the current `Uploader` is `NoOp`. + pub fn readme_location(&self, crate_name: &str, version: &str) -> Option { + match *self { + Uploader::S3 { ref bucket, .. } => { + Some(format!( + "https://{}/{}", + bucket.host(), + Uploader::readme_path(crate_name, version) + )) + } + Uploader::Local => { + Some(format!( + "/local_uploads/{}", + Uploader::readme_path(crate_name, version) + )) + } + Uploader::NoOp => None, + } + } + + /// Returns the interna path of an uploaded crate's version archive. fn crate_path(name: &str, version: &str) -> String { // No slash in front so we can use join format!("crates/{}/{}-{}.crate", name, name, version) } + /// Returns the interna path of an uploaded crate's version readme. + fn readme_path(name: &str, version: &str) -> String { + format!("readmes/{}/{}-{}.html", name, name, version) + } + + /// Uploads a file using the configured uploader (either `S3`, `Local` or `NoOp`). + /// + /// It returns a a tuple containing the path of the uploaded file + /// and its checksum. pub fn upload( &self, - req: &mut Request, - krate: &Crate, - max: u64, - vers: &semver::Version, - ) -> CargoResult<(Vec, Bomb)> { + mut handle: Easy, + path: &str, + body: &mut io::Read, + content_type: &str, + content_length: u64, + ) -> CargoResult<(Option, Vec)> { match *self { Uploader::S3 { ref bucket, .. } => { - let mut handle = req.app().handle(); - let path = format!("/{}", Uploader::crate_path(&krate.name, &vers.to_string())); let (response, cksum) = { - let length = read_le_u32(req.body())?; - let body = LimitErrorReader::new(req.body(), max); let mut body = HashingReader::new(body); let mut response = Vec::new(); { - let mut s3req = bucket.put( - &mut handle, - &path, - &mut body, - "application/x-tar", - length as u64, - ); + let mut s3req = + bucket.put(&mut handle, path, &mut body, content_type, content_length); s3req .write_function(|data| { response.extend(data); @@ -102,57 +133,79 @@ impl Uploader { response ))); } - - Ok(( - cksum, - Bomb { - app: req.app().clone(), - path: Some(path), - }, - )) + Ok((Some(String::from(path)), cksum)) } Uploader::Local => { - let path = Uploader::crate_path(&krate.name, &vers.to_string()); - let crate_filename = env::current_dir() + let filename = env::current_dir() .unwrap() .join("dist") .join("local_uploads") .join(path); - - let crate_dir = crate_filename.parent().unwrap(); - fs::create_dir_all(crate_dir)?; - - let mut crate_file = File::create(&crate_filename)?; - - let cksum = { - read_le_u32(req.body())?; - let body = LimitErrorReader::new(req.body(), max); - let mut body = HashingReader::new(body); - - io::copy(&mut body, &mut crate_file)?; - body.finalize() - }; - - Ok(( - cksum, - Bomb { - app: req.app().clone(), - path: crate_filename.to_str().map(String::from), - }, - )) - } - Uploader::NoOp => { - Ok(( - vec![], - Bomb { - app: req.app().clone(), - path: None, - }, - )) + let dir = filename.parent().unwrap(); + fs::create_dir_all(dir)?; + let mut file = File::create(&filename)?; + let mut body = HashingReader::new(body); + io::copy(&mut body, &mut file)?; + Ok((filename.to_str().map(String::from), body.finalize())) } + Uploader::NoOp => Ok((None, vec![])), } } + /// Uploads a crate and its readme. Returns the checksum of the uploaded crate + /// file, and bombs for the uploaded crate and the uploaded readme. + pub fn upload_crate( + &self, + req: &mut Request, + krate: &Crate, + readme: Option, + max: u64, + vers: &semver::Version, + ) -> CargoResult<(Vec, Bomb, Bomb)> { + let app = req.app().clone(); + let (crate_path, checksum) = { + let path = Uploader::crate_path(&krate.name, &vers.to_string()); + let length = read_le_u32(req.body())?; + let mut body = LimitErrorReader::new(req.body(), max); + self.upload( + app.handle(), + &path, + &mut body, + "application/x-tar", + length as u64, + )? + }; + // We create the bomb for the crate file before uploading the readme so that if the + // readme upload fails, the uploaded crate file is automatically deleted. + let crate_bomb = Bomb { + app: app.clone(), + path: crate_path, + }; + let (readme_path, _) = if let Some(rendered) = readme { + let path = Uploader::readme_path(&krate.name, &vers.to_string()); + let length = rendered.len(); + let mut body = io::Cursor::new(rendered.into_bytes()); + self.upload( + app.handle(), + &path, + &mut body, + "text/html", + length as u64, + )? + } else { + (None, vec![]) + }; + Ok(( + checksum, + crate_bomb, + Bomb { + app: app.clone(), + path: readme_path, + }, + )) + } + + /// Deletes an uploaded file. fn delete(&self, app: Arc, path: &str) -> CargoResult<()> { match *self { Uploader::S3 { ref bucket, .. } => { diff --git a/src/version.rs b/src/version.rs index e45f1209935..1461d2df6d4 100644 --- a/src/version.rs +++ b/src/version.rs @@ -53,6 +53,7 @@ pub struct EncodableVersion { pub krate: String, pub num: String, pub dl_path: String, + pub readme_path: String, pub updated_at: String, pub created_at: String, pub downloads: i32, @@ -85,6 +86,7 @@ impl Version { let num = num.to_string(); EncodableVersion { dl_path: format!("/api/v1/crates/{}/{}/download", crate_name, num), + readme_path: format!("/api/v1/crates/{}/{}/readme", crate_name, num), num: num.clone(), id: id, krate: crate_name.to_string(),