Compare commits

..

81 Commits

Author SHA1 Message Date
GitLab Build Bot
6a46168fb7 js-precompiled 20161110-160103
Former-commit-id: b14aed734e1f16676b2ec442b1b5a63d86f7bb5d
2016-11-10 17:02:57 +01:00
Jaco Greeff
3da0632771 Make transactions load (#3348)
Former-commit-id: 11a485ea1abb82551f0369b88cfce1ac9a43ce55
2016-11-10 16:39:20 +01:00
arkpar
613b89010f Updated tests
Former-commit-id: 1e3255e6f10dad8b7a0fd0e36ab3aa358144ac40
2016-11-10 16:23:48 +01:00
arkpar
e85ffdbcd1 Updated tests
Former-commit-id: 9f79e6a91837099d7aa537c7c13e64a5e1d2cc77
2016-11-10 16:10:29 +01:00
Arkadiy Paronyan
f5e1bf08ab Disarm the HF and add more bootnodes (#3323)
* Disarm the HF

* More bootnodes

* Updated tests


Former-commit-id: 828de69b1b0011b90812d53ffaa13ed6be2cdd15
2016-11-10 16:10:14 +01:00
GitLab Build Bot
77f3b87627 js-precompiled 20161110-143746
Former-commit-id: fcf0b92f05731b817dd82bde6632bd58b237de7a
2016-11-10 15:44:27 +01:00
Arkadiy Paronyan
75166bd0e0 Backporting to beta (#3344)
* Disarm the HF and add more bootnodes (#3323)

* Disarm the HF

* More bootnodes

* Updated tests

* Windows app and installer fixes (#3338)

* Windows app and installer fixes

* Sorted out comments


Former-commit-id: f64a70feb8594ab4c9494a56702b8283c06a0d19
2016-11-10 15:17:18 +01:00
arkpar
623fc569c2 Fixed MD5 generation
Former-commit-id: 3f885a87bb50a159f82c8b8a1cda0e32b015db27
2016-11-10 14:51:37 +01:00
GitLab Build Bot
7e77f1b62c [ci skip] js-precompiled 20161109-211612
Former-commit-id: f57c299d090347467c4917a1f6a9528936d5314b
2016-11-09 21:17:25 +00:00
Jaco Greeff
e919b2d597 Remove 127.0.0.1 references (#3303)
Former-commit-id: 10ff9e134bad432871f860b9e588624dedb43038
2016-11-09 20:40:57 +01:00
Jaco Greeff
8660b057bf Handle redirects from /api/content on manifest.json gracefully (#3315)
* Add redirect follow mode to manifest.json

* Remove (now) unused count parameter

* autoRewrite: true for dev mode proxy redirects


Former-commit-id: 73143932199b78961760208a9f20dac62b55f0bd
2016-11-09 20:40:57 +01:00
arkpar
e8d6c3a699 v1.4.2
Former-commit-id: 612229ca2ffa1cbb9060c219eac81b4d569df969
2016-11-09 20:40:57 +01:00
Tomasz Drwięga
d0df85d50e Dapps interface RPC (#3311)
* Dapps Interface RPC

* Adding JS apis

* Support for signer interface in proxypac and embeds

* Fixing tests

* fixing tests again


Former-commit-id: 7b998f7ebf1f13bc8e2d948ae896b78e5870b53a
2016-11-09 20:40:57 +01:00
Arkadiy Paronyan
20ca56490e Additional snapshot sync checks (#3318)
* Additional snapshot sync checks

* Proper checks

* Proper highset block check


Former-commit-id: 402a6ca55670f9c197b3d673e91528b853b1b7d6
2016-11-09 20:40:57 +01:00
Jaco Greeff
bfc99f5a76 Fix signer token updates (#3302)
* Manual bump of package.json (recovery)

* Debug

* Simplify status connections


Former-commit-id: c5d0c002b60adeabd7726050437d0632763629ed
2016-11-09 20:40:57 +01:00
Jaco Greeff
59372af0af Update account recovery phrase hint
Former-commit-id: 016e9fd1ab905f46f63bbd0a85210ddcf4cb47cc
2016-11-09 20:40:57 +01:00
Gav Wood
d898cc49ed New transaction tests (#3313)
* Add new transaction tests.

* Add new test.


Former-commit-id: 4549bfc759ee8095965ac34f9173796ce07835a1
2016-11-09 20:40:57 +01:00
Jaco Greeff
adf4e65759 expose api as window.secureApi (#3207)
Former-commit-id: 6b1ca33b8da4ddd96efb28b879e568f713b5fb29
2016-11-09 20:40:57 +01:00
Arkadiy Paronyan
1d2fbdebb2 Fixes for open UI after installation (#3300)
Former-commit-id: 373d61f0102ea6767027b233f756d89b9f838f52
2016-11-09 20:40:57 +01:00
Arkadiy Paronyan
184b0f27e7 Fixed uncle details (#3299)
Former-commit-id: 297b84f4eb80db16a5c33966892b585e43590c11
2016-11-09 20:40:57 +01:00
Jaco Greeff
618cc072b9 Change to more common focused spelling (#3264)
Former-commit-id: b39e5438d9aa2cecab3cc880181d065758a8dd87
2016-11-09 20:40:57 +01:00
Jaco Greeff
f9a75e8e57 Updated blance display with max decimals (#3266)
Former-commit-id: 308371799a47aea45dfceed9b8273f85b5dff728
2016-11-09 20:40:57 +01:00
Björn Wagner
31d75c2ba5 Fix typo (#3298)
Former-commit-id: 5f7bfe2926ff291670b56b7d14ddd9a4e4a4b979
2016-11-09 20:40:57 +01:00
arkpar
7ddb54fd78 Corrected startup executable
Former-commit-id: a7ddb9e26affe6d5836473cd715feffcd3a75b57
2016-11-09 01:15:04 +01:00
GitLab Build Bot
314d0759d0 js-precompiled 20161108-224010
Former-commit-id: 888d69fa5119547736d918660c81f41543677256
2016-11-08 23:43:11 +01:00
Jaco Greeff
da7492fd29 Fix initial token generation (#3289)
* Fix initial token generation

* Remove extra debug info


Former-commit-id: 84e06150f784a55e63414aa412354d18f51f05e4
2016-11-08 23:21:00 +01:00
arkpar
a38a222ade v1.4.1 for macOS installer
Former-commit-id: 2e462f76cb8b86925936a3a3ba003a183ab573e9
2016-11-08 23:21:00 +01:00
Gav Wood
8e44212536 Fixes for 1.4 (#3260)
* Fix --geth

* Fix mac installer.

* Fix up installer.

* Additional tweaks.

* Fix for empty APIs

* Another case of empty apis


Former-commit-id: 9cb8309b2c10e64815505af014c6fc33235bd007
2016-11-08 23:21:00 +01:00
Arkadiy Paronyan
972f1b11d6 Fixed IO service shutdown (#3286)
Former-commit-id: feba57498d122773d4aeb567d1bf2c25f5853ed4
2016-11-08 23:21:00 +01:00
Arkadiy Paronyan
a573b0d3e3 Autostart checkbox (#3269)
Former-commit-id: 2b78fe1f8b6ef404bdf806b830d55b59862ceda1
2016-11-08 23:21:00 +01:00
arkpar
b42717050d v1.4.1
Former-commit-id: a5b896cc57a7b8ba7049f1a82dbdc6317d6b3252
2016-11-08 23:21:00 +01:00
GitLab Build Bot
9059754a23 [ci skip] js-precompiled 20161108-132446
Former-commit-id: 84a9c67a51233313d8c2acbe9e96b9357c34dc89
2016-11-08 13:26:02 +00:00
Denis S. Soldatov aka General-Beck
afd115863b Update gitlab-ci
fix cache

Former-commit-id: e9820a2a86594d66f33b8ccb07a6d01aa65b9dda
2016-11-08 19:44:00 +07:00
Denis S. Soldatov aka General-Beck
05bbfb8f0d Update gitlab-ci
Add installer for OSX

Former-commit-id: fcacc6248e59054b2202df55a4a0590fb36f5d4e
2016-11-08 19:33:22 +07:00
GitLab Build Bot
6e51c88caa [ci skip] js-precompiled 20161108-105355
Former-commit-id: fad795f8723b49ad92d641843b743a6041cf09e7
2016-11-08 10:55:06 +00:00
GitLab Build Bot
fbb05d0079 [ci skip] js-precompiled 20161108-102316
Former-commit-id: da39de40220c963f9647034a48f9d0725e941501
2016-11-08 10:24:34 +00:00
arkpar
551cae9a94 Updated signing path
Former-commit-id: 13dc5715ad01d10d1a1f355a11f5317c656fd6ef
2016-11-08 11:05:51 +01:00
arkpar
1a189912f0 Updated signing path
Former-commit-id: 1d1443e32331332a873a92f4a61a9ce929097fb2
2016-11-08 11:04:53 +01:00
GitLab Build Bot
21bfeb7d3c [ci skip] js-precompiled 20161108-094358
Former-commit-id: 5f5863abdc97bc7f20452c0da43ac8b97ec58199
2016-11-08 09:45:08 +00:00
arkpar
8d9902fd4c Fixed command
Former-commit-id: dae5e4d95ef1412025fa81fffbcdbc9a4d939b53
2016-11-08 10:22:48 +01:00
arkpar
594debb00e Build tray app for x64
Former-commit-id: 9042677ec16e7c949dd554cdcad0c61766ab06b5
2016-11-08 10:22:40 +01:00
arkpar
23d16db952 Removed junk files
Former-commit-id: 5102775fe230e63900d68532b61ce4d1391018f7
2016-11-08 10:22:11 +01:00
GitLab Build Bot
cd57b362fa [ci skip] js-precompiled 20161108-074441
Former-commit-id: 0f0a61ff0fe425f1a15ec2e0338526ce9b04a278
2016-11-08 07:45:51 +00:00
GitLab Build Bot
5e6a0b4660 js-precompiled 20161107-193144
Former-commit-id: 170bd035f1099aa5e32c8f9abbb33b944697643a
2016-11-07 21:37:03 +01:00
Arkadiy Paronyan
73f94c33f7 Add secure flag back (#3246)
* Add secure flag back

* Don't run js-release for tags


Former-commit-id: e61c6c4af3c7720a74bd4093b2fa5271c389236c
2016-11-07 20:08:24 +01:00
arkpar
e73f6573d1 Removed junk files
Former-commit-id: 6c5c8eff7ce635f860723ab226d0e93480253387
2016-11-07 19:39:20 +01:00
Robert Habermeier
a19e5f5628 verify chunk hashes in cli restore (#3242)
Former-commit-id: 0afe25bef862202d74513cdb81afae1e91023d07
2016-11-07 19:34:18 +01:00
GitLab Build Bot
cfdc0d8cfb [ci skip] js-precompiled 20161107-183039
Former-commit-id: 35545a02bbdb877d419652432eab72a50b544737
2016-11-07 18:31:50 +00:00
Arkadiy Paronyan
0d20b21ee8 Backporting to beta (#3239)
* Fixed some typos (#3236)

* Add store for dapps state (#3211)

* Add mobx

* Use mobx store for dapps

* Cleanup hidden reads

* Remove (now) unused hidden.js

* _ denotes internal functions

* s/visibleApps/visible/

* AddDapps now use the mobx store as well

* Move modalOpen state to store

* Simplify

* Complete master merge

* Remove extra indirection

* Remove unneeded check

* Readability improvements

* Remove final debug info

* Load network apps manifests as contentHash (no coding) (#3235)

* Add mobx

* Use mobx store for dapps

* Cleanup hidden reads

* Remove (now) unused hidden.js

* _ denotes internal functions

* s/visibleApps/visible/

* AddDapps now use the mobx store as well

* Move modalOpen state to store

* Simplify

* Complete master merge

* Remove extra indirection

* Remove unneeded check

* Readability improvements

* Remove final debug info

* Load network manifests from the network

* Swallow manifest errors

* introduce fetchManifest

* Rename cli and config options signer->ui (#3232)

* rename options signer->ui

* rename config signer->ui


Former-commit-id: f4c4934831f80f9217ff4e6afcaa95cdd41caf45
2016-11-07 18:25:58 +01:00
Arkadiy Paronyan
2e9cde38e4 UI fixes backporting (#3234)
* Fix /parity-utils/{web3,parity}.js webpack errors (#3221)

* Don't override library builds

* Don't override libraries on build

* Enhance working blockNumber display

* Don't push empty tags to input (#3222)


Former-commit-id: d3e51d21c8fc1b7cf9105e28c4719bf3ecba14e0
2016-11-07 17:41:55 +01:00
Denis S. Soldatov aka General-Beck
b88823b51f Update gitlab-ci
remove armv6 from beta, tags
remove i686 from tags add to beta
[ci skip]

Former-commit-id: a90746a0b907703930cc059070d090f386cf3cc9
2016-11-07 23:20:06 +07:00
GitLab Build Bot
1cdb17cd24 [ci skip] js-precompiled 20161107-161215
Former-commit-id: 75ab8de321ee9553574f170352c408a45cd2cfb7
2016-11-07 16:13:21 +00:00
Denis S. Soldatov aka General-Beck
8f89235a25 Update gitlab-ci
remove $NIGHTLY from beta branch

Former-commit-id: 7c403d6c9f50e546c62507a11390c35298c61cda
2016-11-07 22:52:36 +07:00
Denis S. Soldatov aka General-Beck
3df2e3358c Update Dockerfile
add make

Former-commit-id: 368aab19954e110c8d249bfca7787c015abfe25b
2016-11-07 21:45:22 +07:00
Denis S. Soldatov aka General-Beck
78c04856e8 Update Docker file
add make

Former-commit-id: 0c8d5104f80d4e27b292c9c2ce5a9a7b2d3562a9
2016-11-07 21:44:37 +07:00
Denis S. Soldatov aka General-Beck
e49ba9d0ae Update gitlab-ci
cache optimization

Former-commit-id: 372464f556fe12bbf28fa6e0422b8e4ea64c2db5
2016-11-07 20:49:00 +07:00
Arkadiy Paronyan
043ca21863 Backporting to beta (#3229)
* Use ethcore_dappsPort when constructing URLs (#3139)

* Upon connect, retrieve the available api ports

* Update dapps to load from dappsPort

* Update dapps summary with dappsPort

* Allow proxy to use dappsPort

* Replace /api/ping with HEAD /

* Dynamic port for available apps

* Retrieve content images with dappsPort

* Fix /

* Transfer token dropdown image fix

* IdentityIcon loads images via contentHash

* Update apps fetch to cater for dev & prod

* DRY up 127.0.0.1:${dappsPort} with ${dappsUrl}

* Cleaning up polluted namespaces (#3143)

* Renaming ethcore_ to parity_

* Renaming files

* Renaming poluted EthSigning

* Tidy up the namespaces

* Renaming files to match new structure

* Splitting EthSigning into separate traits

* jsapi move ethcore.* -> parity.*

* Move jsonrpc parity definitions

* Update UI API calls for parity interfaces

* Move jsapi signer interfaces from personal to signer

* Update UI to use signer.* where applicable

* Updsate jsapi subscriptions for signer

* Fix dodgy merge.

* Update README.

* Fix some tests.

* Move parity-only personal.* to parity.*

* Update UI for personal -> parity API moves

* Update subscription APIs after personal -> parity move

* personal. generateAuthorizationToken -> parity. generateAuthorizationToken (UI)

* enode, dappsPort & signerPort (UI)

* Update subscription tests (accountsInfo)

* subscription update

* personal -> parity

* Additional error logging on method failures

* move postTransaction to parity

* Additional debug info with method failures

* Fix personal tests.

* Console wrning shows parameters, error object does not

* Include parity_ signing methods.

* Console log http transport info

* Fix failing tests

* Add RPC stubs for parity_accounts.

* Allow some secure built-in dapps

* Use parity_accounts in place of accountsInfo

* Improve error reporting

* Cleanup GHH error handling


Former-commit-id: 5a094ccb9f0596d0e07abc23504b80dc099ad584
2016-11-07 14:46:41 +01:00
Denis S. Soldatov aka General-Beck
aea995cf55 Update gitlab-ci
skip i686
skip windows and darwin test's

Former-commit-id: 441657dc5f4c9b9ebf2a12af9a2be008d44dac40
2016-11-07 20:45:16 +07:00
Denis S. Soldatov aka General-Beck
0799dbf95f Update gitlab-ci
add i686

Former-commit-id: 3333433b17828b3bb6273cfecd654c3ea5dd8984
2016-11-07 20:06:19 +07:00
GitLab Build Bot
054f0c0014 [ci skip] js-precompiled 20161107-111138
Former-commit-id: 3b480f5e8c9fd7f24afb41e114f9346809790d98
2016-11-07 11:12:47 +00:00
Administrator
47729ae199 Update .gitlab-ci.yml
Former-commit-id: 5c77f1eedeec1e62df80895e40e354aeed77ecb0
2016-11-07 12:00:01 +01:00
Arkadiy Paronyan
e086b4e94a Beta branch cleanup (#3226)
* Removed binaries

* Version string set to beta


Former-commit-id: 2a650287ceca214b23caba603571d2ed93513656
2016-11-07 11:54:23 +01:00
GitLab Build Bot
fbe02fc6b6 [ci skip] js-precompiled 20161105-151003
Former-commit-id: 15ee147168b274fe9c23385fede541d20e42a24e
2016-11-05 15:11:10 +00:00
Arkadiy Paronyan
f978b3e833 Set passive mode for first run only (#3214) (#3216)
Former-commit-id: 17f57c6b70bb2ec2bfbbb0f6721057ee4a5e349a
2016-11-05 15:51:14 +01:00
GitLab Build Bot
20d7f8a9d9 [ci skip] js-precompiled 20161105-135925
Former-commit-id: 4f44a021f7f1461dc699bc4a482c8dd1685be645
2016-11-05 14:01:16 +00:00
Arkadiy Paronyan
e727d92e0f Mode configuration backported to beta (#3213)
* Mode improvements for UI (#3109)

* `--mode=off` now works.

* Add Mode::Off as a persistent CLI option.

* "last" not "auto" as default.

* Commit accidentally unsaved file.

* Whitespace

[ci:skip]

* Mode CLI parse fix

* or offline

* Save mode when it gets changed.

* Fix Offline mode

* Fix up mode operations.

* Make passive default, but not overriding.

* Fix test

* Maybe not everyone wants to run an archive node...

* Parity configuration settings, i.e. mode (#3212)

* Add initial page

* Add parity icon

* opacity for parity icon

* Mode selector

* Actually set mode when value changes


Former-commit-id: 64386d94adfa58aa30bab2005eeb64f6343c8248
2016-11-05 14:37:45 +01:00
GitLab Build Bot
8b5a9b701a [ci skip] js-precompiled 20161104-181908
Former-commit-id: 074d230fa4c3faed52aa8c75a5bbd8d4720cad4a
2016-11-04 18:20:13 +00:00
Arkadiy Paronyan
33cc10549a Backporting (#3198)
* sendRawTransaction invalid RLP error

* Returning proper error for estimate_gas

* Exposing engine extra info in block RPC (#3169)

* Exposing extra info in RPC

* Proper serialization and client trait API

* Fixing possible race condition in ethcore_hashContent (#3191)

* Remove dapp logos (GHH points to dapp-assets) (#3192)
2016-11-04 18:59:49 +01:00
GitLab Build Bot
522401296d [ci skip] js-precompiled 20161104-162352 2016-11-04 16:25:02 +00:00
Arkadiy Paronyan
a3261d1428 EIP-155 update with Vitalik's new test vectors (#3166) (#3189)
* Vitalik's new test vectors.

* Update to latest EIP155 spec.

* Fix txs.

* Another fix.
2016-11-04 16:38:10 +01:00
Denis S. Soldatov aka General-Beck
250736a085 Update gitlab-ci
[ci skip]
skip linux-armv6 in beta, tags, stable
2016-11-04 20:59:14 +07:00
GitLab Build Bot
ce50286138 [ci skip] js-precompiled 20161104-132825 2016-11-04 13:29:29 +00:00
Arkadiy Paronyan
19d0905cbd Backporting to beta (#3176)
* Linux build case sensitivity fix (#3161)

* Make the footer a bit less ugly. (#3160)

* Make the footer a bit less ugly.

* Whitespace.

* Enabling personal RPC over IPC by default (#3165)

* Push precompiled for beta/stable, npm only master (#3163)

* Push precompiled for beta/stable, npm only master

* Release to npm before comitting updates

* Fixes #3171 (#3175)

* Remove extra cd js (#3177)

* Back to real root (#3178)
2016-11-04 14:12:15 +01:00
GitLab Build Bot
11f2b5d0ae [ci skip] js-precompiled 20161104-130524 2016-11-04 13:06:27 +00:00
GitLab Build Bot
8bd89d05be [ci skip] js-precompiled 20161104-121340 2016-11-04 12:14:46 +00:00
Arkadiy Paronyan
2b5d82c901 parity-ui-precompiled pinned to beta (#3168)
* parity-ui-precompiled pinned to beta

* Fixed test
2016-11-04 12:38:18 +01:00
GitLab Build Bot
ec99be1b28 [ci skip] js-precompiled 20161104-104904 2016-11-04 10:50:35 +00:00
Denis S. Soldatov aka General-Beck
52ce4f9bc1 Update gitlab-ci
remove darwin & winfows test
add $NIGHTLY ENV for future nightly builds
2016-11-04 17:11:25 +07:00
Denis S. Soldatov aka General-Beck
9e9157b1bd Update gitlab-ci
[ci skip]
fix for JS_FILES_MODIFIED master -> beta
2016-11-04 16:48:08 +07:00
Denis S. Soldatov aka General-Beck
576c9e7801 Update gitlab-ci
stages:
  - test
  - js-build
  - build
2016-11-04 16:17:16 +07:00
Denis S. Soldatov aka General-Beck
5d83c60b75 Update gitlab-ci
backport gitlab-ci.yml from master
2016-11-04 16:07:23 +07:00
2824 changed files with 58644 additions and 227428 deletions

2
.cargo/config Normal file
View File

@@ -0,0 +1,2 @@
[target.armv7-unknown-linux-gnueabihf]
linker= "arm-linux-gnueabihf-gcc"

View File

@@ -1,22 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
target
*.swp
*.swo
*.swn
*.DS_Store
# Visual Studio Code stuff
.vscode
# GitEye stuff
.project
# idea ide
.idea
# git stuff
.git
ethcore/res/ethereum/tests

6
.gitignore vendored
View File

@@ -19,10 +19,6 @@
# mac stuff
.DS_Store
# npm stuff
npm-debug.log
node_modules
# gdb files
.gdb_history
@@ -36,5 +32,3 @@ node_modules
out/
.vscode
/parity.*

View File

@@ -1,7 +1,6 @@
stages:
- test
- js-build
- push-release
- build
variables:
GIT_DEPTH: "3"
@@ -9,148 +8,47 @@ variables:
RUST_BACKTRACE: "1"
RUSTFLAGS: ""
CARGOFLAGS: ""
CI_SERVER_NAME: "GitLab CI"
cache:
key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
untracked: true
linux-stable:
stage: build
image: parity/rust:gitlab-ci
image: ethcore/rust:stable
only:
- beta
- tags
- stable
- triggers
script:
- rustup default stable
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
- cargo build -j $(nproc) --release -p evmbin
- cargo build -j $(nproc) --release -p ethstore-cli
- cargo build -j $(nproc) --release -p ethkey-cli
- cargo build --release $CARGOFLAGS
- strip target/release/parity
- strip target/release/parity-evm
- strip target/release/ethstore
- strip target/release/ethkey
- export SHA3=$(target/release/parity tools hash target/release/parity)
- md5sum target/release/parity > parity.md5
- sh scripts/deb-build.sh amd64
- cp target/release/parity deb/usr/bin/parity
- cp target/release/parity-evm deb/usr/bin/parity-evm
- cp target/release/ethstore deb/usr/bin/ethstore
- cp target/release/ethkey deb/usr/bin/ethkey
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity --body target/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
tags:
- rust
- rust-stable
artifacts:
paths:
- target/release/parity
- target/release/parity-evm
- target/release/ethstore
- target/release/ethkey
name: "stable-x86_64-unknown-linux-gnu_parity"
linux-snap:
stage: build
image: parity/snapcraft:gitlab-ci
only:
- snap
- beta
- tags
- triggers
script:
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- cd snap
- rm -rf *snap
- sed -i 's/master/'"$VER"'/g' snapcraft.yaml
- echo "Version:"$VER
- snapcraft
- ls
#- cp "parity_"$CI_BUILD"_amd64.snap" "parity_"$VER"_amd64.snap"
- md5sum "parity_"$VER"_amd64.snap" > "parity_"$VER"_amd64.snap.md5"
- file "parity_"$VER"_amd64.snap.md5"
- cat "parity_"$VER"_amd64.snap.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.snap" --body "parity_"$VER"_amd64.snap"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu/"parity_"$VER"_amd64.snap.md5" --body "parity_"$VER"_amd64.snap.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-linux-gnu
tags:
- rust
- rust-stable
artifacts:
paths:
- scripts/parity_*_amd64.snap
name: "stable-x86_64-unknown-snap-gnu_parity"
allow_failure: true
linux-stable-debian:
stage: build
image: parity/rust-debian:gitlab-ci
only:
- beta
- tags
- stable
- triggers
script:
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
- cargo build -j $(nproc) --release -p evmbin
- cargo build -j $(nproc) --release -p ethstore-cli
- cargo build -j $(nproc) --release -p ethkey-cli
- strip target/release/parity
- strip target/release/parity-evm
- strip target/release/ethstore
- strip target/release/ethkey
- export SHA3=$(target/release/parity tools hash target/release/parity)
- md5sum target/release/parity > parity.md5
- sh scripts/deb-build.sh amd64
- cp target/release/parity deb/usr/bin/parity
- cp target/release/parity-evm deb/usr/bin/parity-evm
- cp target/release/ethstore deb/usr/bin/ethstore
- cp target/release/ethkey deb/usr/bin/ethkey
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_amd64.deb"
- md5sum "parity_"$VER"_amd64.deb" > "parity_"$VER"_amd64.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/parity --body target/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/"parity_"$VER"_amd64.deb" --body "parity_"$VER"_amd64.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu/"parity_"$VER"_amd64.deb.md5" --body "parity_"$VER"_amd64.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/x86_64-unknown-debian-gnu
tags:
- rust
- rust-debian
artifacts:
paths:
- target/release/parity
name: "stable-x86_64-unknown-debian-gnu_parity"
linux-beta:
stage: build
image: parity/rust:gitlab-ci
image: ethcore/rust:beta
only:
- beta
- tags
- stable
- triggers
script:
- rustup default beta
- cargo build -j $(nproc) --release $CARGOFLAGS
- cargo build --release $CARGOFLAGS
- strip target/release/parity
tags:
- rust
@@ -162,15 +60,13 @@ linux-beta:
allow_failure: true
linux-nightly:
stage: build
image: parity/rust:gitlab-ci
image: ethcore/rust:nightly
only:
- beta
- tags
- stable
- triggers
script:
- rustup default nightly
- cargo build -j $(nproc) --release $CARGOFLAGS
- cargo build --release $CARGOFLAGS
- strip target/release/parity
tags:
- rust
@@ -182,43 +78,21 @@ linux-nightly:
allow_failure: true
linux-centos:
stage: build
image: parity/rust-centos:gitlab-ci
image: ethcore/rust-centos:latest
only:
- beta
- tags
- stable
- triggers
script:
- export CXX="g++"
- export CC="gcc"
- export PLATFORM=x86_64-unknown-centos-gnu
- cargo build -j $(nproc) --release --features final $CARGOFLAGS
- cargo build -j $(nproc) --release -p evmbin
- cargo build -j $(nproc) --release -p ethstore-cli
- cargo build -j $(nproc) --release -p ethkey-cli
- cargo build --release $CARGOFLAGS
- strip target/release/parity
- strip target/release/parity-evm
- strip target/release/ethstore
- strip target/release/ethkey
- md5sum target/release/parity > parity.md5
- md5sum target/release/parity-evm > parity-evm.md5
- md5sum target/release/ethstore > ethstore.md5
- md5sum target/release/ethkey > ethkey.md5
- export SHA3=$(target/release/parity tools hash target/release/parity)
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity --body target/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity-evm --body target/release/parity-evm
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/parity-evm.md5 --body parity-evm.md5
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/ethstore --body target/release/ethstore
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/ethstore.md5 --body ethstore.md5
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/ethkey --body target/release/ethkey
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-unknown-centos-gnu/ethkey.md5 --body ethkey.md5
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
tags:
- rust
- rust-centos
@@ -226,48 +100,31 @@ linux-centos:
paths:
- target/release/parity
name: "x86_64-unknown-centos-gnu_parity"
linux-i686:
stage: build
image: parity/rust-i686:gitlab-ci
allow_failure: true
linux-i686:
stage: build
image: ethcore/rust-i686:latest
only:
- beta
- tags
# - tags
- stable
- triggers
script:
- export HOST_CC=gcc
- export HOST_CXX=g++
- export COMMIT=$(git rev-parse HEAD)
- export PLATFORM=i686-unknown-linux-gnu
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- cargo build -j $(nproc) --target $PLATFORM --release -p evmbin
- cargo build -j $(nproc) --target $PLATFORM --release -p ethstore-cli
- cargo build -j $(nproc) --target $PLATFORM --release -p ethkey-cli
- strip target/$PLATFORM/release/parity
- strip target/$PLATFORM/release/parity-evm
- strip target/$PLATFORM/release/ethstore
- strip target/$PLATFORM/release/ethkey
- strip target/$PLATFORM/release/parity
- md5sum target/$PLATFORM/release/parity > parity.md5
- export SHA3=$(target/$PLATFORM/release/parity tools hash target/$PLATFORM/release/parity)
- cargo build --target i686-unknown-linux-gnu --release $CARGOFLAGS
- strip target/i686-unknown-linux-gnu/release/parity
- md5sum target/i686-unknown-linux-gnu/release/parity > parity.md5
- sh scripts/deb-build.sh i386
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
- cp target/$PLATFORM/release/parity-evm deb/usr/bin/parity-evm
- cp target/$PLATFORM/release/ethstore deb/usr/bin/ethstore
- cp target/$PLATFORM/release/ethkey deb/usr/bin/ethkey
- cp target/i686-unknown-linux-gnu/release/parity deb/usr/bin/parity
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_i386.deb"
- md5sum "parity_"$VER"_i386.deb" > "parity_"$VER"_i386.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity --body target/i686-unknown-linux-gnu/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb"
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5"
tags:
- rust
- rust-i686
@@ -278,54 +135,35 @@ linux-i686:
allow_failure: true
linux-armv7:
stage: build
image: parity/rust-armv7:gitlab-ci
image: ethcore/rust-armv7:latest
only:
- beta
- tags
- stable
- triggers
script:
- export CC=arm-linux-gnueabihf-gcc
- export CXX=arm-linux-gnueabihf-g++
- export HOST_CC=gcc
- export HOST_CXX=g++
- export PLATFORM=armv7-unknown-linux-gnueabihf
- rm -rf .cargo
- mkdir -p .cargo
- echo "[target.$PLATFORM]" >> .cargo/config
- echo "[target.armv7-unknown-linux-gnueabihf]" >> .cargo/config
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
- cat .cargo/config
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- cargo build -j $(nproc) --target $PLATFORM --release -p evmbin
- cargo build -j $(nproc) --target $PLATFORM --release -p ethstore-cli
- cargo build -j $(nproc) --target $PLATFORM --release -p ethkey-cli
- md5sum target/$PLATFORM/release/parity > parity.md5
- export SHA3=$(target/$PLATFORM/release/parity tools hash target/$PLATFORM/release/parity)
- sh scripts/deb-build.sh i386
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity-evm
- arm-linux-gnueabihf-strip target/$PLATFORM/release/ethstore
- arm-linux-gnueabihf-strip target/$PLATFORM/release/ethkey
- export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h)
- md5sum target/$PLATFORM/release/parity > parity.md5
- cargo build --target armv7-unknown-linux-gnueabihf --release $CARGOFLAGS
- arm-linux-gnueabihf-strip target/armv7-unknown-linux-gnueabihf/release/parity
- md5sum target/armv7-unknown-linux-gnueabihf/release/parity > parity.md5
- sh scripts/deb-build.sh armhf
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
- cp target/$PLATFORM/release/parity-evm deb/usr/bin/parity-evm
- cp target/$PLATFORM/release/ethstore deb/usr/bin/ethstore
- cp target/$PLATFORM/release/ethkey deb/usr/bin/ethkey
- cp target/armv7-unknown-linux-gnueabihf/release/parity deb/usr/bin/parity
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_armhf.deb"
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity --body target/armv7-unknown-linux-gnueabihf/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/parity.md5 --body parity.md5
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/armv7-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
tags:
- rust
- rust-arm
@@ -336,51 +174,35 @@ linux-armv7:
allow_failure: true
linux-arm:
stage: build
image: parity/rust-arm:gitlab-ci
image: ethcore/rust-arm:latest
only:
- beta
- tags
- stable
- triggers
script:
- export CC=arm-linux-gnueabihf-gcc
- export CXX=arm-linux-gnueabihf-g++
- export HOST_CC=gcc
- export HOST_CXX=g++
- export PLATFORM=arm-unknown-linux-gnueabihf
- rm -rf .cargo
- mkdir -p .cargo
- echo "[target.$PLATFORM]" >> .cargo/config
- echo "[target.arm-unknown-linux-gnueabihf]" >> .cargo/config
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
- cat .cargo/config
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- cargo build -j $(nproc) --target $PLATFORM --release -p evmbin
- cargo build -j $(nproc) --target $PLATFORM --release -p ethstore-cli
- cargo build -j $(nproc) --target $PLATFORM --release -p ethkey-cli
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity
- arm-linux-gnueabihf-strip target/$PLATFORM/release/parity-evm
- arm-linux-gnueabihf-strip target/$PLATFORM/release/ethstore
- arm-linux-gnueabihf-strip target/$PLATFORM/release/ethkey
- export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h)
- md5sum target/$PLATFORM/release/parity > parity.md5
- cargo build --target arm-unknown-linux-gnueabihf --release $CARGOFLAGS
- arm-linux-gnueabihf-strip target/arm-unknown-linux-gnueabihf/release/parity
- md5sum target/arm-unknown-linux-gnueabihf/release/parity > parity.md5
- sh scripts/deb-build.sh armhf
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
- cp target/$PLATFORM/release/parity-evm deb/usr/bin/parity-evm
- cp target/$PLATFORM/release/ethstore deb/usr/bin/ethstore
- cp target/$PLATFORM/release/ethkey deb/usr/bin/ethkey
- cp target/arm-unknown-linux-gnueabihf/release/parity deb/usr/bin/parity
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_armhf.deb"
- md5sum "parity_"$VER"_armhf.deb" > "parity_"$VER"_armhf.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/$PLATFORM/release/parity
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity --body target/arm-unknown-linux-gnueabihf/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/parity.md5 --body parity.md5
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb" --body "parity_"$VER"_armhf.deb"
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabihf/"parity_"$VER"_armhf.deb.md5" --body "parity_"$VER"_armhf.deb.md5"
tags:
- rust
- rust-arm
@@ -389,52 +211,69 @@ linux-arm:
- target/arm-unknown-linux-gnueabihf/release/parity
name: "arm-unknown-linux-gnueabihf_parity"
allow_failure: true
linux-armv6:
stage: build
image: ethcore/rust-armv6:latest
only:
# - beta
# - tags
- stable
script:
- export CC=arm-linux-gnueabi-gcc
- export CXX=arm-linux-gnueabi-g++
- export HOST_CC=gcc
- export HOST_CXX=g++
- rm -rf .cargo
- mkdir -p .cargo
- echo "[target.arm-unknown-linux-gnueabi]" >> .cargo/config
- echo "linker= \"arm-linux-gnueabi-gcc\"" >> .cargo/config
- cat .cargo/config
- cargo build --target arm-unknown-linux-gnueabi --release $CARGOFLAGS
- arm-linux-gnueabi-strip target/arm-unknown-linux-gnueabi/release/parity
- md5sum target/arm-unknown-linux-gnueabi/release/parity > parity.md5
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity --body target/arm-unknown-linux-gnueabi/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/arm-unknown-linux-gnueabi/parity.md5 --body parity.md5
tags:
- rust
- rust-arm
artifacts:
paths:
- target/arm-unknown-linux-gnueabi/release/parity
name: "arm-unknown-linux-gnueabi_parity"
allow_failure: true
linux-aarch64:
stage: build
image: parity/rust-arm64:gitlab-ci
image: ethcore/rust-aarch64:latest
only:
- beta
- tags
- stable
- triggers
script:
- export CC=aarch64-linux-gnu-gcc
- export CXX=aarch64-linux-gnu-g++
- export HOST_CC=gcc
- export HOST_CXX=g++
- export PLATFORM=aarch64-unknown-linux-gnu
- rm -rf .cargo
- mkdir -p .cargo
- echo "[target.$PLATFORM]" >> .cargo/config
- echo "[target.aarch64-unknown-linux-gnu]" >> .cargo/config
- echo "linker= \"aarch64-linux-gnu-gcc\"" >> .cargo/config
- cat .cargo/config
- cargo build -j $(nproc) --target $PLATFORM --features final --release $CARGOFLAGS
- cargo build -j $(nproc) --target $PLATFORM --release -p evmbin
- cargo build -j $(nproc) --target $PLATFORM --release -p ethstore-cli
- cargo build -j $(nproc) --target $PLATFORM --release -p ethkey-cli
- aarch64-linux-gnu-strip target/$PLATFORM/release/parity
- aarch64-linux-gnu-strip target/$PLATFORM/release/parity-evm
- aarch64-linux-gnu-strip target/$PLATFORM/release/ethstore
- aarch64-linux-gnu-strip target/$PLATFORM/release/ethkey
- export SHA3=$(rhash --sha3-256 target/$PLATFORM/release/parity -p %h)
- md5sum target/$PLATFORM/release/parity > parity.md5
- cargo build --target aarch64-unknown-linux-gnu --release $CARGOFLAGS
- aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/parity
- md5sum target/aarch64-unknown-linux-gnu/release/parity > parity.md5
- sh scripts/deb-build.sh arm64
- cp target/$PLATFORM/release/parity deb/usr/bin/parity
- cp target/$PLATFORM/release/parity-evm deb/usr/bin/parity-evm
- cp target/$PLATFORM/release/ethstore deb/usr/bin/ethstore
- cp target/$PLATFORM/release/ethkey deb/usr/bin/ethkey
- cp target/aarch64-unknown-linux-gnu/release/parity deb/usr/bin/parity
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
- dpkg-deb -b deb "parity_"$VER"_arm64.deb"
- md5sum "parity_"$VER"_arm64.deb" > "parity_"$VER"_arm64.deb.md5"
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
- aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb"
- aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5"
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity --body target/aarch64-unknown-linux-gnu/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/parity.md5 --body parity.md5
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb" --body "parity_"$VER"_arm64.deb"
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/aarch64-unknown-linux-gnu/"parity_"$VER"_arm64.deb.md5" --body "parity_"$VER"_arm64.deb.md5"
tags:
- rust
- rust-arm
@@ -449,37 +288,20 @@ darwin:
- beta
- tags
- stable
- triggers
script: |
export COMMIT=$(git rev-parse HEAD)
export PLATFORM=x86_64-apple-darwin
rustup default stable
cargo clean
cargo build -j 8 --features final --release #$CARGOFLAGS
cargo build -j 8 --release -p ethstore-cli #$CARGOFLAGS
cargo build -j 8 --release -p ethkey-cli #$CARGOFLAGS
cargo build -j 8 --release -p evmbin #$CARGOFLAGS
rm -rf parity.md5
md5sum target/release/parity > parity.md5
export SHA3=$(target/release/parity tools hash target/release/parity)
cd mac
xcodebuild -configuration Release
cd ..
packagesbuild -v mac/Parity.pkgproj
productsign --sign 'Developer ID Installer: PARITY TECHNOLOGIES LIMITED (P2PX3JU8FT)' target/release/Parity\ Ethereum.pkg target/release/Parity\ Ethereum-signed.pkg
export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
mv target/release/Parity\ Ethereum-signed.pkg "parity-"$VER"-macos-installer.pkg"
md5sum "parity-"$VER"-macos-installer.pkg" >> "parity-"$VER"-macos-installer.pkg.md5"
aws configure set aws_access_key_id $s3_key
aws configure set aws_secret_access_key $s3_secret
if [[ $CI_BUILD_REF_NAME =~ ^(master|beta|stable|nightly)$ ]]; then export S3_BUCKET=builds-parity-published; else export S3_BUCKET=builds-parity; fi
aws s3 rm --recursive s3://$S3_BUCKET/$CI_BUILD_REF_NAME/$PLATFORM
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity --body target/release/parity
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/parity.md5 --body parity.md5
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-macos-installer.pkg" --body "parity-"$VER"-macos-installer.pkg"
aws s3api put-object --bucket $S3_BUCKET --key $CI_BUILD_REF_NAME/$PLATFORM/"parity-"$VER"-macos-installer.pkg.md5" --body "parity-"$VER"-macos-installer.pkg.md5"
curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1337/push-build/$CI_BUILD_REF_NAME/$PLATFORM
curl --data "commit=$CI_BUILD_REF&sha3=$SHA3&filename=parity&secret=$RELEASES_SECRET" http://update.parity.io:1338/push-build/$CI_BUILD_REF_NAME/$PLATFORM
script:
- cargo build --release -p ethstore $CARGOFLAGS
- cargo build --release $CARGOFLAGS
- rm -rf parity.md5
- md5sum target/release/parity > parity.md5
- packagesbuild -v mac/Parity.pkgproj
- mv target/release/Parity\ Ethereum.pkg parity-osx-installer-EXPERIMENTAL.pkg
- md5sum parity-osx-installer-EXPERIMENTAL.pkg > parity-osx-installer-EXPERIMENTAL.pkg.md5
- aws configure set aws_access_key_id $s3_key
- aws configure set aws_secret_access_key $s3_secret
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity --body target/release/parity
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity.md5 --body parity.md5
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity-osx-installer-EXPERIMENTAL.pkg --body parity-osx-installer-EXPERIMENTAL.pkg
- aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/x86_64-apple-darwin/parity-osx-installer-EXPERIMENTAL.pkg.md5 --body parity-osx-installer-EXPERIMENTAL.pkg.md5
tags:
- osx
artifacts:
@@ -495,24 +317,16 @@ windows:
- beta
- tags
- stable
- triggers
script:
- set PLATFORM=x86_64-pc-windows-msvc
- set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;C:\vs2015\VC\include;C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt
- set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64
- set RUST_BACKTRACE=1
- set RUSTFLAGS=%RUSTFLAGS%
- set RUSTFLAGS=%RUSTFLAGS% -Zorbit=off
- rustup default stable-x86_64-pc-windows-msvc
- cargo clean
- cargo build --features final --release #%CARGOFLAGS%
- cargo build --release -p ethstore-cli #%CARGOFLAGS%
- cargo build --release -p ethkey-cli #%CARGOFLAGS%
- cargo build --release -p evmbin #%CARGOFLAGS%
- cargo build --release %CARGOFLAGS%
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll
- curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe
- signtool sign /f %keyfile% /p %certpass% target\release\parity.exe
- target\release\parity.exe tools hash target\release\parity.exe > parity.sha3
- set /P SHA3=<parity.sha3
- curl -sL --url "https://github.com/paritytech/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll
- curl -sL --url "https://github.com/paritytech/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x64 /p:Configuration=Release
- signtool sign /f %keyfile% /p %certpass% windows\ptray\x64\release\ptray.exe
- cd nsis
@@ -523,29 +337,21 @@ windows:
- zip win-installer.zip InstallParity.exe InstallParity.exe.md5
- md5sums win-installer.zip > win-installer.zip.md5
- cd ..\target\release\
- md5sums parity.exe parity.pdb > parity.md5
- md5sums parity.exe > parity.exe.md5
- zip parity.zip parity.exe parity.md5
- zip parity.zip parity.exe parity.pdb parity.md5
- md5sums parity.zip > parity.zip.md5
- cd ..\..
- aws configure set aws_access_key_id %s3_key%
- aws configure set aws_secret_access_key %s3_secret%
- echo %CI_BUILD_REF_NAME%
- echo %CI_BUILD_REF_NAME% | findstr /R "master" >nul 2>&1 && set S3_BUCKET=builds-parity-published|| set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "beta" >nul 2>&1 && set S3_BUCKET=builds-parity-published|| set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "stable" >nul 2>&1 && set S3_BUCKET=builds-parity-published|| set S3_BUCKET=builds-parity
- echo %CI_BUILD_REF_NAME% | findstr /R "nightly" >nul 2>&1 && set S3_BUCKET=builds-parity-published|| set S3_BUCKET=builds-parity
- echo %S3_BUCKET%
- aws s3 rm --recursive s3://%S3_BUCKET%/%CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe --body target\release\parity.exe
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe.md5 --body target\release\parity.exe.md5
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip --body target\release\parity.zip
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip.md5 --body target\release\parity.zip.md5
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe --body nsis\InstallParity.exe
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip
- aws s3api put-object --bucket %S3_BUCKET% --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1337/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
- curl --data "commit=%CI_BUILD_REF%&sha3=%SHA3%&filename=parity.exe&secret=%RELEASES_SECRET%" http://update.parity.io:1338/push-build/%CI_BUILD_REF_NAME%/%PLATFORM%
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe --body target\release\parity.exe
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.exe.md5 --body target\release\parity.exe.md5
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip --body target\release\parity.zip
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/parity.zip.md5 --body target\release\parity.zip.md5
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe --body nsis\InstallParity.exe
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/InstallParity.exe.md5 --body nsis\InstallParity.exe.md5
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip --body nsis\win-installer.zip
- aws s3api put-object --bucket builds-parity --key %CI_BUILD_REF_NAME%/x86_64-pc-windows-msvc/win-installer.zip.md5 --body nsis\win-installer.zip.md5
tags:
- rust-windows
artifacts:
@@ -554,147 +360,70 @@ windows:
- target/release/parity.pdb
- nsis/InstallParity.exe
name: "x86_64-pc-windows-msvc_parity"
docker-build:
stage: build
only:
- tags
- triggers
before_script:
- docker info
script:
- if [ "$CI_BUILD_REF_NAME" == "beta-release" ]; then DOCKER_TAG="latest"; else DOCKER_TAG=$CI_BUILD_REF_NAME; fi
- echo "Tag:" $DOCKER_TAG
- docker login -u $Docker_Hub_User_Parity -p $Docker_Hub_Pass_Parity
- sh scripts/docker-build.sh $DOCKER_TAG
- docker logout
tags:
- docker
test-coverage:
stage: test
only:
- master
script:
- git submodule update --init --recursive
- rm -rf target/*
- rm -rf js/.coverage
- scripts/cov.sh
# - COVERAGE=$(grep -Po 'covered":.*?[^\\]"' target/cov/index.json | grep "[0-9]*\.[0-9]" -o)
# - echo "Coverage:" $COVERAGE
tags:
- kcov
allow_failure: true
test-darwin:
stage: test
only:
- triggers
# - beta
# - tags
- stable
before_script:
- git submodule update --init --recursive
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e "^js/" -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
script:
- export RUST_BACKTRACE=1
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
- ./test.sh $CARGOFLAGS --no-release
tags:
- osx
allow_failure: true
test-windows:
stage: test
only:
- triggers
# - beta
# - tags
- stable
before_script:
- git submodule update --init --recursive
script:
- set RUST_BACKTRACE=1
- echo cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p parity-dapps -p parity-rpc -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity-rpc-client -p parity %CARGOFLAGS% --verbose --release
- cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release
tags:
- rust-windows
allow_failure: true
test-rust-stable:
stage: test
image: parity/rust:gitlab-ci
image: ethcore/rust:stable
before_script:
- git submodule update --init --recursive
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF $CI_BUILD_REF@{1} | grep \.js | wc -l)
- echo $JS_FILES_MODIFIED
- if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; fi
script:
- rustup show
- export RUST_BACKTRACE=1
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
- echo $JS_FILES_MODIFIED
- if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; else ./test.sh $CARGOFLAGS --no-release; fi
tags:
- rust
- rust-stable
js-test:
js-tests:
stage: test
image: parity/rust:gitlab-ci
image: ethcore/rust:stable
before_script:
- git submodule update --init --recursive
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
- ./js/scripts/install-deps.sh
script:
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS lint since no JS files modified."; else ./js/scripts/lint.sh && ./js/scripts/test.sh && ./js/scripts/build.sh; fi
- ./js/scripts/lint.sh
- ./js/scripts/test.sh
- ./js/scripts/build.sh
tags:
- rust
- rust-stable
test-rust-beta:
stage: test
only:
- triggers
- master
image: parity/rust:gitlab-ci
before_script:
- git submodule update --init --recursive
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
script:
- rustup default beta
- export RUST_BACKTRACE=1
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
tags:
- rust
- rust-beta
allow_failure: true
test-rust-nightly:
stage: test
only:
- triggers
- master
image: parity/rust:gitlab-ci
before_script:
- git submodule update --init --recursive
- export RUST_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep -v -e ^js -e ^\\. -e ^LICENSE -e ^README.md -e ^appveyor.yml -e ^test.sh -e ^windows/ -e ^scripts/ -e^mac/ -e ^nsis/ | wc -l)
script:
- rustup default nightly
- export RUST_BACKTRACE=1
- if [ $RUST_FILES_MODIFIED -eq 0 ]; then echo "Skipping Rust tests since no Rust files modified."; else ./test.sh $CARGOFLAGS; fi
tags:
- rust
- rust-nightly
allow_failure: true
- javascript-test
js-release:
stage: js-build
only:
- master
- beta
- stable
- tags
- triggers
image: parity/rust:gitlab-ci
image: ethcore/rust:stable
before_script:
- export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep ^js/ | wc -l)
- echo $JS_FILES_MODIFIED
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS deps install since no JS files modified."; else ./js/scripts/install-deps.sh;fi
- ./js/scripts/install-deps.sh
script:
- rustup default stable
- echo $JS_FILES_MODIFIED
- if [ $JS_FILES_MODIFIED -eq 0 ]; then echo "Skipping JS rebuild since no JS files modified."; else ./js/scripts/build.sh && ./js/scripts/release.sh; fi
- ./js/scripts/build.sh
- ./js/scripts/release.sh
tags:
- javascript
push-release:
stage: push-release
only:
- tags
- triggers
image: parity/rust:gitlab-ci
script:
- rustup default stable
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1338/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
tags:
- curl

3
.gitmodules vendored
View File

@@ -2,6 +2,3 @@
path = ethcore/res/ethereum/tests
url = https://github.com/ethereum/tests.git
branch = develop
[submodule "ethcore/res/wasm-tests"]
path = ethcore/res/wasm-tests
url = https://github.com/paritytech/wasm-tests

86
.travis.yml Normal file
View File

@@ -0,0 +1,86 @@
sudo: required
dist: trusty
language: rust
branches:
only:
- master
- /^beta-.*$/
- /^stable-.*$/
- /^beta$/
- /^stable$/
git:
depth: 3
matrix:
include:
- rust: stable
env: RUN_TESTS="true" TEST_OPTIONS="--no-release"
- rust: beta
env: RUN_COVERAGE="true"
- rust: stable
env: RUN_DOCS="true"
env:
global:
- CXX="g++-4.8"
- CC="gcc-4.8"
- RUST_BACKTRACE="1"
- RUN_TESTS="false"
- RUN_COVERAGE="false"
- RUN_DOCS="false"
- TEST_OPTIONS=""
- RUSTFLAGS="-D warnings"
- TRAVIS_NODE_VERSION="6"
# GH_TOKEN for documentation
- secure: bumJASbZSU8bxJ0EyPUJmu16AiV9EXOpyOj86Jlq/Ty9CfwGqsSXt96uDyE+OUJf34RUFQMsw0nk37/zC4lcn6kqk2wpuH3N/o85Zo/cVZY/NusBWLQqtT5VbYWsV+u2Ua4Tmmsw8yVYQhYwU2ZOejNpflL+Cs9XGgORp1L+/gMRMC2y5Se6ZhwnKPQlRJ8LGsG1dzjQULxzADIt3/zuspNBS8a2urJwlHfGMkvHDoUWCviP/GXoSqw3TZR7FmKyxE19I8n9+iSvm9+oZZquvcgfUxMHn8Gq/b44UbPvjtFOg2yam4xdWXF/RyWCHdc/R9EHorSABeCbefIsm+zcUF3/YQxwpSxM4IZEeH2rTiC7dcrsKw3XsO16xFQz5YI5Bay+CT/wTdMmJd7DdYz7Dyf+pOvcM9WOf/zorxYWSBOMYy0uzbusU2iyIghQ82s7E/Ahg+WARtPgkuTLSB5aL1oCTBKHqQscMr7lo5Ti6RpWLxEdTQMBznc+bMr+6dEtkEcG9zqc6cE9XX+ox3wTU6+HVMfQ1ltCntJ4UKcw3A6INEbw9wgocQa812CIASQ2fE+SCAbz6JxBjIAlFUnD1lUB7S8PdMPwn9plfQgKQ2A5YZqg6FnBdf0rQXIJYxQWKHXj/rBHSUCT0tHACDlzTA+EwWggvkP5AGIxRxm8jhw=
- KCOV_CMD="./kcov-master/tmp/usr/local/bin/kcov"
cache:
apt: true
directories:
- $TRAVIS_BUILD_DIR/target
- $TRAVIS_BUILD_DIR/kcov-master
- $TRAVIS_BUILD_DIR/js/node_modules
- $HOME/.cargo
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- libcurl4-openssl-dev
- libelf-dev
- libdw-dev
- gcc-4.8
- g++-4.8
install:
- ([ "$RUN_COVERAGE" = "false" ]) || (test -x $KCOV_CMD) || (
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
tar xzf master.tar.gz &&
mkdir -p kcov-master/build &&
cd kcov-master/build &&
cmake .. &&
make && make install DESTDIR=../tmp &&
cd
)
- nvm install $TRAVIS_NODE_VERSION && nvm use $TRAVIS_NODE_VERSION && ./js/scripts/install-deps.sh
script:
- if [ "$RUN_TESTS" = "true" ]; then
./js/scripts/lint.sh &&
./js/scripts/test.sh &&
./test.sh $TEST_OPTIONS --verbose;
fi
- if [ "$RUN_COVERAGE" = "true" ]; then ./scripts/cov.sh "$KCOV_CMD"; fi
after_success: |
[ $TRAVIS_BRANCH = master ] &&
[ $TRAVIS_PULL_REQUEST = false ] &&
[ "$RUN_DOCS" = "true" ] &&
./scripts/doc.sh &&
pip install --user ghp-import &&
/home/travis/.local/bin/ghp-import -n target/doc &&
git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages

File diff suppressed because it is too large Load Diff

2859
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,69 +1,52 @@
[package]
description = "Parity Ethereum client"
description = "Ethcore client."
name = "parity"
version = "1.7.2"
version = "1.4.2"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
authors = ["Ethcore <admin@ethcore.io>"]
build = "build.rs"
[build-dependencies]
rustc_version = "0.1"
ethcore-ipc-codegen = { path = "ipc/codegen" }
ethcore-ipc-tests = { path = "ipc/tests" }
[dependencies]
log = "0.3"
env_logger = "0.4"
rustc-hex = "1.0"
docopt = "0.8"
env_logger = "0.3"
rustc-serialize = "0.3"
docopt = "0.6"
time = "0.1"
num_cpus = "1.2"
num_cpus = "0.2"
number_prefix = "0.2"
rpassword = "0.2.1"
semver = "0.6"
ansi_term = "0.9"
regex = "0.2"
semver = "0.2"
ansi_term = "0.7"
lazy_static = "0.2"
regex = "0.1"
isatty = "0.1"
toml = "0.4"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
app_dirs = "1.1.1"
futures = "0.1"
futures-cpupool = "0.1"
fdlimit = "0.1"
ws2_32-sys = "0.2"
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
ethsync = { path = "sync" }
toml = "0.2"
serde = "0.8.0"
serde_json = "0.8.0"
hyper = { version = "0.9", default-features = false }
ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" }
json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" }
fdlimit = { path = "util/fdlimit" }
ethcore = { path = "ethcore" }
ethcore-util = { path = "util" }
ethsync = { path = "sync" }
ethcore-io = { path = "util/io" }
ethcore-devtools = { path = "devtools" }
ethcore-ipc = { path = "ipc/rpc" }
ethcore-rpc = { path = "rpc" }
ethcore-signer = { path = "signer" }
ethcore-ipc-nano = { path = "ipc/nano" }
ethcore-ipc = { path = "ipc/rpc" }
ethcore-ipc-hypervisor = { path = "ipc/hypervisor" }
ethcore-light = { path = "ethcore/light" }
ethcore-logger = { path = "logger" }
ethcore-stratum = { path = "stratum" }
ethkey = { path = "ethkey" }
node-health = { path = "dapps/node-health" }
rlp = { path = "util/rlp" }
rpc-cli = { path = "rpc_cli" }
parity-hash-fetch = { path = "hash-fetch" }
parity-ipfs-api = { path = "ipfs" }
parity-local-store = { path = "local-store" }
parity-reactor = { path = "util/reactor" }
parity-rpc = { path = "rpc" }
parity-rpc-client = { path = "rpc_client" }
parity-updater = { path = "updater" }
path = { path = "util/path" }
parity-dapps = { path = "dapps", optional = true }
clippy = { version = "0.0.103", optional = true}
ethcore-secretstore = { path = "secret_store", optional = true }
[build-dependencies]
rustc_version = "0.2"
[dev-dependencies]
ethcore-ipc-tests = { path = "ipc/tests" }
pretty_assertions = "0.1"
ethcore-stratum = { path = "stratum" }
ethcore-dapps = { path = "dapps", optional = true }
clippy = { version = "0.0.96", optional = true}
[target.'cfg(windows)'.dependencies]
winapi = "0.2"
@@ -73,38 +56,36 @@ daemonize = "0.2"
[features]
default = ["ui-precompiled"]
ui = [
"ui-enabled",
"parity-dapps/ui",
"dapps",
"ethcore-dapps/ui",
"ethcore-signer/ui",
]
ui-precompiled = [
"ui-enabled",
"parity-dapps/ui-precompiled",
"dapps",
"ethcore-signer/ui-precompiled",
"ethcore-dapps/ui-precompiled",
]
ui-enabled = ["dapps"]
dapps = ["parity-dapps"]
dapps = ["ethcore-dapps"]
ipc = ["ethcore/ipc", "ethsync/ipc"]
jit = ["ethcore/jit"]
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "parity-rpc/dev", "parity-dapps/dev"]
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethcore-dapps/dev", "ethcore-signer/dev"]
json-tests = ["ethcore/json-tests"]
test-heavy = ["ethcore/test-heavy"]
stratum = ["ipc"]
ethkey-cli = ["ethcore/ethkey-cli"]
ethstore-cli = ["ethcore/ethstore-cli"]
evm-debug = ["ethcore/evm-debug"]
evm-debug-tests = ["ethcore/evm-debug-tests"]
slow-blocks = ["ethcore/slow-blocks"]
final = ["ethcore-util/final"]
secretstore = ["ethcore-secretstore"]
[[bin]]
path = "parity/main.rs"
name = "parity"
[profile.dev]
panic = "abort"
[profile.release]
debug = false
lto = false
panic = "abort"
[workspace]
members = ["ethstore/cli", "ethkey/cli", "evmbin", "dapps/node-health"]

View File

@@ -1,62 +1,68 @@
# [Parity](https://parity.io/parity.html) - fast, light, and robust Ethereum client
# [Parity](https://ethcore.io/parity.html)
### Fast, light, and robust Ethereum implementation
[![build status](https://gitlab.parity.io/parity/parity/badges/master/build.svg)](https://gitlab.parity.io/parity/parity/commits/master)
[![Snap Status](https://build.snapcraft.io/badge/paritytech/parity.svg)](https://build.snapcraft.io/user/paritytech/parity)
[![GPLv3](https://img.shields.io/badge/license-GPL%20v3-green.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html)
- [Download the latest release here.](https://github.com/paritytech/parity/releases)
[![Build Status][travis-image]][travis-url] [![build status](https://gitlab.ethcore.io/Mirrors/ethcore-parity/badges/master/build.svg)](https://gitlab.ethcore.io/Mirrors/ethcore-parity/commits/master) [![Coverage Status][coveralls-image]][coveralls-url] [![GPLv3][license-image]][license-url]
### Join the chat!
Get in touch with us on Gitter:
[![Gitter: Parity](https://img.shields.io/badge/gitter-parity-4AB495.svg)](https://gitter.im/paritytech/parity)
[![Gitter: Parity.js](https://img.shields.io/badge/gitter-parity.js-4AB495.svg)](https://gitter.im/paritytech/parity.js)
[![Gitter: Parity/Miners](https://img.shields.io/badge/gitter-parity/miners-4AB495.svg)](https://gitter.im/paritytech/parity/miners)
[![Gitter: Parity-PoA](https://img.shields.io/badge/gitter-parity--poa-4AB495.svg)](https://gitter.im/paritytech/parity-poa)
Parity [![Join the chat at https://gitter.im/ethcore/parity][gitter-image]][gitter-url] and
parity.js [![Join the chat at https://gitter.im/ethcore/parity.js](https://badges.gitter.im/ethcore/parity.js.svg)](https://gitter.im/ethcore/parity.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Be sure to check out [our wiki](https://github.com/paritytech/parity/wiki) and the [internal documentation](https://paritytech.github.io/parity/ethcore/index.html) for more information.
[Internal Documentation][doc-url]
Be sure to check out [our wiki][wiki-url] for more information.
[travis-image]: https://travis-ci.org/ethcore/parity.svg?branch=master
[travis-url]: https://travis-ci.org/ethcore/parity
[coveralls-image]: https://coveralls.io/repos/github/ethcore/parity/badge.svg?branch=master
[coveralls-url]: https://coveralls.io/github/ethcore/parity?branch=master
[gitter-image]: https://badges.gitter.im/Join%20Chat.svg
[gitter-url]: https://gitter.im/ethcore/parity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
[license-image]: https://img.shields.io/badge/license-GPL%20v3-green.svg
[license-url]: https://www.gnu.org/licenses/gpl-3.0.en.html
[doc-url]: https://ethcore.github.io/parity/ethcore/index.html
[wiki-url]: https://github.com/ethcore/parity/wiki
**Parity requires Rust version 1.12.0 to build**
----
## About Parity
Parity's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity using the sophisticated and cutting-edge Rust programming language. Parity is licensed under the GPLv3, and can be used for all your Ethereum needs.
Parity comes with a built-in wallet. To access [Parity Wallet](http://web3.site/) simply go to http://web3.site/ (if you don't have access to the internet, but still want to use the service, you can also use http://127.0.0.1:8180/). It includes various functionality allowing you to:
Parity's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity using the sophisticated and
cutting-edge Rust programming language. Parity is licensed under the GPLv3, and can be used for all your Ethereum needs.
Parity comes with a built-in wallet. To access [Parity Wallet](http://127.0.0.1:8080/) this simply go to http://127.0.0.1:8080/. It
includes various functionality allowing you to:
- create and manage your Ethereum accounts;
- manage your Ether and any Ethereum tokens;
- create and register your own tokens;
- and much more.
- and much more.
By default, Parity will also run a JSONRPC server on `127.0.0.1:8545`. This is fully configurable and supports a number of RPC APIs.
By default, Parity will also run a JSONRPC server on `127.0.0.1:8545`. This is fully configurable and supports a number
of RPC APIs.
If you run into an issue while using parity, feel free to file one in this repository or hop on our [gitter chat room](https://gitter.im/paritytech/parity) to ask a question. We are glad to help!
If you run into an issue while using parity, feel free to file one in this repository
or hop on our [gitter chat room][gitter-url] to ask a question. We are glad to help!
**For security-critical issues**, please refer to the security policy outlined in `SECURITY.MD`.
Parity's current beta-release is 1.7. You can download it at https://github.com/paritytech/parity/releases or follow the instructions below to build from source.
Parity's current release is 1.3. You can download it at https://ethcore.io/parity.html or follow the instructions
below to build from source.
----
## Build dependencies
**Parity 1.7.x-beta requires Rust version 1.19.0 to build**.
Parity is fully compatible with Stable Rust.
We recommend installing Rust through [rustup](https://www.rustup.rs/). If you don't already have rustup, you can install it like this:
- Linux:
- Linux and OSX:
```bash
$ curl https://sh.rustup.rs -sSf | sh
```
Parity also requires `gcc`, `g++`, `libssl-dev`/`openssl`, `libudev-dev` and `pkg-config` packages to be installed.
- OSX:
```bash
$ curl https://sh.rustup.rs -sSf | sh
```
`clang` is required. It comes with Xcode command line tools or can be installed with homebrew.
- Windows
Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the rustup installer from
@@ -69,23 +75,19 @@ Once you have rustup, install parity or download and build from source
----
## Install from the snap store
In any of the [supported Linux distros](https://snapcraft.io/docs/core/install):
## Quick install
```bash
sudo snap install parity --edge
cargo install --git https://github.com/ethcore/parity.git parity
```
(Note that this is an experimental and unstable release, at the moment)
----
## Build from source
```bash
# download Parity code
$ git clone https://github.com/paritytech/parity
$ git clone https://github.com/ethcore/parity
$ cd parity
# build in release mode
@@ -93,18 +95,6 @@ $ cargo build --release
```
This will produce an executable in the `./target/release` subdirectory.
Note: if cargo fails to parse manifest try:
```bash
$ ~/.cargo/bin/cargo build --release
```
----
## Simple one-line installer for Mac and Ubuntu
```bash
bash <(curl https://get.parity.io -Lk)
```
## Start Parity
### Manually
@@ -123,4 +113,4 @@ systemd user directory (usually `~/.config/systemd/user`).
2. To pass any argument to Parity, write a `~/.parity/parity.conf` file this way:
`ARGS="ARG1 ARG2 ARG3"`.
Example: `ARGS="ui --identity MyMachine"`.
Example: `ARGS="ui --geth --identity MyMachine"`.

View File

@@ -1,54 +0,0 @@
# Security Policy
For security inquiries or vulnerability reports, please send a message to security@parity.io.
Please use a descriptive subject line so we can identify the report as such.
If you send a report, we will respond to the e-mail within 48 hours, and provide regular updates from that time onwards.
If you would like to encrypt your report, please use the PGP key provided below.
It is also reproduced [on MIT's key server](https://pgp.mit.edu/pks/lookup?op=get&search=0x5D0F03018D07DE73)
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBFlyIAwBCACe0keNPjgYzZ1Oy/8t3zj/Qw9bHHqrzx7FWy8NbXnYBM19NqOZ
DIP7Oe0DvCaf/uruBskCS0iVstHlEFQ2AYe0Ei0REt9lQdy61GylU/DEB3879IG+
6FO0SnFeYeerv1/hFI2K6uv8v7PyyVDiiJSW0I1KIs2OBwJicTKmWxLAeQsRgx9G
yRGalrVk4KP+6pWTA7k3DxmDZKZyfYV/Ej10NtuzmsemwDbv98HKeomp/kgFOfSy
3AZjeCpctlsNqpjUuXa0/HudmH2WLxZ0fz8XeoRh8XM9UudNIecjrDqmAFrt/btQ
/3guvlzhFCdhYPVGsUusKMECk/JG+Xx1/1ZjABEBAAG0LFBhcml0eSBTZWN1cml0
eSBDb250YWN0IDxzZWN1cml0eUBwYXJpdHkuaW8+iQFUBBMBCAA+FiEE2uUVYCjP
N6B8aTiDXQ8DAY0H3nMFAllyIAwCGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwEC
HgECF4AACgkQXQ8DAY0H3nM60wgAkS3A36Zc+upiaxU7tumcGv+an17j7gin0sif
+0ELSjVfrXInM6ovai+NhUdcLkJ7tCrKS90fvlaELK5Sg9CXBWCTFccKN4A/B7ey
rOg2NPXUecnyBB/XqQgKYH7ujYlOlqBDXMfz6z8Hj6WToxg9PPMGGomyMGh8AWxM
3yRPFs5RKt0VKgN++5N00oly5Y8ri5pgCidDvCLYMGTVDHFKwkuc9w6BlWlu1R1e
/hXFWUFAP1ffTAul3QwyKhjPn2iotCdxXjvt48KaU8DN4iL7aMBN/ZBKqGS7yRdF
D/JbJyaaJ0ZRvFSTSXy/sWY3z1B5mtCPBxco8hqqNfRkCwuZ6LkBDQRZciAMAQgA
8BP8xrwe12TOUTqL/Vrbxv/FLdhKh53J6TrPKvC2TEEKOrTNo5ahRq+XOS5E7G2N
x3b+fq8gR9BzFcldAx0XWUtGs/Wv++ulaSNqTBxj13J3G3WGsUfMKxRgj//piCUD
bCFLQfGZdKk0M1o9QkPVARwwmvCNiNB/l++xGqPtfc44H5jWj3GoGvL2MkShPzrN
yN/bJ+m+R5gtFGdInqa5KXBuxxuW25eDKJ+LzjbgUgeC76wNcfOiQHTdMkcupjdO
bbGFwo10hcbRAOcZEv6//Zrlmk/6nPxEd2hN20St2bSN0+FqfZ267mWEu3ejsgF8
ArdCpv5h4fBvJyNwiTZwIQARAQABiQE8BBgBCAAmFiEE2uUVYCjPN6B8aTiDXQ8D
AY0H3nMFAllyIAwCGwwFCQPCZwAACgkQXQ8DAY0H3nNisggAl4fqhRlA34wIb190
sqXHVxiCuzPaqS6krE9xAa1+gncX485OtcJNqnjugHm2rFE48lv7oasviuPXuInE
/OgVFnXYv9d/Xx2JUeDs+bFTLouCDRY2Unh7KJZasfqnMcCHWcxHx5FvRNZRssaB
WTZVo6sizPurGUtbpYe4/OLFhadBqAE0EUmVRFEUMc1YTnu4eLaRBzoWN4d2UWwi
LN25RSrVSke7LTSFbgn9ntQrQ2smXSR+cdNkkfRCjFcpUaecvFl9HwIqoyVbT4Ym
0hbpbbX/cJdc91tKa+psa29uMeGL/cgL9fAu19yNFRyOTMxjZnvql1X/WE1pLmoP
ETBD1Q==
=K9Qw
-----END PGP PUBLIC KEY BLOCK-----
```
Important Legal Information:
Your submission might be eligible for a bug bounty. The bug bounty program is an experimental and discretionary rewards program for the Parity community to reward those who are helping to improve the Parity software. Rewards are at the sole discretion of Parity Technologies Ltd..
We are not able to issue rewards to individuals who are on sanctions lists or who are in countries on sanctions lists (e.g. North Korea, Iran, etc).
You are responsible for all taxes. All rewards are subject to applicable law.
Finally, your testing must not violate any law or compromise any data that is not yours.

52
appveyor.yml Normal file
View File

@@ -0,0 +1,52 @@
environment:
matrix:
- TARGET: x86_64-pc-windows-msvc
cert:
secure: ESPpYVVAMG1fbJx6kq4ct/g9SQTXac4Hs6xXr6Oh4Zrk2dwYglNjxmzErdPnvu7gs/gekzrJ6KEQHYRc+5+4dKg6rRADQ681NLVx9vOggBs=
certpass:
secure: 0BgXJqxq9Ei34/hZ7121FQ==
keyfile: C:\users\appveyor\Certificates.p12
RUSTFLAGS: -Zorbit=off -D warnings
branches:
only:
- master
- /^beta-.*$/
- /^stable-.*$/
- /^beta$/
- /^stable$/
install:
- git submodule update --init --recursive
- ps: Install-Product node 6
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.12.0-x86_64-pc-windows-msvc.exe"
- ps: Start-FileDownload "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -FileName nsis\SimpleFC.dll
- ps: Start-FileDownload "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -FileName nsis\vc_redist.x64.exe
- rust-1.12.0-x86_64-pc-windows-msvc.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin;C:\Program Files (x86)\NSIS;C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin
- rustc -V
- cargo -V
- node -v
- npm -v
build: off
test_script:
- cargo test --verbose --release
after_test:
- cargo build --verbose --release
- ps: if($env:cert) { Start-FileDownload $env:cert -FileName $env:keyfile }
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass target\release\parity.exe }
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x64 /p:Configuration=Release
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass windows\ptray\x64\release\ptray.exe }
- makensis.exe nsis\installer.nsi
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass nsis\installer.exe }
artifacts:
- path: nsis\installer.exe
name: Windows Installer (x86_64)
cache:
- target
- C:\users\appveyor\.cargo -> appveyor.yml

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -16,20 +16,10 @@
extern crate rustc_version;
const MIN_RUSTC_VERSION: &'static str = "1.15.1";
use rustc_version::{version_meta, Channel};
fn main() {
let is = rustc_version::version().unwrap();
let required = MIN_RUSTC_VERSION.parse().unwrap();
assert!(is >= required, format!("
It looks like you are compiling Parity with an old rustc compiler {}.
Parity requires version {}. Please update your compiler.
If you use rustup, try this:
rustup update stable
and try building Parity again.
", is, required));
if let Channel::Nightly = version_meta().channel {
println!("cargo:rustc-cfg=nightly");
}
}

View File

@@ -1,48 +1,48 @@
[package]
description = "Parity Dapps crate"
name = "parity-dapps"
version = "1.7.0"
name = "ethcore-dapps"
version = "1.4.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
authors = ["Ethcore <admin@ethcore.io"]
build = "build.rs"
[lib]
[dependencies]
base32 = "0.3"
futures = "0.1"
linked-hash-map = "0.3"
rand = "0.3.14"
log = "0.3"
parity-dapps-glue = "1.7"
mime = "0.2"
mime_guess = "1.6.1"
rand = "0.3"
rustc-hex = "1.0"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
time = "0.1.35"
env_logger = "0.3"
jsonrpc-core = "3.0"
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.git" }
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }
unicase = "1.3"
url = "1.0"
rustc-serialize = "0.3"
serde = "0.8"
serde_json = "0.8"
ethabi = "0.2.2"
linked-hash-map = "0.3"
parity-dapps-glue = "1.4"
mime = "0.2"
time = "0.1.35"
serde_macros = { version = "0.8", optional = true }
zip = { version = "0.1", default-features = false }
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }
ethcore-devtools = { path = "../devtools" }
ethcore-rpc = { path = "../rpc" }
ethcore-util = { path = "../util" }
fetch = { path = "../util/fetch" }
node-health = { path = "./node-health" }
parity-hash-fetch = { path = "../hash-fetch" }
parity-reactor = { path = "../util/reactor" }
parity-ui = { path = "./ui" }
clippy = { version = "0.0.103", optional = true}
mime_guess = { version = "1.6.1" }
clippy = { version = "0.0.96", optional = true}
[dev-dependencies]
env_logger = "0.4"
ethcore-devtools = { path = "../devtools" }
[build-dependencies]
serde_codegen = { version = "0.8", optional = true }
[features]
dev = ["clippy", "ethcore-util/dev"]
default = ["serde_codegen"]
nightly = ["serde_macros"]
dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"]
ui = ["parity-ui/no-precompiled-js"]
ui-precompiled = ["parity-ui/use-precompiled-js"]

41
dapps/build.rs Normal file
View File

@@ -0,0 +1,41 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
#[cfg(not(feature = "serde_macros"))]
mod inner {
extern crate serde_codegen;
use std::env;
use std::path::Path;
pub fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let src = Path::new("./src/api/types.rs.in");
let dst = Path::new(&out_dir).join("types.rs");
serde_codegen::expand(&src, &dst).unwrap();
}
}
#[cfg(feature = "serde_macros")]
mod inner {
pub fn main() {}
}
fn main() {
inner::main();
}

View File

@@ -1,23 +1,23 @@
[package]
description = "Base Package for all Parity built-in dapps"
name = "parity-dapps-glue"
version = "1.7.0"
version = "1.4.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
authors = ["Ethcore <admin@ethcore.io"]
build = "build.rs"
[build-dependencies]
quasi_codegen = { version = "0.32", optional = true }
syntex = { version = "0.58", optional = true }
quasi_codegen = { version = "0.11", optional = true }
syntex = { version = "0.33", optional = true }
[dependencies]
glob = { version = "0.2.11" }
mime_guess = { version = "1.6.1" }
aster = { version = "0.41", default-features = false }
quasi = { version = "0.32", default-features = false }
quasi_macros = { version = "0.32", optional = true }
syntex = { version = "0.58", optional = true }
syntex_syntax = { version = "0.58", optional = true }
aster = { version = "0.17", default-features = false }
quasi = { version = "0.11", default-features = false }
quasi_macros = { version = "0.11", optional = true }
syntex = { version = "0.33", optional = true }
syntex_syntax = { version = "0.33", optional = true }
clippy = { version = "0.0.90", optional = true }
[features]

View File

@@ -6,7 +6,7 @@ Code generator to simplify creating a built-in Parity Dapp
1. Clone this repository.
```bash
$ git clone https://github.com/paritytech/parity.git
$ git clone https://github.com/ethcore/parity.git
```
1. Create a new directory for your Dapp. (`./myapp`)
@@ -29,10 +29,10 @@ Code generator to simplify creating a built-in Parity Dapp
The `inject.js` script will create global `web3` instance with proper provider that should be used by your dapp.
1. Create `./parity/dapps/myapp/Cargo.toml` with you apps details. See example here: [parity-status Cargo.toml](https://github.com/paritytech/parity-ui/blob/master/status/Cargo.toml).
1. Create `./parity/dapps/myapp/Cargo.toml` with you apps details. See example here: [parity-status Cargo.toml](https://github.com/ethcore/parity-ui/blob/master/status/Cargo.toml).
```bash
$ git clone https://github.com/paritytech/parity-ui.git
$ git clone https://github.com/ethcore/parity-ui.git
$ cd ./parity-ui/
$ cp ./home/Cargo.toml ../parity/dapps/myapp/Cargo.toml
$ cp ./home/build.rs ../parity/dapps/myapp/build.rs

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -25,11 +25,13 @@ mod inner {
pub fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let mut registry = syntex::Registry::new();
quasi_codegen::register(&mut registry);
let src = Path::new("src/lib.rs.in");
let dst = Path::new(&out_dir).join("lib.rs");
quasi_codegen::expand(&src, &dst).unwrap();
registry.expand("", &src, &dst).unwrap();
}
}

View File

@@ -13,8 +13,9 @@ pub mod inner {
impl fold::Folder for StripAttributeFolder {
fn fold_attribute(&mut self, attr: ast::Attribute) -> Option<ast::Attribute> {
if &*attr.value.name.as_str() == "webapp" {
return None;
match attr.node.value.node {
ast::MetaItemKind::List(ref n, _) if n == &"webapp" => { return None; }
_ => {}
}
Some(attr)

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -22,12 +22,16 @@ use self::mime_guess::guess_mime_type;
use std::path::{self, Path, PathBuf};
use std::ops::Deref;
use syntax::ast::{MetaItem, Item};
use syntax::ast;
use syntax::attr;
use syntax::ast::{self, MetaItem, Item};
use syntax::codemap::Span;
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::print::pprust::lit_to_string;
use syntax::symbol::InternedString;
use syntax::ptr::P;
use syntax::print::pprust::{lit_to_string};
use syntax::parse::token::{InternedString};
pub fn expand_webapp_implementation(
cx: &mut ExtCtxt,
@@ -44,7 +48,7 @@ pub fn expand_webapp_implementation(
},
};
let builder = aster::AstBuilder::new().span(span);
implement_webapp(cx, &builder, item, push);
implement_webapp(cx, &builder, &item, push);
}
fn implement_webapp(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push: &mut FnMut(Annotatable)) {
@@ -113,12 +117,11 @@ fn implement_webapp(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push
}
fn extract_path(cx: &ExtCtxt, item: &Item) -> String {
for meta_items in item.attrs.iter().filter_map(webapp_meta_items) {
for meta_items in item.attrs().iter().filter_map(webapp_meta_items) {
for meta_item in meta_items {
let is_path = &*meta_item.name.as_str() == "path";
match meta_item.node {
ast::MetaItemKind::NameValue(ref lit) if is_path => {
if let Some(s) = get_str_from_lit(cx, lit) {
ast::MetaItemKind::NameValue(ref name, ref lit) if name == &"path" => {
if let Some(s) = get_str_from_lit(cx, name, lit) {
return s.deref().to_owned();
}
},
@@ -131,32 +134,14 @@ fn extract_path(cx: &ExtCtxt, item: &Item) -> String {
"web".to_owned()
}
fn webapp_meta_items(attr: &ast::Attribute) -> Option<Vec<ast::MetaItem>> {
let is_webapp = &*attr.value.name.as_str() == "webapp";
match attr.value.node {
ast::MetaItemKind::List(ref items) if is_webapp => {
attr::mark_used(&attr);
Some(
items.iter()
.map(|item| item.node.clone())
.filter_map(|item| match item {
ast::NestedMetaItemKind::MetaItem(item) => Some(item),
_ => None,
})
.collect()
)
}
_ => None
}
}
fn get_str_from_lit(cx: &ExtCtxt, lit: &ast::Lit) -> Option<InternedString> {
fn get_str_from_lit(cx: &ExtCtxt, name: &str, lit: &ast::Lit) -> Option<InternedString> {
match lit.node {
ast::LitKind::Str(ref s, _) => Some(s.clone().as_str()),
ast::LitKind::Str(ref s, _) => Some(s.clone()),
_ => {
cx.span_err(
lit.span,
&format!("webapp annotation path must be a string, not `{}`",
&format!("webapp annotation `{}` must be a string, not `{}`",
name,
lit_to_string(lit)
)
);
@@ -165,6 +150,16 @@ fn get_str_from_lit(cx: &ExtCtxt, lit: &ast::Lit) -> Option<InternedString> {
}
}
fn webapp_meta_items(attr: &ast::Attribute) -> Option<&[P<ast::MetaItem>]> {
match attr.node.value.node {
ast::MetaItemKind::List(ref name, ref items) if name == &"webapp" => {
attr::mark_used(&attr);
Some(items)
}
_ => None
}
}
fn as_uri(path: &Path) -> String {
let mut s = String::new();
for component in path.iter() {
@@ -174,6 +169,7 @@ fn as_uri(path: &Path) -> String {
s[0..s.len()-1].into()
}
#[test]
fn should_convert_path_separators_on_all_platforms() {
// given

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -22,6 +22,7 @@
extern crate syntex;
#[cfg(feature = "with-syntex")]
#[macro_use]
extern crate syntex_syntax as syntax;
#[cfg(feature = "with-syntex")]

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify

View File

@@ -1,18 +0,0 @@
[package]
name = "node-health"
description = "Node's health status"
version = "0.1.0"
license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"]
[dependencies]
futures = "0.1"
futures-cpupool = "0.1"
log = "0.3"
ntp = "0.2.0"
parking_lot = "0.4"
serde = "1.0"
serde_derive = "1.0"
time = "0.1.35"
parity-reactor = { path = "../../util/reactor" }

View File

@@ -1,122 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Reporting node's health.
use std::sync::Arc;
use std::time;
use futures::{Future, BoxFuture};
use futures::sync::oneshot;
use types::{HealthInfo, HealthStatus, Health};
use time::{TimeChecker, MAX_DRIFT};
use parity_reactor::Remote;
use parking_lot::Mutex;
use {SyncStatus};
const TIMEOUT_SECS: u64 = 5;
const PROOF: &str = "Only one closure is invoked.";
/// A struct enabling you to query for node's health.
#[derive(Debug, Clone)]
pub struct NodeHealth {
sync_status: Arc<SyncStatus>,
time: TimeChecker,
remote: Remote,
}
impl NodeHealth {
/// Creates new `NodeHealth`.
pub fn new(sync_status: Arc<SyncStatus>, time: TimeChecker, remote: Remote) -> Self {
NodeHealth { sync_status, time, remote, }
}
/// Query latest health report.
pub fn health(&self) -> BoxFuture<Health, ()> {
trace!(target: "dapps", "Checking node health.");
// Check timediff
let sync_status = self.sync_status.clone();
let time = self.time.time_drift();
let (tx, rx) = oneshot::channel();
let tx = Arc::new(Mutex::new(Some(tx)));
let tx2 = tx.clone();
self.remote.spawn_with_timeout(
move || time.then(move |result| {
let _ = tx.lock().take().expect(PROOF).send(Ok(result));
Ok(())
}),
time::Duration::from_secs(TIMEOUT_SECS),
move || {
let _ = tx2.lock().take().expect(PROOF).send(Err(()));
},
);
rx.map_err(|err| {
warn!(target: "dapps", "Health request cancelled: {:?}", err);
}).and_then(move |time| {
// Check peers
let peers = {
let (connected, max) = sync_status.peers();
let (status, message) = match connected {
0 => {
(HealthStatus::Bad, "You are not connected to any peers. There is most likely some network issue. Fix connectivity.".into())
},
1 => (HealthStatus::NeedsAttention, "You are connected to only one peer. Your node might not be reliable. Check your network connection.".into()),
_ => (HealthStatus::Ok, "".into()),
};
HealthInfo { status, message, details: (connected, max) }
};
// Check sync
let sync = {
let is_syncing = sync_status.is_major_importing();
let (status, message) = if is_syncing {
(HealthStatus::NeedsAttention, "Your node is still syncing, the values you see might be outdated. Wait until it's fully synced.".into())
} else {
(HealthStatus::Ok, "".into())
};
HealthInfo { status, message, details: is_syncing }
};
// Check time
let time = {
let (status, message, details) = match time {
Ok(Ok(diff)) if diff < MAX_DRIFT && diff > -MAX_DRIFT => {
(HealthStatus::Ok, "".into(), diff)
},
Ok(Ok(diff)) => {
(HealthStatus::Bad, format!(
"Your clock is not in sync. Detected difference is too big for the protocol to work: {}ms. Synchronize your clock.",
diff,
), diff)
},
Ok(Err(err)) => {
(HealthStatus::NeedsAttention, format!(
"Unable to reach time API: {}. Make sure that your clock is synchronized.",
err,
), 0)
},
Err(_) => {
(HealthStatus::NeedsAttention, "Time API request timed out. Make sure that the clock is synchronized.".into(), 0)
},
};
HealthInfo { status, message, details, }
};
Ok(Health { peers, sync, time})
}).boxed()
}
}

View File

@@ -1,49 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Node Health status reporting.
#![warn(missing_docs)]
extern crate futures;
extern crate futures_cpupool;
extern crate ntp;
extern crate time as time_crate;
extern crate parity_reactor;
extern crate parking_lot;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
mod health;
mod time;
mod types;
pub use futures_cpupool::CpuPool;
pub use health::NodeHealth;
pub use types::{Health, HealthInfo, HealthStatus};
pub use time::{TimeChecker, Error};
/// Indicates sync status
pub trait SyncStatus: ::std::fmt::Debug + Send + Sync {
/// Returns true if there is a major sync happening.
fn is_major_importing(&self) -> bool;
/// Returns number of connected and ideal peers.
fn peers(&self) -> (usize, usize);
}

View File

@@ -1,355 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Periodically checks node's time drift using [SNTP](https://tools.ietf.org/html/rfc1769).
//!
//! An NTP packet is sent to the server with a local timestamp, the server then completes the packet, yielding the
//! following timestamps:
//!
//! Timestamp Name ID When Generated
//! ------------------------------------------------------------
//! Originate Timestamp T1 time request sent by client
//! Receive Timestamp T2 time request received at server
//! Transmit Timestamp T3 time reply sent by server
//! Destination Timestamp T4 time reply received at client
//!
//! The drift is defined as:
//!
//! drift = ((T2 - T1) + (T3 - T4)) / 2.
//!
use std::io;
use std::{fmt, mem, time};
use std::sync::Arc;
use std::sync::atomic::{self, AtomicUsize};
use std::collections::VecDeque;
use futures::{self, Future, BoxFuture};
use futures::future::{self, IntoFuture};
use futures_cpupool::{CpuPool, CpuFuture};
use ntp;
use parking_lot::RwLock;
use time_crate::{Duration, Timespec};
/// Time checker error.
#[derive(Debug, Clone, PartialEq)]
pub enum Error {
/// No servers are currently available for a query.
NoServersAvailable,
/// There was an error when trying to reach the NTP server.
Ntp(String),
/// IO error when reading NTP response.
Io(String),
}
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match *self {
NoServersAvailable => write!(fmt, "No NTP servers available"),
Ntp(ref err) => write!(fmt, "NTP error: {}", err),
Io(ref err) => write!(fmt, "Connection Error: {}", err),
}
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self { Error::Io(format!("{}", err)) }
}
impl From<ntp::errors::Error> for Error {
fn from(err: ntp::errors::Error) -> Self { Error::Ntp(format!("{}", err)) }
}
/// NTP time drift checker.
pub trait Ntp {
/// Returned Future.
type Future: IntoFuture<Item=Duration, Error=Error>;
/// Returns the current time drift.
fn drift(&self) -> Self::Future;
}
const SERVER_MAX_POLL_INTERVAL_SECS: u64 = 60;
#[derive(Debug)]
struct Server {
pub address: String,
next_call: RwLock<time::Instant>,
failures: AtomicUsize,
}
impl Server {
pub fn is_available(&self) -> bool {
*self.next_call.read() < time::Instant::now()
}
pub fn report_success(&self) {
self.failures.store(0, atomic::Ordering::SeqCst);
self.update_next_call(1)
}
pub fn report_failure(&self) {
let errors = self.failures.fetch_add(1, atomic::Ordering::SeqCst);
self.update_next_call(1 << errors)
}
fn update_next_call(&self, delay: usize) {
*self.next_call.write() = time::Instant::now() + time::Duration::from_secs(delay as u64 * SERVER_MAX_POLL_INTERVAL_SECS);
}
}
impl<T: AsRef<str>> From<T> for Server {
fn from(t: T) -> Self {
Server {
address: t.as_ref().to_owned(),
next_call: RwLock::new(time::Instant::now()),
failures: Default::default(),
}
}
}
/// NTP client using the SNTP algorithm for calculating drift.
#[derive(Clone)]
pub struct SimpleNtp {
addresses: Vec<Arc<Server>>,
pool: CpuPool,
}
impl fmt::Debug for SimpleNtp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f
.debug_struct("SimpleNtp")
.field("addresses", &self.addresses)
.finish()
}
}
impl SimpleNtp {
fn new<T: AsRef<str>>(addresses: &[T], pool: CpuPool) -> SimpleNtp {
SimpleNtp {
addresses: addresses.iter().map(Server::from).map(Arc::new).collect(),
pool: pool,
}
}
}
impl Ntp for SimpleNtp {
type Future = future::Either<
CpuFuture<Duration, Error>,
future::FutureResult<Duration, Error>,
>;
fn drift(&self) -> Self::Future {
use self::future::Either::{A, B};
let server = self.addresses.iter().find(|server| server.is_available());
server.map(|server| {
let server = server.clone();
A(self.pool.spawn_fn(move || {
debug!(target: "dapps", "Fetching time from {}.", server.address);
match ntp::request(&server.address) {
Ok(packet) => {
let dest_time = ::time_crate::now_utc().to_timespec();
let orig_time = Timespec::from(packet.orig_time);
let recv_time = Timespec::from(packet.recv_time);
let transmit_time = Timespec::from(packet.transmit_time);
let drift = ((recv_time - orig_time) + (transmit_time - dest_time)) / 2;
server.report_success();
Ok(drift)
},
Err(err) => {
server.report_failure();
Err(err.into())
},
}
}))
}).unwrap_or_else(|| B(future::err(Error::NoServersAvailable)))
}
}
// NOTE In a positive scenario first results will be seen after:
// MAX_RESULTS * UPDATE_TIMEOUT_INCOMPLETE_SECS seconds.
const MAX_RESULTS: usize = 4;
const UPDATE_TIMEOUT_OK_SECS: u64 = 6 * 60 * 60;
const UPDATE_TIMEOUT_WARN_SECS: u64 = 15 * 60;
const UPDATE_TIMEOUT_ERR_SECS: u64 = 60;
const UPDATE_TIMEOUT_INCOMPLETE_SECS: u64 = 10;
/// Maximal valid time drift.
pub const MAX_DRIFT: i64 = 500;
#[derive(Debug, Clone)]
/// A time checker.
pub struct TimeChecker<N: Ntp = SimpleNtp> {
ntp: N,
last_result: Arc<RwLock<(time::Instant, VecDeque<Result<i64, Error>>)>>,
}
impl TimeChecker<SimpleNtp> {
/// Creates new time checker given the NTP server address.
pub fn new<T: AsRef<str>>(ntp_addresses: &[T], pool: CpuPool) -> Self {
let last_result = Arc::new(RwLock::new(
// Assume everything is ok at the very beginning.
(time::Instant::now(), vec![Ok(0)].into())
));
let ntp = SimpleNtp::new(ntp_addresses, pool);
TimeChecker {
ntp,
last_result,
}
}
}
impl<N: Ntp> TimeChecker<N> where <N::Future as IntoFuture>::Future: Send + 'static {
/// Updates the time
pub fn update(&self) -> BoxFuture<i64, Error> {
trace!(target: "dapps", "Updating time from NTP.");
let last_result = self.last_result.clone();
self.ntp.drift().into_future().then(move |res| {
let res = res.map(|d| d.num_milliseconds());
if let Err(Error::NoServersAvailable) = res {
debug!(target: "dapps", "No NTP servers available. Selecting an older result.");
return select_result(last_result.read().1.iter());
}
// Update the results.
let mut results = mem::replace(&mut last_result.write().1, VecDeque::new());
let has_all_results = results.len() >= MAX_RESULTS;
let valid_till = time::Instant::now() + time::Duration::from_secs(
match res {
Ok(time) if has_all_results && time < MAX_DRIFT => UPDATE_TIMEOUT_OK_SECS,
Ok(_) if has_all_results => UPDATE_TIMEOUT_WARN_SECS,
Err(_) if has_all_results => UPDATE_TIMEOUT_ERR_SECS,
_ => UPDATE_TIMEOUT_INCOMPLETE_SECS,
}
);
trace!(target: "dapps", "New time drift received: {:?}", res);
// Push the result.
results.push_back(res);
while results.len() > MAX_RESULTS {
results.pop_front();
}
// Select a response and update last result.
let res = select_result(results.iter());
*last_result.write() = (valid_till, results);
res
}).boxed()
}
/// Returns a current time drift or error if last request to NTP server failed.
pub fn time_drift(&self) -> BoxFuture<i64, Error> {
// return cached result
{
let res = self.last_result.read();
if res.0 > time::Instant::now() {
return futures::done(select_result(res.1.iter())).boxed();
}
}
// or update and return result
self.update()
}
}
fn select_result<'a, T: Iterator<Item=&'a Result<i64, Error>>>(results: T) -> Result<i64, Error> {
let mut min = None;
for res in results {
min = Some(match (min.take(), res) {
(Some(Ok(min)), &Ok(ref new)) => Ok(::std::cmp::min(min, *new)),
(Some(Ok(old)), &Err(_)) => Ok(old),
(_, ref new) => (*new).clone(),
})
}
min.unwrap_or_else(|| Err(Error::Ntp("NTP server unavailable.".into())))
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::cell::{Cell, RefCell};
use std::time::Instant;
use time::Duration;
use futures::{future, Future};
use super::{Ntp, TimeChecker, Error};
use parking_lot::RwLock;
#[derive(Clone)]
struct FakeNtp(RefCell<Vec<Duration>>, Cell<u64>);
impl FakeNtp {
fn new() -> FakeNtp {
FakeNtp(
RefCell::new(vec![Duration::milliseconds(150)]),
Cell::new(0))
}
}
impl Ntp for FakeNtp {
type Future = future::FutureResult<Duration, Error>;
fn drift(&self) -> Self::Future {
self.1.set(self.1.get() + 1);
future::ok(self.0.borrow_mut().pop().expect("Unexpected call to drift()."))
}
}
fn time_checker() -> TimeChecker<FakeNtp> {
let last_result = Arc::new(RwLock::new(
(Instant::now(), vec![Err(Error::Ntp("NTP server unavailable".into()))].into())
));
TimeChecker {
ntp: FakeNtp::new(),
last_result: last_result,
}
}
#[test]
fn should_fetch_time_on_start() {
// given
let time = time_checker();
// when
let diff = time.time_drift().wait().unwrap();
// then
assert_eq!(diff, 150);
assert_eq!(time.ntp.1.get(), 1);
}
#[test]
fn should_not_fetch_twice_if_timeout_has_not_passed() {
// given
let time = time_checker();
// when
let diff1 = time.time_drift().wait().unwrap();
let diff2 = time.time_drift().wait().unwrap();
// then
assert_eq!(diff1, 150);
assert_eq!(diff2, 150);
assert_eq!(time.ntp.1.get(), 1);
}
}

View File

@@ -1,57 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Base health types.
/// Health API endpoint status.
#[derive(Debug, PartialEq, Serialize)]
pub enum HealthStatus {
/// Everything's OK.
#[serde(rename = "ok")]
Ok,
/// Node health need attention
/// (the issue is not critical, but may need investigation)
#[serde(rename = "needsAttention")]
NeedsAttention,
/// There is something bad detected with the node.
#[serde(rename = "bad")]
Bad,
}
/// Represents a single check in node health.
/// Cointains the status of that check and apropriate message and details.
#[derive(Debug, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct HealthInfo<T> {
/// Check status.
pub status: HealthStatus,
/// Human-readable message.
pub message: String,
/// Technical details of the check.
pub details: T,
}
/// Node Health status.
#[derive(Debug, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Health {
/// Status of peers.
pub peers: HealthInfo<(usize, usize)>,
/// Sync status.
pub sync: HealthInfo<bool>,
/// Time diff info.
pub time: HealthInfo<i64>,
}

Binary file not shown.

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -15,47 +15,52 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::sync::Arc;
use unicase::UniCase;
use hyper::{server, net, Decoder, Encoder, Next, Control};
use hyper::header;
use hyper::method::Method;
use hyper::status::StatusCode;
use hyper::header::AccessControlAllowOrigin;
use api::{response, types};
use apps::fetcher::Fetcher;
use handlers::{self, extract_url};
use endpoint::{Endpoint, Handler, EndpointPath};
use node_health::{NodeHealth, HealthStatus, Health};
use parity_reactor::Remote;
use api::types::{App, ApiError};
use api::response;
use apps::fetcher::ContentFetcher;
use handlers::extract_url;
use endpoint::{Endpoint, Endpoints, Handler, EndpointPath};
use jsonrpc_http_server::cors;
#[derive(Clone)]
pub struct RestApi {
fetcher: Arc<Fetcher>,
health: NodeHealth,
remote: Remote,
cors_domains: Option<Vec<AccessControlAllowOrigin>>,
endpoints: Arc<Endpoints>,
fetcher: Arc<ContentFetcher>,
}
impl RestApi {
pub fn new(
fetcher: Arc<Fetcher>,
health: NodeHealth,
remote: Remote,
) -> Box<Endpoint> {
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<ContentFetcher>) -> Box<Endpoint> {
Box::new(RestApi {
fetcher,
health,
remote,
cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()),
endpoints: endpoints,
fetcher: fetcher,
})
}
fn list_apps(&self) -> Vec<App> {
self.endpoints.iter().filter_map(|(ref k, ref e)| {
e.info().map(|ref info| App::from_info(k, info))
}).collect()
}
}
impl Endpoint for RestApi {
fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<Handler> {
Box::new(RestApiRouter::new((*self).clone(), path, control))
Box::new(RestApiRouter::new(self.clone(), path, control))
}
}
struct RestApiRouter {
api: RestApi,
origin: Option<String>,
path: Option<EndpointPath>,
control: Option<Control>,
handler: Box<Handler>,
@@ -65,18 +70,18 @@ impl RestApiRouter {
fn new(api: RestApi, path: EndpointPath, control: Control) -> Self {
RestApiRouter {
path: Some(path),
origin: None,
control: Some(control),
api: api,
handler: Box::new(response::as_json_error(StatusCode::NotFound, &types::ApiError {
handler: response::as_json_error(&ApiError {
code: "404".into(),
title: "Not Found".into(),
detail: "Resource you requested has not been found.".into(),
})),
}),
}
}
fn resolve_content(&self, hash: Option<&str>, path: EndpointPath, control: Control) -> Option<Box<Handler>> {
trace!(target: "dapps", "Resolving content: {:?} from path: {:?}", hash, path);
match hash {
Some(hash) if self.api.fetcher.contains(hash) => {
Some(self.api.fetcher.to_async_handler(path, control))
@@ -85,35 +90,39 @@ impl RestApiRouter {
}
}
fn health(&self, control: Control) -> Box<Handler> {
let map = move |health: Result<Result<Health, ()>, ()>| {
let status = match health {
Ok(Ok(ref health)) => {
if [&health.peers.status, &health.sync.status].iter().any(|x| *x != &HealthStatus::Ok) {
StatusCode::PreconditionFailed // HTTP 412
} else {
StatusCode::Ok // HTTP 200
}
},
_ => StatusCode::ServiceUnavailable, // HTTP 503
};
/// Returns basic headers for a response (it may be overwritten by the handler)
fn response_headers(&self) -> header::Headers {
let mut headers = header::Headers::new();
headers.set(header::AccessControlAllowCredentials);
headers.set(header::AccessControlAllowMethods(vec![
Method::Options,
Method::Post,
Method::Get,
]));
headers.set(header::AccessControlAllowHeaders(vec![
UniCase("origin".to_owned()),
UniCase("content-type".to_owned()),
UniCase("accept".to_owned()),
]));
response::as_json(status, &health)
};
let health = self.api.health.health();
let remote = self.api.remote.clone();
Box::new(handlers::AsyncHandler::new(health, map, remote, control))
if let Some(cors_header) = cors::get_cors_header(&self.api.cors_domains, &self.origin) {
headers.set(cors_header);
}
headers
}
}
impl server::Handler<net::HttpStream> for RestApiRouter {
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
self.origin = cors::read_origin(&request);
if let Method::Options = *request.method() {
self.handler = response::empty();
return Next::write();
}
// TODO [ToDr] Consider using `path.app_params` instead
let url = extract_url(&request);
if url.is_none() {
// Just return 404 if we can't parse URL
@@ -131,8 +140,8 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
if let Some(ref hash) = hash { path.app_id = hash.clone().to_owned() }
let handler = endpoint.and_then(|v| match v {
"apps" => Some(response::as_json(&self.api.list_apps())),
"ping" => Some(response::ping()),
"health" => Some(self.health(control)),
"content" => self.resolve_content(hash, path, control),
_ => None
});
@@ -150,10 +159,12 @@ impl server::Handler<net::HttpStream> for RestApiRouter {
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
*res.headers_mut() = self.response_headers();
self.handler.on_response(res)
}
fn on_response_writable(&mut self, encoder: &mut Encoder<net::HttpStream>) -> Next {
self.handler.on_response_writable(encoder)
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -16,8 +16,12 @@
//! REST API
#![cfg_attr(feature="nightly", feature(custom_derive, custom_attribute, plugin))]
#![cfg_attr(feature="nightly", plugin(serde_macros, clippy))]
mod api;
mod response;
mod types;
pub use self::api::RestApi;
pub use self::types::App;

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -16,8 +16,6 @@
use serde::Serialize;
use serde_json;
use hyper::status::StatusCode;
use endpoint::Handler;
use handlers::{ContentHandler, EchoHandler};
@@ -25,16 +23,16 @@ pub fn empty() -> Box<Handler> {
Box::new(ContentHandler::ok("".into(), mime!(Text/Plain)))
}
pub fn as_json<T: Serialize>(status: StatusCode, val: &T) -> ContentHandler {
pub fn as_json<T: Serialize>(val: &T) -> Box<Handler> {
let json = serde_json::to_string(val)
.expect("serialization to string is infallible; qed");
ContentHandler::new(status, json, mime!(Application/Json))
Box::new(ContentHandler::ok(json, mime!(Application/Json)))
}
pub fn as_json_error<T: Serialize>(status: StatusCode, val: &T) -> ContentHandler {
pub fn as_json_error<T: Serialize>(val: &T) -> Box<Handler> {
let json = serde_json::to_string(val)
.expect("serialization to string is infallible; qed");
ContentHandler::new(status, json, mime!(Application/Json))
Box::new(ContentHandler::not_found(json, mime!(Application/Json)))
}
pub fn ping() -> Box<Handler> {

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,14 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/// A structure representing any error in REST API.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ApiError {
/// Error code.
pub code: String,
/// Human-readable error summary.
pub title: String,
/// More technical error details.
pub detail: String,
}
#[cfg(feature = "serde_macros")]
include!("types.rs.in");
#[cfg(not(feature = "serde_macros"))]
include!(concat!(env!("OUT_DIR"), "/types.rs"));

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -17,7 +17,6 @@
use endpoint::EndpointInfo;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct App {
pub id: String,
pub name: String,
@@ -53,3 +52,11 @@ impl Into<EndpointInfo> for App {
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct ApiError {
pub code: String,
pub title: String,
pub detail: String,
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -17,13 +17,14 @@
//! Fetchable Dapps support.
use std::fs;
use std::sync::{Arc};
use linked_hash_map::LinkedHashMap;
use page::LocalPageEndpoint;
use handlers::FetchControl;
pub enum ContentStatus {
Fetching(FetchControl),
Fetching(Arc<FetchControl>),
Ready(LocalPageEndpoint),
}

440
dapps/src/apps/fetcher.rs Normal file
View File

@@ -0,0 +1,440 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Fetchable Dapps support.
//! Manages downloaded (cached) Dapps and downloads them when necessary.
//! Uses `URLHint` to resolve addresses into Dapps bundle file location.
use zip;
use std::{fs, env, fmt};
use std::io::{self, Read, Write};
use std::path::PathBuf;
use std::sync::Arc;
use rustc_serialize::hex::FromHex;
use hyper;
use hyper::status::StatusCode;
use random_filename;
use SyncStatus;
use util::{Mutex, H256};
use util::sha3::sha3;
use page::{LocalPageEndpoint, PageCache};
use handlers::{ContentHandler, ContentFetcherHandler, ContentValidator};
use endpoint::{Endpoint, EndpointPath, Handler};
use apps::cache::{ContentCache, ContentStatus};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest};
use apps::urlhint::{URLHintContract, URLHint, URLHintResult};
/// Limit of cached dapps/content
const MAX_CACHED_DAPPS: usize = 20;
pub struct ContentFetcher<R: URLHint = URLHintContract> {
dapps_path: PathBuf,
resolver: R,
cache: Arc<Mutex<ContentCache>>,
sync: Arc<SyncStatus>,
embeddable_on: Option<(String, u16)>,
}
impl<R: URLHint> Drop for ContentFetcher<R> {
fn drop(&mut self) {
// Clear cache path
let _ = fs::remove_dir_all(&self.dapps_path);
}
}
impl<R: URLHint> ContentFetcher<R> {
pub fn new(resolver: R, sync_status: Arc<SyncStatus>, embeddable_on: Option<(String, u16)>) -> Self {
let mut dapps_path = env::temp_dir();
dapps_path.push(random_filename());
ContentFetcher {
dapps_path: dapps_path,
resolver: resolver,
sync: sync_status,
cache: Arc::new(Mutex::new(ContentCache::default())),
embeddable_on: embeddable_on,
}
}
fn still_syncing(address: Option<(String, u16)>) -> Box<Handler> {
Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable,
"Sync In Progress",
"Your node is still syncing. We cannot resolve any content before it's fully synced.",
Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"),
address,
))
}
#[cfg(test)]
fn set_status(&self, content_id: &str, status: ContentStatus) {
self.cache.lock().insert(content_id.to_owned(), status);
}
pub fn contains(&self, content_id: &str) -> bool {
{
let mut cache = self.cache.lock();
// Check if we already have the app
if cache.get(content_id).is_some() {
return true;
}
}
// fallback to resolver
if let Ok(content_id) = content_id.from_hex() {
// else try to resolve the app_id
let has_content = self.resolver.resolve(content_id).is_some();
// if there is content or we are syncing return true
has_content || self.sync.is_major_importing()
} else {
false
}
}
pub fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler> {
let mut cache = self.cache.lock();
let content_id = path.app_id.clone();
let (new_status, handler) = {
let status = cache.get(&content_id);
match status {
// Just serve the content
Some(&mut ContentStatus::Ready(ref endpoint)) => {
(None, endpoint.to_async_handler(path, control))
},
// Content is already being fetched
Some(&mut ContentStatus::Fetching(ref fetch_control)) => {
trace!(target: "dapps", "Content fetching in progress. Waiting...");
(None, fetch_control.to_handler(control))
},
// We need to start fetching the content
None => {
trace!(target: "dapps", "Content unavailable. Fetching... {:?}", content_id);
let content_hex = content_id.from_hex().expect("to_handler is called only when `contains` returns true.");
let content = self.resolver.resolve(content_hex);
let cache = self.cache.clone();
let on_done = move |id: String, result: Option<LocalPageEndpoint>| {
let mut cache = cache.lock();
match result {
Some(endpoint) => {
cache.insert(id, ContentStatus::Ready(endpoint));
},
// In case of error
None => {
cache.remove(&id);
},
}
};
match content {
// Don't serve dapps if we are still syncing (but serve content)
Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => {
(None, Self::still_syncing(self.embeddable_on.clone()))
},
Some(URLHintResult::Dapp(dapp)) => {
let (handler, fetch_control) = ContentFetcherHandler::new(
dapp.url(),
control,
DappInstaller {
id: content_id.clone(),
dapps_path: self.dapps_path.clone(),
on_done: Box::new(on_done),
embeddable_on: self.embeddable_on.clone(),
},
self.embeddable_on.clone(),
);
(Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box<Handler>)
},
Some(URLHintResult::Content(content)) => {
let (handler, fetch_control) = ContentFetcherHandler::new(
content.url,
control,
ContentInstaller {
id: content_id.clone(),
mime: content.mime,
content_path: self.dapps_path.clone(),
on_done: Box::new(on_done),
},
self.embeddable_on.clone(),
);
(Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box<Handler>)
},
None if self.sync.is_major_importing() => {
(None, Self::still_syncing(self.embeddable_on.clone()))
},
None => {
// This may happen when sync status changes in between
// `contains` and `to_handler`
(None, Box::new(ContentHandler::error(
StatusCode::NotFound,
"Resource Not Found",
"Requested resource was not found.",
None,
self.embeddable_on.clone(),
)) as Box<Handler>)
},
}
},
}
};
if let Some(status) = new_status {
cache.clear_garbage(MAX_CACHED_DAPPS);
cache.insert(content_id, status);
}
handler
}
}
#[derive(Debug)]
pub enum ValidationError {
Io(io::Error),
Zip(zip::result::ZipError),
InvalidContentId,
ManifestNotFound,
ManifestSerialization(String),
HashMismatch { expected: H256, got: H256, },
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
ValidationError::Io(ref io) => write!(f, "Unexpected IO error occured: {:?}", io),
ValidationError::Zip(ref zip) => write!(f, "Unable to read ZIP archive: {:?}", zip),
ValidationError::InvalidContentId => write!(f, "ID is invalid. It should be 256 bits keccak hash of content."),
ValidationError::ManifestNotFound => write!(f, "Downloaded Dapp bundle did not contain valid manifest.json file."),
ValidationError::ManifestSerialization(ref err) => {
write!(f, "There was an error during Dapp Manifest serialization: {:?}", err)
},
ValidationError::HashMismatch { ref expected, ref got } => {
write!(f, "Hash of downloaded content did not match. Expected:{:?}, Got:{:?}.", expected, got)
},
}
}
}
impl From<io::Error> for ValidationError {
fn from(err: io::Error) -> Self {
ValidationError::Io(err)
}
}
impl From<zip::result::ZipError> for ValidationError {
fn from(err: zip::result::ZipError) -> Self {
ValidationError::Zip(err)
}
}
struct ContentInstaller {
id: String,
mime: String,
content_path: PathBuf,
on_done: Box<Fn(String, Option<LocalPageEndpoint>) + Send>,
}
impl ContentValidator for ContentInstaller {
type Error = ValidationError;
fn validate_and_install(&self, path: PathBuf) -> Result<(String, LocalPageEndpoint), ValidationError> {
// Create dir
try!(fs::create_dir_all(&self.content_path));
// Validate hash
let mut file_reader = io::BufReader::new(try!(fs::File::open(&path)));
let hash = try!(sha3(&mut file_reader));
let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId));
if id != hash {
return Err(ValidationError::HashMismatch {
expected: id,
got: hash,
});
}
// And prepare path for a file
let filename = path.file_name().expect("We always fetch a file.");
let mut content_path = self.content_path.clone();
content_path.push(&filename);
if content_path.exists() {
try!(fs::remove_dir_all(&content_path))
}
try!(fs::copy(&path, &content_path));
Ok((self.id.clone(), LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled)))
}
fn done(&self, endpoint: Option<LocalPageEndpoint>) {
(self.on_done)(self.id.clone(), endpoint)
}
}
struct DappInstaller {
id: String,
dapps_path: PathBuf,
on_done: Box<Fn(String, Option<LocalPageEndpoint>) + Send>,
embeddable_on: Option<(String, u16)>,
}
impl DappInstaller {
fn find_manifest(zip: &mut zip::ZipArchive<fs::File>) -> Result<(Manifest, PathBuf), ValidationError> {
for i in 0..zip.len() {
let mut file = try!(zip.by_index(i));
if !file.name().ends_with(MANIFEST_FILENAME) {
continue;
}
// try to read manifest
let mut manifest = String::new();
let manifest = file
.read_to_string(&mut manifest).ok()
.and_then(|_| deserialize_manifest(manifest).ok());
if let Some(manifest) = manifest {
let mut manifest_location = PathBuf::from(file.name());
manifest_location.pop(); // get rid of filename
return Ok((manifest, manifest_location));
}
}
Err(ValidationError::ManifestNotFound)
}
fn dapp_target_path(&self, manifest: &Manifest) -> PathBuf {
let mut target = self.dapps_path.clone();
target.push(&manifest.id);
target
}
}
impl ContentValidator for DappInstaller {
type Error = ValidationError;
fn validate_and_install(&self, app_path: PathBuf) -> Result<(String, LocalPageEndpoint), ValidationError> {
trace!(target: "dapps", "Opening dapp bundle at {:?}", app_path);
let mut file_reader = io::BufReader::new(try!(fs::File::open(app_path)));
let hash = try!(sha3(&mut file_reader));
let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId));
if id != hash {
return Err(ValidationError::HashMismatch {
expected: id,
got: hash,
});
}
let file = file_reader.into_inner();
// Unpack archive
let mut zip = try!(zip::ZipArchive::new(file));
// First find manifest file
let (mut manifest, manifest_dir) = try!(Self::find_manifest(&mut zip));
// Overwrite id to match hash
manifest.id = self.id.clone();
let target = self.dapp_target_path(&manifest);
// Remove old directory
if target.exists() {
warn!(target: "dapps", "Overwriting existing dapp: {}", manifest.id);
try!(fs::remove_dir_all(target.clone()));
}
// Unpack zip
for i in 0..zip.len() {
let mut file = try!(zip.by_index(i));
// TODO [todr] Check if it's consistent on windows.
let is_dir = file.name().chars().rev().next() == Some('/');
let file_path = PathBuf::from(file.name());
let location_in_manifest_base = file_path.strip_prefix(&manifest_dir);
// Create files that are inside manifest directory
if let Ok(location_in_manifest_base) = location_in_manifest_base {
let p = target.join(location_in_manifest_base);
// Check if it's a directory
if is_dir {
try!(fs::create_dir_all(p));
} else {
let mut target = try!(fs::File::create(p));
try!(io::copy(&mut file, &mut target));
}
}
}
// Write manifest
let manifest_str = try!(serialize_manifest(&manifest).map_err(ValidationError::ManifestSerialization));
let manifest_path = target.join(MANIFEST_FILENAME);
let mut manifest_file = try!(fs::File::create(manifest_path));
try!(manifest_file.write_all(manifest_str.as_bytes()));
// Create endpoint
let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone());
// Return modified app manifest
Ok((manifest.id.clone(), app))
}
fn done(&self, endpoint: Option<LocalPageEndpoint>) {
(self.on_done)(self.id.clone(), endpoint)
}
}
#[cfg(test)]
mod tests {
use std::env;
use std::sync::Arc;
use util::Bytes;
use endpoint::EndpointInfo;
use page::LocalPageEndpoint;
use apps::cache::ContentStatus;
use apps::urlhint::{URLHint, URLHintResult};
use super::ContentFetcher;
struct FakeResolver;
impl URLHint for FakeResolver {
fn resolve(&self, _id: Bytes) -> Option<URLHintResult> {
None
}
}
#[test]
fn should_true_if_contains_the_app() {
// given
let path = env::temp_dir();
let fetcher = ContentFetcher::new(FakeResolver, Arc::new(|| false), None);
let handler = LocalPageEndpoint::new(path, EndpointInfo {
name: "fake".into(),
description: "".into(),
version: "".into(),
author: "".into(),
icon_url: "".into(),
}, Default::default(), None);
// when
fetcher.set_status("test", ContentStatus::Ready(handler));
fetcher.set_status("test2", ContentStatus::Fetching(Default::default()));
// then
assert_eq!(fetcher.contains("test"), true);
assert_eq!(fetcher.contains("test2"), true);
assert_eq!(fetcher.contains("test3"), false);
}
}

View File

@@ -1,256 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use zip;
use std::{fs, fmt};
use std::io::{self, Read, Write};
use std::path::PathBuf;
use fetch::{self, Mime};
use util::H256;
use util::sha3::sha3;
use page::{LocalPageEndpoint, PageCache};
use handlers::{ContentValidator, ValidatorResponse};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest};
use Embeddable;
type OnDone = Box<Fn(Option<LocalPageEndpoint>) + Send>;
fn write_response_and_check_hash(
id: &str,
mut content_path: PathBuf,
filename: &str,
response: fetch::Response
) -> Result<(fs::File, PathBuf), ValidationError> {
// try to parse id
let id = id.parse().map_err(|_| ValidationError::InvalidContentId)?;
// check if content exists
if content_path.exists() {
warn!(target: "dapps", "Overwriting existing content at 0x{:?}", id);
fs::remove_dir_all(&content_path)?
}
// create directory
fs::create_dir_all(&content_path)?;
// append filename
content_path.push(filename);
// Now write the response
let mut file = io::BufWriter::new(fs::File::create(&content_path)?);
let mut reader = io::BufReader::new(response);
io::copy(&mut reader, &mut file)?;
file.flush()?;
// Validate hash
// TODO [ToDr] calculate sha3 in-flight while reading the response
let mut file = io::BufReader::new(fs::File::open(&content_path)?);
let hash = sha3(&mut file)?;
if id == hash {
Ok((file.into_inner(), content_path))
} else {
Err(ValidationError::HashMismatch {
expected: id,
got: hash,
})
}
}
pub struct Content {
id: String,
mime: Mime,
content_path: PathBuf,
on_done: OnDone,
}
impl Content {
pub fn new(id: String, mime: Mime, content_path: PathBuf, on_done: OnDone) -> Self {
Content {
id: id,
mime: mime,
content_path: content_path,
on_done: on_done,
}
}
}
impl ContentValidator for Content {
type Error = ValidationError;
fn validate_and_install(&self, response: fetch::Response) -> Result<ValidatorResponse, ValidationError> {
let validate = |content_path: PathBuf| {
// Create dir
let (_, content_path) = write_response_and_check_hash(self.id.as_str(), content_path.clone(), self.id.as_str(), response)?;
Ok(LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled))
};
// Prepare path for a file
let content_path = self.content_path.join(&self.id);
// Make sure to always call on_done (even in case of errors)!
let result = validate(content_path.clone());
// remove the file if there was an error
if result.is_err() {
// Ignore errors since the file might not exist
let _ = fs::remove_dir_all(&content_path);
}
(self.on_done)(result.as_ref().ok().cloned());
result.map(ValidatorResponse::Local)
}
}
pub struct Dapp {
id: String,
dapps_path: PathBuf,
on_done: OnDone,
embeddable_on: Embeddable,
}
impl Dapp {
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Embeddable) -> Self {
Dapp {
id,
dapps_path,
on_done,
embeddable_on,
}
}
fn find_manifest(zip: &mut zip::ZipArchive<fs::File>) -> Result<(Manifest, PathBuf), ValidationError> {
for i in 0..zip.len() {
let mut file = zip.by_index(i)?;
if !file.name().ends_with(MANIFEST_FILENAME) {
continue;
}
// try to read manifest
let mut manifest = String::new();
let manifest = file
.read_to_string(&mut manifest).ok()
.and_then(|_| deserialize_manifest(manifest).ok());
if let Some(manifest) = manifest {
let mut manifest_location = PathBuf::from(file.name());
manifest_location.pop(); // get rid of filename
return Ok((manifest, manifest_location));
}
}
Err(ValidationError::ManifestNotFound)
}
}
impl ContentValidator for Dapp {
type Error = ValidationError;
fn validate_and_install(&self, response: fetch::Response) -> Result<ValidatorResponse, ValidationError> {
let validate = |dapp_path: PathBuf| {
let (file, zip_path) = write_response_and_check_hash(self.id.as_str(), dapp_path.clone(), &format!("{}.zip", self.id), response)?;
trace!(target: "dapps", "Opening dapp bundle at {:?}", zip_path);
// Unpack archive
let mut zip = zip::ZipArchive::new(file)?;
// First find manifest file
let (mut manifest, manifest_dir) = Self::find_manifest(&mut zip)?;
// Overwrite id to match hash
manifest.id = self.id.clone();
// Unpack zip
for i in 0..zip.len() {
let mut file = zip.by_index(i)?;
let is_dir = file.name().chars().rev().next() == Some('/');
let file_path = PathBuf::from(file.name());
let location_in_manifest_base = file_path.strip_prefix(&manifest_dir);
// Create files that are inside manifest directory
if let Ok(location_in_manifest_base) = location_in_manifest_base {
let p = dapp_path.join(location_in_manifest_base);
// Check if it's a directory
if is_dir {
fs::create_dir_all(p)?;
} else {
let mut target = fs::File::create(p)?;
io::copy(&mut file, &mut target)?;
}
}
}
// Remove zip
fs::remove_file(&zip_path)?;
// Write manifest
let manifest_str = serialize_manifest(&manifest).map_err(ValidationError::ManifestSerialization)?;
let manifest_path = dapp_path.join(MANIFEST_FILENAME);
let mut manifest_file = fs::File::create(manifest_path)?;
manifest_file.write_all(manifest_str.as_bytes())?;
// Create endpoint
let endpoint = LocalPageEndpoint::new(dapp_path, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone());
Ok(endpoint)
};
// Prepare directory for dapp
let target = self.dapps_path.join(&self.id);
// Validate the dapp
let result = validate(target.clone());
// remove the file if there was an error
if result.is_err() {
// Ignore errors since the file might not exist
let _ = fs::remove_dir_all(&target);
}
(self.on_done)(result.as_ref().ok().cloned());
result.map(ValidatorResponse::Local)
}
}
#[derive(Debug)]
pub enum ValidationError {
Io(io::Error),
Zip(zip::result::ZipError),
InvalidContentId,
ManifestNotFound,
ManifestSerialization(String),
HashMismatch { expected: H256, got: H256, },
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
ValidationError::Io(ref io) => write!(f, "Unexpected IO error occured: {:?}", io),
ValidationError::Zip(ref zip) => write!(f, "Unable to read ZIP archive: {:?}", zip),
ValidationError::InvalidContentId => write!(f, "ID is invalid. It should be 256 bits keccak hash of content."),
ValidationError::ManifestNotFound => write!(f, "Downloaded Dapp bundle did not contain valid manifest.json file."),
ValidationError::ManifestSerialization(ref err) => {
write!(f, "There was an error during Dapp Manifest serialization: {:?}", err)
},
ValidationError::HashMismatch { ref expected, ref got } => {
write!(f, "Hash of downloaded content did not match. Expected:{:?}, Got:{:?}.", expected, got)
},
}
}
}
impl From<io::Error> for ValidationError {
fn from(err: io::Error) -> Self {
ValidationError::Io(err)
}
}
impl From<zip::result::ZipError> for ValidationError {
fn from(err: zip::result::ZipError) -> Self {
ValidationError::Zip(err)
}
}

View File

@@ -1,314 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Fetchable Dapps support.
//! Manages downloaded (cached) Dapps and downloads them when necessary.
//! Uses `URLHint` to resolve addresses into Dapps bundle file location.
mod installers;
use std::{fs, env};
use std::path::PathBuf;
use std::sync::Arc;
use rustc_hex::FromHex;
use fetch::{Client as FetchClient, Fetch};
use hash_fetch::urlhint::{URLHintContract, URLHint, URLHintResult};
use parity_reactor::Remote;
use hyper;
use hyper::status::StatusCode;
use {Embeddable, SyncStatus, random_filename};
use util::Mutex;
use page::LocalPageEndpoint;
use handlers::{ContentHandler, ContentFetcherHandler};
use endpoint::{Endpoint, EndpointPath, Handler};
use apps::cache::{ContentCache, ContentStatus};
/// Limit of cached dapps/content
const MAX_CACHED_DAPPS: usize = 20;
pub trait Fetcher: Send + Sync + 'static {
fn contains(&self, content_id: &str) -> bool;
fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler>;
}
pub struct ContentFetcher<F: Fetch = FetchClient, R: URLHint + 'static = URLHintContract> {
cache_path: PathBuf,
resolver: R,
cache: Arc<Mutex<ContentCache>>,
sync: Arc<SyncStatus>,
embeddable_on: Embeddable,
remote: Remote,
fetch: F,
only_content: bool,
}
impl<R: URLHint + 'static, F: Fetch> Drop for ContentFetcher<F, R> {
fn drop(&mut self) {
// Clear cache path
let _ = fs::remove_dir_all(&self.cache_path);
}
}
impl<R: URLHint + 'static, F: Fetch> ContentFetcher<F, R> {
pub fn new(
resolver: R,
sync_status: Arc<SyncStatus>,
remote: Remote,
fetch: F,
) -> Self {
let mut cache_path = env::temp_dir();
cache_path.push(random_filename());
ContentFetcher {
cache_path: cache_path,
resolver: resolver,
sync: sync_status,
cache: Arc::new(Mutex::new(ContentCache::default())),
embeddable_on: None,
remote: remote,
fetch: fetch,
only_content: true,
}
}
pub fn allow_dapps(mut self, dapps: bool) -> Self {
self.only_content = !dapps;
self
}
pub fn embeddable_on(mut self, embeddable_on: Embeddable) -> Self {
self.embeddable_on = embeddable_on;
self
}
fn still_syncing(embeddable: Embeddable) -> Box<Handler> {
Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable,
"Sync In Progress",
"Your node is still syncing. We cannot resolve any content before it's fully synced.",
Some("<a href=\"javascript:window.location.reload()\">Refresh</a>"),
embeddable,
))
}
fn dapps_disabled(address: Embeddable) -> Box<Handler> {
Box::new(ContentHandler::error(
StatusCode::ServiceUnavailable,
"Network Dapps Not Available",
"This interface doesn't support network dapps for security reasons.",
None,
address,
))
}
#[cfg(test)]
fn set_status(&self, content_id: &str, status: ContentStatus) {
self.cache.lock().insert(content_id.to_owned(), status);
}
// resolve contract call synchronously.
// TODO: port to futures-based hyper and make it all async.
fn resolve(&self, content_id: Vec<u8>) -> Option<URLHintResult> {
use futures::Future;
self.resolver.resolve(content_id)
.wait()
.unwrap_or_else(|e| { warn!("Error resolving content-id: {}", e); None })
}
}
impl<R: URLHint + 'static, F: Fetch> Fetcher for ContentFetcher<F, R> {
fn contains(&self, content_id: &str) -> bool {
{
let mut cache = self.cache.lock();
// Check if we already have the app
if cache.get(content_id).is_some() {
return true;
}
}
// fallback to resolver
if let Ok(content_id) = content_id.from_hex() {
// if there is content or we are syncing return true
self.sync.is_major_importing() || self.resolve(content_id).is_some()
} else {
false
}
}
fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler> {
let mut cache = self.cache.lock();
let content_id = path.app_id.clone();
let (new_status, handler) = {
let status = cache.get(&content_id);
match status {
// Just serve the content
Some(&mut ContentStatus::Ready(ref endpoint)) => {
(None, endpoint.to_async_handler(path, control))
},
// Content is already being fetched
Some(&mut ContentStatus::Fetching(ref fetch_control)) if !fetch_control.is_deadline_reached() => {
trace!(target: "dapps", "Content fetching in progress. Waiting...");
(None, fetch_control.to_async_handler(path, control))
},
// We need to start fetching the content
_ => {
trace!(target: "dapps", "Content unavailable. Fetching... {:?}", content_id);
let content_hex = content_id.from_hex().expect("to_handler is called only when `contains` returns true.");
let content = self.resolve(content_hex);
let cache = self.cache.clone();
let id = content_id.clone();
let on_done = move |result: Option<LocalPageEndpoint>| {
let mut cache = cache.lock();
match result {
Some(endpoint) => cache.insert(id.clone(), ContentStatus::Ready(endpoint)),
// In case of error
None => cache.remove(&id),
};
};
match content {
// Don't serve dapps if we are still syncing (but serve content)
Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => {
(None, Self::still_syncing(self.embeddable_on.clone()))
},
Some(URLHintResult::Dapp(_)) if self.only_content => {
(None, Self::dapps_disabled(self.embeddable_on.clone()))
},
Some(URLHintResult::Dapp(dapp)) => {
let handler = ContentFetcherHandler::new(
dapp.url(),
path,
control,
installers::Dapp::new(
content_id.clone(),
self.cache_path.clone(),
Box::new(on_done),
self.embeddable_on.clone(),
),
self.embeddable_on.clone(),
self.remote.clone(),
self.fetch.clone(),
);
(Some(ContentStatus::Fetching(handler.fetch_control())), Box::new(handler) as Box<Handler>)
},
Some(URLHintResult::Content(content)) => {
let handler = ContentFetcherHandler::new(
content.url,
path,
control,
installers::Content::new(
content_id.clone(),
content.mime,
self.cache_path.clone(),
Box::new(on_done),
),
self.embeddable_on.clone(),
self.remote.clone(),
self.fetch.clone(),
);
(Some(ContentStatus::Fetching(handler.fetch_control())), Box::new(handler) as Box<Handler>)
},
None if self.sync.is_major_importing() => {
(None, Self::still_syncing(self.embeddable_on.clone()))
},
None => {
// This may happen when sync status changes in between
// `contains` and `to_handler`
(None, Box::new(ContentHandler::error(
StatusCode::NotFound,
"Resource Not Found",
"Requested resource was not found.",
None,
self.embeddable_on.clone(),
)) as Box<Handler>)
},
}
},
}
};
if let Some(status) = new_status {
cache.clear_garbage(MAX_CACHED_DAPPS);
cache.insert(content_id, status);
}
handler
}
}
#[cfg(test)]
mod tests {
use std::env;
use std::sync::Arc;
use util::Bytes;
use fetch::{Fetch, Client};
use futures::{future, Future, BoxFuture};
use hash_fetch::urlhint::{URLHint, URLHintResult};
use parity_reactor::Remote;
use apps::cache::ContentStatus;
use endpoint::EndpointInfo;
use page::LocalPageEndpoint;
use super::{ContentFetcher, Fetcher};
use {SyncStatus};
#[derive(Clone)]
struct FakeResolver;
impl URLHint for FakeResolver {
fn resolve(&self, _id: Bytes) -> BoxFuture<Option<URLHintResult>, String> {
future::ok(None).boxed()
}
}
#[derive(Debug)]
struct FakeSync(bool);
impl SyncStatus for FakeSync {
fn is_major_importing(&self) -> bool { self.0 }
fn peers(&self) -> (usize, usize) { (0, 5) }
}
#[test]
fn should_true_if_contains_the_app() {
// given
let path = env::temp_dir();
let fetcher = ContentFetcher::new(FakeResolver, Arc::new(FakeSync(false)), Remote::new_sync(), Client::new().unwrap())
.allow_dapps(true);
let handler = LocalPageEndpoint::new(path, EndpointInfo {
name: "fake".into(),
description: "".into(),
version: "".into(),
author: "".into(),
icon_url: "".into(),
}, Default::default(), None);
// when
fetcher.set_status("test", ContentStatus::Ready(handler));
fetcher.set_status("test2", ContentStatus::Fetching(Default::default()));
// then
assert_eq!(fetcher.contains("test"), true);
assert_eq!(fetcher.contains("test2"), true);
assert_eq!(fetcher.contains("test3"), false);
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,15 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::collections::BTreeMap;
use std::io;
use std::io::Read;
use std::fs;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use page::{LocalPageEndpoint, PageCache};
use endpoint::{Endpoint, EndpointInfo};
use endpoint::{Endpoints, EndpointInfo};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
use Embeddable;
struct LocalDapp {
id: String,
@@ -30,86 +28,17 @@ struct LocalDapp {
info: EndpointInfo,
}
/// Tries to find and read manifest file in given `path` to extract `EndpointInfo`
/// If manifest is not found sensible default `EndpointInfo` is returned based on given `name`.
fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
path.push(MANIFEST_FILENAME);
fs::File::open(path.clone())
.map_err(|e| format!("{:?}", e))
.and_then(|mut f| {
// Reat file
let mut s = String::new();
f.read_to_string(&mut s).map_err(|e| format!("{:?}", e))?;
// Try to deserialize manifest
deserialize_manifest(s)
})
.map(Into::into)
.unwrap_or_else(|e| {
warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
EndpointInfo {
name: name.into(),
description: name.into(),
version: "0.0.0".into(),
author: "?".into(),
icon_url: "icon.png".into(),
}
})
}
/// Returns Dapp Id and Local Dapp Endpoint for given filesystem path.
/// Parses the path to extract last component (for name).
/// `None` is returned when path is invalid or non-existent.
pub fn local_endpoint<P: AsRef<Path>>(path: P, embeddable: Embeddable) -> Option<(String, Box<LocalPageEndpoint>)> {
let path = path.as_ref().to_owned();
path.canonicalize().ok().and_then(|path| {
let name = path.file_name().and_then(|name| name.to_str());
name.map(|name| {
let dapp = local_dapp(name.into(), path.clone());
(dapp.id, Box::new(LocalPageEndpoint::new(
dapp.path, dapp.info, PageCache::Disabled, embeddable.clone())
))
})
})
}
fn local_dapp(name: String, path: PathBuf) -> LocalDapp {
// try to get manifest file
let info = read_manifest(&name, path.clone());
LocalDapp {
id: name,
path: path,
info: info,
}
}
/// Returns endpoints for Local Dapps found for given filesystem path.
/// Scans the directory and collects `LocalPageEndpoints`.
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, embeddable: Embeddable) -> BTreeMap<String, Box<Endpoint>> {
let mut pages = BTreeMap::<String, Box<Endpoint>>::new();
for dapp in local_dapps(dapps_path.as_ref()) {
pages.insert(
dapp.id,
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, embeddable.clone()))
);
}
pages
}
fn local_dapps(dapps_path: &Path) -> Vec<LocalDapp> {
let files = fs::read_dir(dapps_path);
fn local_dapps(dapps_path: String) -> Vec<LocalDapp> {
let files = fs::read_dir(dapps_path.as_str());
if let Err(e) = files {
warn!(target: "dapps", "Unable to load local dapps from: {}. Reason: {:?}", dapps_path.display(), e);
warn!(target: "dapps", "Unable to load local dapps from: {}. Reason: {:?}", dapps_path, e);
return vec![];
}
let files = files.expect("Check is done earlier");
files.map(|dir| {
let entry = dir?;
let file_type = entry.file_type()?;
let entry = try!(dir);
let file_type = try!(entry.file_type());
// skip files
if file_type.is_file() {
@@ -130,6 +59,51 @@ fn local_dapps(dapps_path: &Path) -> Vec<LocalDapp> {
}
m.ok()
})
.map(|(name, path)| local_dapp(name, path))
.map(|(name, path)| {
// try to get manifest file
let info = read_manifest(&name, path.clone());
LocalDapp {
id: name,
path: path,
info: info,
}
})
.collect()
}
fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
path.push(MANIFEST_FILENAME);
fs::File::open(path.clone())
.map_err(|e| format!("{:?}", e))
.and_then(|mut f| {
// Reat file
let mut s = String::new();
try!(f.read_to_string(&mut s).map_err(|e| format!("{:?}", e)));
// Try to deserialize manifest
deserialize_manifest(s)
})
.map(Into::into)
.unwrap_or_else(|e| {
warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
EndpointInfo {
name: name.into(),
description: name.into(),
version: "0.0.0".into(),
author: "?".into(),
icon_url: "icon.png".into(),
}
})
}
pub fn local_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints {
let mut pages = Endpoints::new();
for dapp in local_dapps(dapps_path) {
pages.insert(
dapp.id,
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone()))
);
}
pages
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use serde_json;
pub use apps::App as Manifest;
pub use api::App as Manifest;
pub const MANIFEST_FILENAME: &'static str = "manifest.json";

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,76 +14,41 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::Arc;
use endpoint::{Endpoints, Endpoint};
use page::PageEndpoint;
use proxypac::ProxyPac;
use web::Web;
use fetch::Fetch;
use parity_dapps::WebApp;
use parity_reactor::Remote;
use parity_ui;
use {WebProxyTokens, ParentFrameSettings};
mod app;
mod cache;
mod fs;
mod ui;
pub mod urlhint;
pub mod fetcher;
pub mod manifest;
pub use self::app::App;
extern crate parity_ui;
pub const HOME_PAGE: &'static str = "home";
pub const RPC_PATH: &'static str = "rpc";
pub const API_PATH: &'static str = "api";
pub const UTILS_PATH: &'static str = "parity-utils";
pub const WEB_PATH: &'static str = "web";
pub const URL_REFERER: &'static str = "__referer=";
pub const DAPPS_DOMAIN : &'static str = ".parity";
pub const RPC_PATH : &'static str = "rpc";
pub const API_PATH : &'static str = "api";
pub const UTILS_PATH : &'static str = "parity-utils";
pub fn utils() -> Box<Endpoint> {
Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned()))
}
pub fn ui() -> Box<Endpoint> {
Box::new(PageEndpoint::with_fallback_to_index(parity_ui::App::default()))
}
pub fn ui_redirection(embeddable: Option<ParentFrameSettings>) -> Box<Endpoint> {
Box::new(ui::Redirection::new(embeddable))
}
pub fn all_endpoints<F: Fetch>(
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
dapps_domain: &str,
embeddable: Option<ParentFrameSettings>,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
fetch: F,
) -> Endpoints {
pub fn all_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints {
// fetch fs dapps at first to avoid overwriting builtins
let mut pages = fs::local_endpoints(dapps_path, embeddable.clone());
for path in extra_dapps {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), embeddable.clone()) {
pages.insert(id, endpoint);
} else {
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
}
}
let mut pages = fs::local_endpoints(dapps_path, signer_address.clone());
// NOTE [ToDr] Dapps will be currently embeded on 8180
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(embeddable.clone()));
pages.insert("proxy".into(), ProxyPac::boxed(embeddable.clone(), dapps_domain.to_owned()));
pages.insert(WEB_PATH.into(), Web::boxed(embeddable.clone(), web_proxy_tokens.clone(), remote.clone(), fetch.clone()));
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_address.clone()));
pages.insert("proxy".into(), ProxyPac::boxed(signer_address));
Arc::new(pages)
pages
}
fn insert<T : WebApp + Default + 'static>(pages: &mut BTreeMap<String, Box<Endpoint>>, id: &str, embed_at: Embeddable) {
fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str, embed_at: Embeddable) {
pages.insert(id.to_owned(), Box::new(match embed_at {
Embeddable::Yes(address) => PageEndpoint::new_safe_to_embed(T::default(), address),
Embeddable::No => PageEndpoint::new(T::default()),
@@ -91,7 +56,7 @@ fn insert<T : WebApp + Default + 'static>(pages: &mut BTreeMap<String, Box<Endpo
}
enum Embeddable {
Yes(Option<ParentFrameSettings>),
Yes(Option<(String, u16)>),
#[allow(dead_code)]
No,
}

View File

@@ -1,55 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! UI redirections
use hyper::{Control, StatusCode};
use endpoint::{Endpoint, Handler, EndpointPath};
use {handlers, Embeddable};
/// Redirection to UI server.
pub struct Redirection {
embeddable_on: Embeddable,
}
impl Redirection {
pub fn new(
embeddable_on: Embeddable,
) -> Self {
Redirection {
embeddable_on,
}
}
}
impl Endpoint for Redirection {
fn to_async_handler(&self, _path: EndpointPath, _control: Control) -> Box<Handler> {
if let Some(ref frame) = self.embeddable_on {
trace!(target: "dapps", "Redirecting to signer interface.");
handlers::Redirection::boxed(&format!("http://{}:{}", &frame.host, frame.port))
} else {
trace!(target: "dapps", "Signer disabled, returning 404.");
Box::new(handlers::ContentHandler::error(
StatusCode::NotFound,
"404 Not Found",
"Your homepage is not available when Trusted Signer is disabled.",
Some("You can still access dapps by writing a correct address, though. Re-enable Signer to get your homepage back."),
None,
))
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,43 +14,25 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! URLHint Contract
use std::fmt;
use std::sync::Arc;
use rustc_hex::ToHex;
use mime::Mime;
use rustc_serialize::hex::ToHex;
use mime_guess;
use futures::{future, BoxFuture, Future};
use native_contracts::{Registry, Urlhint};
use ethabi::{Interface, Contract, Token};
use util::{Address, Bytes, Hashable};
const COMMIT_LEN: usize = 20;
/// RAW Contract interface.
/// Should execute transaction using current blockchain state.
pub trait ContractClient: Send + Sync {
/// Get registrar address
fn registrar(&self) -> Result<Address, String>;
/// Call Contract
fn call(&self, address: Address, data: Bytes) -> BoxFuture<Bytes, String>;
}
/// Github-hosted dapp.
#[derive(Debug, PartialEq)]
pub struct GithubApp {
/// Github Account
pub account: String,
/// Github Repository
pub repo: String,
/// Commit on Github
pub commit: [u8; COMMIT_LEN],
/// Dapp owner address
pub commit: [u8;COMMIT_LEN],
pub owner: Address,
}
impl GithubApp {
/// Returns URL of this Github-hosted dapp package.
pub fn url(&self) -> String {
// Since https fetcher doesn't support redirections we use direct link
// format!("https://github.com/{}/{}/archive/{}.zip", self.account, self.repo, self.commit.to_hex())
@@ -71,17 +53,22 @@ impl GithubApp {
}
}
/// Hash-Addressed Content
#[derive(Debug, PartialEq)]
pub struct Content {
/// URL of the content
pub url: String,
/// MIME type of the content
pub mime: Mime,
/// Content owner address
pub mime: String,
pub owner: Address,
}
/// RAW Contract interface.
/// Should execute transaction using current blockchain state.
pub trait ContractClient: Send + Sync {
/// Get registrar address
fn registrar(&self) -> Result<Address, String>;
/// Call Contract
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String>;
}
/// Result of resolving id to URL
#[derive(Debug, PartialEq)]
pub enum URLHintResult {
@@ -92,97 +79,149 @@ pub enum URLHintResult {
}
/// URLHint Contract interface
pub trait URLHint: Send + Sync {
pub trait URLHint {
/// Resolves given id to registrar entry.
fn resolve(&self, id: Bytes) -> BoxFuture<Option<URLHintResult>, String>;
fn resolve(&self, id: Bytes) -> Option<URLHintResult>;
}
/// `URLHintContract` API
#[derive(Clone)]
pub struct URLHintContract {
urlhint: Arc<Urlhint>,
registrar: Registry,
urlhint: Contract,
registrar: Contract,
client: Arc<ContractClient>,
}
impl URLHintContract {
/// Creates new `URLHintContract`
pub fn new(client: Arc<ContractClient>) -> Self {
let urlhint = Interface::load(include_bytes!("./urlhint.json")).expect("urlhint.json is valid ABI");
let registrar = Interface::load(include_bytes!("./registrar.json")).expect("registrar.json is valid ABI");
URLHintContract {
urlhint: Arc::new(Urlhint::new(Default::default())),
registrar: Registry::new(Default::default()),
urlhint: Contract::new(urlhint),
registrar: Contract::new(registrar),
client: client,
}
}
}
fn decode_urlhint_output(output: (String, ::util::H160, Address)) -> Option<URLHintResult> {
let (account_slash_repo, commit, owner) = output;
fn urlhint_address(&self) -> Option<Address> {
let res = || {
let get_address = try!(self.registrar.function("getAddress".into()).map_err(as_string));
let params = try!(get_address.encode_call(
vec![Token::FixedBytes((*"githubhint".sha3()).to_vec()), Token::String("A".into())]
).map_err(as_string));
let output = try!(self.client.call(try!(self.client.registrar()), params));
let result = try!(get_address.decode_output(output).map_err(as_string));
if owner == Address::default() {
return None;
}
match result.get(0) {
Some(&Token::Address(address)) if address != *Address::default() => Ok(address.into()),
Some(&Token::Address(_)) => Err(format!("Contract not found.")),
e => Err(format!("Invalid result: {:?}", e)),
}
};
let commit = GithubApp::commit(&commit);
if commit == Some(Default::default()) {
let mime = guess_mime_type(&account_slash_repo).unwrap_or(mime!(Application/_));
return Some(URLHintResult::Content(Content {
url: account_slash_repo,
mime: mime,
owner: owner,
}));
}
let (account, repo) = {
let mut it = account_slash_repo.split('/');
match (it.next(), it.next()) {
(Some(account), Some(repo)) => (account.into(), repo.into()),
_ => return None,
match res() {
Ok(res) => Some(res),
Err(e) => {
warn!(target: "dapps", "Error while calling registrar: {:?}", e);
None
}
}
};
}
commit.map(|commit| URLHintResult::Dapp(GithubApp {
account: account,
repo: repo,
commit: commit,
owner: owner,
}))
fn encode_urlhint_call(&self, id: Bytes) -> Option<Bytes> {
let call = self.urlhint
.function("entries".into())
.and_then(|f| f.encode_call(vec![Token::FixedBytes(id)]));
match call {
Ok(res) => {
Some(res)
},
Err(e) => {
warn!(target: "dapps", "Error while encoding urlhint call: {:?}", e);
None
}
}
}
fn decode_urlhint_output(&self, output: Bytes) -> Option<URLHintResult> {
trace!(target: "dapps", "Output: {:?}", output.to_hex());
let output = self.urlhint
.function("entries".into())
.and_then(|f| f.decode_output(output));
if let Ok(vec) = output {
if vec.len() != 3 {
warn!(target: "dapps", "Invalid contract output: {:?}", vec);
return None;
}
let mut it = vec.into_iter();
let account_slash_repo = it.next().expect("element 0 of 3-len vector known to exist; qed");
let commit = it.next().expect("element 1 of 3-len vector known to exist; qed");
let owner = it.next().expect("element 2 of 3-len vector known to exist qed");
match (account_slash_repo, commit, owner) {
(Token::String(account_slash_repo), Token::FixedBytes(commit), Token::Address(owner)) => {
let owner = owner.into();
if owner == Address::default() {
return None;
}
let commit = GithubApp::commit(&commit);
if commit == Some(Default::default()) {
let mime = guess_mime_type(&account_slash_repo).unwrap_or("application/octet-stream".into());
return Some(URLHintResult::Content(Content {
url: account_slash_repo,
mime: mime,
owner: owner,
}));
}
let (account, repo) = {
let mut it = account_slash_repo.split('/');
match (it.next(), it.next()) {
(Some(account), Some(repo)) => (account.into(), repo.into()),
_ => return None,
}
};
commit.map(|commit| URLHintResult::Dapp(GithubApp {
account: account,
repo: repo,
commit: commit,
owner: owner,
}))
},
e => {
warn!(target: "dapps", "Invalid contract output parameters: {:?}", e);
None
},
}
} else {
warn!(target: "dapps", "Invalid contract output: {:?}", output);
None
}
}
}
impl URLHint for URLHintContract {
fn resolve(&self, id: Bytes) -> BoxFuture<Option<URLHintResult>, String> {
use futures::future::Either;
let do_call = |_, data| {
let addr = match self.client.registrar() {
Ok(addr) => addr,
Err(e) => return future::err(e).boxed(),
};
self.client.call(addr, data)
};
let urlhint = self.urlhint.clone();
let client = self.client.clone();
self.registrar.get_address(do_call, "githubhint".sha3(), "A".into())
.map(|addr| if addr == Address::default() { None } else { Some(addr) })
.and_then(move |address| {
let mut fixed_id = [0; 32];
let len = ::std::cmp::min(32, id.len());
fixed_id[..len].copy_from_slice(&id[..len]);
match address {
None => Either::A(future::ok(None)),
Some(address) => {
let do_call = move |_, data| client.call(address, data);
Either::B(urlhint.entries(do_call, ::util::H256(fixed_id)).map(decode_urlhint_output))
fn resolve(&self, id: Bytes) -> Option<URLHintResult> {
self.urlhint_address().and_then(|address| {
// Prepare contract call
self.encode_urlhint_call(id)
.and_then(|data| {
let call = self.client.call(address, data);
if let Err(ref e) = call {
warn!(target: "dapps", "Error while calling urlhint: {:?}", e);
}
}
}).boxed()
call.ok()
})
.and_then(|output| self.decode_urlhint_output(output))
})
}
}
fn guess_mime_type(url: &str) -> Option<Mime> {
fn guess_mime_type(url: &str) -> Option<String> {
const CONTENT_TYPE: &'static str = "content-type=";
let mut it = url.split('#');
@@ -194,39 +233,45 @@ fn guess_mime_type(url: &str) -> Option<Mime> {
for meta in metas.split('&') {
let meta = meta.to_lowercase();
if meta.starts_with(CONTENT_TYPE) {
return meta[CONTENT_TYPE.len()..].parse().ok();
return Some(meta[CONTENT_TYPE.len()..].to_owned());
}
}
}
url.and_then(|url| {
url.split('.').last()
}).and_then(|extension| {
mime_guess::get_mime_type_opt(extension)
mime_guess::get_mime_type_str(extension).map(Into::into)
})
}
#[cfg(test)]
pub mod tests {
pub fn test_guess_mime_type(url: &str) -> Option<String> {
guess_mime_type(url)
}
fn as_string<T: fmt::Debug>(e: T) -> String {
format!("{:?}", e)
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::str::FromStr;
use rustc_hex::FromHex;
use futures::{BoxFuture, Future, IntoFuture};
use rustc_serialize::hex::FromHex;
use super::*;
use super::guess_mime_type;
use util::{Bytes, Address, Mutex, ToPretty};
pub struct FakeRegistrar {
struct FakeRegistrar {
pub calls: Arc<Mutex<Vec<(String, String)>>>,
pub responses: Mutex<Vec<Result<Bytes, String>>>,
}
pub const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
pub const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
impl FakeRegistrar {
pub fn new() -> Self {
fn new() -> Self {
FakeRegistrar {
calls: Arc::new(Mutex::new(Vec::new())),
responses: Mutex::new(
@@ -240,14 +285,14 @@ pub mod tests {
}
impl ContractClient for FakeRegistrar {
fn registrar(&self) -> Result<Address, String> {
Ok(REGISTRAR.parse().unwrap())
}
fn call(&self, address: Address, data: Bytes) -> BoxFuture<Bytes, String> {
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
self.calls.lock().push((address.to_hex(), data.to_hex()));
let res = self.responses.lock().remove(0);
res.into_future().boxed()
self.responses.lock().remove(0)
}
}
@@ -255,19 +300,11 @@ pub mod tests {
fn should_call_registrar_and_urlhint_contracts() {
// given
let registrar = FakeRegistrar::new();
let resolve_result = {
use ethabi::{Encoder, Token};
Encoder::encode(vec![Token::String(String::new()), Token::FixedBytes(vec![0; 20]), Token::Address([0; 20])])
};
registrar.responses.lock()[1] = Ok(resolve_result);
let calls = registrar.calls.clone();
let urlhint = URLHintContract::new(Arc::new(registrar));
// when
let res = urlhint.resolve("test".bytes().collect()).wait().unwrap();
let res = urlhint.resolve("test".bytes().collect());
let calls = calls.lock();
let call0 = calls.get(0).expect("Registrar resolve called");
let call1 = calls.get(1).expect("URLHint Resolve called");
@@ -295,7 +332,7 @@ pub mod tests {
let urlhint = URLHintContract::new(Arc::new(registrar));
// when
let res = urlhint.resolve("test".bytes().collect()).wait().unwrap();
let res = urlhint.resolve("test".bytes().collect());
// then
assert_eq!(res, Some(URLHintResult::Dapp(GithubApp {
@@ -312,17 +349,17 @@ pub mod tests {
let mut registrar = FakeRegistrar::new();
registrar.responses = Mutex::new(vec![
Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()),
Ok("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff000000000000000000000000000000000000000000000000000000000000003c68747470733a2f2f7061726974792e696f2f6173736574732f696d616765732f657468636f72652d626c61636b2d686f72697a6f6e74616c2e706e6700000000".from_hex().unwrap()),
Ok("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff000000000000000000000000000000000000000000000000000000000000003d68747470733a2f2f657468636f72652e696f2f6173736574732f696d616765732f657468636f72652d626c61636b2d686f72697a6f6e74616c2e706e67000000".from_hex().unwrap()),
]);
let urlhint = URLHintContract::new(Arc::new(registrar));
// when
let res = urlhint.resolve("test".bytes().collect()).wait().unwrap();
let res = urlhint.resolve("test".bytes().collect());
// then
assert_eq!(res, Some(URLHintResult::Content(Content {
url: "https://parity.io/assets/images/ethcore-black-horizontal.png".into(),
mime: mime!(Image/Png),
url: "https://ethcore.io/assets/images/ethcore-black-horizontal.png".into(),
mime: "image/png".into(),
owner: Address::from_str("deadcafebeefbeefcafedeaddeedfeedffffffff").unwrap(),
})))
}
@@ -346,17 +383,17 @@ pub mod tests {
#[test]
fn should_guess_mime_type_from_url() {
let url1 = "https://parity.io/parity";
let url2 = "https://parity.io/parity#content-type=image/png";
let url3 = "https://parity.io/parity#something&content-type=image/png";
let url4 = "https://parity.io/parity.png#content-type=image/jpeg";
let url5 = "https://parity.io/parity.png";
let url1 = "https://ethcore.io/parity";
let url2 = "https://ethcore.io/parity#content-type=image/png";
let url3 = "https://ethcore.io/parity#something&content-type=image/png";
let url4 = "https://ethcore.io/parity.png#content-type=image/jpeg";
let url5 = "https://ethcore.io/parity.png";
assert_eq!(guess_mime_type(url1), None);
assert_eq!(guess_mime_type(url2), Some(mime!(Image/Png)));
assert_eq!(guess_mime_type(url3), Some(mime!(Image/Png)));
assert_eq!(guess_mime_type(url4), Some(mime!(Image/Jpeg)));
assert_eq!(guess_mime_type(url5), Some(mime!(Image/Png)));
assert_eq!(test_guess_mime_type(url1), None);
assert_eq!(test_guess_mime_type(url2), Some("image/png".into()));
assert_eq!(test_guess_mime_type(url3), Some("image/png".into()));
assert_eq!(test_guess_mime_type(url4), Some("image/jpeg".into()));
assert_eq!(test_guess_mime_type(url5), Some("image/png".into()));
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -16,15 +16,12 @@
//! URL Endpoint traits
use std::sync::Arc;
use std::collections::BTreeMap;
use hyper::{self, server, net};
use std::collections::BTreeMap;
#[derive(Debug, PartialEq, Default, Clone)]
pub struct EndpointPath {
pub app_id: String,
pub app_params: Vec<String>,
pub host: String,
pub port: u16,
pub using_dapps_domains: bool,
@@ -39,7 +36,7 @@ pub struct EndpointInfo {
pub icon_url: String,
}
pub type Endpoints = Arc<BTreeMap<String, Box<Endpoint>>>;
pub type Endpoints = BTreeMap<String, Box<Endpoint>>;
pub type Handler = server::Handler<net::HttpStream> + Send;
pub trait Endpoint : Send + Sync {

View File

@@ -1,112 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Async Content Handler
//! Temporary solution until we switch to future-based server.
//! Wraps a future and converts it to hyper::server::Handler;
use std::{mem, time};
use std::sync::mpsc;
use futures::Future;
use hyper::{server, Decoder, Encoder, Next, Control};
use hyper::net::HttpStream;
use handlers::ContentHandler;
use parity_reactor::Remote;
const TIMEOUT_SECS: u64 = 15;
enum State<F, T, M> {
Initial(F, M, Remote, Control),
Waiting(mpsc::Receiver<Result<T, ()>>, M),
Done(ContentHandler),
Invalid,
}
pub struct AsyncHandler<F, T, M> {
state: State<F, T, M>,
}
impl<F, T, M> AsyncHandler<F, T, M> {
pub fn new(future: F, map: M, remote: Remote, control: Control) -> Self {
AsyncHandler {
state: State::Initial(future, map, remote, control),
}
}
}
impl<F, T, E, M> server::Handler<HttpStream> for AsyncHandler<F, Result<T, E>, M> where
F: Future<Item=T, Error=E> + Send + 'static,
M: FnOnce(Result<Result<T, E>, ()>) -> ContentHandler,
T: Send + 'static,
E: Send + 'static,
{
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
if let State::Initial(future, map, remote, control) = mem::replace(&mut self.state, State::Invalid) {
let (tx, rx) = mpsc::sync_channel(1);
let control2 = control.clone();
let tx2 = tx.clone();
remote.spawn_with_timeout(move || future.then(move |result| {
// Send a result (ignore errors if the connection was dropped)
let _ = tx.send(Ok(result));
// Resume handler
let _ = control.ready(Next::read());
Ok(())
}), time::Duration::from_secs(TIMEOUT_SECS), move || {
// Notify about error
let _ = tx2.send(Err(()));
// Resume handler
let _ = control2.ready(Next::read());
});
self.state = State::Waiting(rx, map);
}
Next::wait()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
if let State::Waiting(rx, map) = mem::replace(&mut self.state, State::Invalid) {
match rx.try_recv() {
Ok(result) => {
self.state = State::Done(map(result));
},
Err(err) => {
warn!("Resuming handler in incorrect state: {:?}", err);
}
}
}
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
if let State::Done(ref mut handler) = self.state {
handler.on_response(res)
} else {
Next::end()
}
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
if let State::Done(ref mut handler) = self.state {
handler.on_response_writable(encoder)
} else {
Next::end()
}
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Authorization Handlers
use hyper::{server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::status::StatusCode;
pub struct AuthRequiredHandler;
impl server::Handler<HttpStream> for AuthRequiredHandler {
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(StatusCode::Unauthorized);
res.headers_mut().set_raw("WWW-Authenticate", vec![b"Basic realm=\"Parity\"".to_vec()]);
Next::write()
}
fn on_response_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
Next::end()
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -16,6 +16,7 @@
//! Simple Content Handler
use std::io::Write;
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::mime::Mime;
@@ -24,7 +25,6 @@ use hyper::status::StatusCode;
use util::version;
use handlers::add_security_headers;
use Embeddable;
#[derive(Clone)]
pub struct ContentHandler {
@@ -32,7 +32,7 @@ pub struct ContentHandler {
content: String,
mimetype: Mime,
write_pos: usize,
safe_to_embed_on: Embeddable,
safe_to_embed_on: Option<(String, u16)>,
}
impl ContentHandler {
@@ -40,17 +40,15 @@ impl ContentHandler {
Self::new(StatusCode::Ok, content, mimetype)
}
pub fn html(code: StatusCode, content: String, embeddable_on: Embeddable) -> Self {
pub fn not_found(content: String, mimetype: Mime) -> Self {
Self::new(StatusCode::NotFound, content, mimetype)
}
pub fn html(code: StatusCode, content: String, embeddable_on: Option<(String, u16)>) -> Self {
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on)
}
pub fn error(
code: StatusCode,
title: &str,
message: &str,
details: Option<&str>,
embeddable_on: Embeddable,
) -> Self {
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_on: Option<(String, u16)>) -> Self {
Self::html(code, format!(
include_str!("../error_tpl.html"),
title=title,
@@ -64,18 +62,13 @@ impl ContentHandler {
Self::new_embeddable(code, content, mimetype, None)
}
pub fn new_embeddable(
code: StatusCode,
content: String,
mimetype: Mime,
safe_to_embed_on: Embeddable,
) -> Self {
pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self {
ContentHandler {
code,
content,
mimetype,
code: code,
content: content,
mimetype: mimetype,
write_pos: 0,
safe_to_embed_on,
safe_to_embed_on: embeddable_on,
}
}
}
@@ -92,7 +85,7 @@ impl server::Handler<HttpStream> for ContentHandler {
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(self.code);
res.headers_mut().set(header::ContentType(self.mimetype.clone()));
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
Next::write()
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -16,57 +16,41 @@
//! Hyper Server Handler that fetches a file during a request (proxy).
use std::fmt;
use std::{fs, fmt};
use std::path::PathBuf;
use std::sync::{mpsc, Arc};
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Instant, Duration};
use fetch::{self, Fetch};
use futures::Future;
use parity_reactor::Remote;
use util::Mutex;
use url::Url;
use fetch::{Client, Fetch, FetchResult};
use hyper::{server, Decoder, Encoder, Next, Method, Control};
use hyper::net::HttpStream;
use hyper::uri::RequestUri;
use hyper::status::StatusCode;
use endpoint::EndpointPath;
use handlers::{ContentHandler, StreamingHandler};
use page::{LocalPageEndpoint, PageHandlerWaiting};
use {Embeddable};
use handlers::{ContentHandler, Redirection, extract_url};
use page::LocalPageEndpoint;
const FETCH_TIMEOUT: u64 = 300;
pub enum ValidatorResponse {
Local(LocalPageEndpoint),
Streaming(StreamingHandler<fetch::Response>),
}
pub trait ContentValidator: Send + 'static {
type Error: fmt::Debug + fmt::Display;
fn validate_and_install(&self, fetch::Response) -> Result<ValidatorResponse, Self::Error>;
}
const FETCH_TIMEOUT: u64 = 30;
enum FetchState {
Waiting,
NotStarted(String),
Error(ContentHandler),
InProgress(mpsc::Receiver<FetchState>),
Streaming(StreamingHandler<fetch::Response>),
Done(LocalPageEndpoint, Box<PageHandlerWaiting>),
InProgress(mpsc::Receiver<FetchResult>),
Done(String, LocalPageEndpoint, Redirection),
}
enum WaitResult {
Error(ContentHandler),
Done(LocalPageEndpoint),
NonAwaitable,
pub trait ContentValidator {
type Error: fmt::Debug + fmt::Display;
fn validate_and_install(&self, app: PathBuf) -> Result<(String, LocalPageEndpoint), Self::Error>;
fn done(&self, Option<LocalPageEndpoint>);
}
#[derive(Clone)]
pub struct FetchControl {
abort: Arc<AtomicBool>,
listeners: Arc<Mutex<Vec<(Control, mpsc::Sender<WaitResult>)>>>,
listeners: Mutex<Vec<(Control, mpsc::Sender<FetchState>)>>,
deadline: Instant,
}
@@ -74,17 +58,16 @@ impl Default for FetchControl {
fn default() -> Self {
FetchControl {
abort: Arc::new(AtomicBool::new(false)),
listeners: Arc::new(Mutex::new(Vec::new())),
listeners: Mutex::new(Vec::new()),
deadline: Instant::now() + Duration::from_secs(FETCH_TIMEOUT),
}
}
}
impl FetchControl {
fn notify<F: Fn() -> WaitResult>(&self, status: F) {
fn notify<F: Fn() -> FetchState>(&self, status: F) {
let mut listeners = self.listeners.lock();
for (control, sender) in listeners.drain(..) {
trace!(target: "dapps", "Resuming request waiting for content...");
if let Err(e) = sender.send(status()) {
trace!(target: "dapps", "Waiting listener notification failed: {:?}", e);
} else {
@@ -95,246 +78,148 @@ impl FetchControl {
fn set_status(&self, status: &FetchState) {
match *status {
FetchState::Error(ref handler) => self.notify(|| WaitResult::Error(handler.clone())),
FetchState::Done(ref endpoint, _) => self.notify(|| WaitResult::Done(endpoint.clone())),
FetchState::Streaming(_) => self.notify(|| WaitResult::NonAwaitable),
FetchState::NotStarted(_) | FetchState::InProgress(_) | FetchState::Waiting => {},
FetchState::Error(ref handler) => self.notify(|| FetchState::Error(handler.clone())),
FetchState::Done(ref id, ref endpoint, ref handler) => self.notify(|| FetchState::Done(id.clone(), endpoint.clone(), handler.clone())),
FetchState::NotStarted(_) | FetchState::InProgress(_) => {},
}
}
pub fn is_deadline_reached(&self) -> bool {
self.deadline < Instant::now()
}
pub fn abort(&self) {
self.abort.store(true, Ordering::SeqCst);
}
pub fn to_async_handler(&self, path: EndpointPath, control: Control) -> Box<server::Handler<HttpStream> + Send> {
pub fn to_handler(&self, control: Control) -> Box<server::Handler<HttpStream> + Send> {
let (tx, rx) = mpsc::channel();
self.listeners.lock().push((control, tx));
Box::new(WaitingHandler {
receiver: rx,
state: FetchState::Waiting,
uri: RequestUri::default(),
path: path,
state: None,
})
}
}
pub struct WaitingHandler {
receiver: mpsc::Receiver<WaitResult>,
state: FetchState,
uri: RequestUri,
path: EndpointPath,
receiver: mpsc::Receiver<FetchState>,
state: Option<FetchState>,
}
impl server::Handler<HttpStream> for WaitingHandler {
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
self.uri = request.uri().clone();
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
Next::wait()
}
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
let result = self.receiver.try_recv().ok();
self.state = match result {
Some(WaitResult::Error(handler)) => FetchState::Error(handler),
Some(WaitResult::Done(endpoint)) => {
let mut page_handler = endpoint.to_page_handler(self.path.clone());
page_handler.set_uri(&self.uri);
FetchState::Done(endpoint, page_handler)
},
_ => {
warn!("A result for waiting request was not received.");
FetchState::Waiting
},
};
match self.state {
FetchState::Done(_, ref mut handler) => handler.on_request_readable(decoder),
FetchState::Streaming(ref mut handler) => handler.on_request_readable(decoder),
FetchState::Error(ref mut handler) => handler.on_request_readable(decoder),
_ => Next::write(),
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
self.state = self.receiver.try_recv().ok();
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
match self.state {
FetchState::Done(_, ref mut handler) => handler.on_response(res),
FetchState::Streaming(ref mut handler) => handler.on_response(res),
FetchState::Error(ref mut handler) => handler.on_response(res),
Some(FetchState::Done(_, _, ref mut handler)) => handler.on_response(res),
Some(FetchState::Error(ref mut handler)) => handler.on_response(res),
_ => Next::end(),
}
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
match self.state {
FetchState::Done(_, ref mut handler) => handler.on_response_writable(encoder),
FetchState::Streaming(ref mut handler) => handler.on_response_writable(encoder),
FetchState::Error(ref mut handler) => handler.on_response_writable(encoder),
Some(FetchState::Done(_, _, ref mut handler)) => handler.on_response_writable(encoder),
Some(FetchState::Error(ref mut handler)) => handler.on_response_writable(encoder),
_ => Next::end(),
}
}
}
#[derive(Clone)]
struct Errors {
embeddable_on: Embeddable,
}
impl Errors {
fn download_error<E: fmt::Debug>(&self, e: E) -> ContentHandler {
ContentHandler::error(
StatusCode::BadGateway,
"Download Error",
"There was an error when fetching the content.",
Some(&format!("{:?}", e)),
self.embeddable_on.clone(),
)
}
fn invalid_content<E: fmt::Debug>(&self, e: E) -> ContentHandler {
ContentHandler::error(
StatusCode::BadGateway,
"Invalid Dapp",
"Downloaded bundle does not contain a valid content.",
Some(&format!("{:?}", e)),
self.embeddable_on.clone(),
)
}
fn timeout_error(&self) -> ContentHandler {
ContentHandler::error(
StatusCode::GatewayTimeout,
"Download Timeout",
&format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT),
None,
self.embeddable_on.clone(),
)
}
fn method_not_allowed(&self) -> ContentHandler {
ContentHandler::error(
StatusCode::MethodNotAllowed,
"Method Not Allowed",
"Only <code>GET</code> requests are allowed.",
None,
self.embeddable_on.clone(),
)
}
}
pub struct ContentFetcherHandler<H: ContentValidator, F: Fetch> {
fetch_control: FetchControl,
control: Control,
remote: Remote,
pub struct ContentFetcherHandler<H: ContentValidator> {
fetch_control: Arc<FetchControl>,
control: Option<Control>,
status: FetchState,
fetch: F,
installer: Option<H>,
path: EndpointPath,
errors: Errors,
client: Option<Client>,
installer: H,
request_url: Option<Url>,
embeddable_on: Option<(String, u16)>,
}
impl<H: ContentValidator, F: Fetch> ContentFetcherHandler<H, F> {
impl<H: ContentValidator> Drop for ContentFetcherHandler<H> {
fn drop(&mut self) {
let result = match self.status {
FetchState::Done(_, ref result, _) => Some(result.clone()),
_ => None,
};
self.installer.done(result);
}
}
impl<H: ContentValidator> ContentFetcherHandler<H> {
pub fn new(
url: String,
path: EndpointPath,
control: Control,
installer: H,
embeddable_on: Embeddable,
remote: Remote,
fetch: F,
) -> Self {
ContentFetcherHandler {
fetch_control: FetchControl::default(),
control,
remote,
fetch,
handler: H,
embeddable_on: Option<(String, u16)>,
) -> (Self, Arc<FetchControl>) {
let fetch_control = Arc::new(FetchControl::default());
let client = Client::default();
let handler = ContentFetcherHandler {
fetch_control: fetch_control.clone(),
control: Some(control),
client: Some(client),
status: FetchState::NotStarted(url),
installer: Some(installer),
path,
errors: Errors {
embeddable_on,
},
}
installer: handler,
request_url: None,
embeddable_on: embeddable_on,
};
(handler, fetch_control)
}
pub fn fetch_control(&self) -> FetchControl {
self.fetch_control.clone()
fn close_client(client: &mut Option<Client>) {
client.take()
.expect("After client is closed we are going into write, hence we can never close it again")
.close();
}
fn fetch_content(&self, uri: RequestUri, url: &str, installer: H) -> mpsc::Receiver<FetchState> {
let (tx, rx) = mpsc::channel();
let abort = self.fetch_control.abort.clone();
let path = self.path.clone();
let tx2 = tx.clone();
let control = self.control.clone();
let errors = self.errors.clone();
let future = self.fetch.fetch_with_abort(url, abort.into()).then(move |result| {
trace!(target: "dapps", "Fetching content finished. Starting validation: {:?}", result);
let new_state = match result {
Ok(response) => match installer.validate_and_install(response) {
Ok(ValidatorResponse::Local(endpoint)) => {
trace!(target: "dapps", "Validation OK. Returning response.");
let mut handler = endpoint.to_page_handler(path);
handler.set_uri(&uri);
FetchState::Done(endpoint, handler)
},
Ok(ValidatorResponse::Streaming(handler)) => {
trace!(target: "dapps", "Validation OK. Streaming response.");
FetchState::Streaming(handler)
},
Err(e) => {
trace!(target: "dapps", "Error while validating content: {:?}", e);
FetchState::Error(errors.invalid_content(e))
},
},
Err(e) => {
warn!(target: "dapps", "Unable to fetch content: {:?}", e);
FetchState::Error(errors.download_error(e))
},
};
// Content may be resolved when the connection is already dropped.
let _ = tx2.send(new_state);
fn fetch_content(client: &mut Client, url: &str, abort: Arc<AtomicBool>, control: Control) -> Result<mpsc::Receiver<FetchResult>, String> {
client.request(url, abort, Box::new(move || {
trace!(target: "dapps", "Fetching finished.");
// Ignoring control errors
let _ = control.ready(Next::read());
Ok(()) as Result<(), ()>
});
// make sure to run within fetch thread pool.
let future = self.fetch.process(future);
// spawn to event loop
let control = self.control.clone();
let errors = self.errors.clone();
self.remote.spawn_with_timeout(|| future, Duration::from_secs(FETCH_TIMEOUT), move || {
// Notify about the timeout
let _ = tx.send(FetchState::Error(errors.timeout_error()));
// Ignoring control errors
let _ = control.ready(Next::read());
});
rx
})).map_err(|e| format!("{:?}", e))
}
}
impl<H: ContentValidator, F: Fetch> server::Handler<HttpStream> for ContentFetcherHandler<H, F> {
impl<H: ContentValidator> server::Handler<HttpStream> for ContentFetcherHandler<H> {
fn on_request(&mut self, request: server::Request<HttpStream>) -> Next {
self.request_url = extract_url(&request);
let status = if let FetchState::NotStarted(ref url) = self.status {
let uri = request.uri().clone();
let installer = self.installer.take().expect("Installer always set initialy; installer used only in on_request; on_request invoked only once; qed");
Some(match *request.method() {
// Start fetching content
Method::Get => {
trace!(target: "dapps", "Fetching content from: {:?}", url);
let receiver = self.fetch_content(uri, url, installer);
FetchState::InProgress(receiver)
let control = self.control.take().expect("on_request is called only once, thus control is always Some");
let client = self.client.as_mut().expect("on_request is called before client is closed.");
let fetch = Self::fetch_content(client, url, self.fetch_control.abort.clone(), control);
match fetch {
Ok(receiver) => FetchState::InProgress(receiver),
Err(e) => FetchState::Error(ContentHandler::error(
StatusCode::BadGateway,
"Unable To Start Dapp Download",
"Could not initialize download of the dapp. It might be a problem with the remote server.",
Some(&format!("{}", e)),
self.embeddable_on.clone(),
)),
}
},
// or return error
_ => FetchState::Error(self.errors.method_not_allowed()),
_ => FetchState::Error(ContentHandler::error(
StatusCode::MethodNotAllowed,
"Method Not Allowed",
"Only <code>GET</code> requests are allowed.",
None,
self.embeddable_on.clone(),
)),
})
} else { None };
@@ -349,16 +234,60 @@ impl<H: ContentValidator, F: Fetch> server::Handler<HttpStream> for ContentFetch
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
let (status, next) = match self.status {
// Request may time out
FetchState::InProgress(_) if self.fetch_control.is_deadline_reached() => {
FetchState::InProgress(_) if self.fetch_control.deadline < Instant::now() => {
trace!(target: "dapps", "Fetching dapp failed because of timeout.");
(Some(FetchState::Error(self.errors.timeout_error())), Next::write())
let timeout = ContentHandler::error(
StatusCode::GatewayTimeout,
"Download Timeout",
&format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT),
None,
self.embeddable_on.clone(),
);
Self::close_client(&mut self.client);
(Some(FetchState::Error(timeout)), Next::write())
},
FetchState::InProgress(ref receiver) => {
// Check if there is an answer
let rec = receiver.try_recv();
match rec {
// just return the new state
Ok(state) => (Some(state), Next::write()),
// Unpack and validate
Ok(Ok(path)) => {
trace!(target: "dapps", "Fetching content finished. Starting validation ({:?})", path);
Self::close_client(&mut self.client);
// Unpack and verify
let state = match self.installer.validate_and_install(path.clone()) {
Err(e) => {
trace!(target: "dapps", "Error while validating content: {:?}", e);
FetchState::Error(ContentHandler::error(
StatusCode::BadGateway,
"Invalid Dapp",
"Downloaded bundle does not contain a valid content.",
Some(&format!("{:?}", e)),
self.embeddable_on.clone(),
))
},
Ok((id, result)) => {
let url: String = self.request_url.take()
.map(|url| url.raw.into_string())
.expect("Request URL always read in on_request; qed");
FetchState::Done(id, result, Redirection::new(&url))
},
};
// Remove temporary zip file
let _ = fs::remove_file(path);
(Some(state), Next::write())
},
Ok(Err(e)) => {
warn!(target: "dapps", "Unable to fetch content: {:?}", e);
let error = ContentHandler::error(
StatusCode::BadGateway,
"Download Error",
"There was an error when fetching the content.",
Some(&format!("{:?}", e)),
self.embeddable_on.clone(),
);
(Some(FetchState::Error(error)), Next::write())
},
// wait some more
_ => (None, Next::wait())
}
@@ -377,8 +306,7 @@ impl<H: ContentValidator, F: Fetch> server::Handler<HttpStream> for ContentFetch
fn on_response(&mut self, res: &mut server::Response) -> Next {
match self.status {
FetchState::Done(_, ref mut handler) => handler.on_response(res),
FetchState::Streaming(ref mut handler) => handler.on_response(res),
FetchState::Done(_, _, ref mut handler) => handler.on_response(res),
FetchState::Error(ref mut handler) => handler.on_response(res),
_ => Next::end(),
}
@@ -386,8 +314,7 @@ impl<H: ContentValidator, F: Fetch> server::Handler<HttpStream> for ContentFetch
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
match self.status {
FetchState::Done(_, ref mut handler) => handler.on_response_writable(encoder),
FetchState::Streaming(ref mut handler) => handler.on_response_writable(encoder),
FetchState::Done(_, _, ref mut handler) => handler.on_response_writable(encoder),
FetchState::Error(ref mut handler) => handler.on_response_writable(encoder),
_ => Next::end(),
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -16,131 +16,53 @@
//! Hyper handlers implementations.
mod async;
mod content;
mod auth;
mod echo;
mod fetch;
mod content;
mod redirect;
mod streaming;
mod fetch;
pub use self::async::AsyncHandler;
pub use self::content::ContentHandler;
pub use self::auth::AuthRequiredHandler;
pub use self::echo::EchoHandler;
pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl, ValidatorResponse};
pub use self::content::ContentHandler;
pub use self::redirect::Redirection;
pub use self::streaming::StreamingHandler;
use std::iter;
use util::Itertools;
pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl};
use url::Url;
use hyper::{server, header, net, uri};
use {apps, address, Embeddable};
use address;
/// Adds security-related headers to the Response.
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Embeddable) {
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option<(String, u16)>) {
headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]);
headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]);
// Embedding header:
if let None = embeddable_on {
if let Some(embeddable_on) = embeddable_on {
headers.set_raw(
"X-Frame-Options",
vec![format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()]
);
} else {
// TODO [ToDr] Should we be more strict here (DENY?)?
headers.set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
}
// Content Security Policy headers
headers.set_raw("Content-Security-Policy", vec![
// Allow connecting to WS servers and HTTP(S) servers.
// We could be more restrictive and allow only RPC server URL.
b"connect-src http: https: ws: wss:;".to_vec(),
// Allow framing any content from HTTP(S).
// Again we could only allow embedding from RPC server URL.
// (deprecated)
b"frame-src 'self' http: https:;".to_vec(),
// Allow framing and web workers from HTTP(S).
b"child-src 'self' http: https:;".to_vec(),
// We allow data: blob: and HTTP(s) images.
// We could get rid of wildcarding HTTP and only allow RPC server URL.
// (http required for local dapps icons)
b"img-src 'self' 'unsafe-inline' data: blob: http: https:;".to_vec(),
// Allow style from data: blob: and HTTPS.
b"style-src 'self' 'unsafe-inline' data: blob: https:;".to_vec(),
// Allow fonts from data: and HTTPS.
b"font-src 'self' data: https:;".to_vec(),
// Allow inline scripts and scripts eval (webpack/jsconsole)
{
let script_src = embeddable_on.as_ref()
.map(|e| e.extra_script_src.iter()
.map(|&(ref host, port)| address(host, port))
.join(" ")
).unwrap_or_default();
format!(
"script-src 'self' 'unsafe-inline' 'unsafe-eval' {};",
script_src
).into_bytes()
},
// Same restrictions as script-src with additional
// blob: that is required for camera access (worker)
b"worker-src 'self' 'unsafe-inline' 'unsafe-eval' https: blob:;".to_vec(),
// Restrict everything else to the same origin.
b"default-src 'self';".to_vec(),
// Run in sandbox mode (although it's not fully safe since we allow same-origin and script)
b"sandbox allow-same-origin allow-forms allow-modals allow-popups allow-presentation allow-scripts;".to_vec(),
// Disallow subitting forms from any dapps
b"form-action 'none';".to_vec(),
// Never allow mixed content
b"block-all-mixed-content;".to_vec(),
// Specify if the site can be embedded.
match embeddable_on {
Some(ref embed) => {
let std = address(&embed.host, embed.port);
let proxy = format!("{}.{}", apps::HOME_PAGE, embed.dapps_domain);
let domain = format!("*.{}:{}", embed.dapps_domain, embed.port);
let mut ancestors = vec![std, domain, proxy]
.into_iter()
.chain(embed.extra_embed_on
.iter()
.map(|&(ref host, port)| address(host, port))
);
let ancestors = if embed.host == "127.0.0.1" {
let localhost = address("localhost", embed.port);
ancestors.chain(iter::once(localhost)).join(" ")
} else {
ancestors.join(" ")
};
format!("frame-ancestors {};", ancestors)
},
None => format!("frame-ancestors 'self';"),
}.into_bytes(),
]);
}
/// Extracts URL part from the Request.
pub fn extract_url(req: &server::Request<net::HttpStream>) -> Option<Url> {
convert_uri_to_url(req.uri(), req.headers().get::<header::Host>())
}
/// Extracts URL given URI and Host header.
pub fn convert_uri_to_url(uri: &uri::RequestUri, host: Option<&header::Host>) -> Option<Url> {
match *uri {
match *req.uri() {
uri::RequestUri::AbsoluteUri(ref url) => {
match Url::from_generic_url(url.clone()) {
Ok(url) => Some(url),
_ => None,
}
},
uri::RequestUri::AbsolutePath { ref path, ref query } => {
let query = match *query {
Some(ref query) => format!("?{}", query),
None => "".into(),
};
uri::RequestUri::AbsolutePath(ref path) => {
// Attempt to prepend the Host header (mandatory in HTTP/1.1)
let url_string = match host {
let url_string = match req.headers().get::<header::Host>() {
Some(ref host) => {
format!("http://{}:{}{}{}", host.hostname, host.port.unwrap_or(80), path, query)
format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path)
},
None => return None,
};
@@ -153,3 +75,4 @@ pub fn convert_uri_to_url(uri: &uri::RequestUri, host: Option<&header::Host>) ->
_ => None,
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify

View File

@@ -1,102 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Content Stream Response
use std::io::{self, Read};
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::mime::Mime;
use hyper::status::StatusCode;
use handlers::add_security_headers;
use Embeddable;
const BUFFER_SIZE: usize = 1024;
pub struct StreamingHandler<R: io::Read> {
buffer: [u8; BUFFER_SIZE],
buffer_leftover: usize,
status: StatusCode,
content: io::BufReader<R>,
mimetype: Mime,
safe_to_embed_on: Embeddable,
}
impl<R: io::Read> StreamingHandler<R> {
pub fn new(content: R, status: StatusCode, mimetype: Mime, embeddable_on: Embeddable) -> Self {
StreamingHandler {
buffer: [0; BUFFER_SIZE],
buffer_leftover: 0,
status: status,
content: io::BufReader::new(content),
mimetype: mimetype,
safe_to_embed_on: embeddable_on,
}
}
pub fn set_initial_content(&mut self, content: &str) {
assert_eq!(self.buffer_leftover, 0);
let bytes = content.as_bytes();
self.buffer_leftover = bytes.len();
self.buffer[0..self.buffer_leftover].copy_from_slice(bytes);
}
}
impl<R: io::Read> server::Handler<HttpStream> for StreamingHandler<R> {
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
Next::write()
}
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
Next::write()
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
res.set_status(self.status);
res.headers_mut().set(header::ContentType(self.mimetype.clone()));
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
Next::write()
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
fn handle_error(e: io::Error) -> Next {
match e.kind() {
::std::io::ErrorKind::WouldBlock => Next::write(),
_ => Next::end(),
}
}
let write_pos = self.buffer_leftover;
match self.content.read(&mut self.buffer[write_pos..]) {
Err(e) => handle_error(e),
Ok(read) => match encoder.write(&self.buffer[..write_pos + read]) {
Err(e) => handle_error(e),
Ok(0) => Next::end(),
Ok(wrote) => {
self.buffer_leftover = write_pos + read - wrote;
if self.buffer_leftover > 0 {
for i in self.buffer_leftover..write_pos + read {
self.buffer.swap(i, i - self.buffer_leftover);
}
}
Next::write()
},
},
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -15,269 +15,327 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Ethcore Webapplications for Parity
//! ```
//! extern crate jsonrpc_core;
//! extern crate ethcore_dapps;
//!
//! use std::sync::Arc;
//! use jsonrpc_core::IoHandler;
//! use ethcore_dapps::*;
//!
//! struct SayHello;
//! impl MethodCommand for SayHello {
//! fn execute(&self, _params: Params) -> Result<Value, Error> {
//! Ok(Value::String("hello".to_string()))
//! }
//! }
//!
//! fn main() {
//! let io = IoHandler::new();
//! io.add_method("say_hello", SayHello);
//! let _server = Server::start_unsecure_http(
//! &"127.0.0.1:3030".parse().unwrap(),
//! Arc::new(io)
//! );
//! }
//! ```
//!
#![warn(missing_docs)]
#![cfg_attr(feature="nightly", feature(plugin))]
#![cfg_attr(feature="nightly", plugin(clippy))]
extern crate base32;
extern crate futures;
extern crate linked_hash_map;
extern crate mime_guess;
extern crate rand;
extern crate rustc_hex;
extern crate hyper;
extern crate time;
extern crate url as url_lib;
extern crate unicase;
extern crate serde;
extern crate serde_json;
extern crate time;
extern crate unicase;
extern crate url as url_lib;
extern crate zip;
extern crate rand;
extern crate ethabi;
extern crate jsonrpc_core;
extern crate jsonrpc_http_server;
extern crate mime_guess;
extern crate rustc_serialize;
extern crate ethcore_rpc;
extern crate ethcore_util as util;
extern crate linked_hash_map;
extern crate fetch;
extern crate node_health;
extern crate parity_dapps_glue as parity_dapps;
extern crate parity_hash_fetch as hash_fetch;
extern crate parity_reactor;
extern crate parity_ui;
#[macro_use]
extern crate log;
#[macro_use]
extern crate mime;
#[macro_use]
extern crate serde_derive;
#[cfg(test)]
extern crate ethcore_devtools as devtools;
#[cfg(test)]
extern crate env_logger;
mod endpoint;
mod apps;
mod page;
mod router;
mod handlers;
mod rpc;
mod api;
mod proxypac;
mod url;
mod web;
#[cfg(test)]
mod tests;
use std::path::PathBuf;
use std::sync::Arc;
pub use self::apps::urlhint::ContractClient;
use std::sync::{Arc, Mutex};
use std::net::SocketAddr;
use std::collections::HashMap;
use jsonrpc_http_server::{self as http, hyper, Origin};
use jsonrpc_core::{IoHandler, IoDelegate};
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
use ethcore_rpc::Extendable;
use fetch::Fetch;
use node_health::NodeHealth;
use parity_reactor::Remote;
use self::apps::{HOME_PAGE, DAPPS_DOMAIN};
pub use hash_fetch::urlhint::ContractClient;
pub use node_health::SyncStatus;
/// Validates Web Proxy tokens
pub trait WebProxyTokens: Send + Sync {
/// Should return a domain allowed to be accessed by this token or `None` if the token is not valid
fn domain(&self, token: &str) -> Option<Origin>;
/// Indicates sync status
pub trait SyncStatus: Send + Sync {
/// Returns true if there is a major sync happening.
fn is_major_importing(&self) -> bool;
}
impl<F> WebProxyTokens for F where F: Fn(String) -> Option<Origin> + Send + Sync {
fn domain(&self, token: &str) -> Option<Origin> { self(token.to_owned()) }
impl<F> SyncStatus for F where F: Fn() -> bool + Send + Sync {
fn is_major_importing(&self) -> bool { self() }
}
/// Current supported endpoints.
pub struct Endpoints {
endpoints: endpoint::Endpoints,
/// Webapps HTTP+RPC server build.
pub struct ServerBuilder {
dapps_path: String,
handler: Arc<IoHandler>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
signer_address: Option<(String, u16)>,
}
impl Endpoints {
/// Returns a current list of app endpoints.
pub fn list(&self) -> Vec<apps::App> {
self.endpoints.iter().filter_map(|(ref k, ref e)| {
e.info().map(|ref info| apps::App::from_info(k, info))
}).collect()
impl Extendable for ServerBuilder {
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>) {
self.handler.add_delegate(delegate);
}
}
/// Dapps server as `jsonrpc-http-server` request middleware.
pub struct Middleware {
router: router::Router,
endpoints: endpoint::Endpoints,
}
impl Middleware {
/// Get local endpoints handle.
pub fn endpoints(&self) -> Endpoints {
Endpoints {
endpoints: self.endpoints.clone(),
impl ServerBuilder {
/// Construct new dapps server
pub fn new(dapps_path: String, registrar: Arc<ContractClient>) -> Self {
ServerBuilder {
dapps_path: dapps_path,
handler: Arc::new(IoHandler::new()),
registrar: registrar,
sync_status: Arc::new(|| false),
signer_address: None,
}
}
/// Creates new middleware for UI server.
pub fn ui<F: Fetch>(
health: NodeHealth,
remote: Remote,
dapps_domain: &str,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
fetch: F,
) -> Self {
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
hash_fetch::urlhint::URLHintContract::new(registrar),
sync_status.clone(),
remote.clone(),
fetch.clone(),
).embeddable_on(None).allow_dapps(false));
let special = {
let mut special = special_endpoints(
health,
content_fetcher.clone(),
remote.clone(),
);
special.insert(router::SpecialEndpoint::Home, Some(apps::ui()));
special
};
let router = router::Router::new(
content_fetcher,
None,
special,
None,
dapps_domain.to_owned(),
);
/// Change default sync status.
pub fn with_sync_status(&mut self, status: Arc<SyncStatus>) {
self.sync_status = status;
}
Middleware {
router: router,
endpoints: Default::default(),
/// Change default signer port.
pub fn with_signer_address(&mut self, signer_address: Option<(String, u16)>) {
self.signer_address = signer_address;
}
/// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error.
pub fn start_unsecured_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>) -> Result<Server, ServerError> {
Server::start_http(
addr,
hosts,
NoAuth,
self.handler.clone(),
self.dapps_path.clone(),
self.signer_address.clone(),
self.registrar.clone(),
self.sync_status.clone(),
)
}
/// Asynchronously start server with `HTTP Basic Authentication`,
/// return result with `Server` handle on success or an error.
pub fn start_basic_auth_http(&self, addr: &SocketAddr, hosts: Option<Vec<String>>, username: &str, password: &str) -> Result<Server, ServerError> {
Server::start_http(
addr,
hosts,
HttpBasicAuth::single_user(username, password),
self.handler.clone(),
self.dapps_path.clone(),
self.signer_address.clone(),
self.registrar.clone(),
self.sync_status.clone(),
)
}
}
/// Webapps HTTP server.
pub struct Server {
server: Option<hyper::server::Listening>,
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
}
impl Server {
/// Returns a list of allowed hosts or `None` if all hosts are allowed.
fn allowed_hosts(hosts: Option<Vec<String>>, bind_address: String) -> Option<Vec<String>> {
let mut allowed = Vec::new();
match hosts {
Some(hosts) => allowed.extend_from_slice(&hosts),
None => return None,
}
// Add localhost domain as valid too if listening on loopback interface.
allowed.push(bind_address.replace("127.0.0.1", "localhost").into());
allowed.push(bind_address.into());
Some(allowed)
}
/// Returns a list of CORS domains for API endpoint.
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<String> {
match signer_address {
Some(signer_address) => vec![
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("http://{}", address(signer_address)),
],
None => vec![],
}
}
/// Creates new Dapps server middleware.
pub fn dapps<F: Fetch>(
health: NodeHealth,
remote: Remote,
ui_address: Option<(String, u16)>,
extra_embed_on: Vec<(String, u16)>,
extra_script_src: Vec<(String, u16)>,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
dapps_domain: &str,
fn start_http<A: Authorization + 'static>(
addr: &SocketAddr,
hosts: Option<Vec<String>>,
authorization: A,
handler: Arc<IoHandler>,
dapps_path: String,
signer_address: Option<(String, u16)>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
web_proxy_tokens: Arc<WebProxyTokens>,
fetch: F,
) -> Self {
let embeddable = as_embeddable(ui_address, extra_embed_on, extra_script_src, dapps_domain);
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
hash_fetch::urlhint::URLHintContract::new(registrar),
sync_status.clone(),
remote.clone(),
fetch.clone(),
).embeddable_on(embeddable.clone()).allow_dapps(true));
let endpoints = apps::all_endpoints(
dapps_path,
extra_dapps,
dapps_domain,
embeddable.clone(),
web_proxy_tokens,
remote.clone(),
fetch.clone(),
);
) -> Result<Server, ServerError> {
let panic_handler = Arc::new(Mutex::new(None));
let authorization = Arc::new(authorization);
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_address.clone()));
let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone()));
let cors_domains = Self::cors_domains(signer_address.clone());
let special = {
let mut special = special_endpoints(
health,
content_fetcher.clone(),
remote.clone(),
);
let special = Arc::new({
let mut special = HashMap::new();
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone()));
special.insert(router::SpecialEndpoint::Utils, apps::utils());
special.insert(
router::SpecialEndpoint::Home,
Some(apps::ui_redirection(embeddable.clone())),
router::SpecialEndpoint::Api,
api::RestApi::new(cors_domains, endpoints.clone(), content_fetcher.clone())
);
special
};
});
let hosts = Self::allowed_hosts(hosts, format!("{}", addr));
let router = router::Router::new(
content_fetcher,
Some(endpoints.clone()),
special,
embeddable,
dapps_domain.to_owned(),
);
try!(hyper::Server::http(addr))
.handle(move |ctrl| router::Router::new(
ctrl,
signer_address.clone(),
content_fetcher.clone(),
endpoints.clone(),
special.clone(),
authorization.clone(),
hosts.clone(),
))
.map(|(l, srv)| {
Middleware {
router: router,
endpoints: endpoints,
::std::thread::spawn(move || {
srv.run();
});
Server {
server: Some(l),
panic_handler: panic_handler,
}
})
.map_err(ServerError::from)
}
/// Set callback for panics.
pub fn set_panic_handler<F>(&self, handler: F) where F : Fn() -> () + Send + 'static {
*self.panic_handler.lock().unwrap() = Some(Box::new(handler));
}
#[cfg(test)]
/// Returns address that this server is bound to.
pub fn addr(&self) -> &SocketAddr {
self.server.as_ref().expect("server is always Some at the start; it's consumed only when object is dropped; qed").addr()
}
}
impl Drop for Server {
fn drop(&mut self) {
self.server.take().unwrap().close()
}
}
/// Webapp Server startup error
#[derive(Debug)]
pub enum ServerError {
/// Wrapped `std::io::Error`
IoError(std::io::Error),
/// Other `hyper` error
Other(hyper::error::Error),
}
impl From<hyper::error::Error> for ServerError {
fn from(err: hyper::error::Error) -> Self {
match err {
hyper::error::Error::Io(e) => ServerError::IoError(e),
e => ServerError::Other(e),
}
}
}
impl http::RequestMiddleware for Middleware {
fn on_request(&self, req: &hyper::server::Request<hyper::net::HttpStream>, control: &hyper::Control) -> http::RequestMiddlewareAction {
self.router.on_request(req, control)
}
}
fn special_endpoints(
health: NodeHealth,
content_fetcher: Arc<apps::fetcher::Fetcher>,
remote: Remote,
) -> HashMap<router::SpecialEndpoint, Option<Box<endpoint::Endpoint>>> {
let mut special = HashMap::new();
special.insert(router::SpecialEndpoint::Rpc, None);
special.insert(router::SpecialEndpoint::Utils, Some(apps::utils()));
special.insert(router::SpecialEndpoint::Api, Some(api::RestApi::new(
content_fetcher,
health,
remote,
)));
special
}
fn address(host: &str, port: u16) -> String {
format!("{}:{}", host, port)
}
fn as_embeddable(
ui_address: Option<(String, u16)>,
extra_embed_on: Vec<(String, u16)>,
extra_script_src: Vec<(String, u16)>,
dapps_domain: &str,
) -> Option<ParentFrameSettings> {
ui_address.map(|(host, port)| ParentFrameSettings {
host,
port,
extra_embed_on,
extra_script_src,
dapps_domain: dapps_domain.to_owned(),
})
}
/// Random filename
fn random_filename() -> String {
pub fn random_filename() -> String {
use ::rand::Rng;
let mut rng = ::rand::OsRng::new().unwrap();
rng.gen_ascii_chars().take(12).collect()
}
type Embeddable = Option<ParentFrameSettings>;
/// Parent frame host and port allowed to embed the content.
#[derive(Debug, Clone)]
pub struct ParentFrameSettings {
/// Hostname
pub host: String,
/// Port
pub port: u16,
/// Additional URLs the dapps can be embedded on.
pub extra_embed_on: Vec<(String, u16)>,
/// Additional URLs the dapp scripts can be loaded from.
pub extra_script_src: Vec<(String, u16)>,
/// Dapps Domain (web3.site)
pub dapps_domain: String,
fn address(address: (String, u16)) -> String {
format!("{}:{}", address.0, address.1)
}
#[cfg(test)]
mod util_tests {
use super::Server;
#[test]
fn should_return_allowed_hosts() {
// given
let bind_address = "127.0.0.1".to_owned();
// when
let all = Server::allowed_hosts(None, bind_address.clone());
let address = Server::allowed_hosts(Some(Vec::new()), bind_address.clone());
let some = Server::allowed_hosts(Some(vec!["ethcore.io".into()]), bind_address.clone());
// then
assert_eq!(all, None);
assert_eq!(address, Some(vec!["localhost".into(), "127.0.0.1".into()]));
assert_eq!(some, Some(vec!["ethcore.io".into(), "localhost".into(), "127.0.0.1".into()]));
}
#[test]
fn should_return_cors_domains() {
// given
// when
let none = Server::cors_domains(None);
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)));
// then
assert_eq!(none, Vec::<String>::new());
assert_eq!(some, vec!["http://home.parity".to_owned(), "http://127.0.0.1:18180".into()]);
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -18,7 +18,6 @@ use page::{handler, PageCache};
use std::sync::Arc;
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use parity_dapps::{WebApp, File, Info};
use Embeddable;
pub struct PageEndpoint<T : WebApp + 'static> {
/// Content of the files
@@ -26,9 +25,8 @@ pub struct PageEndpoint<T : WebApp + 'static> {
/// Prefix to strip from the path (when `None` deducted from `app_id`)
pub prefix: Option<String>,
/// Safe to be loaded in frame by other origin. (use wisely!)
safe_to_embed_on: Embeddable,
safe_to_embed_on: Option<(String, u16)>,
info: EndpointInfo,
fallback_to_index_html: bool,
}
impl<T: WebApp + 'static> PageEndpoint<T> {
@@ -40,20 +38,6 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
prefix: None,
safe_to_embed_on: None,
info: EndpointInfo::from(info),
fallback_to_index_html: false,
}
}
/// Creates a new `PageEndpoint` for builtin (compile time) Dapp.
/// Instead of returning 404 this endpoint will always server index.html.
pub fn with_fallback_to_index(app: T) -> Self {
let info = app.info();
PageEndpoint {
app: Arc::new(app),
prefix: None,
safe_to_embed_on: None,
info: EndpointInfo::from(info),
fallback_to_index_html: true,
}
}
@@ -67,21 +51,19 @@ impl<T: WebApp + 'static> PageEndpoint<T> {
prefix: Some(prefix),
safe_to_embed_on: None,
info: EndpointInfo::from(info),
fallback_to_index_html: false,
}
}
/// Creates new `PageEndpoint` which can be safely used in iframe
/// even from different origin. It might be dangerous (clickjacking).
/// Use wisely!
pub fn new_safe_to_embed(app: T, address: Embeddable) -> Self {
pub fn new_safe_to_embed(app: T, address: Option<(String, u16)>) -> Self {
let info = app.info();
PageEndpoint {
app: Arc::new(app),
prefix: None,
safe_to_embed_on: address,
info: EndpointInfo::from(info),
fallback_to_index_html: false,
}
}
}
@@ -94,7 +76,7 @@ impl<T: WebApp> Endpoint for PageEndpoint<T> {
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
Box::new(handler::PageHandler {
app: BuiltinDapp::new(self.app.clone(), self.fallback_to_index_html),
app: BuiltinDapp::new(self.app.clone()),
prefix: self.prefix.clone(),
path: path,
file: handler::ServedFile::new(self.safe_to_embed_on.clone()),
@@ -118,14 +100,12 @@ impl From<Info> for EndpointInfo {
struct BuiltinDapp<T: WebApp + 'static> {
app: Arc<T>,
fallback_to_index_html: bool,
}
impl<T: WebApp + 'static> BuiltinDapp<T> {
fn new(app: Arc<T>, fallback_to_index_html: bool) -> Self {
fn new(app: Arc<T>) -> Self {
BuiltinDapp {
app: app,
fallback_to_index_html: fallback_to_index_html,
}
}
}
@@ -134,19 +114,13 @@ impl<T: WebApp + 'static> handler::Dapp for BuiltinDapp<T> {
type DappFile = BuiltinDappFile<T>;
fn file(&self, path: &str) -> Option<Self::DappFile> {
let file = |path| self.app.file(path).map(|_| {
self.app.file(path).map(|_| {
BuiltinDappFile {
app: self.app.clone(),
path: path.into(),
write_pos: 0,
}
});
let res = file(path);
if self.fallback_to_index_html {
res.or_else(|| file("index.html"))
} else {
res
}
})
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::io::Write;
use time::{self, Duration};
use hyper::header;
@@ -24,7 +25,6 @@ use hyper::status::StatusCode;
use hyper::{Decoder, Encoder, Next};
use endpoint::EndpointPath;
use handlers::{ContentHandler, add_security_headers};
use {Embeddable};
/// Represents a file that can be sent to client.
/// Implementation should keep track of bytes already sent internally.
@@ -60,7 +60,7 @@ pub enum ServedFile<T: Dapp> {
}
impl<T: Dapp> ServedFile<T> {
pub fn new(embeddable_on: Embeddable) -> Self {
pub fn new(embeddable_on: Option<(String, u16)>) -> Self {
ServedFile::Error(ContentHandler::error(
StatusCode::NotFound,
"404 Not Found",
@@ -84,45 +84,24 @@ impl Default for PageCache {
}
}
/// A generic type for `PageHandler` allowing to set the URL.
/// Used by dapps fetching to set the URL after the content was downloaded.
pub trait PageHandlerWaiting: server::Handler<HttpStream> + Send {
fn set_uri(&mut self, uri: &RequestUri);
}
/// A handler for a single webapp.
/// Resolves correct paths and serves as a plumbing code between
/// hyper server and dapp.
pub struct PageHandler<T: Dapp> {
/// A Dapp.
pub app: T,
/// File currently being served
/// File currently being served (or `None` if file does not exist).
pub file: ServedFile<T>,
/// Optional prefix to strip from path.
pub prefix: Option<String>,
/// Requested path.
pub path: EndpointPath,
/// Flag indicating if the file can be safely embeded (put in iframe).
pub safe_to_embed_on: Embeddable,
pub safe_to_embed_on: Option<(String, u16)>,
/// Cache settings for this page.
pub cache: PageCache,
}
impl<T: Dapp> PageHandlerWaiting for PageHandler<T> {
fn set_uri(&mut self, uri: &RequestUri) {
trace!(target: "dapps", "Setting URI: {:?}", uri);
self.file = match *uri {
RequestUri::AbsolutePath { ref path, .. } => {
self.app.file(&self.extract_path(path))
},
RequestUri::AbsoluteUri(ref url) => {
self.app.file(&self.extract_path(url.path()))
},
_ => None,
}.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f));
}
}
impl<T: Dapp> PageHandler<T> {
fn extract_path(&self, path: &str) -> String {
let app_id = &self.path.app_id;
@@ -146,7 +125,15 @@ impl<T: Dapp> PageHandler<T> {
impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
self.set_uri(req.uri());
self.file = match *req.uri() {
RequestUri::AbsolutePath(ref path) => {
self.app.file(&self.extract_path(path))
},
RequestUri::AbsoluteUri(ref url) => {
self.app.file(&self.extract_path(url.path()))
},
_ => None,
}.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f));
Next::write()
}
@@ -175,7 +162,7 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
}
// Security headers:
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.take());
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone());
Next::write()
},
ServedFile::Error(ref mut handler) => {
@@ -253,7 +240,6 @@ fn should_extract_path_with_appid() {
prefix: None,
path: EndpointPath {
app_id: "app".to_owned(),
app_params: vec![],
host: "".to_owned(),
port: 8080,
using_dapps_domains: true,

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -18,22 +18,20 @@ use mime_guess;
use std::io::{Seek, Read, SeekFrom};
use std::fs;
use std::path::{Path, PathBuf};
use page::handler::{self, PageCache, PageHandlerWaiting};
use page::handler::{self, PageCache};
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use mime::Mime;
use Embeddable;
#[derive(Debug, Clone)]
pub struct LocalPageEndpoint {
path: PathBuf,
mime: Option<Mime>,
mime: Option<String>,
info: Option<EndpointInfo>,
cache: PageCache,
embeddable_on: Embeddable,
embeddable_on: Option<(String, u16)>,
}
impl LocalPageEndpoint {
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Embeddable) -> Self {
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Option<(String, u16)>) -> Self {
LocalPageEndpoint {
path: path,
mime: None,
@@ -43,7 +41,7 @@ impl LocalPageEndpoint {
}
}
pub fn single_file(path: PathBuf, mime: Mime, cache: PageCache) -> Self {
pub fn single_file(path: PathBuf, mime: String, cache: PageCache) -> Self {
LocalPageEndpoint {
path: path,
mime: Some(mime),
@@ -56,36 +54,6 @@ impl LocalPageEndpoint {
pub fn path(&self) -> PathBuf {
self.path.clone()
}
fn page_handler_with_mime(&self, path: EndpointPath, mime: &Mime) -> handler::PageHandler<LocalSingleFile> {
handler::PageHandler {
app: LocalSingleFile { path: self.path.clone(), mime: format!("{}", mime) },
prefix: None,
path: path,
file: handler::ServedFile::new(None),
safe_to_embed_on: self.embeddable_on.clone(),
cache: self.cache,
}
}
fn page_handler(&self, path: EndpointPath) -> handler::PageHandler<LocalDapp> {
handler::PageHandler {
app: LocalDapp { path: self.path.clone() },
prefix: None,
path: path,
file: handler::ServedFile::new(None),
safe_to_embed_on: self.embeddable_on.clone(),
cache: self.cache,
}
}
pub fn to_page_handler(&self, path: EndpointPath) -> Box<PageHandlerWaiting> {
if let Some(ref mime) = self.mime {
Box::new(self.page_handler_with_mime(path, mime))
} else {
Box::new(self.page_handler(path))
}
}
}
impl Endpoint for LocalPageEndpoint {
@@ -95,9 +63,23 @@ impl Endpoint for LocalPageEndpoint {
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
if let Some(ref mime) = self.mime {
Box::new(self.page_handler_with_mime(path, mime))
Box::new(handler::PageHandler {
app: LocalSingleFile { path: self.path.clone(), mime: mime.clone() },
prefix: None,
path: path,
file: handler::ServedFile::new(None),
safe_to_embed_on: self.embeddable_on.clone(),
cache: self.cache,
})
} else {
Box::new(self.page_handler(path))
Box::new(handler::PageHandler {
app: LocalDapp { path: self.path.clone() },
prefix: None,
path: path,
file: handler::ServedFile::new(None),
safe_to_embed_on: self.embeddable_on.clone(),
cache: self.cache,
})
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -21,5 +21,5 @@ mod handler;
pub use self::local::LocalPageEndpoint;
pub use self::builtin::PageEndpoint;
pub use self::handler::{PageCache, PageHandlerWaiting};
pub use self::handler::PageCache;

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -18,36 +18,36 @@
use endpoint::{Endpoint, Handler, EndpointPath};
use handlers::ContentHandler;
use apps::HOME_PAGE;
use {address, Embeddable};
use apps::{HOME_PAGE, DAPPS_DOMAIN};
use address;
pub struct ProxyPac {
embeddable: Embeddable,
dapps_domain: String,
signer_address: Option<(String, u16)>,
}
impl ProxyPac {
pub fn boxed(embeddable: Embeddable, dapps_domain: String) -> Box<Endpoint> {
Box::new(ProxyPac { embeddable, dapps_domain })
pub fn boxed(signer_address: Option<(String, u16)>) -> Box<Endpoint> {
Box::new(ProxyPac {
signer_address: signer_address
})
}
}
impl Endpoint for ProxyPac {
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
let ui = self.embeddable
.as_ref()
.map(|ref parent| address(&parent.host, parent.port))
let signer = self.signer_address.clone()
.map(address)
.unwrap_or_else(|| format!("{}:{}", path.host, path.port));
let content = format!(
r#"
function FindProxyForURL(url, host) {{
if (shExpMatch(host, "{0}.{1}"))
if (shExpMatch(host, "{0}{1}"))
{{
return "PROXY {4}";
}}
if (shExpMatch(host, "*.{1}"))
if (shExpMatch(host, "*{1}"))
{{
return "PROXY {2}:{3}";
}}
@@ -55,7 +55,7 @@ function FindProxyForURL(url, host) {{
return "DIRECT";
}}
"#,
HOME_PAGE, self.dapps_domain, path.host, path.port, ui);
HOME_PAGE, DAPPS_DOMAIN, path.host, path.port, signer);
Box::new(ContentHandler::ok(content, mime!(Application/Javascript)))
}

View File

@@ -1,325 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Router implementation
//! Dispatch requests to proper application.
use std::cmp;
use std::sync::Arc;
use std::collections::HashMap;
use url::{Url, Host};
use hyper::{self, server, header, Control};
use hyper::net::HttpStream;
use jsonrpc_http_server as http;
use apps;
use apps::fetcher::Fetcher;
use endpoint::{Endpoint, Endpoints, EndpointPath, Handler};
use handlers;
use Embeddable;
/// Special endpoints are accessible on every domain (every dapp)
#[derive(Debug, PartialEq, Hash, Eq)]
pub enum SpecialEndpoint {
Rpc,
Api,
Utils,
Home,
None,
}
pub struct Router {
endpoints: Option<Endpoints>,
fetch: Arc<Fetcher>,
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
embeddable_on: Embeddable,
dapps_domain: String,
}
impl http::RequestMiddleware for Router {
fn on_request(&self, req: &server::Request<HttpStream>, control: &Control) -> http::RequestMiddlewareAction {
// Choose proper handler depending on path / domain
let url = handlers::extract_url(req);
let endpoint = extract_endpoint(&url, &self.dapps_domain);
let referer = extract_referer_endpoint(req, &self.dapps_domain);
let is_utils = endpoint.1 == SpecialEndpoint::Utils;
let is_origin_set = req.headers().get::<header::Origin>().is_some();
let is_get_request = *req.method() == hyper::Method::Get;
let is_head_request = *req.method() == hyper::Method::Head;
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req);
let control = control.clone();
debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint);
let handler: Option<Box<Handler>> = match (endpoint.0, endpoint.1, referer) {
// Handle invalid web requests that we can recover from
(ref path, SpecialEndpoint::None, Some((ref referer, ref referer_url)))
if referer.app_id == apps::WEB_PATH
&& self.endpoints.as_ref().map(|ep| ep.contains_key(apps::WEB_PATH)).unwrap_or(false)
&& !is_web_endpoint(path)
=>
{
trace!(target: "dapps", "Redirecting to correct web request: {:?}", referer_url);
let len = cmp::min(referer_url.path.len(), 2); // /web/<encoded>/
let base = referer_url.path[..len].join("/");
let requested = url.map(|u| u.path.join("/")).unwrap_or_default();
Some(handlers::Redirection::boxed(&format!("/{}/{}", base, requested)))
},
// First check special endpoints
(ref path, ref endpoint, _) if self.special.contains_key(endpoint) => {
trace!(target: "dapps", "Resolving to special endpoint.");
self.special.get(endpoint)
.expect("special known to contain key; qed")
.as_ref()
.map(|special| special.to_async_handler(path.clone().unwrap_or_default(), control))
},
// Then delegate to dapp
(Some(ref path), _, _) if self.endpoints.as_ref().map(|ep| ep.contains_key(&path.app_id)).unwrap_or(false) => {
trace!(target: "dapps", "Resolving to local/builtin dapp.");
Some(self.endpoints
.as_ref()
.expect("endpoints known to be set; qed")
.get(&path.app_id)
.expect("endpoints known to contain key; qed")
.to_async_handler(path.clone(), control))
},
// Try to resolve and fetch the dapp
(Some(ref path), _, _) if self.fetch.contains(&path.app_id) => {
trace!(target: "dapps", "Resolving to fetchable content.");
Some(self.fetch.to_async_handler(path.clone(), control))
},
// 404 for non-existent content (only if serving endpoints and not homepage)
(Some(ref path), _, _)
if (is_get_request || is_head_request)
&& self.endpoints.is_some()
&& path.app_id != apps::HOME_PAGE
=>
{
trace!(target: "dapps", "Resolving to 404.");
Some(Box::new(handlers::ContentHandler::error(
hyper::StatusCode::NotFound,
"404 Not Found",
"Requested content was not found.",
None,
self.embeddable_on.clone(),
)))
},
// Any other GET|HEAD requests to home page.
_ if (is_get_request || is_head_request) && self.special.contains_key(&SpecialEndpoint::Home) => {
self.special.get(&SpecialEndpoint::Home)
.expect("special known to contain key; qed")
.as_ref()
.map(|special| special.to_async_handler(Default::default(), control))
},
// RPC by default
_ => {
trace!(target: "dapps", "Resolving to RPC call.");
None
}
};
match handler {
Some(handler) => http::RequestMiddlewareAction::Respond {
should_validate_hosts: !is_utils,
handler: handler,
},
None => http::RequestMiddlewareAction::Proceed {
should_continue_on_invalid_cors: !is_origin_set,
},
}
}
}
impl Router {
pub fn new(
content_fetcher: Arc<Fetcher>,
endpoints: Option<Endpoints>,
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
embeddable_on: Embeddable,
dapps_domain: String,
) -> Self {
Router {
endpoints: endpoints,
fetch: content_fetcher,
special: special,
embeddable_on: embeddable_on,
dapps_domain: format!(".{}", dapps_domain),
}
}
}
fn is_web_endpoint(path: &Option<EndpointPath>) -> bool {
match *path {
Some(ref path) if path.app_id == apps::WEB_PATH => true,
_ => false,
}
}
fn extract_referer_endpoint(req: &server::Request<HttpStream>, dapps_domain: &str) -> Option<(EndpointPath, Url)> {
let referer = req.headers().get::<header::Referer>();
let url = referer.and_then(|referer| Url::parse(&referer.0).ok());
url.and_then(|url| {
let option = Some(url);
extract_url_referer_endpoint(&option, dapps_domain).or_else(|| {
extract_endpoint(&option, dapps_domain).0.map(|endpoint| (endpoint, option.expect("Just wrapped; qed")))
})
})
}
fn extract_url_referer_endpoint(url: &Option<Url>, dapps_domain: &str) -> Option<(EndpointPath, Url)> {
let query = url.as_ref().and_then(|url| url.query.as_ref());
match (url, query) {
(&Some(ref url), Some(ref query)) if query.starts_with(apps::URL_REFERER) => {
let referer_url = format!("http://{}:{}/{}", url.host, url.port, &query[apps::URL_REFERER.len()..]);
debug!(target: "dapps", "Recovering referer from query parameter: {}", referer_url);
let referer_url = Url::parse(&referer_url).ok();
extract_endpoint(&referer_url, dapps_domain).0.map(|endpoint| {
(endpoint, referer_url.expect("Endpoint returned only when url `is_some`").clone())
})
},
_ => None,
}
}
fn extract_endpoint(url: &Option<Url>, dapps_domain: &str) -> (Option<EndpointPath>, SpecialEndpoint) {
fn special_endpoint(url: &Url) -> SpecialEndpoint {
if url.path.len() <= 1 {
return SpecialEndpoint::None;
}
match url.path[0].as_ref() {
apps::RPC_PATH => SpecialEndpoint::Rpc,
apps::API_PATH => SpecialEndpoint::Api,
apps::UTILS_PATH => SpecialEndpoint::Utils,
apps::HOME_PAGE => SpecialEndpoint::Home,
_ => SpecialEndpoint::None,
}
}
match *url {
Some(ref url) => match url.host {
Host::Domain(ref domain) if domain.ends_with(dapps_domain) => {
let id = &domain[0..(domain.len() - dapps_domain.len())];
let (id, params) = if let Some(split) = id.rfind('.') {
let (params, id) = id.split_at(split);
(id[1..].to_owned(), [params.to_owned()].into_iter().chain(&url.path).cloned().collect())
} else {
(id.to_owned(), url.path.clone())
};
(Some(EndpointPath {
app_id: id,
app_params: params,
host: domain.clone(),
port: url.port,
using_dapps_domains: true,
}), special_endpoint(url))
},
_ if url.path.len() > 1 => {
let id = url.path[0].to_owned();
(Some(EndpointPath {
app_id: id,
app_params: url.path[1..].to_vec(),
host: format!("{}", url.host),
port: url.port,
using_dapps_domains: false,
}), special_endpoint(url))
},
_ => (None, special_endpoint(url)),
},
_ => (None, SpecialEndpoint::None)
}
}
#[test]
fn should_extract_endpoint() {
let dapps_domain = ".web3.site";
assert_eq!(extract_endpoint(&None, dapps_domain), (None, SpecialEndpoint::None));
// With path prefix
assert_eq!(
extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok(), dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["index.html".to_owned()],
host: "localhost".to_owned(),
port: 8080,
using_dapps_domains: false,
}), SpecialEndpoint::None)
);
// With path prefix
assert_eq!(
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok(), dapps_domain),
(Some(EndpointPath {
app_id: "rpc".to_owned(),
app_params: vec!["".to_owned()],
host: "localhost".to_owned(),
port: 8080,
using_dapps_domains: false,
}), SpecialEndpoint::Rpc)
);
assert_eq!(
extract_endpoint(&Url::parse("http://my.status.web3.site/parity-utils/inject.js").ok(), dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["my".to_owned(), "parity-utils".into(), "inject.js".into()],
host: "my.status.web3.site".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Utils)
);
// By Subdomain
assert_eq!(
extract_endpoint(&Url::parse("http://status.web3.site/test.html").ok(), dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["test.html".to_owned()],
host: "status.web3.site".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::None)
);
// RPC by subdomain
assert_eq!(
extract_endpoint(&Url::parse("http://my.status.web3.site/rpc/").ok(), dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["my".to_owned(), "rpc".into(), "".into()],
host: "my.status.web3.site".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Rpc)
);
// API by subdomain
assert_eq!(
extract_endpoint(&Url::parse("http://my.status.web3.site/api/").ok(), dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["my".to_owned(), "api".into(), "".into()],
host: "my.status.web3.site".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Api)
);
}

106
dapps/src/router/auth.rs Normal file
View File

@@ -0,0 +1,106 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! HTTP Authorization implementations
use std::collections::HashMap;
use hyper::{server, net, header, status};
use endpoint::Handler;
use handlers::{AuthRequiredHandler, ContentHandler};
/// Authorization result
pub enum Authorized {
/// Authorization was successful.
Yes,
/// Unsuccessful authorization. Handler for further work is returned.
No(Box<Handler>),
}
/// Authorization interface
pub trait Authorization : Send + Sync {
/// Checks if authorization is valid.
fn is_authorized(&self, req: &server::Request<net::HttpStream>)-> Authorized;
}
/// HTTP Basic Authorization handler
pub struct HttpBasicAuth {
users: HashMap<String, String>,
}
/// No-authorization implementation (authorization disabled)
pub struct NoAuth;
impl Authorization for NoAuth {
fn is_authorized(&self, _req: &server::Request<net::HttpStream>)-> Authorized {
Authorized::Yes
}
}
impl Authorization for HttpBasicAuth {
fn is_authorized(&self, req: &server::Request<net::HttpStream>) -> Authorized {
let auth = self.check_auth(&req);
match auth {
Access::Denied => {
Authorized::No(Box::new(ContentHandler::error(
status::StatusCode::Unauthorized,
"Unauthorized",
"You need to provide valid credentials to access this page.",
None,
None,
)))
},
Access::AuthRequired => {
Authorized::No(Box::new(AuthRequiredHandler))
},
Access::Granted => {
Authorized::Yes
},
}
}
}
#[derive(Debug)]
enum Access {
Granted,
Denied,
AuthRequired,
}
impl HttpBasicAuth {
/// Creates `HttpBasicAuth` instance with only one user.
pub fn single_user(username: &str, password: &str) -> Self {
let mut users = HashMap::new();
users.insert(username.to_owned(), password.to_owned());
HttpBasicAuth {
users: users
}
}
fn is_authorized(&self, username: &str, password: &str) -> bool {
self.users.get(&username.to_owned()).map_or(false, |pass| pass == password)
}
fn check_auth(&self, req: &server::Request<net::HttpStream>) -> Access {
match req.headers().get::<header::Authorization<header::Basic>>() {
Some(&header::Authorization(
header::Basic { ref username, password: Some(ref password) }
)) if self.is_authorized(username, password) => Access::Granted,
Some(_) => Access::Denied,
None => Access::AuthRequired,
}
}
}

View File

@@ -0,0 +1,47 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use apps::DAPPS_DOMAIN;
use hyper::{server, header, StatusCode};
use hyper::net::HttpStream;
use jsonrpc_http_server::{is_host_header_valid};
use handlers::ContentHandler;
pub fn is_valid(request: &server::Request<HttpStream>, allowed_hosts: &[String], endpoints: Vec<String>) -> bool {
let mut endpoints = endpoints.iter()
.map(|endpoint| format!("{}{}", endpoint, DAPPS_DOMAIN))
.collect::<Vec<String>>();
endpoints.extend_from_slice(allowed_hosts);
let header_valid = is_host_header_valid(request, &endpoints);
match (header_valid, request.headers().get::<header::Host>()) {
(true, _) => true,
(_, Some(host)) => host.hostname.ends_with(DAPPS_DOMAIN),
_ => false,
}
}
pub fn host_invalid_response() -> Box<server::Handler<HttpStream> + Send> {
Box::new(ContentHandler::error(StatusCode::Forbidden,
"Current Host Is Disallowed",
"You are trying to access your node using incorrect address.",
Some("Use allowed URL or specify different <code>hosts</code> CLI options."),
None,
))
}

305
dapps/src/router/mod.rs Normal file
View File

@@ -0,0 +1,305 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Router implementation
//! Processes request handling authorization and dispatching it to proper application.
pub mod auth;
mod host_validation;
use address;
use std::sync::Arc;
use std::collections::HashMap;
use url::{Url, Host};
use hyper::{self, server, Next, Encoder, Decoder, Control, StatusCode};
use hyper::net::HttpStream;
use apps::{self, DAPPS_DOMAIN};
use apps::fetcher::ContentFetcher;
use endpoint::{Endpoint, Endpoints, EndpointPath};
use handlers::{Redirection, extract_url, ContentHandler};
use self::auth::{Authorization, Authorized};
/// Special endpoints are accessible on every domain (every dapp)
#[derive(Debug, PartialEq, Hash, Eq)]
pub enum SpecialEndpoint {
Rpc,
Api,
Utils,
None,
}
pub struct Router<A: Authorization + 'static> {
control: Option<Control>,
signer_address: Option<(String, u16)>,
endpoints: Arc<Endpoints>,
fetch: Arc<ContentFetcher>,
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
authorization: Arc<A>,
allowed_hosts: Option<Vec<String>>,
handler: Box<server::Handler<HttpStream> + Send>,
}
impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
// Choose proper handler depending on path / domain
let url = extract_url(&req);
let endpoint = extract_endpoint(&url);
let is_utils = endpoint.1 == SpecialEndpoint::Utils;
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", url, req);
// Validate Host header
if let Some(ref hosts) = self.allowed_hosts {
trace!(target: "dapps", "Validating host headers against: {:?}", hosts);
let is_valid = is_utils || host_validation::is_valid(&req, hosts, self.endpoints.keys().cloned().collect());
if !is_valid {
debug!(target: "dapps", "Rejecting invalid host header.");
self.handler = host_validation::host_invalid_response();
return self.handler.on_request(req);
}
}
trace!(target: "dapps", "Checking authorization.");
// Check authorization
let auth = self.authorization.is_authorized(&req);
if let Authorized::No(handler) = auth {
debug!(target: "dapps", "Authorization denied.");
self.handler = handler;
return self.handler.on_request(req);
}
let control = self.control.take().expect("on_request is called only once; control is always defined at start; qed");
debug!(target: "dapps", "Handling endpoint request: {:?}", endpoint);
self.handler = match endpoint {
// First check special endpoints
(ref path, ref endpoint) if self.special.contains_key(endpoint) => {
trace!(target: "dapps", "Resolving to special endpoint.");
self.special.get(endpoint)
.expect("special known to contain key; qed")
.to_async_handler(path.clone().unwrap_or_default(), control)
},
// Then delegate to dapp
(Some(ref path), _) if self.endpoints.contains_key(&path.app_id) => {
trace!(target: "dapps", "Resolving to local/builtin dapp.");
self.endpoints.get(&path.app_id)
.expect("special known to contain key; qed")
.to_async_handler(path.clone(), control)
},
// Try to resolve and fetch the dapp
(Some(ref path), _) if self.fetch.contains(&path.app_id) => {
trace!(target: "dapps", "Resolving to fetchable content.");
self.fetch.to_async_handler(path.clone(), control)
},
// NOTE [todr] /home is redirected to home page since some users may have the redirection cached
// (in the past we used 301 instead of 302)
// It should be safe to remove it in (near) future.
//
// 404 for non-existent content
(Some(ref path), _) if *req.method() == hyper::Method::Get && path.app_id != "home" => {
trace!(target: "dapps", "Resolving to 404.");
Box::new(ContentHandler::error(
StatusCode::NotFound,
"404 Not Found",
"Requested content was not found.",
None,
self.signer_address.clone(),
))
},
// Redirect any other GET request to signer.
_ if *req.method() == hyper::Method::Get => {
if let Some(signer_address) = self.signer_address.clone() {
trace!(target: "dapps", "Redirecting to signer interface.");
Redirection::boxed(&format!("http://{}", address(signer_address)))
} else {
trace!(target: "dapps", "Signer disabled, returning 404.");
Box::new(ContentHandler::error(
StatusCode::NotFound,
"404 Not Found",
"Your homepage is not available when Trusted Signer is disabled.",
Some("You can still access dapps by writing a correct address, though. Re-enabled Signer to get your homepage back."),
self.signer_address.clone(),
))
}
},
// RPC by default
_ => {
trace!(target: "dapps", "Resolving to RPC call.");
self.special.get(&SpecialEndpoint::Rpc)
.expect("RPC endpoint always stored; qed")
.to_async_handler(EndpointPath::default(), control)
}
};
// Delegate on_request to proper handler
self.handler.on_request(req)
}
/// This event occurs each time the `Request` is ready to be read from.
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
self.handler.on_request_readable(decoder)
}
/// This event occurs after the first time this handled signals `Next::write()`.
fn on_response(&mut self, response: &mut server::Response) -> Next {
self.handler.on_response(response)
}
/// This event occurs each time the `Response` is ready to be written to.
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
self.handler.on_response_writable(encoder)
}
}
impl<A: Authorization> Router<A> {
pub fn new(
control: Control,
signer_address: Option<(String, u16)>,
content_fetcher: Arc<ContentFetcher>,
endpoints: Arc<Endpoints>,
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
authorization: Arc<A>,
allowed_hosts: Option<Vec<String>>,
) -> Self {
let handler = special.get(&SpecialEndpoint::Utils)
.expect("Utils endpoint always stored; qed")
.to_handler(EndpointPath::default());
Router {
control: Some(control),
signer_address: signer_address,
endpoints: endpoints,
fetch: content_fetcher,
special: special,
authorization: authorization,
allowed_hosts: allowed_hosts,
handler: handler,
}
}
}
fn extract_endpoint(url: &Option<Url>) -> (Option<EndpointPath>, SpecialEndpoint) {
fn special_endpoint(url: &Url) -> SpecialEndpoint {
if url.path.len() <= 1 {
return SpecialEndpoint::None;
}
match url.path[0].as_ref() {
apps::RPC_PATH => SpecialEndpoint::Rpc,
apps::API_PATH => SpecialEndpoint::Api,
apps::UTILS_PATH => SpecialEndpoint::Utils,
_ => SpecialEndpoint::None,
}
}
match *url {
Some(ref url) => match url.host {
Host::Domain(ref domain) if domain.ends_with(DAPPS_DOMAIN) => {
let len = domain.len() - DAPPS_DOMAIN.len();
let id = domain[0..len].to_owned();
(Some(EndpointPath {
app_id: id,
host: domain.clone(),
port: url.port,
using_dapps_domains: true,
}), special_endpoint(url))
},
_ if url.path.len() > 1 => {
let id = url.path[0].clone();
(Some(EndpointPath {
app_id: id.clone(),
host: format!("{}", url.host),
port: url.port,
using_dapps_domains: false,
}), special_endpoint(url))
},
_ => (None, special_endpoint(url)),
},
_ => (None, SpecialEndpoint::None)
}
}
#[test]
fn should_extract_endpoint() {
assert_eq!(extract_endpoint(&None), (None, SpecialEndpoint::None));
// With path prefix
assert_eq!(
extract_endpoint(&Url::parse("http://localhost:8080/status/index.html").ok()),
(Some(EndpointPath {
app_id: "status".to_owned(),
host: "localhost".to_owned(),
port: 8080,
using_dapps_domains: false,
}), SpecialEndpoint::None)
);
// With path prefix
assert_eq!(
extract_endpoint(&Url::parse("http://localhost:8080/rpc/").ok()),
(Some(EndpointPath {
app_id: "rpc".to_owned(),
host: "localhost".to_owned(),
port: 8080,
using_dapps_domains: false,
}), SpecialEndpoint::Rpc)
);
assert_eq!(
extract_endpoint(&Url::parse("http://my.status.parity/parity-utils/inject.js").ok()),
(Some(EndpointPath {
app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Utils)
);
// By Subdomain
assert_eq!(
extract_endpoint(&Url::parse("http://my.status.parity/test.html").ok()),
(Some(EndpointPath {
app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::None)
);
// RPC by subdomain
assert_eq!(
extract_endpoint(&Url::parse("http://my.status.parity/rpc/").ok()),
(Some(EndpointPath {
app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Rpc)
);
// API by subdomain
assert_eq!(
extract_endpoint(&Url::parse("http://my.status.parity/api/").ok()),
(Some(EndpointPath {
app_id: "my.status".to_owned(),
host: "my.status.parity".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Api)
);
}

51
dapps/src/rpc.rs Normal file
View File

@@ -0,0 +1,51 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::sync::{Arc, Mutex};
use hyper;
use jsonrpc_core::IoHandler;
use jsonrpc_http_server::{ServerHandler, PanicHandler, AccessControlAllowOrigin};
use endpoint::{Endpoint, EndpointPath, Handler};
pub fn rpc(handler: Arc<IoHandler>, panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>) -> Box<Endpoint> {
Box::new(RpcEndpoint {
handler: handler,
panic_handler: panic_handler,
cors_domain: None,
// NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router.
allowed_hosts: None,
})
}
struct RpcEndpoint {
handler: Arc<IoHandler>,
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
cors_domain: Option<Vec<AccessControlAllowOrigin>>,
allowed_hosts: Option<Vec<String>>,
}
impl Endpoint for RpcEndpoint {
fn to_async_handler(&self, _path: EndpointPath, control: hyper::Control) -> Box<Handler> {
let panic_handler = PanicHandler { handler: self.panic_handler.clone() };
Box::new(ServerHandler::new(
self.handler.clone(),
self.cors_domain.clone(),
self.allowed_hosts.clone(),
panic_handler,
control,
))
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -33,12 +33,35 @@ fn should_return_error() {
);
// then
response.assert_status("HTTP/1.1 404 Not Found");
response.assert_header("Content-Type", "application/json");
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json");
assert_eq!(response.body, format!("58\n{}\n0\n\n", r#"{"code":"404","title":"Not Found","detail":"Resource you requested has not been found."}"#));
assert_security_headers(&response.headers);
}
#[test]
fn should_serve_apps() {
// given
let server = serve();
// when
let response = request(server,
"\
GET /api/apps HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json");
assert!(response.body.contains("Parity UI"), response.body);
assert_security_headers(&response.headers);
}
#[test]
fn should_handle_ping() {
// given
@@ -56,8 +79,8 @@ fn should_handle_ping() {
);
// then
response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Content-Type", "application/json");
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_eq!(response.headers.get(3).unwrap(), "Content-Type: application/json");
assert_eq!(response.body, "0\n\n".to_owned());
assert_security_headers(&response.headers);
}
@@ -79,7 +102,59 @@ fn should_try_to_resolve_dapp() {
);
// then
response.assert_status("HTTP/1.1 404 Not Found");
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert_eq!(registrar.calls.lock().len(), 2);
assert_security_headers(&response.headers);
}
#[test]
fn should_return_signer_port_cors_headers() {
// given
let server = serve();
// when
let response = request(server,
"\
POST /api/ping HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Origin: http://127.0.0.1:18180\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert!(
response.headers_raw.contains("Access-Control-Allow-Origin: http://127.0.0.1:18180"),
"CORS header for signer missing: {:?}",
response.headers
);
}
#[test]
fn should_return_signer_port_cors_headers_for_home_parity() {
// given
let server = serve();
// when
let response = request(server,
"\
POST /api/ping HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Origin: http://home.parity\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert!(
response.headers_raw.contains("Access-Control-Allow-Origin: http://home.parity"),
"CORS header for home.parity missing: {:?}",
response.headers
);
}

View File

@@ -0,0 +1,80 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use tests::helpers::{serve_with_auth, request, assert_security_headers_for_embed};
#[test]
fn should_require_authorization() {
// given
let server = serve_with_auth("test", "test");
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(response.status, "HTTP/1.1 401 Unauthorized".to_owned());
assert_eq!(response.headers.get(0).unwrap(), "WWW-Authenticate: Basic realm=\"Parity\"");
}
#[test]
fn should_reject_on_invalid_auth() {
// given
let server = serve_with_auth("test", "test");
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\r\n
\r\n\
"
);
// then
assert_eq!(response.status, "HTTP/1.1 401 Unauthorized".to_owned());
assert!(response.body.contains("Unauthorized"), response.body);
assert_eq!(response.headers_raw.contains("WWW-Authenticate"), false);
}
#[test]
fn should_allow_on_valid_auth() {
// given
let server = serve_with_auth("Aladdin", "OpenSesame");
// when
let response = request(server,
"\
GET /ui/ HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l\r\n
\r\n\
"
);
// then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_security_headers_for_embed(&response.headers);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,13 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use devtools::http_client;
use rustc_hex::FromHex;
use tests::helpers::{
serve_with_registrar, serve_with_registrar_and_sync, serve_with_fetch,
serve_with_registrar_and_fetch, serve_with_registrar_and_fetch_and_threads,
request, assert_security_headers_for_embed,
};
use tests::helpers::{serve_with_registrar, serve_with_registrar_and_sync, request, assert_security_headers_for_embed};
#[test]
fn should_resolve_dapp() {
@@ -31,14 +25,14 @@ fn should_resolve_dapp() {
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.web3.site\r\n\
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.parity\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 404 Not Found");
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert_eq!(registrar.calls.lock().len(), 2);
assert_security_headers_for_embed(&response.headers);
}
@@ -47,494 +41,27 @@ fn should_resolve_dapp() {
fn should_return_503_when_syncing_but_should_make_the_calls() {
// given
let (server, registrar) = serve_with_registrar_and_sync();
{
let mut responses = registrar.responses.lock();
let res1 = responses.get(0).unwrap().clone();
let res2 = responses.get(1).unwrap().clone();
// Registrar will be called twice - fill up the responses.
responses.push(res1);
responses.push(res2);
}
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 503 Service Unavailable");
assert_eq!(registrar.calls.lock().len(), 2);
assert_security_headers_for_embed(&response.headers);
}
const GAVCOIN_DAPP: &'static str = "00000000000000000000000000000000000000000000000000000000000000609faf32e1e3845e237cc6efd27187cee13b3b99db000000000000000000000000000000000000000000000000d8bd350823e28ff75e74a34215faefdc8a52fd8e00000000000000000000000000000000000000000000000000000000000000116761766f66796f726b2f676176636f696e000000000000000000000000000000";
const GAVCOIN_ICON: &'static str = "00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d8bd350823e28ff75e74a34215faefdc8a52fd8e000000000000000000000000000000000000000000000000000000000000007768747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f657468636f72652f646170702d6173736574732f623838653938336162616131613661363334356238643934343863313562313137646462353430652f746f6b656e732f676176636f696e2d36347836342e706e67000000000000000000";
#[test]
fn should_return_502_on_hash_mismatch() {
// given
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
let gavcoin = GAVCOIN_DAPP.from_hex().unwrap();
registrar.set_result(
"94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd".parse().unwrap(),
Ok(gavcoin.clone())
);
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 94f093625c06887d94d9fee0d5f9cc4aaa46f33d24d1c7e4b5237e7c37d547dd.web3.site\r\n\
Host: 1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d.parity\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(response.status, "HTTP/1.1 503 Service Unavailable".to_owned());
assert_eq!(registrar.calls.lock().len(), 4);
fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db");
fetch.assert_no_more_requests();
response.assert_status("HTTP/1.1 502 Bad Gateway");
assert!(response.body.contains("HashMismatch"), "Expected hash mismatch response, got: {:?}", response.body);
assert_security_headers_for_embed(&response.headers);
}
#[test]
fn should_return_error_for_invalid_dapp_zip() {
// given
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
let gavcoin = GAVCOIN_DAPP.from_hex().unwrap();
registrar.set_result(
"2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(),
Ok(gavcoin.clone())
);
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(registrar.calls.lock().len(), 4);
fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db");
fetch.assert_no_more_requests();
response.assert_status("HTTP/1.1 502 Bad Gateway");
assert!(response.body.contains("InvalidArchive"), "Expected invalid zip response, got: {:?}", response.body);
assert_security_headers_for_embed(&response.headers);
}
#[test]
fn should_return_fetched_dapp_content() {
// given
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
let gavcoin = GAVCOIN_DAPP.from_hex().unwrap();
registrar.set_result(
"9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a".parse().unwrap(),
Ok(gavcoin.clone())
);
fetch.set_response(include_bytes!("../../res/gavcoin.zip"));
// when
let response1 = http_client::request(server.addr(),
"\
GET /index.html HTTP/1.1\r\n\
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
let response2 = http_client::request(server.addr(),
"\
GET /manifest.json HTTP/1.1\r\n\
Host: 9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(registrar.calls.lock().len(), 4);
fetch.assert_requested("https://codeload.github.com/gavofyork/gavcoin/zip/9faf32e1e3845e237cc6efd27187cee13b3b99db");
fetch.assert_no_more_requests();
response1.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response1.headers);
assert_eq!(
response1.body,
r#"18
<h1>Hello Gavcoin!</h1>
"#
);
response2.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response2.headers);
assert_eq!(
response2.body,
r#"BE
{
"id": "9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a",
"name": "Gavcoin",
"description": "Gavcoin",
"version": "1.0.0",
"author": "",
"iconUrl": "icon.png"
}
0
"#
);
}
#[test]
fn should_return_fetched_content() {
// given
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
let gavcoin = GAVCOIN_ICON.from_hex().unwrap();
registrar.set_result(
"2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(),
Ok(gavcoin.clone())
);
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(registrar.calls.lock().len(), 4);
fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png");
fetch.assert_no_more_requests();
response.assert_status("HTTP/1.1 200 OK");
response.assert_security_headers_present(None);
}
#[test]
fn should_cache_content() {
// given
let (server, fetch, registrar) = serve_with_registrar_and_fetch();
let gavcoin = GAVCOIN_ICON.from_hex().unwrap();
registrar.set_result(
"2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(),
Ok(gavcoin.clone())
);
let request_str = "\
GET / HTTP/1.1\r\n\
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
Connection: close\r\n\
\r\n\
";
let response = http_client::request(server.addr(), request_str);
fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png");
fetch.assert_no_more_requests();
response.assert_status("HTTP/1.1 200 OK");
// when
let response = http_client::request(server.addr(), request_str);
// then
fetch.assert_no_more_requests();
response.assert_status("HTTP/1.1 200 OK");
}
#[test]
fn should_not_request_content_twice() {
use std::thread;
// given
let (server, fetch, registrar) = serve_with_registrar_and_fetch_and_threads(true);
let gavcoin = GAVCOIN_ICON.from_hex().unwrap();
registrar.set_result(
"2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e".parse().unwrap(),
Ok(gavcoin.clone())
);
let request_str = "\
GET / HTTP/1.1\r\n\
Host: 2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e.web3.site\r\n\
Connection: close\r\n\
\r\n\
";
let fire_request = || {
let addr = server.addr().to_owned();
let req = request_str.to_owned();
thread::spawn(move || {
http_client::request(&addr, &req)
})
};
let control = fetch.manual();
// when
// Fire two requests at the same time
let r1 = fire_request();
let r2 = fire_request();
// wait for single request in fetch, the second one should go into waiting state.
control.wait_for_requests(1);
control.respond();
let response1 = r1.join().unwrap();
let response2 = r2.join().unwrap();
// then
fetch.assert_requested("https://raw.githubusercontent.com/ethcore/dapp-assets/b88e983abaa1a6a6345b8d9448c15b117ddb540e/tokens/gavcoin-64x64.png");
fetch.assert_no_more_requests();
response1.assert_status("HTTP/1.1 200 OK");
response2.assert_status("HTTP/1.1 200 OK");
}
#[test]
fn should_encode_and_decode_base32() {
use base32;
let encoded = base32::encode(base32::Alphabet::Crockford, "token+https://parity.io".as_bytes());
assert_eq!("EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY", &encoded);
let data = base32::decode(base32::Alphabet::Crockford, "EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY").unwrap();
assert_eq!("token+https://parity.io", &String::from_utf8(data).unwrap());
}
#[test]
fn should_stream_web_content() {
// given
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response.headers);
fetch.assert_requested("https://parity.io/");
fetch.assert_no_more_requests();
}
#[test]
fn should_support_base32_encoded_web_urls() {
// given
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
// when
let response = request(server,
"\
GET /styles.css?test=123 HTTP/1.1\r\n\
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response.headers);
fetch.assert_requested("https://parity.io/styles.css?test=123");
fetch.assert_no_more_requests();
}
#[test]
fn should_correctly_handle_long_label_when_splitted() {
// given
let (server, fetch) = serve_with_fetch("xolrg9fePeQyKLnL", "https://contribution.melonport.com");
// when
let response = request(server,
"\
GET /styles.css?test=123 HTTP/1.1\r\n\
Host: f1qprwk775k6am35a5wmpk3e9gnpgx3me1sk.mbsfcdqpwx3jd5h7ax39dxq2wvb5dhqpww3fe9t2wrvfdm.web.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response.headers);
fetch.assert_requested("https://contribution.melonport.com/styles.css?test=123");
fetch.assert_no_more_requests();
}
#[test]
fn should_support_base32_encoded_web_urls_as_path() {
// given
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
// when
let response = request(server,
"\
GET /web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/styles.css?test=123 HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response.headers);
fetch.assert_requested("https://parity.io/styles.css?test=123");
fetch.assert_no_more_requests();
}
#[test]
fn should_return_error_on_non_whitelisted_domain() {
// given
let (server, fetch) = serve_with_fetch("token", "https://ethcore.io");
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 400 Bad Request");
assert_security_headers_for_embed(&response.headers);
fetch.assert_no_more_requests();
}
#[test]
fn should_return_error_on_invalid_token() {
// given
let (server, fetch) = serve_with_fetch("test", "https://parity.io");
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 400 Bad Request");
assert_security_headers_for_embed(&response.headers);
fetch.assert_no_more_requests();
}
#[test]
fn should_return_error_on_invalid_protocol() {
// given
let (server, fetch) = serve_with_fetch("token", "ftp://parity.io");
// when
let response = request(server,
"\
GET /web/token/ftp/parity.io/ HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 400 Bad Request");
assert_security_headers_for_embed(&response.headers);
fetch.assert_no_more_requests();
}
#[test]
fn should_disallow_non_get_requests() {
// given
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
// when
let response = request(server,
"\
POST / HTTP/1.1\r\n\
Host: EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY.web.web3.site\r\n\
Content-Type: application/json\r\n\
Connection: close\r\n\
\r\n\
123\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 405 Method Not Allowed");
assert_security_headers_for_embed(&response.headers);
fetch.assert_no_more_requests();
}
#[test]
fn should_fix_absolute_requests_based_on_referer() {
// given
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
// when
let response = request(server,
"\
GET /styles.css HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Connection: close\r\n\
Referer: http://localhost:8080/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 302 Found");
response.assert_header("Location", "/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/styles.css");
fetch.assert_no_more_requests();
}
#[test]
fn should_fix_absolute_requests_based_on_referer_in_url() {
// given
let (server, fetch) = serve_with_fetch("token", "https://parity.io");
// when
let response = request(server,
"\
GET /styles.css HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Connection: close\r\n\
Referer: http://localhost:8080/?__referer=web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 302 Found");
response.assert_header("Location", "/web/EHQPPSBE5DM78X3GECX2YBVGC5S6JX3S5SMPY/styles.css");
fetch.assert_no_more_requests();
}

121
dapps/src/tests/helpers.rs Normal file
View File

@@ -0,0 +1,121 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::env;
use std::str;
use std::sync::Arc;
use rustc_serialize::hex::FromHex;
use env_logger::LogBuilder;
use ServerBuilder;
use Server;
use apps::urlhint::ContractClient;
use util::{Bytes, Address, Mutex, ToPretty};
use devtools::http_client;
const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
const SIGNER_PORT: u16 = 18180;
pub struct FakeRegistrar {
pub calls: Arc<Mutex<Vec<(String, String)>>>,
pub responses: Mutex<Vec<Result<Bytes, String>>>,
}
impl FakeRegistrar {
fn new() -> Self {
FakeRegistrar {
calls: Arc::new(Mutex::new(Vec::new())),
responses: Mutex::new(
vec![
Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()),
Ok(Vec::new())
]
),
}
}
}
impl ContractClient for FakeRegistrar {
fn registrar(&self) -> Result<Address, String> {
Ok(REGISTRAR.parse().unwrap())
}
fn call(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
self.calls.lock().push((address.to_hex(), data.to_hex()));
self.responses.lock().remove(0)
}
}
fn init_logger() {
// Initialize logger
if let Ok(log) = env::var("RUST_LOG") {
let mut builder = LogBuilder::new();
builder.parse(&log);
builder.init().expect("Logger is initialized only once.");
}
}
pub fn init_server(hosts: Option<Vec<String>>, is_syncing: bool) -> (Server, Arc<FakeRegistrar>) {
init_logger();
let registrar = Arc::new(FakeRegistrar::new());
let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone());
builder.with_sync_status(Arc::new(move || is_syncing));
builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)));
(
builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(),
registrar,
)
}
pub fn serve_with_auth(user: &str, pass: &str) -> Server {
init_logger();
let registrar = Arc::new(FakeRegistrar::new());
let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar);
builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)));
builder.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap()
}
pub fn serve_hosts(hosts: Option<Vec<String>>) -> Server {
init_server(hosts, false).0
}
pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
init_server(None, false)
}
pub fn serve_with_registrar_and_sync() -> (Server, Arc<FakeRegistrar>) {
init_server(None, true)
}
pub fn serve() -> Server {
init_server(None, false).0
}
pub fn request(server: Server, request: &str) -> http_client::Response {
http_client::request(server.addr(), request)
}
pub fn assert_security_headers(headers: &[String]) {
http_client::assert_security_headers_present(headers, None)
}
pub fn assert_security_headers_for_embed(headers: &[String]) {
http_client::assert_security_headers_present(headers, Some(SIGNER_PORT))
}

View File

@@ -1,122 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::{io, thread, time};
use std::sync::{atomic, mpsc, Arc};
use util::Mutex;
use futures::{self, Future};
use fetch::{self, Fetch};
pub struct FetchControl {
sender: mpsc::Sender<()>,
fetch: FakeFetch,
}
impl FetchControl {
pub fn respond(self) {
self.sender.send(())
.expect("Fetch cannot be finished without sending a response at least once.");
}
pub fn wait_for_requests(&self, len: usize) {
const MAX_TIMEOUT_MS: u64 = 5000;
const ATTEMPTS: u64 = 10;
let mut attempts_left = ATTEMPTS;
loop {
let current = self.fetch.requested.lock().len();
if current == len {
break;
} else if attempts_left == 0 {
panic!(
"Timeout reached when waiting for pending requests. Expected: {}, current: {}",
len, current
);
} else {
attempts_left -= 1;
// Should we handle spurious timeouts better?
thread::park_timeout(time::Duration::from_millis(MAX_TIMEOUT_MS / ATTEMPTS));
}
}
}
}
#[derive(Clone, Default)]
pub struct FakeFetch {
manual: Arc<Mutex<Option<mpsc::Receiver<()>>>>,
response: Arc<Mutex<Option<&'static [u8]>>>,
asserted: Arc<atomic::AtomicUsize>,
requested: Arc<Mutex<Vec<String>>>,
}
impl FakeFetch {
pub fn set_response(&self, data: &'static [u8]) {
*self.response.lock() = Some(data);
}
pub fn manual(&self) -> FetchControl {
assert!(self.manual.lock().is_none(), "Only one manual control may be active.");
let (tx, rx) = mpsc::channel();
*self.manual.lock() = Some(rx);
FetchControl {
sender: tx,
fetch: self.clone(),
}
}
pub fn assert_requested(&self, url: &str) {
let requests = self.requested.lock();
let idx = self.asserted.fetch_add(1, atomic::Ordering::SeqCst);
assert_eq!(requests.get(idx), Some(&url.to_owned()), "Expected fetch from specific URL.");
}
pub fn assert_no_more_requests(&self) {
let requests = self.requested.lock();
let len = self.asserted.load(atomic::Ordering::SeqCst);
assert_eq!(requests.len(), len, "Didn't expect any more requests, got: {:?}", &requests[len..]);
}
}
impl Fetch for FakeFetch {
type Result = futures::BoxFuture<fetch::Response, fetch::Error>;
fn new() -> Result<Self, fetch::Error> where Self: Sized {
Ok(FakeFetch::default())
}
fn fetch_with_abort(&self, url: &str, _abort: fetch::Abort) -> Self::Result {
self.requested.lock().push(url.into());
let manual = self.manual.clone();
let response = self.response.clone();
let (tx, rx) = futures::oneshot();
thread::spawn(move || {
if let Some(rx) = manual.lock().take() {
// wait for manual resume
let _ = rx.recv();
}
let data = response.lock().take().unwrap_or(b"Some content");
let cursor = io::Cursor::new(data);
tx.send(fetch::Response::from_reader(cursor)).unwrap();
});
rx.map_err(|_| fetch::Error::Aborted).boxed()
}
}

View File

@@ -1,308 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::env;
use std::str;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use env_logger::LogBuilder;
use jsonrpc_core::IoHandler;
use jsonrpc_http_server::{self as http, Host, DomainsValidation};
use devtools::http_client;
use hash_fetch::urlhint::ContractClient;
use fetch::{Fetch, Client as FetchClient};
use node_health::{NodeHealth, TimeChecker, CpuPool};
use parity_reactor::Remote;
use {Middleware, SyncStatus, WebProxyTokens};
mod registrar;
mod fetch;
use self::registrar::FakeRegistrar;
use self::fetch::FakeFetch;
const SIGNER_PORT: u16 = 18180;
#[derive(Debug)]
struct FakeSync(bool);
impl SyncStatus for FakeSync {
fn is_major_importing(&self) -> bool { self.0 }
fn peers(&self) -> (usize, usize) { (0, 5) }
}
fn init_logger() {
// Initialize logger
if let Ok(log) = env::var("RUST_LOG") {
let mut builder = LogBuilder::new();
builder.parse(&log);
let _ = builder.init(); // ignore errors since ./test.sh will call this multiple times.
}
}
pub fn init_server<F, B>(process: F, io: IoHandler, remote: Remote) -> (Server, Arc<FakeRegistrar>) where
F: FnOnce(ServerBuilder) -> ServerBuilder<B>,
B: Fetch,
{
init_logger();
let registrar = Arc::new(FakeRegistrar::new());
let mut dapps_path = env::temp_dir();
dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading");
let server = process(ServerBuilder::new(
&dapps_path, registrar.clone(), remote,
))
.signer_address(Some(("127.0.0.1".into(), SIGNER_PORT)))
.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), io).unwrap();
(
server,
registrar,
)
}
pub fn serve_with_rpc(io: IoHandler) -> Server {
init_server(|builder| builder, io, Remote::new_sync()).0
}
pub fn serve_hosts(hosts: Option<Vec<String>>) -> Server {
let hosts = hosts.map(|hosts| hosts.into_iter().map(Into::into).collect());
init_server(|builder| builder.allowed_hosts(hosts.into()), Default::default(), Remote::new_sync()).0
}
pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
init_server(|builder| builder, Default::default(), Remote::new_sync())
}
pub fn serve_with_registrar_and_sync() -> (Server, Arc<FakeRegistrar>) {
init_server(|builder| {
builder.sync_status(Arc::new(FakeSync(true)))
}, Default::default(), Remote::new_sync())
}
pub fn serve_with_registrar_and_fetch() -> (Server, FakeFetch, Arc<FakeRegistrar>) {
serve_with_registrar_and_fetch_and_threads(false)
}
pub fn serve_with_registrar_and_fetch_and_threads(multi_threaded: bool) -> (Server, FakeFetch, Arc<FakeRegistrar>) {
let fetch = FakeFetch::default();
let f = fetch.clone();
let (server, reg) = init_server(move |builder| {
builder.fetch(f.clone())
}, Default::default(), if multi_threaded { Remote::new_thread_per_future() } else { Remote::new_sync() });
(server, fetch, reg)
}
pub fn serve_with_fetch(web_token: &'static str, domain: &'static str) -> (Server, FakeFetch) {
let fetch = FakeFetch::default();
let f = fetch.clone();
let (server, _) = init_server(move |builder| {
builder
.fetch(f.clone())
.web_proxy_tokens(Arc::new(move |token| {
if &token == web_token { Some(domain.into()) } else { None }
}))
}, Default::default(), Remote::new_sync());
(server, fetch)
}
pub fn serve() -> Server {
init_server(|builder| builder, Default::default(), Remote::new_sync()).0
}
pub fn request(server: Server, request: &str) -> http_client::Response {
http_client::request(server.addr(), request)
}
pub fn assert_security_headers(headers: &[String]) {
http_client::assert_security_headers_present(headers, None)
}
pub fn assert_security_headers_for_embed(headers: &[String]) {
http_client::assert_security_headers_present(headers, Some(SIGNER_PORT))
}
/// Webapps HTTP+RPC server build.
pub struct ServerBuilder<T: Fetch = FetchClient> {
dapps_path: PathBuf,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
web_proxy_tokens: Arc<WebProxyTokens>,
signer_address: Option<(String, u16)>,
allowed_hosts: DomainsValidation<Host>,
remote: Remote,
fetch: Option<T>,
}
impl ServerBuilder {
/// Construct new dapps server
pub fn new<P: AsRef<Path>>(dapps_path: P, registrar: Arc<ContractClient>, remote: Remote) -> Self {
ServerBuilder {
dapps_path: dapps_path.as_ref().to_owned(),
registrar: registrar,
sync_status: Arc::new(FakeSync(false)),
web_proxy_tokens: Arc::new(|_| None),
signer_address: None,
allowed_hosts: DomainsValidation::Disabled,
remote: remote,
fetch: None,
}
}
}
impl<T: Fetch> ServerBuilder<T> {
/// Set a fetch client to use.
pub fn fetch<X: Fetch>(self, fetch: X) -> ServerBuilder<X> {
ServerBuilder {
dapps_path: self.dapps_path,
registrar: self.registrar,
sync_status: self.sync_status,
web_proxy_tokens: self.web_proxy_tokens,
signer_address: self.signer_address,
allowed_hosts: self.allowed_hosts,
remote: self.remote,
fetch: Some(fetch),
}
}
/// Change default sync status.
pub fn sync_status(mut self, status: Arc<SyncStatus>) -> Self {
self.sync_status = status;
self
}
/// Change default web proxy tokens validator.
pub fn web_proxy_tokens(mut self, tokens: Arc<WebProxyTokens>) -> Self {
self.web_proxy_tokens = tokens;
self
}
/// Change default signer port.
pub fn signer_address(mut self, signer_address: Option<(String, u16)>) -> Self {
self.signer_address = signer_address;
self
}
/// Change allowed hosts.
/// `None` - All hosts are allowed
/// `Some(whitelist)` - Allow only whitelisted hosts (+ listen address)
pub fn allowed_hosts(mut self, allowed_hosts: DomainsValidation<Host>) -> Self {
self.allowed_hosts = allowed_hosts;
self
}
/// Asynchronously start server with no authentication,
/// returns result with `Server` handle on success or an error.
pub fn start_unsecured_http(self, addr: &SocketAddr, io: IoHandler) -> Result<Server, http::Error> {
let fetch = self.fetch_client();
Server::start_http(
addr,
io,
self.allowed_hosts,
self.signer_address,
self.dapps_path,
vec![],
self.registrar,
self.sync_status,
self.web_proxy_tokens,
self.remote,
fetch,
)
}
fn fetch_client(&self) -> T {
match self.fetch.clone() {
Some(fetch) => fetch,
None => T::new().unwrap(),
}
}
}
const DAPPS_DOMAIN: &'static str = "web3.site";
/// Webapps HTTP server.
pub struct Server {
server: Option<http::Server>,
}
impl Server {
fn start_http<F: Fetch>(
addr: &SocketAddr,
io: IoHandler,
allowed_hosts: DomainsValidation<Host>,
signer_address: Option<(String, u16)>,
dapps_path: PathBuf,
extra_dapps: Vec<PathBuf>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
fetch: F,
) -> Result<Server, http::Error> {
let health = NodeHealth::new(
sync_status.clone(),
TimeChecker::new::<String>(&[], CpuPool::new(1)),
remote.clone(),
);
let middleware = Middleware::dapps(
health,
remote,
signer_address,
vec![],
vec![],
dapps_path,
extra_dapps,
DAPPS_DOMAIN.into(),
registrar,
sync_status,
web_proxy_tokens,
fetch,
);
let mut allowed_hosts: Option<Vec<Host>> = allowed_hosts.into();
allowed_hosts.as_mut().map(|mut hosts| {
hosts.push(format!("http://*.{}:*", DAPPS_DOMAIN).into());
hosts.push(format!("http://*.{}", DAPPS_DOMAIN).into());
});
http::ServerBuilder::new(io)
.request_middleware(middleware)
.allowed_hosts(allowed_hosts.into())
.cors(http::DomainsValidation::Disabled)
.start_http(addr)
.map(|server| Server {
server: Some(server),
})
}
/// Returns address that this server is bound to.
pub fn addr(&self) -> &SocketAddr {
self.server.as_ref()
.expect("server is always Some at the start; it's consumed only when object is dropped; qed")
.addrs()
.first()
.expect("You cannot start the server without binding to at least one address; qed")
}
}
impl Drop for Server {
fn drop(&mut self) {
self.server.take().unwrap().close()
}
}

View File

@@ -1,73 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::str;
use std::sync::Arc;
use std::collections::HashMap;
use rustc_hex::FromHex;
use hash_fetch::urlhint::ContractClient;
use util::{Bytes, Address, Mutex, H256, ToPretty};
const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
const URLHINT_RESOLVE: &'static str = "267b6922";
const DEFAULT_HASH: &'static str = "1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d";
pub struct FakeRegistrar {
pub calls: Arc<Mutex<Vec<(String, String)>>>,
pub responses: Mutex<HashMap<(String, String), Result<Bytes, String>>>,
}
impl FakeRegistrar {
pub fn new() -> Self {
FakeRegistrar {
calls: Arc::new(Mutex::new(Vec::new())),
responses: Mutex::new({
let mut map = HashMap::new();
map.insert(
(REGISTRAR.into(), "6795dbcd058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000000".into()),
Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()),
);
map.insert(
(URLHINT.into(), format!("{}{}", URLHINT_RESOLVE, DEFAULT_HASH)),
Ok(vec![])
);
map
}),
}
}
pub fn set_result(&self, hash: H256, result: Result<Bytes, String>) {
self.responses.lock().insert(
(URLHINT.into(), format!("{}{:?}", URLHINT_RESOLVE, hash)),
result
);
}
}
impl ContractClient for FakeRegistrar {
fn registrar(&self) -> Result<Address, String> {
Ok(REGISTRAR.parse().unwrap())
}
fn call(&self, address: Address, data: Bytes) -> ::futures::BoxFuture<Bytes, String> {
let call = (address.to_hex(), data.to_hex());
self.calls.lock().push(call.clone());
let res = self.responses.lock().get(&call).cloned().expect(&format!("No response for call: {:?}", call));
Box::new(::futures::future::done(res))
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -19,8 +19,8 @@
mod helpers;
mod api;
mod authorization;
mod fetch;
mod redirection;
mod rpc;
mod validation;

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -32,27 +32,7 @@ fn should_redirect_to_home() {
);
// then
response.assert_status("HTTP/1.1 302 Found");
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
}
#[test]
fn should_redirect_to_home_with_domain() {
// given
let server = serve();
// when
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: home.web3.site\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 302 Found");
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned());
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
}
@@ -72,7 +52,27 @@ fn should_redirect_to_home_when_trailing_slash_is_missing() {
);
// then
response.assert_status("HTTP/1.1 302 Found");
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned());
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
}
#[test]
fn should_redirect_to_home_for_users_with_cached_redirection() {
// given
let server = serve();
// when
let response = request(server,
"\
GET /home/ HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
assert_eq!(response.status, "HTTP/1.1 302 Found".to_owned());
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
}
@@ -92,7 +92,7 @@ fn should_display_404_on_invalid_dapp() {
);
// then
response.assert_status("HTTP/1.1 404 Not Found");
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert_security_headers_for_embed(&response.headers);
}
@@ -105,14 +105,14 @@ fn should_display_404_on_invalid_dapp_with_domain() {
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: invaliddapp.web3.site\r\n\
Host: invaliddapp.parity\r\n\
Connection: close\r\n\
\r\n\
"
);
// then
response.assert_status("HTTP/1.1 404 Not Found");
assert_eq!(response.status, "HTTP/1.1 404 Not Found".to_owned());
assert_security_headers_for_embed(&response.headers);
}
@@ -134,8 +134,8 @@ fn should_serve_rpc() {
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, format!("4C\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#));
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_eq!(response.body, format!("58\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error","data":null},"id":null}"#));
}
#[test]
@@ -156,8 +156,8 @@ fn should_serve_rpc_at_slash_rpc() {
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, format!("4C\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}"#));
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_eq!(response.body, format!("58\n{}\n\n0\n\n", r#"{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error","data":null},"id":null}"#));
}
@@ -178,8 +178,8 @@ fn should_serve_proxy_pac() {
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, "DB\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"home.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.web3.site\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned());
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_eq!(response.body, "D5\n\nfunction FindProxyForURL(url, host) {\n\tif (shExpMatch(host, \"home.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:18180\";\n\t}\n\n\tif (shExpMatch(host, \"*.parity\"))\n\t{\n\t\treturn \"PROXY 127.0.0.1:8080\";\n\t}\n\n\treturn \"DIRECT\";\n}\n\n0\n\n".to_owned());
assert_security_headers(&response.headers);
}
@@ -200,7 +200,7 @@ fn should_serve_utils() {
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert_eq!(response.body.contains("function(){"), true);
assert_security_headers(&response.headers);
}

View File

@@ -1,49 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use jsonrpc_core::{IoHandler, Value};
use tests::helpers::{serve_with_rpc, request};
#[test]
fn should_serve_rpc() {
// given
let mut io = IoHandler::default();
io.add_method("rpc_test", |_| {
Ok(Value::String("Hello World!".into()))
});
let server = serve_with_rpc(io);
// when
let req = r#"{"jsonrpc":"2.0","id":1,"method":"rpc_test","params":[]}"#;
let response = request(server, &format!(
"\
POST /rpc/ HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
Content-Type: application/json\r\n\
Content-Length: {}\r\n\
\r\n\
{}\r\n\
",
req.as_bytes().len(),
req,
));
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.body, "31\n{\"jsonrpc\":\"2.0\",\"result\":\"Hello World!\",\"id\":1}\n\n0\n\n".to_owned());
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -34,7 +34,7 @@ fn should_reject_invalid_host() {
// then
assert_eq!(response.status, "HTTP/1.1 403 Forbidden".to_owned());
assert!(response.body.contains("Provided Host header is not whitelisted."), response.body);
assert!(response.body.contains("Current Host Is Disallowed"), response.body);
}
#[test]
@@ -66,7 +66,7 @@ fn should_serve_dapps_domains() {
let response = request(server,
"\
GET / HTTP/1.1\r\n\
Host: ui.web3.site\r\n\
Host: ui.parity\r\n\
Connection: close\r\n\
\r\n\
{}
@@ -97,3 +97,31 @@ fn should_allow_parity_utils_even_on_invalid_domain() {
// then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
}
#[test]
fn should_not_return_cors_headers_for_rpc() {
// given
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
// when
let response = request(server,
"\
POST /rpc HTTP/1.1\r\n\
Host: localhost:8080\r\n\
Origin: null\r\n\
Content-Type: application/json\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
assert!(
!response.headers_raw.contains("Access-Control-Allow-Origin"),
"CORS headers were not expected: {:?}",
response.headers
);
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -37,9 +37,6 @@ pub struct Url {
/// Empty entries of `""` correspond to trailing slashes.
pub path: Vec<String>,
/// The URL query.
pub query: Option<String>,
/// The URL username field, from the userinfo section of the URL.
///
/// `None` if the `@` character was not part of the input OR
@@ -85,17 +82,15 @@ impl Url {
_ => None,
};
let port = raw_url.port_or_known_default().ok_or_else(|| format!("Unknown port for scheme: `{}`", raw_url.scheme()))?;
let host = raw_url.host().ok_or_else(|| "Valid host, because only data:, mailto: protocols does not have host.".to_owned())?.to_owned();
let path = raw_url.path_segments().ok_or_else(|| "Valid path segments. In HTTP we won't get cannot-be-a-base URLs".to_owned())?
let port = try!(raw_url.port_or_known_default().ok_or_else(|| format!("Unknown port for scheme: `{}`", raw_url.scheme())));
let host = try!(raw_url.host().ok_or_else(|| "Valid host, because only data:, mailto: protocols does not have host.".to_owned())).to_owned();
let path = try!(raw_url.path_segments().ok_or_else(|| "Valid path segments. In HTTP we won't get cannot-be-a-base URLs".to_owned()))
.map(|part| part.to_owned()).collect();
let query = raw_url.query().map(|x| x.to_owned());
Ok(Url {
port: port,
host: host,
path: path,
query: query,
raw: raw_url,
username: username,
password: password,

View File

@@ -1,245 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Serving web-based content (proxying)
use std::sync::Arc;
use fetch::{self, Fetch};
use parity_reactor::Remote;
use base32;
use hyper::{self, server, net, Next, Encoder, Decoder};
use hyper::status::StatusCode;
use apps;
use endpoint::{Endpoint, Handler, EndpointPath};
use handlers::{
ContentFetcherHandler, ContentHandler, ContentValidator, ValidatorResponse,
StreamingHandler, extract_url,
};
use url::Url;
use {Embeddable, WebProxyTokens};
pub struct Web<F> {
embeddable_on: Embeddable,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
fetch: F,
}
impl<F: Fetch> Web<F> {
pub fn boxed(
embeddable_on: Embeddable,
web_proxy_tokens: Arc<WebProxyTokens>,
remote: Remote,
fetch: F,
) -> Box<Endpoint> {
Box::new(Web {
embeddable_on,
web_proxy_tokens,
remote,
fetch,
})
}
}
impl<F: Fetch> Endpoint for Web<F> {
fn to_async_handler(&self, path: EndpointPath, control: hyper::Control) -> Box<Handler> {
Box::new(WebHandler {
control: control,
state: State::Initial,
path: path,
remote: self.remote.clone(),
fetch: self.fetch.clone(),
web_proxy_tokens: self.web_proxy_tokens.clone(),
embeddable_on: self.embeddable_on.clone(),
})
}
}
struct WebInstaller {
embeddable_on: Embeddable,
referer: String,
}
impl ContentValidator for WebInstaller {
type Error = String;
fn validate_and_install(&self, response: fetch::Response) -> Result<ValidatorResponse, String> {
let status = StatusCode::from_u16(response.status().to_u16());
let is_html = response.is_html();
let mime = response.content_type().unwrap_or(mime!(Text/Html));
let mut handler = StreamingHandler::new(
response,
status,
mime,
self.embeddable_on.clone(),
);
if is_html {
handler.set_initial_content(&format!(
r#"<script src="/{}/inject.js"></script><script>history.replaceState({{}}, "", "/?{}{}/{}")</script>"#,
apps::UTILS_PATH,
apps::URL_REFERER,
apps::WEB_PATH,
&self.referer,
));
}
Ok(ValidatorResponse::Streaming(handler))
}
}
enum State<F: Fetch> {
Initial,
Error(ContentHandler),
Fetching(ContentFetcherHandler<WebInstaller, F>),
}
struct WebHandler<F: Fetch> {
control: hyper::Control,
state: State<F>,
path: EndpointPath,
remote: Remote,
fetch: F,
web_proxy_tokens: Arc<WebProxyTokens>,
embeddable_on: Embeddable,
}
impl<F: Fetch> WebHandler<F> {
fn extract_target_url(&self, url: Option<Url>) -> Result<String, State<F>> {
let token_and_url = self.path.app_params.get(0)
.map(|encoded| encoded.replace('.', ""))
.and_then(|encoded| base32::decode(base32::Alphabet::Crockford, &encoded.to_uppercase()))
.and_then(|data| String::from_utf8(data).ok())
.ok_or_else(|| State::Error(ContentHandler::error(
StatusCode::BadRequest,
"Invalid parameter",
"Couldn't parse given parameter:",
self.path.app_params.get(0).map(String::as_str),
self.embeddable_on.clone()
)))?;
let mut token_it = token_and_url.split('+');
let token = token_it.next();
let target_url = token_it.next();
// Check if token supplied in URL is correct.
let domain = match token.and_then(|token| self.web_proxy_tokens.domain(token)) {
Some(domain) => domain,
_ => {
return Err(State::Error(ContentHandler::error(
StatusCode::BadRequest, "Invalid Access Token", "Invalid or old web proxy access token supplied.", Some("Try refreshing the page."), self.embeddable_on.clone()
)));
}
};
// Validate protocol
let mut target_url = match target_url {
Some(url) if url.starts_with("http://") || url.starts_with("https://") => url.to_owned(),
_ => {
return Err(State::Error(ContentHandler::error(
StatusCode::BadRequest, "Invalid Protocol", "Invalid protocol used.", None, self.embeddable_on.clone()
)));
}
};
if !target_url.starts_with(&*domain) {
return Err(State::Error(ContentHandler::error(
StatusCode::BadRequest, "Invalid Domain", "Dapp attempted to access invalid domain.", Some(&target_url), self.embeddable_on.clone(),
)));
}
if !target_url.ends_with("/") {
target_url = format!("{}/", target_url);
}
// TODO [ToDr] Should just use `path.app_params`
let (path, query) = match (&url, self.path.using_dapps_domains) {
(&Some(ref url), true) => (&url.path[..], &url.query),
(&Some(ref url), false) => (&url.path[2..], &url.query),
_ => {
return Err(State::Error(ContentHandler::error(
StatusCode::BadRequest, "Invalid URL", "Couldn't parse URL", None, self.embeddable_on.clone()
)));
}
};
let query = match *query {
Some(ref query) => format!("?{}", query),
None => "".into(),
};
Ok(format!("{}{}{}", target_url, path.join("/"), query))
}
}
impl<F: Fetch> server::Handler<net::HttpStream> for WebHandler<F> {
fn on_request(&mut self, request: server::Request<net::HttpStream>) -> Next {
let url = extract_url(&request);
// First extract the URL (reject invalid URLs)
let target_url = match self.extract_target_url(url) {
Ok(url) => url,
Err(error) => {
self.state = error;
return Next::write();
}
};
let mut handler = ContentFetcherHandler::new(
target_url,
self.path.clone(),
self.control.clone(),
WebInstaller {
embeddable_on: self.embeddable_on.clone(),
referer: self.path.app_params.get(0)
.expect("`target_url` is valid; app_params is not empty;qed")
.to_owned(),
},
self.embeddable_on.clone(),
self.remote.clone(),
self.fetch.clone(),
);
let res = handler.on_request(request);
self.state = State::Fetching(handler);
res
}
fn on_request_readable(&mut self, decoder: &mut Decoder<net::HttpStream>) -> Next {
match self.state {
State::Initial => Next::end(),
State::Error(ref mut handler) => handler.on_request_readable(decoder),
State::Fetching(ref mut handler) => handler.on_request_readable(decoder),
}
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
match self.state {
State::Initial => Next::end(),
State::Error(ref mut handler) => handler.on_response(res),
State::Fetching(ref mut handler) => handler.on_response(res),
}
}
fn on_response_writable(&mut self, encoder: &mut Encoder<net::HttpStream>) -> Next {
match self.state {
State::Initial => Next::end(),
State::Error(ref mut handler) => handler.on_response_writable(encoder),
State::Fetching(ref mut handler) => handler.on_response_writable(encoder),
}
}
}

View File

@@ -1,18 +1,17 @@
[package]
description = "Ethcore Parity UI"
homepage = "http://parity.io"
homepage = "http://ethcore.io"
license = "GPL-3.0"
name = "parity-ui"
version = "1.7.0"
authors = ["Parity Technologies <admin@parity.io>"]
version = "1.4.0"
authors = ["Ethcore <admin@ethcore.io>"]
[build-dependencies]
rustc_version = "0.1"
[dependencies]
parity-ui-dev = { path = "../../js", optional = true }
# This is managed by the js/scripts/release.sh script on CI - keep it in a single line
parity-ui-precompiled = { git = "https://github.com/paritytech/js-precompiled.git", optional = true, branch = "beta" }
parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", optional = true, branch = "beta" }
[features]
no-precompiled-js = ["parity-ui-dev"]

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify

25
db/Cargo.toml Normal file
View File

@@ -0,0 +1,25 @@
[package]
description = "Ethcore Database"
homepage = "http://ethcore.io"
license = "GPL-3.0"
name = "ethcore-db"
version = "1.4.0"
authors = ["Ethcore <admin@ethcore.io>"]
build = "build.rs"
[build-dependencies]
ethcore-ipc-codegen = { path = "../ipc/codegen" }
[dependencies]
clippy = { version = "0.0.96", optional = true}
ethcore-devtools = { path = "../devtools" }
ethcore-ipc = { path = "../ipc/rpc" }
rocksdb = { git = "https://github.com/ethcore/rust-rocksdb" }
semver = "0.2"
ethcore-ipc-nano = { path = "../ipc/nano" }
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
crossbeam = "0.2"
ethcore-util = { path = "../util" }
[features]
dev = ["clippy"]

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,20 +14,25 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Dapps Service
extern crate ethcore_ipc_codegen as codegen;
use v1::types::LocalDapp;
use std::env;
use std::path::Path;
/// Dapps Server service.
pub trait DappsService: Send + Sync + 'static {
/// List available local dapps.
fn list_dapps(&self) -> Vec<LocalDapp>;
}
pub fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
impl<F> DappsService for F where
F: Fn() -> Vec<LocalDapp> + Send + Sync + 'static
{
fn list_dapps(&self) -> Vec<LocalDapp> {
(*self)()
// ipc pass
{
let src = Path::new("src/lib.rs.in");
let dst = Path::new(&out_dir).join("lib.intermediate.rs.in");
codegen::expand(&src, &dst);
}
// binary serialization pass
{
let src = Path::new(&out_dir).join("lib.intermediate.rs.in");
let dst = Path::new(&out_dir).join("lib.rs");
codegen::expand(&src, &dst);
}
}

566
db/src/database.rs Normal file
View File

@@ -0,0 +1,566 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Ethcore rocksdb ipc service
use traits::*;
use rocksdb::{DB, Writable, WriteBatch, IteratorMode, DBIterator, IndexType, Options, DBCompactionStyle, BlockBasedOptions, Direction};
use std::sync::{RwLock, Arc};
use std::convert::From;
use ipc::IpcConfig;
use std::mem;
use ipc::binary::BinaryConvertError;
use std::collections::{VecDeque, HashMap, BTreeMap};
enum WriteCacheEntry {
Remove,
Write(Vec<u8>),
}
pub struct WriteCache {
entries: HashMap<Vec<u8>, WriteCacheEntry>,
preferred_len: usize,
}
const FLUSH_BATCH_SIZE: usize = 4096;
impl WriteCache {
fn new(cache_len: usize) -> WriteCache {
WriteCache {
entries: HashMap::new(),
preferred_len: cache_len,
}
}
fn write(&mut self, key: Vec<u8>, val: Vec<u8>) {
self.entries.insert(key, WriteCacheEntry::Write(val));
}
fn remove(&mut self, key: Vec<u8>) {
self.entries.insert(key, WriteCacheEntry::Remove);
}
fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
self.entries.get(key).and_then(
|vec_ref| match vec_ref {
&WriteCacheEntry::Write(ref val) => Some(val.clone()),
&WriteCacheEntry::Remove => None
})
}
/// WriteCache should be locked for this
fn flush(&mut self, db: &DB, amount: usize) -> Result<(), Error> {
let batch = WriteBatch::new();
let mut removed_so_far = 0;
while removed_so_far < amount {
if self.entries.len() == 0 { break; }
let removed_key = {
let (key, cache_entry) = self.entries.iter().nth(0)
.expect("if entries.len == 0, we should have break in the loop, still we got here somehow");
match *cache_entry {
WriteCacheEntry::Write(ref val) => {
try!(batch.put(&key, val));
},
WriteCacheEntry::Remove => {
try!(batch.delete(&key));
},
}
key.clone()
};
self.entries.remove(&removed_key);
removed_so_far = removed_so_far + 1;
}
if removed_so_far > 0 {
try!(db.write(batch));
}
Ok(())
}
/// flushes until cache is empty
fn flush_all(&mut self, db: &DB) -> Result<(), Error> {
while !self.is_empty() { try!(self.flush(db, FLUSH_BATCH_SIZE)); }
Ok(())
}
fn is_empty(&self) -> bool {
self.entries.is_empty()
}
fn try_shrink(&mut self, db: &DB) -> Result<(), Error> {
if self.entries.len() > self.preferred_len {
try!(self.flush(db, FLUSH_BATCH_SIZE));
}
Ok(())
}
}
pub struct Database {
db: RwLock<Option<DB>>,
/// Iterators - dont't use between threads!
iterators: RwLock<BTreeMap<IteratorHandle, DBIterator>>,
write_cache: RwLock<WriteCache>,
}
unsafe impl Send for Database {}
unsafe impl Sync for Database {}
impl Database {
pub fn new() -> Database {
Database {
db: RwLock::new(None),
iterators: RwLock::new(BTreeMap::new()),
write_cache: RwLock::new(WriteCache::new(DEFAULT_CACHE_LEN)),
}
}
pub fn flush(&self) -> Result<(), Error> {
let mut cache_lock = self.write_cache.write();
let db_lock = self.db.read();
if db_lock.is_none() { return Ok(()); }
let db = db_lock.as_ref().unwrap();
try!(cache_lock.try_shrink(&db));
Ok(())
}
pub fn flush_all(&self) -> Result<(), Error> {
let mut cache_lock = self.write_cache.write();
let db_lock = self.db.read();
if db_lock.is_none() { return Ok(()); }
let db = db_lock.as_ref().expect("we should have exited with Ok(()) on the previous step");
try!(cache_lock.flush_all(&db));
Ok(())
}
}
impl Drop for Database {
fn drop(&mut self) {
self.flush().unwrap();
}
}
#[ipc]
impl DatabaseService for Database {
fn open(&self, config: DatabaseConfig, path: String) -> Result<(), Error> {
let mut db = self.db.write();
if db.is_some() { return Err(Error::AlreadyOpen); }
let mut opts = Options::new();
opts.set_max_open_files(256);
opts.create_if_missing(true);
opts.set_use_fsync(false);
opts.set_compaction_style(DBCompactionStyle::DBUniversalCompaction);
if let Some(size) = config.prefix_size {
let mut block_opts = BlockBasedOptions::new();
block_opts.set_index_type(IndexType::HashSearch);
opts.set_block_based_table_factory(&block_opts);
opts.set_prefix_extractor_fixed_size(size);
}
*db = Some(try!(DB::open(&opts, &path)));
Ok(())
}
/// Opens database in the specified path with the default config
fn open_default(&self, path: String) -> Result<(), Error> {
self.open(DatabaseConfig::default(), path)
}
fn close(&self) -> Result<(), Error> {
try!(self.flush_all());
let mut db = self.db.write();
if db.is_none() { return Err(Error::IsClosed); }
*db = None;
Ok(())
}
fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Error> {
let mut cache_lock = self.write_cache.write();
cache_lock.write(key.to_vec(), value.to_vec());
Ok(())
}
fn delete(&self, key: &[u8]) -> Result<(), Error> {
let mut cache_lock = self.write_cache.write();
cache_lock.remove(key.to_vec());
Ok(())
}
fn write(&self, transaction: DBTransaction) -> Result<(), Error> {
let mut cache_lock = self.write_cache.write();
let mut writes = transaction.writes.borrow_mut();
for kv in writes.drain(..) {
cache_lock.write(kv.key, kv.value);
}
let mut removes = transaction.removes.borrow_mut();
for k in removes.drain(..) {
cache_lock.remove(k);
}
Ok(())
}
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
{
let key_vec = key.to_vec();
let cache_hit = self.write_cache.read().get(&key_vec);
if cache_hit.is_some() {
return Ok(Some(cache_hit.expect("cache_hit.is_some() = true, still there is none somehow here")))
}
}
let db_lock = self.db.read();
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
match try!(db.get(key)) {
Some(db_vec) => {
Ok(Some(db_vec.to_vec()))
},
None => Ok(None),
}
}
fn get_by_prefix(&self, prefix: &[u8]) -> Result<Option<Vec<u8>>, Error> {
let db_lock = self.db.read();
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
let mut iter = db.iterator(IteratorMode::From(prefix, Direction::Forward));
match iter.next() {
// TODO: use prefix_same_as_start read option (not availabele in C API currently)
Some((k, v)) => if k[0 .. prefix.len()] == prefix[..] { Ok(Some(v.to_vec())) } else { Ok(None) },
_ => Ok(None)
}
}
fn is_empty(&self) -> Result<bool, Error> {
let db_lock = self.db.read();
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
Ok(db.iterator(IteratorMode::Start).next().is_none())
}
fn iter(&self) -> Result<IteratorHandle, Error> {
let db_lock = self.db.read();
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
let mut iterators = self.iterators.write();
let next_iterator = iterators.keys().last().unwrap_or(&0) + 1;
iterators.insert(next_iterator, db.iterator(IteratorMode::Start));
Ok(next_iterator)
}
fn iter_next(&self, handle: IteratorHandle) -> Option<KeyValue>
{
let mut iterators = self.iterators.write();
let mut iterator = match iterators.get_mut(&handle) {
Some(some_iterator) => some_iterator,
None => { return None; },
};
iterator.next().and_then(|(some_key, some_val)| {
Some(KeyValue {
key: some_key.to_vec(),
value: some_val.to_vec(),
})
})
}
fn dispose_iter(&self, handle: IteratorHandle) -> Result<(), Error> {
let mut iterators = self.iterators.write();
iterators.remove(&handle);
Ok(())
}
}
// TODO : put proper at compile-time
impl IpcConfig for Database {}
/// Database iterator
pub struct DatabaseIterator {
client: Arc<DatabaseClient<::nanomsg::Socket>>,
handle: IteratorHandle,
}
impl Iterator for DatabaseIterator {
type Item = (Vec<u8>, Vec<u8>);
fn next(&mut self) -> Option<Self::Item> {
self.client.iter_next(self.handle).and_then(|kv| Some((kv.key, kv.value)))
}
}
impl Drop for DatabaseIterator {
fn drop(&mut self) {
self.client.dispose_iter(self.handle).unwrap();
}
}
#[cfg(test)]
mod test {
use super::Database;
use traits::*;
use devtools::*;
#[test]
fn can_be_created() {
let db = Database::new();
assert!(db.is_empty().is_err());
}
#[test]
fn can_be_open_empty() {
let db = Database::new();
let path = RandomTempPath::create_dir();
db.open_default(path.as_str().to_owned()).unwrap();
assert!(db.is_empty().is_ok());
}
#[test]
fn can_store_key() {
let db = Database::new();
let path = RandomTempPath::create_dir();
db.open_default(path.as_str().to_owned()).unwrap();
db.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
db.flush_all().unwrap();
assert!(!db.is_empty().unwrap());
}
#[test]
fn can_retrieve() {
let db = Database::new();
let path = RandomTempPath::create_dir();
db.open_default(path.as_str().to_owned()).unwrap();
db.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
db.close().unwrap();
db.open_default(path.as_str().to_owned()).unwrap();
assert_eq!(db.get("xxx".as_bytes()).unwrap().unwrap(), "1".as_bytes().to_vec());
}
}
#[cfg(test)]
mod write_cache_tests {
use super::Database;
use traits::*;
use devtools::*;
#[test]
fn cache_write_flush() {
let db = Database::new();
let path = RandomTempPath::create_dir();
db.open_default(path.as_str().to_owned()).unwrap();
db.put("100500".as_bytes(), "1".as_bytes()).unwrap();
db.delete("100500".as_bytes()).unwrap();
db.flush_all().unwrap();
let val = db.get("100500".as_bytes()).unwrap();
assert!(val.is_none());
}
}
#[cfg(test)]
mod client_tests {
use super::{DatabaseClient, Database};
use traits::*;
use devtools::*;
use nanoipc;
use std::sync::Arc;
use std::sync::atomic::{Ordering, AtomicBool};
use crossbeam;
use run_worker;
fn init_worker(addr: &str) -> nanoipc::Worker<Database> {
let mut worker = nanoipc::Worker::<Database>::new(&Arc::new(Database::new()));
worker.add_duplex(addr).unwrap();
worker
}
#[test]
fn can_call_handshake() {
let url = "ipc:///tmp/parity-db-ipc-test-10.ipc";
let worker_should_exit = Arc::new(AtomicBool::new(false));
let worker_is_ready = Arc::new(AtomicBool::new(false));
let c_worker_should_exit = worker_should_exit.clone();
let c_worker_is_ready = worker_is_ready.clone();
::std::thread::spawn(move || {
let mut worker = init_worker(url);
while !c_worker_should_exit.load(Ordering::Relaxed) {
worker.poll();
c_worker_is_ready.store(true, Ordering::Relaxed);
}
});
while !worker_is_ready.load(Ordering::Relaxed) { }
let client = nanoipc::init_duplex_client::<DatabaseClient<_>>(url).unwrap();
let hs = client.handshake();
worker_should_exit.store(true, Ordering::Relaxed);
assert!(hs.is_ok());
}
#[test]
fn can_open_db() {
let url = "ipc:///tmp/parity-db-ipc-test-20.ipc";
let path = RandomTempPath::create_dir();
let worker_should_exit = Arc::new(AtomicBool::new(false));
let worker_is_ready = Arc::new(AtomicBool::new(false));
let c_worker_should_exit = worker_should_exit.clone();
let c_worker_is_ready = worker_is_ready.clone();
::std::thread::spawn(move || {
let mut worker = init_worker(url);
while !c_worker_should_exit.load(Ordering::Relaxed) {
worker.poll();
c_worker_is_ready.store(true, Ordering::Relaxed);
}
});
while !worker_is_ready.load(Ordering::Relaxed) { }
let client = nanoipc::init_duplex_client::<DatabaseClient<_>>(url).unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
assert!(client.is_empty().unwrap());
worker_should_exit.store(true, Ordering::Relaxed);
}
#[test]
fn can_put() {
let url = "ipc:///tmp/parity-db-ipc-test-30.ipc";
let path = RandomTempPath::create_dir();
crossbeam::scope(move |scope| {
let stop = Arc::new(AtomicBool::new(false));
run_worker(scope, stop.clone(), url);
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
client.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
client.close().unwrap();
stop.store(true, Ordering::Relaxed);
});
}
#[test]
fn can_put_and_read() {
let url = "ipc:///tmp/parity-db-ipc-test-40.ipc";
let path = RandomTempPath::create_dir();
crossbeam::scope(move |scope| {
let stop = Arc::new(AtomicBool::new(false));
run_worker(scope, stop.clone(), url);
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
client.put("xxx".as_bytes(), "1".as_bytes()).unwrap();
client.close().unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
assert_eq!(client.get("xxx".as_bytes()).unwrap().unwrap(), "1".as_bytes().to_vec());
stop.store(true, Ordering::Relaxed);
});
}
#[test]
fn can_read_empty() {
let url = "ipc:///tmp/parity-db-ipc-test-45.ipc";
let path = RandomTempPath::create_dir();
crossbeam::scope(move |scope| {
let stop = Arc::new(AtomicBool::new(false));
run_worker(scope, stop.clone(), url);
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
assert!(client.get("xxx".as_bytes()).unwrap().is_none());
stop.store(true, Ordering::Relaxed);
});
}
#[test]
fn can_commit_client_transaction() {
let url = "ipc:///tmp/parity-db-ipc-test-60.ipc";
let path = RandomTempPath::create_dir();
crossbeam::scope(move |scope| {
let stop = Arc::new(AtomicBool::new(false));
run_worker(scope, stop.clone(), url);
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
let transaction = DBTransaction::new();
transaction.put("xxx".as_bytes(), "1".as_bytes());
client.write(transaction).unwrap();
client.close().unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
assert_eq!(client.get("xxx".as_bytes()).unwrap().unwrap(), "1".as_bytes().to_vec());
stop.store(true, Ordering::Relaxed);
});
}
#[test]
fn key_write_read_ipc() {
let url = "ipc:///tmp/parity-db-ipc-test-70.ipc";
let path = RandomTempPath::create_dir();
crossbeam::scope(|scope| {
let stop = StopGuard::new();
run_worker(&scope, stop.share(), url);
let client = nanoipc::generic_client::<DatabaseClient<_>>(url).unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
let mut batch = Vec::new();
for _ in 0..100 {
batch.push((random_str(256).as_bytes().to_vec(), random_str(256).as_bytes().to_vec()));
batch.push((random_str(256).as_bytes().to_vec(), random_str(2048).as_bytes().to_vec()));
batch.push((random_str(2048).as_bytes().to_vec(), random_str(2048).as_bytes().to_vec()));
batch.push((random_str(2048).as_bytes().to_vec(), random_str(256).as_bytes().to_vec()));
}
for &(ref k, ref v) in batch.iter() {
client.put(k, v).unwrap();
}
client.close().unwrap();
client.open_default(path.as_str().to_owned()).unwrap();
for &(ref k, ref v) in batch.iter() {
assert_eq!(v, &client.get(k).unwrap().unwrap());
}
});
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,8 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Types used in the public api
//! Database ipc service
#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/mod.rs.in"));
include!(concat!(env!("OUT_DIR"), "/lib.rs"));

89
db/src/lib.rs.in Normal file
View File

@@ -0,0 +1,89 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate ethcore_ipc as ipc;
extern crate rocksdb;
extern crate ethcore_devtools as devtools;
extern crate semver;
extern crate ethcore_ipc_nano as nanoipc;
extern crate nanomsg;
extern crate crossbeam;
extern crate ethcore_util as util;
pub mod database;
pub mod traits;
pub use traits::{DatabaseService, DBTransaction, Error};
pub use database::{Database, DatabaseClient, DatabaseIterator};
use std::sync::Arc;
use std::sync::atomic::*;
use std::path::PathBuf;
pub type DatabaseNanoClient = DatabaseClient<::nanomsg::Socket>;
pub type DatabaseConnection = nanoipc::GuardedSocket<DatabaseNanoClient>;
#[derive(Debug)]
pub enum ServiceError {
Io(std::io::Error),
Socket(nanoipc::SocketError),
}
impl std::convert::From<std::io::Error> for ServiceError {
fn from(io_error: std::io::Error) -> ServiceError { ServiceError::Io(io_error) }
}
impl std::convert::From<nanoipc::SocketError> for ServiceError {
fn from(socket_error: nanoipc::SocketError) -> ServiceError { ServiceError::Socket(socket_error) }
}
pub fn blocks_service_url(db_path: &str) -> Result<String, std::io::Error> {
let mut path = PathBuf::from(db_path);
try!(::std::fs::create_dir_all(db_path));
path.push("blocks.ipc");
Ok(format!("ipc://{}", path.to_str().unwrap()))
}
pub fn extras_service_url(db_path: &str) -> Result<String, ::std::io::Error> {
let mut path = PathBuf::from(db_path);
try!(::std::fs::create_dir_all(db_path));
path.push("extras.ipc");
Ok(format!("ipc://{}", path.to_str().unwrap()))
}
pub fn blocks_client(db_path: &str) -> Result<DatabaseConnection, ServiceError> {
let url = try!(blocks_service_url(db_path));
let client = try!(nanoipc::generic_client::<DatabaseClient<_>>(&url));
Ok(client)
}
pub fn extras_client(db_path: &str) -> Result<DatabaseConnection, ServiceError> {
let url = try!(extras_service_url(db_path));
let client = try!(nanoipc::generic_client::<DatabaseClient<_>>(&url));
Ok(client)
}
// for tests
pub fn run_worker(scope: &crossbeam::Scope, stop: Arc<AtomicBool>, socket_path: &str) {
let socket_path = socket_path.to_owned();
scope.spawn(move || {
let mut worker = nanoipc::Worker::new(&Arc::new(Database::new()));
worker.add_reqrep(&socket_path).unwrap();
while !stop.load(Ordering::Relaxed) {
worker.poll();
}
});
}

135
db/src/traits.rs Normal file
View File

@@ -0,0 +1,135 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Ethcore database trait
use std::cell::RefCell;
pub type IteratorHandle = u32;
pub const DEFAULT_CACHE_LEN: usize = 12288;
#[derive(Binary)]
pub struct KeyValue {
pub key: Vec<u8>,
pub value: Vec<u8>,
}
#[derive(Debug, Binary)]
pub enum Error {
AlreadyOpen,
IsClosed,
RocksDb(String),
TransactionUnknown,
IteratorUnknown,
UncommitedTransactions,
}
impl From<String> for Error {
fn from(s: String) -> Error {
Error::RocksDb(s)
}
}
/// Database configuration
#[derive(Binary)]
pub struct DatabaseConfig {
/// Optional prefix size in bytes. Allows lookup by partial key.
pub prefix_size: Option<usize>,
/// write cache length
pub cache: usize,
}
impl Default for DatabaseConfig {
fn default() -> DatabaseConfig {
DatabaseConfig {
prefix_size: None,
cache: DEFAULT_CACHE_LEN,
}
}
}
impl DatabaseConfig {
fn with_prefix(prefix: usize) -> DatabaseConfig {
DatabaseConfig {
prefix_size: Some(prefix),
cache: DEFAULT_CACHE_LEN,
}
}
}
pub trait DatabaseService : Sized {
/// Opens database in the specified path
fn open(&self, config: DatabaseConfig, path: String) -> Result<(), Error>;
/// Opens database in the specified path with the default config
fn open_default(&self, path: String) -> Result<(), Error>;
/// Closes database
fn close(&self) -> Result<(), Error>;
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten.
fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Error>;
/// Delete value by key.
fn delete(&self, key: &[u8]) -> Result<(), Error>;
/// Get value by key.
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error>;
/// Get value by partial key. Prefix size should match configured prefix size.
fn get_by_prefix(&self, prefix: &[u8]) -> Result<Option<Vec<u8>>, Error>;
/// Check if there is anything in the database.
fn is_empty(&self) -> Result<bool, Error>;
/// Get handle to iterate through keys
fn iter(&self) -> Result<IteratorHandle, Error>;
/// Next key-value for the the given iterator
fn iter_next(&self, iterator: IteratorHandle) -> Option<KeyValue>;
/// Dispose iteration that is no longer needed
fn dispose_iter(&self, handle: IteratorHandle) -> Result<(), Error>;
/// Write client transaction
fn write(&self, transaction: DBTransaction) -> Result<(), Error>;
}
#[derive(Binary)]
pub struct DBTransaction {
pub writes: RefCell<Vec<KeyValue>>,
pub removes: RefCell<Vec<Vec<u8>>>,
}
impl DBTransaction {
pub fn new() -> DBTransaction {
DBTransaction {
writes: RefCell::new(Vec::new()),
removes: RefCell::new(Vec::new()),
}
}
pub fn put(&self, key: &[u8], value: &[u8]) {
let mut brw = self.writes.borrow_mut();
brw.push(KeyValue { key: key.to_vec(), value: value.to_vec() });
}
pub fn delete(&self, key: &[u8]) {
let mut brw = self.removes.borrow_mut();
brw.push(key.to_vec());
}
}

1
deb/DEBIAN/compat Normal file
View File

@@ -0,0 +1 @@
8

13
deb/DEBIAN/control Normal file
View File

@@ -0,0 +1,13 @@
Package: parity
Version: 1.4.0
Source: parity
Section: science
Priority: extra
Maintainer: Ethcore <devops@ethcore.io>
Build-Depends: debhelper (>=9)
Standards-Version: 3.9.5
Homepage: https://ethcore.io
Vcs-Git: git://github.com/ethcore/parity.git
Vcs-Browser: https://github.com/ethcore/parity
Architecture: armhf
Description: Ethereum network client by Ethcore

675
deb/DEBIAN/copyright Normal file
View File

@@ -0,0 +1,675 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

1
deb/DEBIAN/docs Normal file
View File

@@ -0,0 +1 @@
https://github.com/ethcore/parity/wiki

View File

@@ -0,0 +1 @@
d3a6fd5582dd14ad718b2eb1a0662c3e817a63cf

View File

@@ -1,10 +1,10 @@
[package]
description = "Ethcore development/test/build tools"
homepage = "http://parity.io"
homepage = "http://ethcore.io"
license = "GPL-3.0"
name = "ethcore-devtools"
version = "1.7.0"
authors = ["Parity Technologies <admin@parity.io>"]
version = "1.4.0"
authors = ["Ethcore <admin@ethcore.io>"]
[dependencies]
rand = "0.3"

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::thread;
use std::time::Duration;
use std::io::{Read, Write};
use std::str::{self, Lines};
@@ -27,21 +26,6 @@ pub struct Response {
pub body: String,
}
impl Response {
pub fn assert_header(&self, header: &str, value: &str) {
let header = format!("{}: {}", header, value);
assert!(self.headers.iter().find(|h| *h == &header).is_some(), "Couldn't find header {} in {:?}", header, &self.headers)
}
pub fn assert_status(&self, status: &str) {
assert_eq!(self.status, status.to_owned(), "Got unexpected code. Body: {:?}", self.body);
}
pub fn assert_security_headers_present(&self, port: Option<u16>) {
assert_security_headers_present(&self.headers, port)
}
}
pub fn read_block(lines: &mut Lines, all: bool) -> String {
let mut block = String::new();
loop {
@@ -58,36 +42,16 @@ pub fn read_block(lines: &mut Lines, all: bool) -> String {
block
}
fn connect(address: &SocketAddr) -> TcpStream {
let mut retries = 0;
let mut last_error = None;
while retries < 10 {
retries += 1;
let res = TcpStream::connect(address);
match res {
Ok(stream) => {
return stream;
},
Err(e) => {
last_error = Some(e);
thread::sleep(Duration::from_millis(retries * 10));
}
}
}
panic!("Unable to connect to the server. Last error: {:?}", last_error);
}
pub fn request(address: &SocketAddr, request: &str) -> Response {
let mut req = connect(address);
req.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
let mut req = TcpStream::connect(address).unwrap();
req.set_read_timeout(Some(Duration::from_secs(1))).unwrap();
req.write_all(request.as_bytes()).unwrap();
let mut response = String::new();
let _ = req.read_to_string(&mut response);
let mut lines = response.lines();
let status = lines.next().expect("Expected a response").to_owned();
let status = lines.next().unwrap().to_owned();
let headers_raw = read_block(&mut lines, false);
let headers = headers_raw.split('\n').map(|v| v.to_owned()).collect();
let body = read_block(&mut lines, true);
@@ -102,7 +66,12 @@ pub fn request(address: &SocketAddr, request: &str) -> Response {
/// Check if all required security headers are present
pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) {
if let None = port {
if let Some(port) = port {
assert!(
headers.iter().find(|header| header.as_str() == &format!("X-Frame-Options: ALLOW-FROM http://127.0.0.1:{}", port)).is_some(),
"X-Frame-Options: ALLOW-FROM missing: {:?}", headers
);
} else {
assert!(
headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(),
"X-Frame-Options: SAMEORIGIN missing: {:?}", headers
@@ -116,8 +85,4 @@ pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) {
headers.iter().find(|header| header.as_str() == "X-Content-Type-Options: nosniff").is_some(),
"X-Content-Type-Options missing: {:?}", headers
);
assert!(
headers.iter().find(|header| header.starts_with("Content-Security-Policy: ")).is_some(),
"Content-Security-Policy missing: {:?}", headers
)
}

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
@@ -96,7 +96,7 @@ impl Drop for RandomTempPath {
pub struct GuardedTempResult<T> {
pub result: Option<T>,
pub _temp: RandomTempPath,
pub _temp: RandomTempPath
}
impl<T> GuardedTempResult<T> {

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify

View File

@@ -1,3 +0,0 @@
Usage
```docker build -f docker/ubuntu/Dockerfile --tag ethcore/parity:branch_or_tag_name .```

View File

@@ -1,32 +1,29 @@
FROM centos:latest
WORKDIR /build
# install tools and dependencies
RUN yum -y update&& \
yum install -y git make gcc-c++ gcc file binutils
# install rustup
RUN curl -sSf https://static.rust-lang.org/rustup.sh -o rustup.sh &&\
ls&&\
sh rustup.sh --disable-sudo
sh rustup.sh -s -- --disable-sudo
# show backtraces
ENV RUST_BACKTRACE 1
# set compiler
ENV CXX g++
ENV CC gcc
# show tools
RUN rustc -vV && \
cargo -V && \
gcc -v &&\
g++ -v
# build parity
ADD . /build/parity
RUN cd parity&&\
cargo build --release --verbose && \
RUN git clone https://github.com/ethcore/parity && \
cd parity&&\
git checkout beta && \
git pull && \
ls -a&&\
cargo build --release --verbose && \
ls /build/parity/target/release/parity && \
strip /build/parity/target/release/parity

Some files were not shown because too many files have changed in this diff Show More