├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── client └── index.html ├── sandstorm-pkgdef.capnp └── src ├── main.rs └── server.rs /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bytes" 13 | version = "1.4.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 16 | 17 | [[package]] 18 | name = "capnp" 19 | version = "0.21.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "b1d1b4a00e80b7c4b1a49e845365f25c9d8fd0a19c9cd8d66f68afea47b1f020" 22 | dependencies = [ 23 | "embedded-io", 24 | ] 25 | 26 | [[package]] 27 | name = "capnp-futures" 28 | version = "0.21.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "d04478adeb234836f886ec554a0d96e3af3a939ba7b3962af5addddf7ab71231" 31 | dependencies = [ 32 | "capnp", 33 | "futures-channel", 34 | "futures-util", 35 | ] 36 | 37 | [[package]] 38 | name = "capnp-rpc" 39 | version = "0.21.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "85e9c19ef52ff1b9c9822fb21bfa68a72bc58711676295ff06eb88e64c7877f7" 42 | dependencies = [ 43 | "capnp", 44 | "capnp-futures", 45 | "futures", 46 | ] 47 | 48 | [[package]] 49 | name = "capnpc" 50 | version = "0.21.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "5af589f7a7f3e6d920120b913345bd9a2fc65dfd76c5053a142852a5ea2e8609" 53 | dependencies = [ 54 | "capnp", 55 | ] 56 | 57 | [[package]] 58 | name = "cfg-if" 59 | version = "1.0.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 62 | 63 | [[package]] 64 | name = "embedded-io" 65 | version = "0.6.1" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" 68 | 69 | [[package]] 70 | name = "futures" 71 | version = "0.3.28" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" 74 | dependencies = [ 75 | "futures-channel", 76 | "futures-core", 77 | "futures-executor", 78 | "futures-io", 79 | "futures-sink", 80 | "futures-task", 81 | "futures-util", 82 | ] 83 | 84 | [[package]] 85 | name = "futures-channel" 86 | version = "0.3.28" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 89 | dependencies = [ 90 | "futures-core", 91 | "futures-sink", 92 | ] 93 | 94 | [[package]] 95 | name = "futures-core" 96 | version = "0.3.28" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 99 | 100 | [[package]] 101 | name = "futures-executor" 102 | version = "0.3.28" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 105 | dependencies = [ 106 | "futures-core", 107 | "futures-task", 108 | "futures-util", 109 | ] 110 | 111 | [[package]] 112 | name = "futures-io" 113 | version = "0.3.28" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 116 | 117 | [[package]] 118 | name = "futures-macro" 119 | version = "0.3.28" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" 122 | dependencies = [ 123 | "proc-macro2", 124 | "quote", 125 | "syn", 126 | ] 127 | 128 | [[package]] 129 | name = "futures-sink" 130 | version = "0.3.28" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 133 | 134 | [[package]] 135 | name = "futures-task" 136 | version = "0.3.28" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 139 | 140 | [[package]] 141 | name = "futures-util" 142 | version = "0.3.28" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 145 | dependencies = [ 146 | "futures-channel", 147 | "futures-core", 148 | "futures-io", 149 | "futures-macro", 150 | "futures-sink", 151 | "futures-task", 152 | "memchr", 153 | "pin-project-lite", 154 | "pin-utils", 155 | "slab", 156 | ] 157 | 158 | [[package]] 159 | name = "libc" 160 | version = "0.2.158" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 163 | 164 | [[package]] 165 | name = "log" 166 | version = "0.4.17" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 169 | dependencies = [ 170 | "cfg-if", 171 | ] 172 | 173 | [[package]] 174 | name = "memchr" 175 | version = "2.5.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 178 | 179 | [[package]] 180 | name = "mio" 181 | version = "0.8.11" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 184 | dependencies = [ 185 | "libc", 186 | "log", 187 | "wasi", 188 | "windows-sys 0.48.0", 189 | ] 190 | 191 | [[package]] 192 | name = "pin-project-lite" 193 | version = "0.2.9" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 196 | 197 | [[package]] 198 | name = "pin-utils" 199 | version = "0.1.0" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 202 | 203 | [[package]] 204 | name = "proc-macro2" 205 | version = "1.0.56" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 208 | dependencies = [ 209 | "unicode-ident", 210 | ] 211 | 212 | [[package]] 213 | name = "quote" 214 | version = "1.0.26" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" 217 | dependencies = [ 218 | "proc-macro2", 219 | ] 220 | 221 | [[package]] 222 | name = "sandstorm" 223 | version = "0.21.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "6dc50540bd7d6b41250db4c4fc6f0d828d4f1e7eb4c74a38a00d8fb49cce1106" 226 | dependencies = [ 227 | "capnp", 228 | "capnpc", 229 | ] 230 | 231 | [[package]] 232 | name = "sandstorm-raw-api-example" 233 | version = "0.0.1" 234 | dependencies = [ 235 | "capnp", 236 | "capnp-rpc", 237 | "futures", 238 | "sandstorm", 239 | "tokio", 240 | "tokio-util", 241 | ] 242 | 243 | [[package]] 244 | name = "slab" 245 | version = "0.4.8" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 248 | dependencies = [ 249 | "autocfg", 250 | ] 251 | 252 | [[package]] 253 | name = "socket2" 254 | version = "0.4.9" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 257 | dependencies = [ 258 | "libc", 259 | "winapi", 260 | ] 261 | 262 | [[package]] 263 | name = "syn" 264 | version = "2.0.15" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" 267 | dependencies = [ 268 | "proc-macro2", 269 | "quote", 270 | "unicode-ident", 271 | ] 272 | 273 | [[package]] 274 | name = "tokio" 275 | version = "1.27.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" 278 | dependencies = [ 279 | "autocfg", 280 | "libc", 281 | "mio", 282 | "pin-project-lite", 283 | "socket2", 284 | "tokio-macros", 285 | "windows-sys 0.45.0", 286 | ] 287 | 288 | [[package]] 289 | name = "tokio-macros" 290 | version = "2.0.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" 293 | dependencies = [ 294 | "proc-macro2", 295 | "quote", 296 | "syn", 297 | ] 298 | 299 | [[package]] 300 | name = "tokio-util" 301 | version = "0.7.7" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" 304 | dependencies = [ 305 | "bytes", 306 | "futures-core", 307 | "futures-io", 308 | "futures-sink", 309 | "pin-project-lite", 310 | "tokio", 311 | ] 312 | 313 | [[package]] 314 | name = "unicode-ident" 315 | version = "1.0.8" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 318 | 319 | [[package]] 320 | name = "wasi" 321 | version = "0.11.0+wasi-snapshot-preview1" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 324 | 325 | [[package]] 326 | name = "winapi" 327 | version = "0.3.9" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 330 | dependencies = [ 331 | "winapi-i686-pc-windows-gnu", 332 | "winapi-x86_64-pc-windows-gnu", 333 | ] 334 | 335 | [[package]] 336 | name = "winapi-i686-pc-windows-gnu" 337 | version = "0.4.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 340 | 341 | [[package]] 342 | name = "winapi-x86_64-pc-windows-gnu" 343 | version = "0.4.0" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 346 | 347 | [[package]] 348 | name = "windows-sys" 349 | version = "0.45.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 352 | dependencies = [ 353 | "windows-targets 0.42.2", 354 | ] 355 | 356 | [[package]] 357 | name = "windows-sys" 358 | version = "0.48.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 361 | dependencies = [ 362 | "windows-targets 0.48.5", 363 | ] 364 | 365 | [[package]] 366 | name = "windows-targets" 367 | version = "0.42.2" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 370 | dependencies = [ 371 | "windows_aarch64_gnullvm 0.42.2", 372 | "windows_aarch64_msvc 0.42.2", 373 | "windows_i686_gnu 0.42.2", 374 | "windows_i686_msvc 0.42.2", 375 | "windows_x86_64_gnu 0.42.2", 376 | "windows_x86_64_gnullvm 0.42.2", 377 | "windows_x86_64_msvc 0.42.2", 378 | ] 379 | 380 | [[package]] 381 | name = "windows-targets" 382 | version = "0.48.5" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 385 | dependencies = [ 386 | "windows_aarch64_gnullvm 0.48.5", 387 | "windows_aarch64_msvc 0.48.5", 388 | "windows_i686_gnu 0.48.5", 389 | "windows_i686_msvc 0.48.5", 390 | "windows_x86_64_gnu 0.48.5", 391 | "windows_x86_64_gnullvm 0.48.5", 392 | "windows_x86_64_msvc 0.48.5", 393 | ] 394 | 395 | [[package]] 396 | name = "windows_aarch64_gnullvm" 397 | version = "0.42.2" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 400 | 401 | [[package]] 402 | name = "windows_aarch64_gnullvm" 403 | version = "0.48.5" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 406 | 407 | [[package]] 408 | name = "windows_aarch64_msvc" 409 | version = "0.42.2" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 412 | 413 | [[package]] 414 | name = "windows_aarch64_msvc" 415 | version = "0.48.5" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 418 | 419 | [[package]] 420 | name = "windows_i686_gnu" 421 | version = "0.42.2" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 424 | 425 | [[package]] 426 | name = "windows_i686_gnu" 427 | version = "0.48.5" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 430 | 431 | [[package]] 432 | name = "windows_i686_msvc" 433 | version = "0.42.2" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 436 | 437 | [[package]] 438 | name = "windows_i686_msvc" 439 | version = "0.48.5" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 442 | 443 | [[package]] 444 | name = "windows_x86_64_gnu" 445 | version = "0.42.2" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 448 | 449 | [[package]] 450 | name = "windows_x86_64_gnu" 451 | version = "0.48.5" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 454 | 455 | [[package]] 456 | name = "windows_x86_64_gnullvm" 457 | version = "0.42.2" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 460 | 461 | [[package]] 462 | name = "windows_x86_64_gnullvm" 463 | version = "0.48.5" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 466 | 467 | [[package]] 468 | name = "windows_x86_64_msvc" 469 | version = "0.42.2" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 472 | 473 | [[package]] 474 | name = "windows_x86_64_msvc" 475 | version = "0.48.5" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 478 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "sandstorm-raw-api-example" 4 | version = "0.0.1" 5 | authors = [ "David Renshaw " ] 6 | edition = "2021" 7 | 8 | [[bin]] 9 | 10 | name = "server" 11 | path = "src/main.rs" 12 | 13 | [dependencies] 14 | capnp = "0.21" 15 | capnp-rpc = "0.21" 16 | futures = "0.3" 17 | sandstorm = "0.21.0" 18 | tokio = { version = "1.0.0", features = ["macros", "net", "rt"]} 19 | tokio-util = { version = "0.7.0", features = ["compat"] } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2016 Sandstorm Development Group, Inc. and contributors 2 | Licensed under the MIT License: 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sandstorm Raw API Example App In Rust 2 | 3 | This is an example [Sandstorm](https://sandstorm.io) application which uses the raw [Cap'n 4 | Proto](https://capnproto.org)-based Sandstorm API to serve a web UI without an HTTP server. 5 | The [original version](https://github.com/sandstorm-io/sandstorm-rawapi-example) 6 | was written in C++. 7 | 8 | # Running 9 | 10 | Install [Rust](https://www.rust-lang.org/), [Cap'n Proto](https://capnproto.org/install.html), 11 | and [Sandstorm](https://sandstorm.io/install). Then do: 12 | 13 | ``` 14 | $ cargo build --release 15 | $ spk dev 16 | ``` 17 | 18 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sandstorm Raw API sample app 5 | 6 | 75 | 76 | 77 |

Hello World!

78 |

This is client/index.html. Go edit it and refresh! 79 |


91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /sandstorm-pkgdef.capnp: -------------------------------------------------------------------------------- 1 | @0xc9f1d65b3c4314a6; 2 | 3 | using Spk = import "/sandstorm/package.capnp"; 4 | 5 | const pkgdef :Spk.PackageDefinition = ( 6 | id = "6sd2qefzq1zpn52aefujgkprfc7jprerud8dng1hywaq76qg3au0", 7 | 8 | manifest = ( 9 | appTitle = (defaultText = "Sandstorm Raw Api Example in Rust"), 10 | 11 | appVersion = 0, # Increment this for every release. 12 | appMarketingVersion = (defaultText = "0.0.0"), 13 | actions = [ 14 | # Define your "new document" handlers here. 15 | ( title = (defaultText = "New Instance"), 16 | command = .myCommand 17 | ) 18 | ], 19 | 20 | continueCommand = .myCommand, 21 | metadata = ( 22 | icons = ( 23 | # Various icons to represent the app in various contexts. 24 | #appGrid = (svg = embed "path/to/appgrid-128x128.svg"), 25 | #grain = (svg = embed "path/to/grain-24x24.svg"), 26 | #market = (svg = embed "path/to/market-150x150.svg"), 27 | #marketBig = (svg = embed "path/to/market-big-300x300.svg"), 28 | ), 29 | 30 | website = "http://example.com", 31 | 32 | codeUrl = "http://example.com", 33 | 34 | categories = [], 35 | 36 | author = ( 37 | # Fields relating to the author of this app. 38 | 39 | contactEmail = "youremail@example.com", 40 | # Email address to contact for any issues with this app. This includes end-user support 41 | # requests as well as app store administrator requests, so it is very important that this be a 42 | # valid address with someone paying attention to it. 43 | 44 | #pgpSignature = embed "path/to/pgp-signature", 45 | # PGP signature attesting responsibility for the app ID. This is a binary-format detached 46 | # signature of the following ASCII message (not including the quotes, no newlines, and 47 | # replacing with the standard base-32 text format of the app's ID): 48 | # 49 | # "I am the author of the Sandstorm.io app with the following ID: " 50 | # 51 | # You can create a signature file using `gpg` like so: 52 | # 53 | # echo -n "I am the author of the Sandstorm.io app with the following ID: " | gpg --sign > pgp-signature 54 | # 55 | # Further details including how to set up GPG and how to use keybase.io can be found 56 | # at https://docs.sandstorm.io/en/latest/developing/publishing-apps/#verify-your-identity 57 | 58 | upstreamAuthor = "Example App Team", 59 | # Name of the original primary author of this app, if it is different from the person who 60 | # produced the Sandstorm package. Setting this implies that the author connected to the PGP 61 | # signature only "packaged" the app for Sandstorm, rather than developing the app. 62 | # Remove this line if you consider yourself as the author of the app. 63 | ), 64 | 65 | #pgpKeyring = embed "path/to/pgp-keyring", 66 | # A keyring in GPG keyring format containing all public keys needed to verify PGP signatures in 67 | # this manifest (as of this writing, there is only one: `author.pgpSignature`). 68 | # 69 | # To generate a keyring containing just your public key, do: 70 | # 71 | # gpg --export > keyring 72 | # 73 | # Where `` is a PGP key ID or email address associated with the key. 74 | 75 | #description = (defaultText = embed "path/to/description.md"), 76 | # The app's description description in Github-flavored Markdown format, to be displayed e.g. 77 | # in an app store. Note that the Markdown is not permitted to contain HTML nor image tags (but 78 | # you can include a list of screenshots separately). 79 | 80 | shortDescription = (defaultText = "one-to-three words"), 81 | # A very short (one-to-three words) description of what the app does. For example, 82 | # "Document editor", or "Notetaking", or "Email client". This will be displayed under the app 83 | # title in the grid view in the app market. 84 | 85 | screenshots = [ 86 | # Screenshots to use for marketing purposes. Examples below. 87 | # Sizes are given in device-independent pixels, so if you took these 88 | # screenshots on a Retina-style high DPI screen, divide each dimension by two. 89 | 90 | #(width = 746, height = 795, jpeg = embed "path/to/screenshot-1.jpeg"), 91 | #(width = 640, height = 480, png = embed "path/to/screenshot-2.png"), 92 | ], 93 | #changeLog = (defaultText = embed "path/to/sandstorm-specific/changelog.md"), 94 | # Documents the history of changes in Github-flavored markdown format (with the same restrictions 95 | # as govern `description`). We recommend formatting this with an H1 heading for each version 96 | # followed by a bullet list of changes. 97 | ), 98 | ), 99 | 100 | sourceMap = ( 101 | # Here we defined where to look for files to copy into your package. The 102 | # `spk dev` command actually figures out what files your app needs 103 | # automatically by running it on a FUSE filesystem. So, the mappings 104 | # here are only to tell it where to find files that the app wants. 105 | searchPath = [ 106 | ( sourcePath = "." ), # Search this directory first. 107 | ( sourcePath = "/", # Then search the system root directory. 108 | hidePaths = [ "home", "proc", "sys", 109 | "etc/passwd", "etc/hosts", "etc/host.conf", 110 | "etc/nsswitch.conf", "etc/resolv.conf" ] 111 | # You probably don't want the app pulling files from these places, 112 | # so we hide them. Note that /dev, /var, and /tmp are implicitly 113 | # hidden because Sandstorm itself provides them. 114 | ) 115 | ] 116 | ), 117 | 118 | fileList = "sandstorm-files.list", 119 | # `spk dev` will write a list of all the files your app uses to this file. 120 | # You should review it later, before shipping your app. 121 | 122 | alwaysInclude = [], 123 | # Fill this list with more names of files or directories that should be 124 | # included in your package, even if not listed in sandstorm-files.list. 125 | # Use this to force-include stuff that you know you need but which may 126 | # not have been detected as a dependency during `spk dev`. If you list 127 | # a directory here, its entire contents will be included recursively. 128 | 129 | ); 130 | 131 | const myCommand :Spk.Manifest.Command = ( 132 | argv = ["./target/release/server"], 133 | environ = [ 134 | (key = "PATH", value = "/usr/local/bin:/usr/bin:/bin"), 135 | ] 136 | ); 137 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Sandstorm Development Group, Inc. 2 | // Licensed under the MIT License: 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | 22 | #[macro_use] extern crate capnp_rpc; 23 | 24 | pub mod server; 25 | 26 | #[tokio::main(flavor = "current_thread")] 27 | async fn main() -> Result<(), Box> { 28 | server::main().await?; 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 Sandstorm Development Group, Inc. 2 | // Licensed under the MIT License: 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | // THE SOFTWARE. 21 | 22 | use capnp::Error; 23 | use capnp::capability::Promise; 24 | use capnp_rpc::{RpcSystem, twoparty, rpc_twoparty_capnp}; 25 | 26 | use futures::{AsyncReadExt, TryFutureExt}; 27 | 28 | use sandstorm::grain_capnp::{session_context, ui_view, ui_session, sandstorm_api}; 29 | use sandstorm::identity_capnp::{user_info}; 30 | use sandstorm::web_session_capnp::{web_session}; 31 | 32 | pub struct WebSession { 33 | can_write: bool, 34 | } 35 | 36 | impl WebSession { 37 | pub fn new(user_info: user_info::Reader, 38 | _context: session_context::Client, 39 | _params: web_session::params::Reader) 40 | -> ::capnp::Result 41 | { 42 | // Permission #0 is "write". Check if bit 0 in the PermissionSet is set. 43 | let permissions = user_info.get_permissions()?; 44 | let can_write = permissions.len() > 0 && permissions.get(0); 45 | 46 | Ok(WebSession { 47 | can_write: can_write, 48 | }) 49 | 50 | // `UserInfo` is defined in `sandstorm/grain.capnp` and contains info like: 51 | // - A stable ID for the user, so you can correlate sessions from the same user. 52 | // - The user's display name, e.g. "Mark Miller", useful for identifying the user to other 53 | // users. 54 | // - The user's permissions (seen above). 55 | 56 | // `WebSession::Params` is defined in `sandstorm/web-session.capnp` and contains info like: 57 | // - The hostname where the grain was mapped for this user. Every time a user opens a grain, 58 | // it is mapped at a new random hostname for security reasons. 59 | // - The user's User-Agent and Accept-Languages headers. 60 | 61 | // `SessionContext` is defined in `sandstorm/grain.capnp` and implements callbacks for 62 | // sharing/access control and service publishing/discovery. 63 | } 64 | } 65 | 66 | impl ui_session::Server for WebSession {} 67 | 68 | impl web_session::Server for WebSession { 69 | fn get(&mut self, 70 | params: web_session::GetParams, 71 | mut results: web_session::GetResults) 72 | -> Promise<(), Error> 73 | { 74 | // HTTP GET request. 75 | let path = pry!(pry!(pry!(params.get()).get_path()).to_str()); 76 | pry!(self.require_canonical_path(path)); 77 | 78 | if path == "var" || path == "var/" { 79 | // Return a listing of the directory contents, one per line. 80 | let mut entries = Vec::new(); 81 | for entry in pry!(::std::fs::read_dir(path)) { 82 | let entry = pry!(entry); 83 | let name = entry.file_name().into_string().expect("bad file name"); 84 | if (&name != ".") && (&name != "..") { 85 | entries.push(name); 86 | } 87 | } 88 | let text = entries.join("\n"); 89 | let mut response = results.get().init_content(); 90 | response.set_mime_type("text/plain"); 91 | response.init_body().set_bytes(text.as_bytes()); 92 | Promise::ok(()) 93 | } else if path.starts_with("var/") { 94 | // Serve all files under /var with type application/octet-stream since it comes from the 95 | // user. E.g. serving as "text/html" here would allow someone to trivially XSS other users 96 | // of the grain by PUTing malicious HTML content. (Such an attack wouldn't be a huge deal: 97 | // it would only allow the attacker to hijack another user's access to this grain, not to 98 | // Sandstorm in general, and if they attacker already has write access to upload the 99 | // malicious content, they have little to gain from hijacking another session.) 100 | self.read_file(path, results, "application/octet-stream") 101 | } else if path == ".can-write" { 102 | // Fetch "/.can-write" to determine if the user has write permission, so you can show them 103 | // a different UI if not. 104 | let mut response = results.get().init_content(); 105 | response.set_mime_type("text/plain"); 106 | response.init_body().set_bytes(&format!("{}", self.can_write).as_bytes()); 107 | Promise::ok(()) 108 | } else if path == "" || path.ends_with("/") { 109 | // A directory. Serve "index.html". 110 | self.read_file(&format!("client/{}index.html", path), results, "text/html; charset=UTF-8") 111 | } else { 112 | // Request for a static file. Look for it under "client/". 113 | let filename = format!("client/{}", path); 114 | 115 | // Check if it's a directory. 116 | if let Ok(true) = ::std::fs::metadata(&filename).map(|md| md.is_dir()) { 117 | // It is. Return redirect to add '/'. 118 | let mut redirect = results.get().init_redirect(); 119 | redirect.set_is_permanent(true); 120 | redirect.set_switch_to_get(true); 121 | redirect.set_location(format!("{}/", path)); 122 | Promise::ok(()) 123 | } else { 124 | // Regular file (or non-existent). 125 | self.read_file(&filename, results, self.infer_content_type(path)) 126 | } 127 | } 128 | } 129 | 130 | fn put(&mut self, 131 | params: web_session::PutParams, 132 | mut results: web_session::PutResults) 133 | -> Promise<(), Error> 134 | { 135 | // HTTP PUT request. 136 | 137 | let params = pry!(params.get()); 138 | let path = pry!(pry!(params.get_path()).to_str()); 139 | pry!(self.require_canonical_path(path)); 140 | 141 | if !path.starts_with("var/") { 142 | return Promise::err(Error::failed("PUT only supported under /var.".to_string())); 143 | } 144 | 145 | if !self.can_write { 146 | results.get().init_client_error() 147 | .set_status_code(web_session::response::ClientErrorCode::Forbidden); 148 | } else { 149 | use std::io::Write; 150 | let temp_path = format!("{}.uploading", path); 151 | let data = pry!(pry!(params.get_content()).get_content()); 152 | 153 | let mut writer = pry!(::std::fs::File::create(&temp_path)); 154 | pry!(writer.write_all(data)); 155 | pry!(::std::fs::rename(temp_path, path)); 156 | pry!(writer.sync_all()); 157 | 158 | results.get().init_no_content(); 159 | } 160 | Promise::ok(()) 161 | } 162 | 163 | fn delete(&mut self, 164 | params: web_session::DeleteParams, 165 | mut results: web_session::DeleteResults) 166 | -> Promise<(), Error> 167 | { 168 | // HTTP DELETE request. 169 | 170 | let path = pry!(pry!(pry!(params.get()).get_path()).to_str()); 171 | pry!(self.require_canonical_path(path)); 172 | 173 | if !path.starts_with("var/") { 174 | return Promise::err(Error::failed("DELETE only supported under /var.".to_string())); 175 | } 176 | 177 | if !self.can_write { 178 | results.get().init_client_error() 179 | .set_status_code(web_session::response::ClientErrorCode::Forbidden); 180 | Promise::ok(()) 181 | } else { 182 | if let Err(e) = ::std::fs::remove_file(path) { 183 | if e.kind() != ::std::io::ErrorKind::NotFound { 184 | return Promise::err(e.into()) 185 | } 186 | } 187 | results.get().init_no_content(); 188 | Promise::ok(()) 189 | } 190 | } 191 | } 192 | 193 | impl WebSession { 194 | fn require_canonical_path(&self, path: &str) -> Result<(), Error> { 195 | // Require that the path doesn't contain "." or ".." or consecutive slashes, to prevent path 196 | // injection attacks. 197 | // 198 | // Note that such attacks wouldn't actually accomplish much since everything outside /var 199 | // is a read-only filesystem anyway, containing the app package contents which are non-secret. 200 | 201 | for (idx, component) in path.split_terminator("/").enumerate() { 202 | if component == "." || component == ".." || (component == "" && idx > 0) { 203 | return Err(Error::failed(format!("non-canonical path: {:?}", path))); 204 | } 205 | } 206 | Ok(()) 207 | } 208 | 209 | fn infer_content_type(&self, filename: &str) -> &'static str { 210 | if filename.ends_with(".html") { 211 | "text/html; charset=UTF-8" 212 | } else if filename.ends_with(".js") { 213 | "text/javascript; charset=UTF-8" 214 | } else if filename.ends_with(".css") { 215 | "text/css; charset=UTF-8" 216 | } else if filename.ends_with(".png") { 217 | "image/png" 218 | } else if filename.ends_with(".gif") { 219 | "image/gif" 220 | } else if filename.ends_with(".jpg") || filename.ends_with(".jpeg") { 221 | "image/jpeg" 222 | } else if filename.ends_with(".svg") { 223 | "image/svg+xml; charset=UTF-8" 224 | } else if filename.ends_with(".txt") { 225 | "text/plain; charset=UTF-8" 226 | } else { 227 | "application/octet-stream" 228 | } 229 | } 230 | 231 | fn read_file(&self, 232 | filename: &str, 233 | mut results: web_session::GetResults, 234 | content_type: &str) 235 | -> Promise<(), Error> 236 | { 237 | match ::std::fs::File::open(filename) { 238 | Ok(mut f) => { 239 | let size = pry!(f.metadata()).len(); 240 | let mut content = results.get().init_content(); 241 | content.set_status_code(web_session::response::SuccessCode::Ok); 242 | content.set_mime_type(content_type); 243 | let mut body = content.init_body().init_bytes(size as u32); 244 | pry!(::std::io::copy(&mut f, &mut body)); 245 | Promise::ok(()) 246 | } 247 | Err(ref e) if e.kind() == ::std::io::ErrorKind::NotFound => { 248 | let mut error = results.get().init_client_error(); 249 | error.set_status_code(web_session::response::ClientErrorCode::NotFound); 250 | Promise::ok(()) 251 | } 252 | Err(e) => { 253 | Promise::err(e.into()) 254 | } 255 | } 256 | } 257 | } 258 | 259 | pub struct UiView { 260 | _sandstorm_api: sandstorm_api::Client<::capnp::any_pointer::Owned>, 261 | } 262 | 263 | impl UiView { 264 | fn new(sandstorm_api: sandstorm_api::Client<::capnp::any_pointer::Owned>) -> UiView { 265 | UiView { _sandstorm_api: sandstorm_api } 266 | } 267 | } 268 | 269 | impl ui_view::Server for UiView { 270 | fn get_view_info( 271 | &mut self, 272 | _params: ui_view::GetViewInfoParams, 273 | mut results: ui_view::GetViewInfoResults) 274 | -> Promise<(), Error> 275 | { 276 | let mut view_info = results.get(); 277 | 278 | // Define a "write" permission, and then define roles "editor" and "viewer" where only "editor" 279 | // has the "write" permission. This will allow people to share read-only. 280 | { 281 | let perms = view_info.reborrow().init_permissions(1); 282 | let mut write = perms.get(0); 283 | write.set_name("write"); 284 | write.init_title().set_default_text("write"); 285 | } 286 | 287 | let mut roles = view_info.init_roles(2); 288 | { 289 | let mut editor = roles.reborrow().get(0); 290 | editor.reborrow().init_title().set_default_text("editor"); 291 | editor.reborrow().init_verb_phrase().set_default_text("can edit"); 292 | editor.init_permissions(1).set(0, true); // has "write" permission 293 | } 294 | { 295 | let mut viewer = roles.get(1); 296 | viewer.reborrow().init_title().set_default_text("viewer"); 297 | viewer.reborrow().init_verb_phrase().set_default_text("can view"); 298 | viewer.init_permissions(1).set(0, false); // does not have "write" permission 299 | } 300 | Promise::ok(()) 301 | } 302 | 303 | 304 | fn new_session(&mut self, 305 | params: ui_view::NewSessionParams, 306 | mut results: ui_view::NewSessionResults) 307 | -> Promise<(), Error> 308 | { 309 | use ::capnp::traits::HasTypeId; 310 | let params = pry!(params.get()); 311 | 312 | if params.get_session_type() != web_session::Client::TYPE_ID { 313 | return Promise::err(Error::failed("unsupported session type".to_string())); 314 | } 315 | 316 | let session = pry!(WebSession::new(pry!(params.get_user_info()), 317 | pry!(params.get_context()), 318 | pry!(params.get_session_params().get_as()))); 319 | let client: web_session::Client = capnp_rpc::new_client(session); 320 | 321 | // we need to do this dance to upcast. 322 | results.get().set_session(ui_session::Client { client : client.client}); 323 | Promise::ok(()) 324 | } 325 | } 326 | 327 | pub async fn main() -> Result<(), Box> { 328 | use ::std::os::unix::io::{FromRawFd}; 329 | 330 | let stream: ::std::os::unix::net::UnixStream = unsafe { FromRawFd::from_raw_fd(3) }; 331 | stream.set_nonblocking(true)?; 332 | let stream = tokio::net::UnixStream::from_std(stream)?; 333 | let (read_half, write_half) = 334 | tokio_util::compat::TokioAsyncReadCompatExt::compat(stream).split(); 335 | 336 | let network = 337 | Box::new(twoparty::VatNetwork::new(read_half, write_half, 338 | rpc_twoparty_capnp::Side::Client, 339 | Default::default())); 340 | 341 | let (tx, rx) = ::futures::channel::oneshot::channel(); 342 | let sandstorm_api: sandstorm_api::Client<::capnp::any_pointer::Owned> = 343 | ::capnp_rpc::new_future_client(rx.map_err(|_e| capnp::Error::failed(format!("oneshot was canceled")))); 344 | 345 | let client: ui_view::Client = capnp_rpc::new_client(UiView::new(sandstorm_api)); 346 | let mut rpc_system = RpcSystem::new(network, Some(client.client)); 347 | 348 | drop(tx.send(rpc_system.bootstrap::>( 349 | ::capnp_rpc::rpc_twoparty_capnp::Side::Server))); 350 | 351 | rpc_system.await?; 352 | Ok(()) 353 | } 354 | --------------------------------------------------------------------------------