Compare commits

..

38 Commits

Author SHA1 Message Date
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
3708 changed files with 147424 additions and 331021 deletions

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

View File

@@ -9,7 +9,7 @@ trim_trailing_whitespace=true
max_line_length=120
insert_final_newline=true
[*.{yml,sh}]
[.travis.yml]
indent_style=space
indent_size=2
tab_width=8

View File

@@ -1,84 +0,0 @@
# Code of Conduct
## 1. Purpose
A primary goal of Parity is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
We invite all those who participate in Parity to help us create safe and positive experiences for everyone.
## 2. Open Source Citizenship
A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community.
Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society.
If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know.
## 3. Expected Behavior
The following behaviors are expected and requested of all community members:
* Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
* Exercise consideration and respect in your speech and actions.
* Attempt collaboration before conflict.
* Refrain from demeaning, discriminatory, or harassing behavior and speech.
* Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
* Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.
## 4. Unacceptable Behavior
The following behaviors are considered harassment and are unacceptable within our community:
* Violence, threats of violence or violent language directed against another person.
* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
* Posting or displaying sexually explicit or violent material.
* Posting or threatening to post other peoples personally identifying information ("doxing").
* Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
* Inappropriate photography or recording.
* Inappropriate physical contact. You should have someones consent before touching them.
* Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
* Deliberate intimidation, stalking or following (online or in person).
* Advocating for, or encouraging, any of the above behavior.
* Sustained disruption of community events, including talks and presentations.
## 5. Consequences of Unacceptable Behavior
Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated.
Anyone asked to stop unacceptable behavior is expected to comply immediately.
If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event).
## 6. Reporting Guidelines
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. community@parity.io.
Link to reporting guidelines: [CONTRIBUTING.md](CONTRIBUTING.md)
Link to security policy: [SECURITY.md](../SECURITY.md)
Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.
## 7. Addressing Grievances
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify Parity Technologies with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.
## 8. Scope
We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venuesonline and in-personas well as in all one-on-one communications pertaining to community business.
This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members.
## 9. Contact info
You can contact Parity via Email: community@parity.io
## 10. License and attribution
This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).
Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).
Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/)

View File

@@ -1,33 +0,0 @@
# Contributing Guidelines
## Do you have a question?
Check out our [Basic Usage](https://github.com/paritytech/parity/wiki/Basic-Usage), [Configuration](https://github.com/paritytech/parity/wiki/Configuring-Parity), and [FAQ](https://github.com/paritytech/parity/wiki/FAQ) articles on our [wiki](https://github.com/paritytech/parity/wiki)!
See also frequently asked questions [tagged with `parity`](https://ethereum.stackexchange.com/questions/tagged/parity?sort=votes&pageSize=50) on Stack Exchange.
## Report bugs!
Do **not** open an issue on Github if you think your discovered bug could be a **security-relevant vulnerability**. Please, read our [security policy](../SECURITY.md) instead.
Otherwise, just create a [new issue](https://github.com/paritytech/parity/issues/new) in our repository and state:
- What's your Parity version?
- What's your operating system and version?
- How did you install parity?
- Is your node fully synchronized?
- Did you try turning it off and on again?
Also, try to include **steps to reproduce** the issue and expand on the **actual versus expected behavior**.
## Contribute!
If you would like to contribute to Parity, please **fork it**, fix bugs or implement features, and [propose a pull request](https://github.com/paritytech/parity/compare).
Please, refer to the [Coding Guide](https://github.com/paritytech/parity/wiki/Coding-guide) in our wiki for more details about hacking on Parity.
## License.
By contributing to Parity, you agree that your contributions will be licensed under the [GPLv3 License](../LICENSE).
Each contributor has to sign our Contributor License Agreement. The purpose of the CLA is to ensure that the guardian of a project's outputs has the necessary ownership or grants of rights over all contributions to allow them to distribute under the chosen license. You can read and sign our full Contributor License Agreement at [cla.parity.io](https://cla.parity.io) before submitting a pull request.

View File

@@ -1,13 +0,0 @@
_Before filing a new issue, please **provide the following information**._
> I'm running:
>
> - **Which Parity version?**: 0.0.0
> - **Which operating system?**: Windows / MacOS / Linux
> - **How installed?**: via installer / homebrew / binaries / from source
> - **Are you fully synchronized?**: no / yes
> - **Which network are you connected to?**: ethereum / ropsten / kovan / ...
> - **Did you try to restart the node?**: no / yes
_Your issue description goes here below. Try to include **actual** vs. **expected behavior** and **steps to reproduce** the issue._

10
.gitignore vendored
View File

@@ -15,18 +15,10 @@
# vim stuff
*.swp
*.swo
# mac stuff
.DS_Store
# npm stuff
npm-debug.log
node_modules
# js build artifacts
.git-release.log
# gdb files
.gdb_history
@@ -40,5 +32,3 @@ node_modules
out/
.vscode
/parity.*

View File

@@ -1,153 +1,286 @@
stages:
- test
- js-build
- push-release
- build
variables:
GIT_DEPTH: "3"
SIMPLECOV: "true"
RUST_BACKTRACE: "1"
RUSTFLAGS: ""
CARGOFLAGS: ""
CI_SERVER_NAME: "GitLab CI"
LIBSSL: "libssl1.0.0 (>=1.0.0)"
cache:
key: "$CI_BUILD_STAGE-$CI_BUILD_REF_NAME"
paths:
- target/
key: "CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
untracked: true
linux-ubuntu:
linux-stable:
stage: build
image: parity/rust:gitlab-ci
image: ethcore/rust:stable
only:
- beta
- tags
- stable
- triggers
script:
- rustup default stable
# ARGUMENTS: 1. BUILD_PLATFORM (target for binaries) 2. PLATFORM (target for cargo) 3. ARC (architecture) 4. & 5. CC & CXX flags 6. binary identifier
- scripts/gitlab-build.sh x86_64-unknown-linux-gnu x86_64-unknown-linux-gnu amd64 gcc g++ ubuntu
- cargo build --release $CARGOFLAGS
- strip target/release/parity
- md5sum target/release/parity >> parity.md5
- sh scripts/deb-build.sh amd64
- cp target/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"_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
- 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:
- parity.zip
- target/release/parity
name: "stable-x86_64-unknown-linux-gnu_parity"
linux-debian:
linux-beta:
stage: build
image: parity/rust-debian:gitlab-ci
image: ethcore/rust:beta
only:
- beta
- tags
- stable
- triggers
script:
- export LIBSSL="libssl1.1 (>=1.1.0)"
- scripts/gitlab-build.sh x86_64-unknown-debian-gnu x86_64-unknown-linux-gnu amd64 gcc g++ debian
- cargo build --release $CARGOFLAGS
- strip target/release/parity
tags:
- rust-debian
- rust
- rust-beta
artifacts:
paths:
- parity.zip
name: "stable-x86_64-unknown-debian-gnu_parity"
linux-centos:
- target/release/parity
name: "beta-x86_64-unknown-linux-gnu_parity"
allow_failure: true
linux-nightly:
stage: build
image: parity/rust-centos:gitlab-ci
image: ethcore/rust:nightly
only:
- beta
- tags
- stable
- triggers
script:
- scripts/gitlab-build.sh x86_64-unknown-centos-gnu x86_64-unknown-linux-gnu x86_64 gcc g++ centos
- cargo build --release $CARGOFLAGS
- strip target/release/parity
tags:
- rust
- rust-nightly
artifacts:
paths:
- target/release/parity
name: "nigthly-x86_64-unknown-linux-gnu_parity"
allow_failure: true
linux-centos:
stage: build
image: ethcore/rust-centos:latest
only:
- beta
- tags
- stable
script:
- export CXX="g++"
- export CC="gcc"
- cargo build --release $CARGOFLAGS
- strip target/release/parity
- md5sum target/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/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
tags:
- rust
- rust-centos
artifacts:
paths:
- parity.zip
- 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:
- scripts/gitlab-build.sh i686-unknown-linux-gnu i686-unknown-linux-gnu i386 gcc g++ ubuntu
- export HOST_CC=gcc
- export HOST_CXX=g++
- 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/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
- 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
artifacts:
paths:
- parity.zip
- target/i686-unknown-linux-gnu/release/parity
name: "i686-unknown-linux-gnu"
allow_failure: true
linux-armv7:
stage: build
image: parity/rust-armv7:gitlab-ci
image: ethcore/rust-armv7:latest
only:
- beta
- tags
- stable
- triggers
script:
- scripts/gitlab-build.sh armv7-unknown-linux-gnueabihf armv7-unknown-linux-gnueabihf armhf arm-linux-gnueabihf-gcc arm-linux-gnueabihf-g++ ubuntu
- export CC=arm-linux-gnueabihf-gcc
- export CXX=arm-linux-gnueabihf-g++
- export HOST_CC=gcc
- export HOST_CXX=g++
- rm -rf .cargo
- mkdir -p .cargo
- echo "[target.armv7-unknown-linux-gnueabihf]" >> .cargo/config
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
- cat .cargo/config
- 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/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
- 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
artifacts:
paths:
- parity.zip
- target/armv7-unknown-linux-gnueabihf/release/parity
name: "armv7_unknown_linux_gnueabihf_parity"
allow_failure: true
linux-arm:
stage: build
image: parity/rust-arm:gitlab-ci
image: ethcore/rust-arm:latest
only:
- beta
- tags
- stable
- triggers
script:
- scripts/gitlab-build.sh arm-unknown-linux-gnueabihf arm-unknown-linux-gnueabihf armhf arm-linux-gnueabihf-gcc arm-linux-gnueabihf-g++ ubuntu
- export CC=arm-linux-gnueabihf-gcc
- export CXX=arm-linux-gnueabihf-g++
- export HOST_CC=gcc
- export HOST_CXX=g++
- rm -rf .cargo
- mkdir -p .cargo
- echo "[target.arm-unknown-linux-gnueabihf]" >> .cargo/config
- echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config
- cat .cargo/config
- 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/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
- 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
artifacts:
paths:
- parity.zip
- 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:
- scripts/gitlab-build.sh aarch64-unknown-linux-gnu aarch64-unknown-linux-gnu arm64 aarch64-linux-gnu-gcc aarch64-linux-gnu-g++ ubuntu
- export CC=aarch64-linux-gnu-gcc
- export CXX=aarch64-linux-gnu-g++
- export HOST_CC=gcc
- export HOST_CXX=g++
- rm -rf .cargo
- mkdir -p .cargo
- echo "[target.aarch64-unknown-linux-gnu]" >> .cargo/config
- echo "linker= \"aarch64-linux-gnu-gcc\"" >> .cargo/config
- cat .cargo/config
- 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/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
- 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
artifacts:
paths:
- parity.zip
- target/aarch64-unknown-linux-gnu/release/parity
name: "aarch64-unknown-linux-gnu_parity"
linux-snap:
stage: build
image: parity/snapcraft:gitlab-ci
only:
- stable
- beta
- tags
- triggers
script:
- scripts/gitlab-build.sh x86_64-unknown-snap-gnu x86_64-unknown-linux-gnu amd64 gcc g++ snap
tags:
- rust-stable
artifacts:
paths:
- parity.zip
name: "stable-x86_64-unknown-snap-gnu_parity"
allow_failure: true
darwin:
stage: build
@@ -155,114 +288,136 @@ darwin:
- beta
- tags
- stable
- triggers
script:
- scripts/gitlab-build.sh x86_64-apple-darwin x86_64-apple-darwin macos gcc g++ macos
- cargo build --release $CARGOFLAGS
- rm -rf parity.md5
- md5sum target/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/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
tags:
- osx
artifacts:
paths:
- parity.zip
- target/release/parity
name: "x86_64-apple-darwin_parity"
windows:
cache:
key: "%CI_BUILD_STAGE%-%CI_BUILD_REF_NAME%"
key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%"
untracked: true
stage: build
only:
- beta
- tags
- stable
- triggers
script:
- sh scripts/gitlab-build.sh x86_64-pc-windows-msvc x86_64-pc-windows-msvc installer "" "" windows
- 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% -Zorbit=off
- rustup default stable-x86_64-pc-windows-msvc
- 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
- msbuild windows\ptray\ptray.vcxproj /p:Platform=x86 /p:Configuration=Release
- signtool sign /f %keyfile% /p %certpass% windows\ptray\release\ptray.exe
- cd nsis
- makensis.exe installer.nsi
- copy installer.exe InstallParity.exe
- signtool sign /f %keyfile% /p %certpass% InstallParity.exe
- md5sums InstallParity.exe > InstallParity.exe.md5
- 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.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%
- 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:
paths:
- parity.zip
- target/release/parity.exe
- 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
- scripts/docker-build.sh $DOCKER_TAG
- docker logout
tags:
- docker
test-coverage:
test-darwin:
stage: test
only:
- master
# - beta
# - tags
- stable
before_script:
- git submodule update --init --recursive
script:
- scripts/gitlab-test.sh test-coverage
- export RUST_BACKTRACE=1
- ./test.sh $CARGOFLAGS --no-release
tags:
- kcov
- osx
test-windows:
stage: test
only:
# - beta
# - tags
- stable
before_script:
- git submodule update --init --recursive
script:
- set RUST_BACKTRACE=1
- 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 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:
- scripts/gitlab-test.sh stable
tags:
- rust-stable
test-rust-beta:
stage: test
only:
- triggers
- master
image: parity/rust:gitlab-ci
script:
- scripts/gitlab-test.sh beta
tags:
- rust-beta
allow_failure: true
test-rust-nightly:
stage: test
only:
- triggers
- master
image: parity/rust:gitlab-ci
script:
- scripts/gitlab-test.sh nightly
- export RUST_BACKTRACE=1
- echo $JS_FILES_MODIFIED
- if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; else ./test.sh $CARGOFLAGS --no-release; fi
tags:
- rust
- rust-nightly
allow_failure: true
js-test:
stage: test
image: parity/rust:gitlab-ci
script:
- scripts/gitlab-test.sh js-test
tags:
- rust-stable
js-tests:
stage: test
image: ethcore/rust:stable
before_script:
- ./js/scripts/install-deps.sh
script:
- ./js/scripts/lint.sh
- ./js/scripts/test.sh
- ./js/scripts/build.sh
tags:
- javascript-test
js-release:
stage: js-build
only:
- master
- stable
- beta
- tags
- triggers
image: parity/rust:gitlab-ci
- stable
image: ethcore/rust:stable
before_script:
- ./js/scripts/install-deps.sh
script:
- scripts/gitlab-test.sh js-release
- ./js/scripts/build.sh
- ./js/scripts/release.sh
tags:
- javascript
push-release:
stage: push-release
only:
- tags
- triggers
image: parity/rust:gitlab-ci
script:
- scripts/gitlab-push-release.sh
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

View File

@@ -1,371 +0,0 @@
## Parity [v1.8.5](https://github.com/paritytech/parity/releases/tag/v1.8.5) (2017-12-29)
Parity 1.8.5 changes the default behavior of JSON-RPC CORS setting, detects same-key engine signers in Aura networks, and updates bootnodes for the Kovan and Foundation networks.
Note: The default value of `--jsonrpc-cors` option has been altered to disallow (potentially malicious) websites from accessing the low-sensitivity RPCs (viewing exposed accounts, proposing transactions for signing). Currently domains need to be whitelisted manually. To bring back previous behaviour run with `--jsonrpc-cors all` or `--jsonrpc-cors http://example.com`.
The full list of included changes:
- Beta Backports ([#7297](https://github.com/paritytech/parity/pull/7297))
- New warp enodes ([#7287](https://github.com/paritytech/parity/pull/7287))
- New warp enodes
- Added one more warp enode; replaced spaces with tabs
- Bump beta to 1.8.5
- Update kovan boot nodes
- Detect different node, same-key signing in aura ([#7245](https://github.com/paritytech/parity/pull/7245))
- Detect different node, same-key signing in aura
- Reduce scope of warning
- Fix Cargo.lock
- Updating mainnet bootnodes.
- Update bootnodes ([#7363](https://github.com/paritytech/parity/pull/7363))
- Updating mainnet bootnodes.
- Add additional parity-beta bootnodes.
- Restore old parity bootnodes and update foudation bootnodes
- Fix default CORS. ([#7388](https://github.com/paritytech/parity/pull/7388))
## Parity [v1.8.4](https://github.com/paritytech/parity/releases/tag/v1.8.4) (2017-12-12)
Parity 1.8.4 applies fixes for Proof-of-Authority networks and schedules the Kovan-Byzantium hard-fork.
- The Kovan testnet will fork on block `5067000` at `Thu Dec 14 2017 05:40:03 UTC`.
- This enables Byzantium features on Kovan.
- This disables uncles on Kovan for stability reasons.
- Proof-of-Authority networks are advised to set `maximumUncleCount` to 0 in a future `maximumUncleCountTransition` for stability reasons.
- See the [Kovan chain spec](https://github.com/paritytech/parity/blob/master/ethcore/res/ethereum/kovan.json) for an example.
- New PoA networks created with Parity will have this feature enabled by default.
Furthermore, this release includes the ECIP-1039 Monetary policy rounding specification for Ethereum Classic, reduces the maximum Ethash-block timestamp drift to 15 seconds, and fixes various bugs for WASM and the RPC APIs.
The full list of included changes:
- Beta Backports and HF block update ([#7244](https://github.com/paritytech/parity/pull/7244))
- Reduce max block timestamp drift to 15 seconds ([#7240](https://github.com/paritytech/parity/pull/7240))
- Add test for block timestamp validation within allowed drift
- Update kovan HF block number.
- Beta Kovan HF ([#7234](https://github.com/paritytech/parity/pull/7234))
- Kovan HF.
- Bump version.
- Fix aura difficulty race ([#7198](https://github.com/paritytech/parity/pull/7198))
- Fix test key
- Extract out score calculation
- Fix build
- Update kovan HF block number.
- Add missing byzantium builtins.
- Bump installers versions.
- Increase allowed time drift to 10s. ([#7238](https://github.com/paritytech/parity/pull/7238))
- Beta Backports ([#7197](https://github.com/paritytech/parity/pull/7197))
- Maximum uncle count transition ([#7196](https://github.com/paritytech/parity/pull/7196))
- Enable delayed maximum_uncle_count activation.
- Fix tests.
- Defer kovan HF.
- Disable uncles by default ([#7006](https://github.com/paritytech/parity/pull/7006))
- Escape inifinite loop in estimte_gas ([#7075](https://github.com/paritytech/parity/pull/7075))
- ECIP-1039: Monetary policy rounding specification ([#7067](https://github.com/paritytech/parity/pull/7067))
- WASM Remove blockhash error ([#7121](https://github.com/paritytech/parity/pull/7121))
- Remove blockhash error
- Update tests.
- WASM storage_read and storage_write don't return anything ([#7110](https://github.com/paritytech/parity/pull/7110))
- WASM parse payload from panics ([#7097](https://github.com/paritytech/parity/pull/7097))
- Fix no-default-features. ([#7096](https://github.com/paritytech/parity/pull/7096))
## Parity [v1.8.3](https://github.com/paritytech/parity/releases/tag/v1.8.3) (2017-11-15)
Parity 1.8.3 contains several bug-fixes and removes the ability to deploy built-in multi-signature wallets.
The full list of included changes:
- Backports to beta ([#7043](https://github.com/paritytech/parity/pull/7043))
- pwasm-std update ([#7018](https://github.com/paritytech/parity/pull/7018))
- Version 1.8.3
- Make CLI arguments parsing more backwards compatible ([#7004](https://github.com/paritytech/parity/pull/7004))
- Skip nonce check for gas estimation ([#6997](https://github.com/paritytech/parity/pull/6997))
- Events in WASM runtime ([#6967](https://github.com/paritytech/parity/pull/6967))
- Return decoded seal fields. ([#6932](https://github.com/paritytech/parity/pull/6932))
- Fix serialization of status in transaction receipts. ([#6926](https://github.com/paritytech/parity/pull/6926))
- Windows fixes ([#6921](https://github.com/paritytech/parity/pull/6921))
- Disallow built-in multi-sig deploy (only watch) ([#7014](https://github.com/paritytech/parity/pull/7014))
- Add hint in ActionParams for splitting code/data ([#6968](https://github.com/paritytech/parity/pull/6968))
- Action params and embedded params handling
- Fix name-spaces
## Parity [v1.8.2](https://github.com/paritytech/parity/releases/tag/v1.8.2) (2017-10-26)
Parity 1.8.2 fixes an important potential consensus issue and a few additional minor issues:
- `blockNumber` transaction field is now returned correctly in RPC calls.
- Possible crash when `--force-sealing` option is used.
The full list of included changes:
- Beta Backports ([#6891](https://github.com/paritytech/parity/pull/6891))
- Bump to v1.8.2
- Refactor static context check in CREATE. ([#6886](https://github.com/paritytech/parity/pull/6886))
- Refactor static context check in CREATE.
- Fix wasm.
- Fix serialization of non-localized transactions ([#6868](https://github.com/paritytech/parity/pull/6868))
- Fix serialization of non-localized transactions.
- Return proper SignedTransactions representation.
- Allow force sealing and reseal=0 for non-dev chains. ([#6878](https://github.com/paritytech/parity/pull/6878))
## Parity [v1.8.1](https://github.com/paritytech/parity/releases/tag/v1.8.1) (2017-10-20)
Parity 1.8.1 fixes several bugs with token balances, tweaks snapshot-sync, improves the performance of nodes with huge amounts of accounts and changes the Trezor account derivation path.
**Important Note**: The **Trezor** account derivation path was changed in this release ([#6815](https://github.com/paritytech/parity/pull/6815)) to always use the first account (`m/44'/60'/0'/0/0` instead of `m/44'/60'/0'/0`). This way we enable compatibility with other Ethereum wallets supporting Trezor hardware-wallets. However, **action is required** before upgrading, if you have funds on your Parity Trezor wallet. If you already upgraded to 1.8.1, please downgrade to 1.8.0 first to recover the funds with the following steps:
1. Make sure you have 1.8.0-beta and your Trezor plugged in.
2. Create a new standard Parity account. Make sure you have backups of the recovery phrase and don't forget the password.
3. Move your funds from the Trezor hardware-wallet account to the freshly generated Parity account.
4. Upgrade to 1.8.1-beta and plug in your Trezor.
5. Move your funds from your Parity account to the new Trezor account.
6. Keep using Parity as normal.
If you don't want to downgrade or move your funds off your Trezor-device, you can also use the official Trezor application or other wallets allowing to select the derivation path to access the funds.
The full list of included changes:
- Add ECIP1017 to Morden config ([#6845](https://github.com/paritytech/parity/pull/6845))
- Ethstore optimizations ([#6844](https://github.com/paritytech/parity/pull/6844))
- Bumb to v1.8.1 ([#6843](https://github.com/paritytech/parity/pull/6843))
- Backport ([#6837](https://github.com/paritytech/parity/pull/6837))
- Tweaked snapshot sync threshold ([#6829](https://github.com/paritytech/parity/pull/6829))
- Change keypath derivation logic ([#6815](https://github.com/paritytech/parity/pull/6815))
- Refresh cached tokens based on registry info & random balances ([#6824](https://github.com/paritytech/parity/pull/6824))
- Refresh cached tokens based on registry info & random balances ([#6818](https://github.com/paritytech/parity/pull/6818))
- Don't display errored token images
## Parity [v1.8.0](https://github.com/paritytech/parity/releases/tag/v1.8.0) (2017-10-15)
We are happy to announce our newest Parity 1.8 release. Among others, it enables the following features:
- Full Whisper v6 integration
- Trezor hardware-wallet support
- WASM contract support
- PICOPS KYC-certified accounts and vouching for community-dapps
- Light client compatibility for Proof-of-Authority networks
- Transaction permissioning and permissioned p2p-connections
- Full Byzantium-fork compatibility
- Full Musicoin MCIP-3 UBI-fork compatibility
Further, users upgrading from 1.7 should acknowledge the following changes:
- The chain-engine was further abstracted and chain-specs need to be upgraded. [#6134](https://github.com/paritytech/parity/pull/6134) [#6591](https://github.com/paritytech/parity/pull/6591)
- `network_id` was renamed to `chain_id` where applicable. [#6345](https://github.com/paritytech/parity/pull/6345)
- `trace_filter` RPC method now comes with pagination. [#6312](https://github.com/paritytech/parity/pull/6312)
- Added tracing of rewards on closing blocks. [#6194](https://github.com/paritytech/parity/pull/6194)
The full list of included changes:
- Updated ethabi to fix auto-update ([#6771](https://github.com/paritytech/parity/pull/6771))
- Fixed kovan chain validation ([#6760](https://github.com/paritytech/parity/pull/6760))
- Fixed kovan chain validation
- Fork detection
- Fixed typo
- Bumped fork block number for auto-update ([#6755](https://github.com/paritytech/parity/pull/6755))
- CLI: Reject invalid argument values rather than ignore them ([#6747](https://github.com/paritytech/parity/pull/6747))
- Fixed modexp gas calculation overflow ([#6745](https://github.com/paritytech/parity/pull/6745))
- Backport beta - Fixes Badges ([#6732](https://github.com/paritytech/parity/pull/6732))
- Fix badges not showing up ([#6730](https://github.com/paritytech/parity/pull/6730))
- Always fetch meta data first [badges]
- Bump to v1.8.0 in beta
- Fix tokens and badges ([#6725](https://github.com/paritytech/parity/pull/6725))
- Update new token fetching
- Working Certifications Monitoring
- Update on Certification / Revoke
- Fix none-fetched tokens value display
- Fix tests
- Check vouch status on appId in addition to contentHash ([#6719](https://github.com/paritytech/parity/pull/6719))
- Check vouch status on appId in addition to contentHash
- Simplify var expansion
- Prevent going offline when restoring or taking a snapshot [#6694](https://github.com/paritytech/parity/pull/6694)
- Graceful exit when invalid CLI flags are passed (#6485) [#6711](https://github.com/paritytech/parity/pull/6711)
- Fixed RETURNDATA out of bounds check [#6718](https://github.com/paritytech/parity/pull/6718)
- Display vouched overlay on dapps [#6710](https://github.com/paritytech/parity/pull/6710)
- Fix gas estimation if `from` is not provided. [#6714](https://github.com/paritytech/parity/pull/6714)
- Emulate signer pubsub on public node [#6708](https://github.com/paritytech/parity/pull/6708)
- Removes dependency on rustc_serialize (#5988) [#6705](https://github.com/paritytech/parity/pull/6705)
- Fixed potential modexp exp len overflow [#6686](https://github.com/paritytech/parity/pull/6686)
- Fix asciiToHex for characters < 0x10 [#6702](https://github.com/paritytech/parity/pull/6702)
- Fix address input [#6701](https://github.com/paritytech/parity/pull/6701)
- Allow signer signing display of markdown [#6707](https://github.com/paritytech/parity/pull/6707)
- Fixed build warnings [#6664](https://github.com/paritytech/parity/pull/6664)
- Fix warp sync blockers detection [#6691](https://github.com/paritytech/parity/pull/6691)
- Difficulty tests [#6687](https://github.com/paritytech/parity/pull/6687)
- Separate migrations from util [#6690](https://github.com/paritytech/parity/pull/6690)
- Changelog for 1.7.3 [#6678](https://github.com/paritytech/parity/pull/6678)
- WASM gas schedule [#6638](https://github.com/paritytech/parity/pull/6638)
- Fix wallet view [#6597](https://github.com/paritytech/parity/pull/6597)
- Byzantium fork block number [#6660](https://github.com/paritytech/parity/pull/6660)
- Fixed RETURNDATA size for built-ins [#6652](https://github.com/paritytech/parity/pull/6652)
- Light Client: fetch transactions/receipts by transaction hash [#6641](https://github.com/paritytech/parity/pull/6641)
- Add Musicoin and MCIP-3 UBI hardfork. [#6621](https://github.com/paritytech/parity/pull/6621)
- fix 1.8 backcompat: revert to manual encoding/decoding of transition proofs [#6665](https://github.com/paritytech/parity/pull/6665)
- Tweaked block download timeouts (#6595) [#6655](https://github.com/paritytech/parity/pull/6655)
- Renamed RPC receipt statusCode field to status [#6650](https://github.com/paritytech/parity/pull/6650)
- SecretStore: session level timeout [#6631](https://github.com/paritytech/parity/pull/6631)
- SecretStore: ShareRemove of 'isolated' nodes [#6630](https://github.com/paritytech/parity/pull/6630)
- SecretStore: exclusive sessions [#6624](https://github.com/paritytech/parity/pull/6624)
- Fixed network protocol version negotiation [#6649](https://github.com/paritytech/parity/pull/6649)
- Updated systemd files for linux (Resolves #6592) [#6598](https://github.com/paritytech/parity/pull/6598)
- move additional_params to machine, fixes registry on non-ethash chains [#6646](https://github.com/paritytech/parity/pull/6646)
- Fix Token Transfer in transaction list [#6589](https://github.com/paritytech/parity/pull/6589)
- Update jsonrpc dependencies and rewrite dapps to futures. [#6522](https://github.com/paritytech/parity/pull/6522)
- Balance queries implemented in WASM runtime [#6639](https://github.com/paritytech/parity/pull/6639)
- Don't expose port 80 for parity anymore [#6633](https://github.com/paritytech/parity/pull/6633)
- WASM Runtime refactoring [#6596](https://github.com/paritytech/parity/pull/6596)
- Fix compilation [#6625](https://github.com/paritytech/parity/pull/6625)
- Downgrade futures to suppress warnings. [#6620](https://github.com/paritytech/parity/pull/6620)
- Add pagination for trace_filter rpc method [#6312](https://github.com/paritytech/parity/pull/6312)
- Disallow pasting recovery phrases on first run [#6602](https://github.com/paritytech/parity/pull/6602)
- fix typo: Unkown => Unknown [#6559](https://github.com/paritytech/parity/pull/6559)
- SecretStore: administrative sessions prototypes [#6605](https://github.com/paritytech/parity/pull/6605)
- fix parity.io link 404 [#6617](https://github.com/paritytech/parity/pull/6617)
- SecretStore: add node to existing session poc + discussion [#6480](https://github.com/paritytech/parity/pull/6480)
- Generalize engine trait [#6591](https://github.com/paritytech/parity/pull/6591)
- Add RPC eth_chainId for querying the current blockchain chain ID [#6329](https://github.com/paritytech/parity/pull/6329)
- Debounce sync status. [#6572](https://github.com/paritytech/parity/pull/6572)
- [Public Node] Disable tx scheduling and hardware wallets [#6588](https://github.com/paritytech/parity/pull/6588)
- Use memmap for dag cache [#6193](https://github.com/paritytech/parity/pull/6193)
- Rename Requests to Batch [#6582](https://github.com/paritytech/parity/pull/6582)
- Use host as ws/dapps url if present. [#6566](https://github.com/paritytech/parity/pull/6566)
- Sync progress and error handling fixes [#6560](https://github.com/paritytech/parity/pull/6560)
- Fixed receipt serialization and RPC [#6555](https://github.com/paritytech/parity/pull/6555)
- Fix number of confirmations for transaction [#6552](https://github.com/paritytech/parity/pull/6552)
- Fix #6540 [#6556](https://github.com/paritytech/parity/pull/6556)
- Fix failing hardware tests [#6553](https://github.com/paritytech/parity/pull/6553)
- Required validators >= num owners in Wallet Creation [#6551](https://github.com/paritytech/parity/pull/6551)
- Random cleanups / improvements to a state [#6472](https://github.com/paritytech/parity/pull/6472)
- Changelog for 1.7.2 [#6363](https://github.com/paritytech/parity/pull/6363)
- Ropsten fork [#6533](https://github.com/paritytech/parity/pull/6533)
- Byzantium updates [#5855](https://github.com/paritytech/parity/pull/5855)
- Fix extension detection [#6452](https://github.com/paritytech/parity/pull/6452)
- Downgrade futures to supress warnings [#6521](https://github.com/paritytech/parity/pull/6521)
- separate trie from util and make its dependencies into libs [#6478](https://github.com/paritytech/parity/pull/6478)
- WASM sha3 test [#6512](https://github.com/paritytech/parity/pull/6512)
- Fix broken JavaScript tests [#6498](https://github.com/paritytech/parity/pull/6498)
- SecretStore: use random key to encrypt channel + session-level nonce [#6470](https://github.com/paritytech/parity/pull/6470)
- Trezor Support [#6403](https://github.com/paritytech/parity/pull/6403)
- Fix compiler warning [#6491](https://github.com/paritytech/parity/pull/6491)
- Fix typo [#6505](https://github.com/paritytech/parity/pull/6505)
- WASM: added math overflow test [#6474](https://github.com/paritytech/parity/pull/6474)
- Fix slow balances [#6471](https://github.com/paritytech/parity/pull/6471)
- WASM runtime update [#6467](https://github.com/paritytech/parity/pull/6467)
- Compatibility with whisper v6 [#6179](https://github.com/paritytech/parity/pull/6179)
- light-poa round 2: allow optional casting of engine client to full client [#6468](https://github.com/paritytech/parity/pull/6468)
- Moved attributes under docs [#6475](https://github.com/paritytech/parity/pull/6475)
- cleanup util dependencies [#6464](https://github.com/paritytech/parity/pull/6464)
- removed redundant earlymergedb trace guards [#6463](https://github.com/paritytech/parity/pull/6463)
- UtilError utilizes error_chain! [#6461](https://github.com/paritytech/parity/pull/6461)
- fixed master [#6465](https://github.com/paritytech/parity/pull/6465)
- Refactor and port CLI from Docopt to Clap (#2066) [#6356](https://github.com/paritytech/parity/pull/6356)
- Add language selector in production [#6317](https://github.com/paritytech/parity/pull/6317)
- eth_call returns output of contract creations [#6420](https://github.com/paritytech/parity/pull/6420)
- Refactor: Don't reexport bigint from util [#6459](https://github.com/paritytech/parity/pull/6459)
- Transaction permissioning [#6441](https://github.com/paritytech/parity/pull/6441)
- Added missing SecretStore tests - signing session [#6411](https://github.com/paritytech/parity/pull/6411)
- Light-client sync for contract-based PoA [#6370](https://github.com/paritytech/parity/pull/6370)
- triehash is separated from util [#6428](https://github.com/paritytech/parity/pull/6428)
- remove re-export of parking_lot in util [#6435](https://github.com/paritytech/parity/pull/6435)
- fix modexp bug: return 0 if base is zero [#6424](https://github.com/paritytech/parity/pull/6424)
- separate semantic_version from util [#6438](https://github.com/paritytech/parity/pull/6438)
- move timer.rs to ethcore [#6437](https://github.com/paritytech/parity/pull/6437)
- remove re-export of ansi_term in util [#6433](https://github.com/paritytech/parity/pull/6433)
- Pub sub blocks [#6139](https://github.com/paritytech/parity/pull/6139)
- replace trait Hashable with fn keccak [#6423](https://github.com/paritytech/parity/pull/6423)
- add more hash backward compatibility test for bloom [#6425](https://github.com/paritytech/parity/pull/6425)
- remove the redundant hasher in Bloom [#6404](https://github.com/paritytech/parity/pull/6404)
- Remove re-export of HeapSizeOf in util (part of #6418) [#6419](https://github.com/paritytech/parity/pull/6419)
- Rewards on closing blocks [#6194](https://github.com/paritytech/parity/pull/6194)
- ensure balances of constructor accounts are kept [#6413](https://github.com/paritytech/parity/pull/6413)
- removed recursion from triedbmut::lookup [#6394](https://github.com/paritytech/parity/pull/6394)
- do not activate genesis epoch in immediate transition validator contract [#6349](https://github.com/paritytech/parity/pull/6349)
- Use git for the snap version [#6271](https://github.com/paritytech/parity/pull/6271)
- Permissioned p2p connections [#6359](https://github.com/paritytech/parity/pull/6359)
- Don't accept transactions above block gas limit. [#6408](https://github.com/paritytech/parity/pull/6408)
- Fix memory tracing. [#6399](https://github.com/paritytech/parity/pull/6399)
- earlydb optimizations [#6393](https://github.com/paritytech/parity/pull/6393)
- Optimized PlainHasher hashing. Trie insertions are >15 faster [#6321](https://github.com/paritytech/parity/pull/6321)
- Trie optimizations [#6389](https://github.com/paritytech/parity/pull/6389)
- small optimizations for triehash [#6392](https://github.com/paritytech/parity/pull/6392)
- Bring back IPFS tests. [#6398](https://github.com/paritytech/parity/pull/6398)
- Running state test using parity-evm [#6355](https://github.com/paritytech/parity/pull/6355)
- Wasm math tests extended [#6354](https://github.com/paritytech/parity/pull/6354)
- Expose health status over RPC [#6274](https://github.com/paritytech/parity/pull/6274)
- fix bloom bitvecjournal storage allocation [#6390](https://github.com/paritytech/parity/pull/6390)
- fixed pending block panic [#6391](https://github.com/paritytech/parity/pull/6391)
- Infoline less opaque for UI/visibility [#6364](https://github.com/paritytech/parity/pull/6364)
- Fix eth_call. [#6365](https://github.com/paritytech/parity/pull/6365)
- updated bigint [#6341](https://github.com/paritytech/parity/pull/6341)
- Optimize trie iter by avoiding redundant copying [#6347](https://github.com/paritytech/parity/pull/6347)
- Only keep a single rocksdb debug log file [#6346](https://github.com/paritytech/parity/pull/6346)
- Tweaked snapshot params [#6344](https://github.com/paritytech/parity/pull/6344)
- Rename network_id to chain_id where applicable. [#6345](https://github.com/paritytech/parity/pull/6345)
- Itertools are no longer reexported from util, optimized triedb iter [#6322](https://github.com/paritytech/parity/pull/6322)
- Better check the created accounts before showing Startup Wizard [#6331](https://github.com/paritytech/parity/pull/6331)
- Better error messages for invalid types in RPC [#6311](https://github.com/paritytech/parity/pull/6311)
- fix panic in parity-evm json tracer [#6338](https://github.com/paritytech/parity/pull/6338)
- WASM math test [#6305](https://github.com/paritytech/parity/pull/6305)
- rlp_derive [#6125](https://github.com/paritytech/parity/pull/6125)
- Fix --chain parsing in parity-evm. [#6314](https://github.com/paritytech/parity/pull/6314)
- Unexpose RPC methods on :8180 [#6295](https://github.com/paritytech/parity/pull/6295)
- Ignore errors from dappsUrl when starting UI. [#6296](https://github.com/paritytech/parity/pull/6296)
- updated bigint with optimized mul and from_big_indian [#6323](https://github.com/paritytech/parity/pull/6323)
- SecretStore: bunch of fixes and improvements [#6168](https://github.com/paritytech/parity/pull/6168)
- Master requires rust 1.19 [#6308](https://github.com/paritytech/parity/pull/6308)
- Add more descriptive error when signing/decrypting using hw wallet. [#6302](https://github.com/paritytech/parity/pull/6302)
- Increase default gas limit for eth_call. [#6299](https://github.com/paritytech/parity/pull/6299)
- rust-toolchain file on master [#6266](https://github.com/paritytech/parity/pull/6266)
- Migrate wasm-tests to updated runtime [#6278](https://github.com/paritytech/parity/pull/6278)
- Extension fixes [#6284](https://github.com/paritytech/parity/pull/6284)
- Fix a hash displayed in tooltip when signing arbitrary data [#6283](https://github.com/paritytech/parity/pull/6283)
- Time should not contribue to overall status. [#6276](https://github.com/paritytech/parity/pull/6276)
- Add --to and --gas-price to evmbin [#6277](https://github.com/paritytech/parity/pull/6277)
- Fix dapps CSP when UI is exposed externally [#6178](https://github.com/paritytech/parity/pull/6178)
- Add warning to web browser and fix links. [#6232](https://github.com/paritytech/parity/pull/6232)
- Update Settings/Proxy view to match entries in proxy.pac [#4771](https://github.com/paritytech/parity/pull/4771)
- Dapp refresh [#5752](https://github.com/paritytech/parity/pull/5752)
- Add support for ConsenSys multisig wallet [#6153](https://github.com/paritytech/parity/pull/6153)
- updated jsonrpc [#6264](https://github.com/paritytech/parity/pull/6264)
- SecretStore: encrypt messages using private key from key store [#6146](https://github.com/paritytech/parity/pull/6146)
- Wasm storage read test [#6255](https://github.com/paritytech/parity/pull/6255)
- propagate stratum submit share error upstream [#6260](https://github.com/paritytech/parity/pull/6260)
- Using multiple NTP servers [#6173](https://github.com/paritytech/parity/pull/6173)
- Add GitHub issue templates. [#6259](https://github.com/paritytech/parity/pull/6259)
- format instant change proofs correctly [#6241](https://github.com/paritytech/parity/pull/6241)
- price-info does not depend on util [#6231](https://github.com/paritytech/parity/pull/6231)
- native-contracts crate does not depend on util any more [#6233](https://github.com/paritytech/parity/pull/6233)
- Bump master to 1.8.0 [#6256](https://github.com/paritytech/parity/pull/6256)
- SecretStore: do not cache ACL contract + on-chain key servers configuration [#6107](https://github.com/paritytech/parity/pull/6107)
- Fix the README badges [#6229](https://github.com/paritytech/parity/pull/6229)
- updated tiny-keccak to 1.3 [#6248](https://github.com/paritytech/parity/pull/6248)
- Small grammatical error [#6244](https://github.com/paritytech/parity/pull/6244)
- Multi-call RPC [#6195](https://github.com/paritytech/parity/pull/6195)
- InstantSeal fix [#6223](https://github.com/paritytech/parity/pull/6223)
- Untrusted RLP length overflow check [#6227](https://github.com/paritytech/parity/pull/6227)
- Chainspec validation [#6197](https://github.com/paritytech/parity/pull/6197)
- Fix cache path when using --base-path [#6212](https://github.com/paritytech/parity/pull/6212)
- removed std reexports from util && fixed broken tests [#6187](https://github.com/paritytech/parity/pull/6187)
- WASM MVP continued [#6132](https://github.com/paritytech/parity/pull/6132)
- Decouple virtual machines [#6184](https://github.com/paritytech/parity/pull/6184)
- Realloc test added [#6177](https://github.com/paritytech/parity/pull/6177)
- Re-enable wallets, fixed forgetting accounts [#6196](https://github.com/paritytech/parity/pull/6196)
- Move more params to the common section. [#6134](https://github.com/paritytech/parity/pull/6134)
- Whisper js [#6161](https://github.com/paritytech/parity/pull/6161)
- typo in uninstaller [#6185](https://github.com/paritytech/parity/pull/6185)
- fix #6052. honor --no-color for signer command [#6100](https://github.com/paritytech/parity/pull/6100)
- Refactor --allow-ips to handle custom ip-ranges [#6144](https://github.com/paritytech/parity/pull/6144)
- Update Changelog for 1.6.10 and 1.7.0 [#6183](https://github.com/paritytech/parity/pull/6183)
- Fix unsoundness in ethash's unsafe code [#6140](https://github.com/paritytech/parity/pull/6140)
### Previous releases
- [CHANGELOG-1.7](docs/CHANGELOG-1.7.md)
- [CHANGELOG-1.6](docs/CHANGELOG-1.6.md)
- [CHANGELOG-1.5](docs/CHANGELOG-1.5.md)
- [CHANGELOG-1.4](docs/CHANGELOG-1.4.md)
- [CHANGELOG-1.3](docs/CHANGELOG-1.3.md)
- [CHANGELOG-1.2](docs/CHANGELOG-1.2.md)
- [CHANGELOG-1.1](docs/CHANGELOG-1.1.md)
- [CHANGELOG-1.0](docs/CHANGELOG-1.0.md)
- [CHANGELOG-0.9](docs/CHANGELOG-0.9.md)

4022
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,81 +1,52 @@
[package]
description = "Parity Ethereum client"
description = "Ethcore client."
name = "parity"
version = "1.9.7"
version = "1.4.0"
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"
clap = "2"
term_size = "0.3"
textwrap = "0.9"
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 = "1.0"
semver = "0.6"
ansi_term = "0.9"
parking_lot = "0.4"
regex = "0.2"
rpassword = "0.2.1"
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"
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.9" }
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" }
ethcore-bytes = { path = "util/bytes" }
ethcore-bigint = { path = "util/bigint" }
ethsync = { path = "sync" }
ethcore-io = { path = "util/io" }
ethcore-devtools = { path = "devtools" }
ethcore-light = { path = "ethcore/light" }
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-logger = { path = "logger" }
ethcore-stratum = { path = "stratum" }
ethcore-network = { path = "util/network" }
node-filter = { path = "ethcore/node_filter" }
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" }
parity-version = { path = "util/version" }
parity-whisper = { path = "whisper" }
path = { path = "util/path" }
dir = { path = "util/dir" }
panic_hook = { path = "panic_hook" }
keccak-hash = { path = "util/hash" }
migration = { path = "util/migration" }
kvdb = { path = "util/kvdb" }
kvdb-rocksdb = { path = "util/kvdb-rocksdb" }
journaldb = { path = "util/journaldb" }
parity-dapps = { path = "dapps", optional = true }
ethcore-secretstore = { path = "secret_store", optional = true }
[build-dependencies]
rustc_version = "0.2"
[dev-dependencies]
pretty_assertions = "0.1"
ipnetwork = "0.12.6"
ethcore-stratum = { path = "stratum" }
ethcore-dapps = { path = "dapps", optional = true }
clippy = { version = "0.0.96", optional = true}
[target.'cfg(windows)'.dependencies]
winapi = "0.2"
@@ -85,45 +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", "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"]
secretstore = ["ethcore-secretstore"]
final = ["parity-version/final"]
[[bin]]
path = "parity/main.rs"
name = "parity"
[profile.dev]
panic = "abort"
[profile.release]
debug = false
lto = false
panic = "abort"
[workspace]
members = [
"chainspec",
"dapps/js-glue",
"ethcore/wasm/run",
"ethkey/cli",
"ethstore/cli",
"evmbin",
"transaction-pool",
"whisper",
]

133
README.md
View File

@@ -1,95 +1,93 @@
# [Parity](https://parity.io/) - 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/latest)
[![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)
Or join our community on Matrix:
[![Riot: +Parity](https://img.shields.io/badge/riot-%2Bparity%3Amatrix.parity.io-orange.svg)](https://riot.im/app/#/group/+parity:matrix.parity.io)
[Internal Documentation][doc-url]
Be sure to check out [our wiki](https://paritytech.github.io/wiki/) and the [internal documentation](https://paritytech.github.io/parity/ethcore/index.html) for more information.
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` and a websockets server on `127.0.0.1:8546`. This is fully configurable and supports a number of 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](https://gitter.im/paritytech/parity) or [Riot](https://riot.im/app/#/group/+parity:matrix.parity.io) chat room 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](SECURITY.md).
Parity's current release is 1.8. 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 requires Rust version 1.21.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
https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe, start "VS2015 x64 Native Tools Command Prompt", and use the following command to install and set up the msvc toolchain:
```bash
$ rustup default stable-x86_64-pc-windows-msvc
```
Once you have rustup, install Parity or download and build from source
Make sure you have Visual Studio 2015 with C++ support installed. Next, download and run the rustup installer from
https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe, start "VS2015 x64 Native Tools Command Prompt", and use the following command to install and set up the msvc toolchain:
```
$ rustup default stable-x86_64-pc-windows-msvc
```
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
@@ -98,42 +96,9 @@ $ 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
```
Note: When compiling a crate and you receive the following error:
```
error: the crate is compiled with the panic strategy `abort` which is incompatible with this crate's strategy of `unwind`
```
Cleaning the repository will most likely solve the issue, try:
```bash
$ cargo clean
```
This will always compile the latest nightly builds. If you want to build stable or beta, do a `git checkout stable` or `git checkout beta` first.
----
## Simple one-line installer for Mac and Ubuntu
```bash
bash <(curl https://get.parity.io -Lk)
```
The one-line installer always defaults to the latest beta release.
## Start Parity
### Manually
To start Parity manually, just run
```bash
$ ./target/release/parity
```
@@ -141,9 +106,11 @@ $ ./target/release/parity
and Parity will begin syncing the Ethereum blockchain.
### Using systemd service file
To start Parity as a regular user using systemd init:
1. Copy `./scripts/parity.service` to your
1. Copy `parity/scripts/parity.service` to your
systemd user directory (usually `~/.config/systemd/user`).
2. To configure Parity, write a `/etc/parity/config.toml` config file, see [Configuring Parity](https://github.com/paritytech/parity/wiki/Configuring-Parity) for details.
2. To pass any argument to Parity, write a `~/.parity/parity.conf` file this way:
`ARGS="ARG1 ARG2 ARG3"`.
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=x86 /p:Configuration=Release
- ps: if($env:cert) { signtool sign /f $env:keyfile /p $env:certpass windows\ptray\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,9 +0,0 @@
[package]
name = "chainspec"
version = "0.1.0"
authors = ["debris <marek.kotewicz@gmail.com>"]
[dependencies]
ethjson = { path = "../json" }
serde_json = "1.0"
serde_ignored = "0.0.4"

View File

@@ -1,48 +0,0 @@
extern crate serde_json;
extern crate serde_ignored;
extern crate ethjson;
use std::collections::BTreeSet;
use std::{fs, env, process};
use ethjson::spec::Spec;
fn quit(s: &str) -> ! {
println!("{}", s);
process::exit(1);
}
fn main() {
let mut args = env::args();
if args.len() != 2 {
quit("You need to specify chainspec.json\n\
\n\
./chainspec <chainspec.json>");
}
let path = args.nth(1).expect("args.len() == 2; qed");
let file = match fs::File::open(&path) {
Ok(file) => file,
Err(_) => quit(&format!("{} could not be opened", path)),
};
let mut unused = BTreeSet::new();
let mut deserializer = serde_json::Deserializer::from_reader(file);
let spec: Result<Spec, _> = serde_ignored::deserialize(&mut deserializer, |field| {
unused.insert(field.to_string());
});
if let Err(err) = spec {
quit(&format!("{} {}", path, err.to_string()));
}
if !unused.is_empty() {
let err = unused.into_iter()
.map(|field| format!("{} unexpected field `{}`", path, field))
.collect::<Vec<_>>()
.join("\n");
quit(&err);
}
println!("{} is valid", path);
}

View File

@@ -1,48 +1,48 @@
[package]
description = "Parity Dapps crate"
name = "parity-dapps"
version = "1.9.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"
futures-cpupool = "0.1"
linked-hash-map = "0.5"
rand = "0.3.14"
log = "0.3"
parity-dapps-glue = "1.9"
parking_lot = "0.4"
mime_guess = "2.0.0-alpha.2"
rand = "0.3"
rustc-hex = "1.0"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
unicase = "1.4"
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 }
itertools = "0.5"
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.9" }
jsonrpc-http-server = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.9" }
ethcore-util = { path = "../util" }
ethcore-bigint = { path = "../util/bigint" }
ethcore-bytes = { path = "../util/bytes" }
fetch = { path = "../util/fetch" }
node-health = { path = "./node-health" }
parity-hash-fetch = { path = "../hash-fetch" }
parity-reactor = { path = "../util/reactor" }
parity-ui = { path = "./ui" }
keccak-hash = { path = "../util/hash" }
parity-version = { path = "../util/version" }
[dev-dependencies]
env_logger = "0.4"
ethcore-devtools = { path = "../devtools" }
ethcore-rpc = { path = "../rpc" }
ethcore-util = { path = "../util" }
fetch = { path = "../util/fetch" }
parity-ui = { path = "./ui" }
mime_guess = { version = "1.6.1" }
clippy = { version = "0.0.96", optional = true}
[build-dependencies]
serde_codegen = { version = "0.8", optional = true }
[features]
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,27 +1,30 @@
[package]
description = "Base Package for all Parity built-in dapps"
name = "parity-dapps-glue"
version = "1.9.1"
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 = "2.0.0-alpha.2" }
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 }
mime_guess = { version = "1.6.1" }
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]
dev = ["clippy"]
default = ["with-syntex"]
nightly = ["quasi_macros"]
nightly-testing = ["clippy"]
with-syntex = ["quasi/with-syntex", "quasi_codegen", "quasi_codegen/with-syntex", "syntex", "syntex_syntax"]
use-precompiled-js = []

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)) {
@@ -101,7 +105,6 @@ fn implement_webapp(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push
let files_impl = quote_item!(cx,
impl $type_name {
#[allow(unused_mut)]
fn files() -> ::std::collections::HashMap<&'static str, File> {
let mut files = ::std::collections::HashMap::new();
$statements
@@ -114,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();
}
},
@@ -132,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)
)
);
@@ -166,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() {
@@ -175,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
@@ -25,7 +25,7 @@ mod platform {
use std::process::Command;
pub static NPM_CMD: &'static str = "npm";
pub fn handle_cmd(cmd: &mut Command) -> &mut Command {
pub fn handle_fd(cmd: &mut Command) -> &mut Command {
cmd
}
}
@@ -34,14 +34,14 @@ mod platform {
mod platform {
use std::process::{Command, Stdio};
pub static NPM_CMD: &'static str = "cmd.exe";
pub static NPM_CMD: &'static str = "npm.cmd";
// NOTE [ToDr] For some reason on windows
// The command doesn't have %~dp0 set properly
// and it cannot load globally installed node.exe
pub fn handle_cmd(cmd: &mut Command) -> &mut Command {
// We cannot have any file descriptors open when running a child process
// during build phase.
pub fn handle_fd(cmd: &mut Command) -> &mut Command {
cmd.stdin(Stdio::null())
.arg("/c")
.arg("npm.cmd")
.stdout(Stdio::null())
.stderr(Stdio::null())
}
}
@@ -58,7 +58,7 @@ pub fn build(_path: &str, _dest: &str) {
#[cfg(not(feature = "use-precompiled-js"))]
pub fn build(path: &str, dest: &str) {
let child = platform::handle_cmd(&mut Command::new(platform::NPM_CMD))
let child = platform::handle_fd(&mut Command::new(platform::NPM_CMD))
.arg("install")
.arg("--no-progress")
.current_dir(path)
@@ -66,7 +66,7 @@ pub fn build(path: &str, dest: &str) {
.unwrap_or_else(|e| die("Installing node.js dependencies with npm", e));
assert!(child.success(), "There was an error installing dependencies.");
let child = platform::handle_cmd(&mut Command::new(platform::NPM_CMD))
let child = platform::handle_fd(&mut Command::new(platform::NPM_CMD))
.arg("run")
.arg("build")
.env("NODE_ENV", "production")

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.3.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;
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) -> Box<Future<Item = Health, Error = ()> + Send> {
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(()));
},
);
Box::new(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})
}))
}
}

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,357 +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::collections::VecDeque;
use std::sync::atomic::{self, AtomicUsize};
use std::sync::Arc;
use futures::{self, Future};
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 = 10_000;
type BoxFuture<A, B> = Box<Future<Item = A, Error = B> + Send>;
#[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();
Box::new(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
}))
}
/// 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 Box::new(futures::done(select_result(res.1.iter())));
}
}
// 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,83 +15,156 @@
// 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::header::AccessControlAllowOrigin;
use hyper::{Method, StatusCode};
use api::types::{App, ApiError};
use api::response;
use apps::fetcher::Fetcher;
use endpoint::{Endpoint, Request, Response, EndpointPath};
use futures::{future, Future};
use node_health::{NodeHealth, HealthStatus};
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,
}
impl Endpoint for RestApi {
fn respond(&self, mut path: EndpointPath, req: Request) -> Response {
if let Method::Options = *req.method() {
return Box::new(future::ok(response::empty()));
}
let endpoint = path.app_params.get(0).map(String::to_owned);
let hash = path.app_params.get(1).map(String::to_owned);
// at this point path.app_id contains 'api', adjust it to the hash properly, otherwise
// we will try and retrieve 'api' as the hash when doing the /api/content route
if let Some(ref hash) = hash {
path.app_id = hash.to_owned();
}
trace!(target: "dapps", "Handling /api request: {:?}/{:?}", endpoint, hash);
match endpoint.as_ref().map(String::as_str) {
Some("ping") => Box::new(future::ok(response::ping(req))),
Some("health") => self.health(),
Some("content") => self.resolve_content(hash.as_ref().map(String::as_str), path, req),
_ => Box::new(future::ok(response::not_found())),
}
}
cors_domains: Option<Vec<AccessControlAllowOrigin>>,
endpoints: Arc<Endpoints>,
fetcher: Arc<ContentFetcher>,
}
impl RestApi {
pub fn new(
fetcher: Arc<Fetcher>,
health: NodeHealth,
) -> Box<Endpoint> {
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<ContentFetcher>) -> Box<Endpoint> {
Box::new(RestApi {
fetcher,
health,
cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()),
endpoints: endpoints,
fetcher: fetcher,
})
}
fn resolve_content(&self, hash: Option<&str>, path: EndpointPath, req: Request) -> Response {
trace!(target: "dapps", "Resolving content: {:?} from path: {:?}", hash, path);
match hash {
Some(hash) if self.fetcher.contains(hash) => {
self.fetcher.respond(path, req)
},
_ => Box::new(future::ok(response::not_found())),
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))
}
}
struct RestApiRouter {
api: RestApi,
origin: Option<String>,
path: Option<EndpointPath>,
control: Option<Control>,
handler: Box<Handler>,
}
impl RestApiRouter {
fn new(api: RestApi, path: EndpointPath, control: Control) -> Self {
RestApiRouter {
path: Some(path),
origin: None,
control: Some(control),
api: api,
handler: response::as_json_error(&ApiError {
code: "404".into(),
title: "Not Found".into(),
detail: "Resource you requested has not been found.".into(),
}),
}
}
fn health(&self) -> Response {
Box::new(self.health.health()
.then(|health| {
let status = match health {
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
};
fn resolve_content(&self, hash: Option<&str>, path: EndpointPath, control: Control) -> Option<Box<Handler>> {
match hash {
Some(hash) if self.api.fetcher.contains(hash) => {
Some(self.api.fetcher.to_async_handler(path, control))
},
_ => None
}
}
Ok(response::as_json(status, &health).into())
})
)
/// 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()),
]));
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();
}
let url = extract_url(&request);
if url.is_none() {
// Just return 404 if we can't parse URL
return Next::write();
}
let url = url.expect("Check for None early-exists above; qed");
let mut path = self.path.take().expect("on_request called only once, and path is always defined in new; qed");
let control = self.control.take().expect("on_request called only once, and control is always defined in new; qed");
let endpoint = url.path.get(1).map(|v| v.as_str());
let hash = url.path.get(2).map(|v| v.as_str());
// at this point path.app_id contains 'api', adjust it to the hash properly, otherwise
// we will try and retrieve 'api' as the hash when doing the /api/content route
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()),
"content" => self.resolve_content(hash, path, control),
_ => None
});
// Overwrite default
if let Some(h) = handler {
self.handler = h;
}
self.handler.on_request(request)
}
fn on_request_readable(&mut self, decoder: &mut Decoder<net::HttpStream>) -> Next {
self.handler.on_request_readable(decoder)
}
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,28 +16,25 @@
use serde::Serialize;
use serde_json;
use hyper::{self, mime, StatusCode};
use endpoint::Handler;
use handlers::{ContentHandler, EchoHandler};
pub fn empty() -> hyper::Response {
ContentHandler::ok("".into(), mime::TEXT_PLAIN).into()
pub fn empty() -> Box<Handler> {
Box::new(ContentHandler::ok("".into(), mime!(Text/Plain)))
}
pub fn as_json<T: Serialize>(status: StatusCode, val: &T) -> hyper::Response {
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).into()
Box::new(ContentHandler::ok(json, mime!(Application/Json)))
}
pub fn ping(req: hyper::Request) -> hyper::Response {
EchoHandler::new(req).into()
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");
Box::new(ContentHandler::not_found(json, mime!(Application/Json)))
}
pub fn not_found() -> hyper::Response {
as_json(StatusCode::NotFound, &::api::types::ApiError {
code: "404".into(),
title: "Not Found".into(),
detail: "Resource you requested has not been found.".into(),
})
pub fn ping() -> Box<Handler> {
Box::new(EchoHandler::default())
}

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"));

62
dapps/src/api/types.rs.in Normal file
View File

@@ -0,0 +1,62 @@
// 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 endpoint::EndpointInfo;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct App {
pub id: String,
pub name: String,
pub description: String,
pub version: String,
pub author: String,
#[serde(rename="iconUrl")]
pub icon_url: String,
}
impl App {
/// Creates `App` instance from `EndpointInfo` and `id`.
pub fn from_info(id: &str, info: &EndpointInfo) -> Self {
App {
id: id.to_owned(),
name: info.name.to_owned(),
description: info.description.to_owned(),
version: info.version.to_owned(),
author: info.author.to_owned(),
icon_url: info.icon_url.to_owned(),
}
}
}
impl Into<EndpointInfo> for App {
fn into(self) -> EndpointInfo {
EndpointInfo {
name: self.name,
description: self.description,
version: self.version,
author: self.author,
icon_url: self.icon_url,
}
}
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct ApiError {
pub code: String,
pub title: String,
pub detail: String,
}

View File

@@ -1,39 +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/>.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct App {
pub id: Option<String>,
pub name: String,
pub description: String,
pub version: String,
pub author: String,
#[serde(rename="iconUrl")]
pub icon_url: String,
#[serde(rename="localUrl")]
pub local_url: Option<String>,
#[serde(rename="allowJsEval")]
pub allow_js_eval: Option<bool>,
}
impl App {
pub fn with_id(&self, id: &str) -> Self {
let mut app = self.clone();
app.id = Some(id.into());
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
@@ -17,14 +17,15 @@
//! Fetchable Dapps support.
use std::fs;
use std::sync::{Arc};
use linked_hash_map::LinkedHashMap;
use page::local;
use page::LocalPageEndpoint;
use handlers::FetchControl;
pub enum ContentStatus {
Fetching(FetchControl),
Ready(local::Dapp),
Fetching(Arc<FetchControl>),
Ready(LocalPageEndpoint),
}
#[derive(Default)]

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_at: Option<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_at: Option<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_at: embeddable_at,
}
}
fn still_syncing(port: Option<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>"),
port,
))
}
#[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_at))
},
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_at: self.embeddable_at,
},
self.embeddable_at,
);
(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_at,
);
(Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box<Handler>)
},
None if self.sync.is_major_importing() => {
(None, Self::still_syncing(self.embeddable_at))
},
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_at,
)) 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_at: Option<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_at);
// 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,267 +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 bigint::hash::H256;
use fetch::{self, Mime};
use futures_cpupool::CpuPool;
use hash::keccak_buffer;
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest};
use handlers::{ContentValidator, ValidatorResponse};
use page::{local, PageCache};
use Embeddable;
type OnDone = Box<Fn(Option<local::Dapp>) + 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 keccak in-flight while reading the response
let mut file = io::BufReader::new(fs::File::open(&content_path)?);
let hash = keccak_buffer(&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,
pool: CpuPool,
}
impl Content {
pub fn new(id: String, mime: Mime, content_path: PathBuf, on_done: OnDone, pool: CpuPool) -> Self {
Content {
id,
mime,
content_path,
on_done,
pool,
}
}
}
impl ContentValidator for Content {
type Error = ValidationError;
fn validate_and_install(self, response: fetch::Response) -> Result<ValidatorResponse, ValidationError> {
let pool = self.pool;
let id = self.id.clone();
let mime = self.mime;
let validate = move |content_path: PathBuf| {
// Create dir
let (_, content_path) = write_response_and_check_hash(&id, content_path, &id, response)?;
Ok(local::Dapp::single_file(pool, content_path, mime, 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,
pool: CpuPool,
}
impl Dapp {
pub fn new(id: String, dapps_path: PathBuf, on_done: OnDone, embeddable_on: Embeddable, pool: CpuPool) -> Self {
Dapp {
id,
dapps_path,
on_done,
embeddable_on,
pool,
}
}
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 id = self.id.clone();
let pool = self.pool;
let embeddable_on = self.embeddable_on;
let validate = move |dapp_path: PathBuf| {
let (file, zip_path) = write_response_and_check_hash(&id, dapp_path.clone(), &format!("{}.zip", 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 = Some(id);
// 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 = local::Dapp::new(pool, dapp_path, manifest.into(), PageCache::Enabled, embeddable_on);
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,341 +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 futures::{future, Future};
use futures_cpupool::CpuPool;
use fetch::{Client as FetchClient, Fetch};
use hash_fetch::urlhint::{URLHintContract, URLHint, URLHintResult};
use hyper::StatusCode;
use {Embeddable, SyncStatus, random_filename};
use parking_lot::Mutex;
use page::local;
use handlers::{ContentHandler, ContentFetcherHandler};
use endpoint::{self, Endpoint, EndpointPath};
use apps::cache::{ContentCache, ContentStatus};
/// Limit of cached dapps/content
const MAX_CACHED_DAPPS: usize = 20;
pub trait Fetcher: Endpoint + 'static {
fn contains(&self, content_id: &str) -> bool;
}
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,
fetch: F,
pool: CpuPool,
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: Arc<SyncStatus>,
fetch: F,
pool: CpuPool,
) -> Self {
let mut cache_path = env::temp_dir();
cache_path.push(random_filename());
ContentFetcher {
cache_path,
resolver,
sync,
cache: Arc::new(Mutex::new(ContentCache::default())),
embeddable_on: None,
fetch,
pool,
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 not_found(embeddable: Embeddable) -> endpoint::Response {
Box::new(future::ok(ContentHandler::error(
StatusCode::NotFound,
"Resource Not Found",
"Requested resource was not found.",
None,
embeddable,
).into()))
}
fn still_syncing(embeddable: Embeddable) -> endpoint::Response {
Box::new(future::ok(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,
).into()))
}
fn dapps_disabled(address: Embeddable) -> endpoint::Response {
Box::new(future::ok(ContentHandler::error(
StatusCode::ServiceUnavailable,
"Network Dapps Not Available",
"This interface doesn't support network dapps for security reasons.",
None,
address,
).into()))
}
#[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> {
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
}
}
}
impl<R: URLHint + 'static, F: Fetch> Endpoint for ContentFetcher<F, R> {
fn respond(&self, path: EndpointPath, req: endpoint::Request) -> endpoint::Response {
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_response(&path))
},
// 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_response(path))
},
// 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<local::Dapp>| {
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(content) => {
let handler = match content {
URLHintResult::Dapp(dapp) => {
ContentFetcherHandler::new(
req.method(),
&dapp.url(),
path,
installers::Dapp::new(
content_id.clone(),
self.cache_path.clone(),
Box::new(on_done),
self.embeddable_on.clone(),
self.pool.clone(),
),
self.embeddable_on.clone(),
self.fetch.clone(),
)
},
URLHintResult::GithubDapp(content) => {
ContentFetcherHandler::new(
req.method(),
&content.url,
path,
installers::Dapp::new(
content_id.clone(),
self.cache_path.clone(),
Box::new(on_done),
self.embeddable_on.clone(),
self.pool.clone(),
),
self.embeddable_on.clone(),
self.fetch.clone(),
)
},
URLHintResult::Content(content) => {
ContentFetcherHandler::new(
req.method(),
&content.url,
path,
installers::Content::new(
content_id.clone(),
content.mime,
self.cache_path.clone(),
Box::new(on_done),
self.pool.clone(),
),
self.embeddable_on.clone(),
self.fetch.clone(),
)
},
};
(Some(ContentStatus::Fetching(handler.fetch_control())), Box::new(handler) as endpoint::Response)
},
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, Self::not_found(self.embeddable_on.clone()))
},
}
},
}
};
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 bytes::Bytes;
use fetch::{Fetch, Client};
use futures::future;
use hash_fetch::urlhint::{URLHint, URLHintResult, BoxFuture};
use apps::cache::ContentStatus;
use endpoint::EndpointInfo;
use page::local;
use super::{ContentFetcher, Fetcher};
use {SyncStatus};
#[derive(Clone)]
struct FakeResolver;
impl URLHint for FakeResolver {
fn resolve(&self, _id: Bytes) -> BoxFuture<Option<URLHintResult>, String> {
Box::new(future::ok(None))
}
}
#[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 pool = ::futures_cpupool::CpuPool::new(1);
let path = env::temp_dir();
let fetcher = ContentFetcher::new(
FakeResolver,
Arc::new(FakeSync(false)),
Client::new().unwrap(),
pool.clone(),
).allow_dapps(true);
let handler = local::Dapp::new(pool, path, EndpointInfo {
id: None,
name: "fake".into(),
description: "".into(),
version: "".into(),
author: "".into(),
icon_url: "".into(),
local_url: Some("".into()),
allow_js_eval: None,
}, 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,17 +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 futures_cpupool::CpuPool;
use std::path::PathBuf;
use page::{LocalPageEndpoint, PageCache};
use endpoint::{Endpoints, EndpointInfo};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
use endpoint::{Endpoint, EndpointInfo};
use page::{local, PageCache};
use Embeddable;
struct LocalDapp {
id: String,
@@ -32,88 +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)
})
.unwrap_or_else(|e| {
warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
EndpointInfo {
id: None,
name: name.into(),
description: name.into(),
version: "0.0.0".into(),
author: "?".into(),
icon_url: "icon.png".into(),
local_url: None,
allow_js_eval: Some(false),
}
})
}
/// 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, pool: CpuPool) -> Option<(String, Box<local::Dapp>)> {
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(local::Dapp::new(
pool.clone(), 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 `local::Dapp`.
pub fn local_endpoints<P: AsRef<Path>>(dapps_path: P, embeddable: Embeddable, pool: CpuPool) -> 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(local::Dapp::new(pool.clone(), 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() {
@@ -134,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_port: Option<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_port))
);
}
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,18 +15,13 @@
// 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";
pub fn deserialize_manifest(manifest: String) -> Result<Manifest, String> {
let mut manifest = serde_json::from_str::<Manifest>(&manifest).map_err(|e| format!("{:?}", e))?;
if manifest.id.is_none() {
return Err("App 'id' is missing.".into());
}
manifest.allow_js_eval = Some(manifest.allow_js_eval.unwrap_or(false));
Ok(manifest)
serde_json::from_str::<Manifest>(&manifest).map_err(|e| format!("{:?}", e))
// TODO [todr] Manifest validation (especialy: id (used as path))
}
pub fn serialize_manifest(manifest: &Manifest) -> Result<String, 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
@@ -14,93 +14,49 @@
// 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::path::PathBuf;
use std::sync::Arc;
use endpoint::{Endpoints, Endpoint};
use futures_cpupool::CpuPool;
use page;
use page::PageEndpoint;
use proxypac::ProxyPac;
use web::Web;
use fetch::Fetch;
use parity_dapps::WebApp;
use parity_ui;
use {WebProxyTokens, ParentFrameSettings};
mod app;
mod cache;
mod ui;
pub mod fs;
mod fs;
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(pool: CpuPool) -> Box<Endpoint> {
Box::new(page::builtin::Dapp::new(pool, false, parity_ui::App::default()))
pub fn utils() -> Box<Endpoint> {
Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned()))
}
pub fn ui(pool: CpuPool) -> Box<Endpoint> {
Box::new(page::builtin::Dapp::with_fallback_to_index(pool, 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>,
fetch: F,
pool: CpuPool,
) -> (Vec<String>, Endpoints) {
pub fn all_endpoints(dapps_path: String, signer_port: Option<u16>) -> Endpoints {
// fetch fs dapps at first to avoid overwriting builtins
let mut pages = fs::local_endpoints(dapps_path.clone(), embeddable.clone(), pool.clone());
let local_endpoints: Vec<String> = pages.keys().cloned().collect();
for path in extra_dapps {
if let Some((id, endpoint)) = fs::local_endpoint(path.clone(), embeddable.clone(), pool.clone()) {
pages.insert(id, endpoint);
} else {
warn!(target: "dapps", "Ignoring invalid dapp at {}", path.display());
}
}
let mut pages = fs::local_endpoints(dapps_path, signer_port);
// NOTE [ToDr] Dapps will be currently embeded on 8180
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(embeddable.clone()), pool.clone(), true);
// old version
insert::<parity_ui::old::App>(&mut pages, "v1", Embeddable::Yes(embeddable.clone()), pool.clone(), true);
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(signer_port));
pages.insert("proxy".into(), ProxyPac::boxed(signer_port));
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(), fetch.clone()));
(local_endpoints, pages)
pages
}
fn insert<T : WebApp + Default + 'static>(
pages: &mut Endpoints,
id: &str,
embed_at: Embeddable,
pool: CpuPool,
allow_js_eval: bool,
) {
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) => page::builtin::Dapp::new_safe_to_embed(pool, allow_js_eval, T::default(), address),
Embeddable::No => page::builtin::Dapp::new(pool, allow_js_eval, T::default()),
Embeddable::Yes(port) => PageEndpoint::new_safe_to_embed(T::default(), port),
Embeddable::No => PageEndpoint::new(T::default()),
}));
}
enum Embeddable {
Yes(Option<ParentFrameSettings>),
Yes(Option<u16>),
#[allow(dead_code)]
No,
}

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/>.
//! UI redirections
use hyper::StatusCode;
use futures::future;
use endpoint::{Endpoint, Request, Response, 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 respond(&self, _path: EndpointPath, req: Request) -> Response {
Box::new(future::ok(if let Some(ref frame) = self.embeddable_on {
trace!(target: "dapps", "Redirecting to signer interface.");
let protocol = req.uri().scheme().unwrap_or("http");
handlers::Redirection::new(format!("{}://{}:{}", protocol, &frame.host, frame.port)).into()
} else {
trace!(target: "dapps", "Signer disabled, returning 404.");
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,
).into()
}))
}
}

399
dapps/src/apps/urlhint.rs Normal file
View File

@@ -0,0 +1,399 @@
// 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::fmt;
use std::sync::Arc;
use rustc_serialize::hex::ToHex;
use mime_guess;
use ethabi::{Interface, Contract, Token};
use util::{Address, Bytes, Hashable};
const COMMIT_LEN: usize = 20;
#[derive(Debug, PartialEq)]
pub struct GithubApp {
pub account: String,
pub repo: String,
pub commit: [u8;COMMIT_LEN],
pub owner: Address,
}
impl GithubApp {
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())
format!("https://codeload.github.com/{}/{}/zip/{}", self.account, self.repo, self.commit.to_hex())
}
fn commit(bytes: &[u8]) -> Option<[u8;COMMIT_LEN]> {
if bytes.len() < COMMIT_LEN {
return None;
}
let mut commit = [0; COMMIT_LEN];
for i in 0..COMMIT_LEN {
commit[i] = bytes[i];
}
Some(commit)
}
}
#[derive(Debug, PartialEq)]
pub struct Content {
pub url: String,
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 {
/// Dapp
Dapp(GithubApp),
/// Content
Content(Content),
}
/// URLHint Contract interface
pub trait URLHint {
/// Resolves given id to registrar entry.
fn resolve(&self, id: Bytes) -> Option<URLHintResult>;
}
pub struct URLHintContract {
urlhint: Contract,
registrar: Contract,
client: Arc<ContractClient>,
}
impl 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: Contract::new(urlhint),
registrar: Contract::new(registrar),
client: client,
}
}
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));
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)),
}
};
match res() {
Ok(res) => Some(res),
Err(e) => {
warn!(target: "dapps", "Error while calling registrar: {:?}", e);
None
}
}
}
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) -> 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);
}
call.ok()
})
.and_then(|output| self.decode_urlhint_output(output))
})
}
}
fn guess_mime_type(url: &str) -> Option<String> {
const CONTENT_TYPE: &'static str = "content-type=";
let mut it = url.split('#');
// skip url
let url = it.next();
// get meta headers
let metas = it.next();
if let Some(metas) = metas {
for meta in metas.split('&') {
let meta = meta.to_lowercase();
if meta.starts_with(CONTENT_TYPE) {
return Some(meta[CONTENT_TYPE.len()..].to_owned());
}
}
}
url.and_then(|url| {
url.split('.').last()
}).and_then(|extension| {
mime_guess::get_mime_type_str(extension).map(Into::into)
})
}
#[cfg(test)]
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_serialize::hex::FromHex;
use super::*;
use util::{Bytes, Address, Mutex, ToPretty};
struct FakeRegistrar {
pub calls: Arc<Mutex<Vec<(String, String)>>>,
pub responses: Mutex<Vec<Result<Bytes, String>>>,
}
const REGISTRAR: &'static str = "8e4e9b13d4b45cb0befc93c3061b1408f67316b2";
const URLHINT: &'static str = "deadbeefcafe0000000000000000000000000000";
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)
}
}
#[test]
fn should_call_registrar_and_urlhint_contracts() {
// given
let registrar = FakeRegistrar::new();
let calls = registrar.calls.clone();
let urlhint = URLHintContract::new(Arc::new(registrar));
// when
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");
// then
assert!(res.is_none());
assert_eq!(call0.0, REGISTRAR);
assert_eq!(call0.1,
"6795dbcd058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000014100000000000000000000000000000000000000000000000000000000000000".to_owned()
);
assert_eq!(call1.0, URLHINT);
assert_eq!(call1.1,
"267b69227465737400000000000000000000000000000000000000000000000000000000".to_owned()
);
}
#[test]
fn should_decode_urlhint_output() {
// given
let mut registrar = FakeRegistrar::new();
registrar.responses = Mutex::new(vec![
Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()),
Ok("0000000000000000000000000000000000000000000000000000000000000060ec4c1fe06c808fe3739858c347109b1f5f1ed4b5000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff0000000000000000000000000000000000000000000000000000000000000011657468636f72652f64616f2e636c61696d000000000000000000000000000000".from_hex().unwrap()),
]);
let urlhint = URLHintContract::new(Arc::new(registrar));
// when
let res = urlhint.resolve("test".bytes().collect());
// then
assert_eq!(res, Some(URLHintResult::Dapp(GithubApp {
account: "ethcore".into(),
repo: "dao.claim".into(),
commit: GithubApp::commit(&"ec4c1fe06c808fe3739858c347109b1f5f1ed4b5".from_hex().unwrap()).unwrap(),
owner: Address::from_str("deadcafebeefbeefcafedeaddeedfeedffffffff").unwrap(),
})))
}
#[test]
fn should_decode_urlhint_content_output() {
// given
let mut registrar = FakeRegistrar::new();
registrar.responses = Mutex::new(vec![
Ok(format!("000000000000000000000000{}", URLHINT).from_hex().unwrap()),
Ok("00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000deadcafebeefbeefcafedeaddeedfeedffffffff000000000000000000000000000000000000000000000000000000000000003d68747470733a2f2f657468636f72652e696f2f6173736574732f696d616765732f657468636f72652d626c61636b2d686f72697a6f6e74616c2e706e67000000".from_hex().unwrap()),
]);
let urlhint = URLHintContract::new(Arc::new(registrar));
// when
let res = urlhint.resolve("test".bytes().collect());
// then
assert_eq!(res, Some(URLHintResult::Content(Content {
url: "https://ethcore.io/assets/images/ethcore-black-horizontal.png".into(),
mime: "image/png".into(),
owner: Address::from_str("deadcafebeefbeefcafedeaddeedfeedffffffff").unwrap(),
})))
}
#[test]
fn should_return_valid_url() {
// given
let app = GithubApp {
account: "test".into(),
repo: "xyz".into(),
commit: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
owner: Address::default(),
};
// when
let url = app.url();
// then
assert_eq!(url, "https://codeload.github.com/test/xyz/zip/000102030405060708090a0b0c0d0e0f10111213".to_owned());
}
#[test]
fn should_guess_mime_type_from_url() {
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!(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,34 +16,37 @@
//! URL Endpoint traits
use hyper::{self, server, net};
use std::collections::BTreeMap;
use futures::Future;
use hyper;
#[derive(Debug, PartialEq, Default, Clone)]
pub struct EndpointPath {
pub app_id: String,
pub app_params: Vec<String>,
pub query: Option<String>,
pub host: String,
pub port: u16,
pub using_dapps_domains: bool,
}
impl EndpointPath {
pub fn has_no_params(&self) -> bool {
self.app_params.is_empty() || self.app_params.iter().all(|x| x.is_empty())
}
#[derive(Debug, PartialEq, Clone)]
pub struct EndpointInfo {
pub name: String,
pub description: String,
pub version: String,
pub author: String,
pub icon_url: String,
}
pub type EndpointInfo = ::apps::App;
pub type Endpoints = BTreeMap<String, Box<Endpoint>>;
pub type Response = Box<Future<Item=hyper::Response, Error=hyper::Error> + Send>;
pub type Request = hyper::Request;
pub type Handler = server::Handler<net::HttpStream> + Send;
pub trait Endpoint : Send + Sync {
fn info(&self) -> Option<&EndpointInfo> { None }
fn respond(&self, path: EndpointPath, req: Request) -> Response;
fn to_handler(&self, _path: EndpointPath) -> Box<Handler> {
panic!("This Endpoint is asynchronous and requires Control object.");
}
fn to_async_handler(&self, path: EndpointPath, _control: hyper::Control) -> Box<Handler> {
self.to_handler(path)
}
}

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,73 +16,94 @@
//! Simple Content Handler
use hyper::{self, mime, header};
use hyper::StatusCode;
use std::io::Write;
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::mime::Mime;
use hyper::status::StatusCode;
use parity_version::version;
use util::version;
use handlers::add_security_headers;
use Embeddable;
#[derive(Debug, Clone)]
#[derive(Clone)]
pub struct ContentHandler {
code: StatusCode,
content: String,
mimetype: mime::Mime,
safe_to_embed_on: Embeddable,
mimetype: Mime,
write_pos: usize,
safe_to_embed_at_port: Option<u16>,
}
impl ContentHandler {
pub fn ok(content: String, mimetype: mime::Mime) -> Self {
pub fn ok(content: String, mimetype: Mime) -> Self {
Self::new(StatusCode::Ok, content, mimetype)
}
pub fn html(code: StatusCode, content: String, embeddable_on: Embeddable) -> Self {
Self::new_embeddable(code, content, mime::TEXT_HTML, embeddable_on)
pub fn not_found(content: String, mimetype: Mime) -> Self {
Self::new(StatusCode::NotFound, content, mimetype)
}
pub fn error(
code: StatusCode,
title: &str,
message: &str,
details: Option<&str>,
embeddable_on: Embeddable,
) -> Self {
pub fn html(code: StatusCode, content: String, embeddable_at: Option<u16>) -> Self {
Self::new_embeddable(code, content, mime!(Text/Html), embeddable_at)
}
pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_at: Option<u16>) -> Self {
Self::html(code, format!(
include_str!("../error_tpl.html"),
title=title,
message=message,
details=details.unwrap_or_else(|| ""),
version=version(),
), embeddable_on)
), embeddable_at)
}
pub fn new(code: StatusCode, content: String, mimetype: mime::Mime) -> Self {
pub fn new(code: StatusCode, content: String, mimetype: Mime) -> Self {
Self::new_embeddable(code, content, mimetype, None)
}
pub fn new_embeddable(
code: StatusCode,
content: String,
mimetype: mime::Mime,
safe_to_embed_on: Embeddable,
) -> Self {
pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_at: Option<u16>) -> Self {
ContentHandler {
code,
content,
mimetype,
safe_to_embed_on,
code: code,
content: content,
mimetype: mimetype,
write_pos: 0,
safe_to_embed_at_port: embeddable_at,
}
}
}
impl Into<hyper::Response> for ContentHandler {
fn into(self) -> hyper::Response {
let mut res = hyper::Response::new()
.with_status(self.code)
.with_header(header::ContentType(self.mimetype))
.with_body(self.content);
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on, false);
res
impl server::Handler<HttpStream> for ContentHandler {
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.code);
res.headers_mut().set(header::ContentType(self.mimetype.clone()));
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port.clone());
Next::write()
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
let bytes = self.content.as_bytes();
if self.write_pos == bytes.len() {
return Next::end();
}
match encoder.write(&bytes[self.write_pos..]) {
Ok(bytes) => {
self.write_pos += bytes;
Next::write()
},
Err(e) => match e.kind() {
::std::io::ErrorKind::WouldBlock => Next::write(),
_ => 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,31 +16,45 @@
//! Echo Handler
use hyper::{self, header};
use std::io::Read;
use hyper::{server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use super::ContentHandler;
use handlers::add_security_headers;
#[derive(Debug)]
#[derive(Default)]
pub struct EchoHandler {
request: hyper::Request,
content: String,
handler: Option<ContentHandler>,
}
impl EchoHandler {
pub fn new(request: hyper::Request) -> Self {
EchoHandler {
request,
impl server::Handler<HttpStream> for EchoHandler {
fn on_request(&mut self, _: server::Request<HttpStream>) -> Next {
Next::read()
}
fn on_request_readable(&mut self, decoder: &mut Decoder<HttpStream>) -> Next {
match decoder.read_to_string(&mut self.content) {
Ok(0) => {
self.handler = Some(ContentHandler::ok(self.content.clone(), mime!(Application/Json)));
Next::write()
},
Ok(_) => Next::read(),
Err(e) => match e.kind() {
::std::io::ErrorKind::WouldBlock => Next::read(),
_ => Next::end(),
}
}
}
}
impl Into<hyper::Response> for EchoHandler {
fn into(self) -> hyper::Response {
let content_type = self.request.headers().get().cloned();
let mut res = hyper::Response::new()
.with_header(content_type.unwrap_or(header::ContentType::json()))
.with_body(self.request.body());
fn on_response(&mut self, res: &mut server::Response) -> Next {
self.handler.as_mut()
.expect("handler always set in on_request, which is before now; qed")
.on_response(res)
}
add_security_headers(res.headers_mut(), None, false);
res
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
self.handler.as_mut()
.expect("handler always set in on_request, which is before now; qed")
.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,38 +16,41 @@
//! Hyper Server Handler that fetches a file during a request (proxy).
use std::{fmt, mem};
use std::sync::Arc;
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::sync::oneshot;
use futures::{self, Future};
use hyper::{self, Method, StatusCode};
use parking_lot::Mutex;
use util::Mutex;
use url::Url;
use fetch::{Client, Fetch, FetchResult};
use endpoint::{self, EndpointPath};
use handlers::{ContentHandler, StreamingHandler};
use page::local;
use {Embeddable};
use hyper::{server, Decoder, Encoder, Next, Method, Control};
use hyper::net::HttpStream;
use hyper::status::StatusCode;
const FETCH_TIMEOUT: u64 = 300;
use handlers::{ContentHandler, Redirection, extract_url};
use page::LocalPageEndpoint;
pub enum ValidatorResponse {
Local(local::Dapp),
Streaming(StreamingHandler<fetch::Response>),
const FETCH_TIMEOUT: u64 = 30;
enum FetchState {
NotStarted(String),
Error(ContentHandler),
InProgress(mpsc::Receiver<FetchResult>),
Done(String, LocalPageEndpoint, Redirection),
}
pub trait ContentValidator: Sized + Send + 'static {
pub trait ContentValidator {
type Error: fmt::Debug + fmt::Display;
fn validate_and_install(self, fetch::Response) -> Result<ValidatorResponse, Self::Error>;
fn validate_and_install(&self, app: PathBuf) -> Result<(String, LocalPageEndpoint), Self::Error>;
fn done(&self, Option<LocalPageEndpoint>);
}
#[derive(Debug, Clone)]
pub struct FetchControl {
abort: Arc<AtomicBool>,
listeners: Arc<Mutex<Vec<oneshot::Sender<WaitResult>>>>,
listeners: Mutex<Vec<(Control, mpsc::Sender<FetchState>)>>,
deadline: Instant,
}
@@ -55,315 +58,265 @@ 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 {
pub fn is_deadline_reached(&self) -> bool {
self.deadline < Instant::now()
}
pub fn abort(&self) {
self.abort.store(true, Ordering::SeqCst);
}
pub fn to_response(&self, path: EndpointPath) -> endpoint::Response {
let (tx, receiver) = oneshot::channel();
self.listeners.lock().push(tx);
Box::new(WaitingHandler {
path,
state: WaitState::Waiting(receiver),
})
}
fn notify<F: Fn() -> WaitResult>(&self, status: F) {
fn notify<F: Fn() -> FetchState>(&self, status: F) {
let mut listeners = self.listeners.lock();
for sender in listeners.drain(..) {
trace!(target: "dapps", "Resuming request waiting for content...");
if let Err(_) = sender.send(status()) {
trace!(target: "dapps", "Waiting listener notification failed.");
for (control, sender) in listeners.drain(..) {
if let Err(e) = sender.send(status()) {
trace!(target: "dapps", "Waiting listener notification failed: {:?}", e);
} else {
let _ = control.ready(Next::read());
}
}
}
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::InProgress(_) => {},
FetchState::Empty => {},
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 abort(&self) {
self.abort.store(true, Ordering::SeqCst);
}
enum WaitState {
Waiting(oneshot::Receiver<WaitResult>),
Done(endpoint::Response),
}
pub fn to_handler(&self, control: Control) -> Box<server::Handler<HttpStream> + Send> {
let (tx, rx) = mpsc::channel();
self.listeners.lock().push((control, tx));
#[derive(Debug)]
enum WaitResult {
Error(ContentHandler),
Done(local::Dapp),
NonAwaitable,
Box::new(WaitingHandler {
receiver: rx,
state: None,
})
}
}
pub struct WaitingHandler {
path: EndpointPath,
state: WaitState,
receiver: mpsc::Receiver<FetchState>,
state: Option<FetchState>,
}
impl Future for WaitingHandler {
type Item = hyper::Response;
type Error = hyper::Error;
impl server::Handler<HttpStream> for WaitingHandler {
fn on_request(&mut self, _request: server::Request<HttpStream>) -> Next {
Next::wait()
}
fn poll(&mut self) -> futures::Poll<Self::Item, Self::Error> {
loop {
let new_state = match self.state {
WaitState::Waiting(ref mut receiver) => {
let result = try_ready!(receiver.poll().map_err(|_| hyper::Error::Timeout));
fn on_request_readable(&mut self, _decoder: &mut Decoder<HttpStream>) -> Next {
self.state = self.receiver.try_recv().ok();
Next::write()
}
match result {
WaitResult::Error(handler) => {
return Ok(futures::Async::Ready(handler.into()));
},
WaitResult::NonAwaitable => {
let errors = Errors { embeddable_on: None };
return Ok(futures::Async::Ready(errors.streaming().into()));
},
WaitResult::Done(endpoint) => {
WaitState::Done(endpoint.to_response(&self.path).into())
},
}
},
WaitState::Done(ref mut response) => {
return response.poll()
},
};
fn on_response(&mut self, res: &mut server::Response) -> Next {
match self.state {
Some(FetchState::Done(_, _, ref mut handler)) => handler.on_response(res),
Some(FetchState::Error(ref mut handler)) => handler.on_response(res),
_ => Next::end(),
}
}
self.state = new_state;
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
match self.state {
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(Debug, Clone)]
struct Errors {
embeddable_on: Embeddable,
}
impl Errors {
fn streaming(&self) -> ContentHandler {
ContentHandler::error(
StatusCode::BadGateway,
"Streaming Error",
"This content is being streamed in other place.",
None,
self.embeddable_on.clone(),
)
}
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(),
)
}
}
enum FetchState {
Error(ContentHandler),
InProgress(Box<Future<Item=FetchState, Error=()> + Send>),
Streaming(hyper::Response),
Done(local::Dapp, endpoint::Response),
Empty,
}
impl fmt::Debug for FetchState {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use self::FetchState::*;
write!(fmt, "FetchState(")?;
match *self {
Error(ref error) => write!(fmt, "error: {:?}", error),
InProgress(_) => write!(fmt, "in progress"),
Streaming(ref res) => write!(fmt, "streaming: {:?}", res),
Done(ref endpoint, _) => write!(fmt, "done: {:?}", endpoint),
Empty => write!(fmt, "?"),
}?;
write!(fmt, ")")
}
}
#[derive(Debug)]
pub struct ContentFetcherHandler {
fetch_control: FetchControl,
pub struct ContentFetcherHandler<H: ContentValidator> {
fetch_control: Arc<FetchControl>,
control: Option<Control>,
status: FetchState,
errors: Errors,
client: Option<Client>,
installer: H,
request_url: Option<Url>,
embeddable_at: Option<u16>,
}
impl ContentFetcherHandler {
pub fn fetch_control(&self) -> FetchControl {
self.fetch_control.clone()
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);
}
}
pub fn new<H: ContentValidator, F: Fetch>(
method: &hyper::Method,
url: &str,
path: EndpointPath,
installer: H,
embeddable_on: Embeddable,
fetch: F,
) -> Self {
let fetch_control = FetchControl::default();
let errors = Errors { embeddable_on };
impl<H: ContentValidator> ContentFetcherHandler<H> {
// Validation of method
let status = match *method {
// Start fetching content
Method::Get => {
trace!(target: "dapps", "Fetching content from: {:?}", url);
FetchState::InProgress(Self::fetch_content(
fetch,
url,
fetch_control.abort.clone(),
path,
errors.clone(),
installer,
))
},
// or return error
_ => FetchState::Error(errors.method_not_allowed()),
pub fn new(
url: String,
control: Control,
handler: H,
embeddable_at: Option<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: handler,
request_url: None,
embeddable_at: embeddable_at,
};
ContentFetcherHandler {
fetch_control,
status,
errors,
}
(handler, fetch_control)
}
fn fetch_content<H: ContentValidator, F: Fetch>(
fetch: F,
url: &str,
abort: Arc<AtomicBool>,
path: EndpointPath,
errors: Errors,
installer: H,
) -> Box<Future<Item=FetchState, Error=()> + Send> {
// Start fetching the content
let fetch2 = fetch.clone();
let future = fetch.fetch_with_abort(url, abort.into()).then(move |result| {
trace!(target: "dapps", "Fetching content finished. Starting validation: {:?}", result);
Ok(match result {
Ok(response) => match installer.validate_and_install(response) {
Ok(ValidatorResponse::Local(endpoint)) => {
trace!(target: "dapps", "Validation OK. Returning response.");
let response = endpoint.to_response(&path);
FetchState::Done(endpoint, response)
},
Ok(ValidatorResponse::Streaming(stream)) => {
trace!(target: "dapps", "Validation OK. Streaming response.");
let (reading, response) = stream.into_response();
fetch2.process_and_forget(reading);
FetchState::Streaming(response)
},
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))
},
})
});
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();
}
// make sure to run within fetch thread pool.
fetch.process(future)
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());
})).map_err(|e| format!("{:?}", e))
}
}
impl Future for ContentFetcherHandler {
type Item = hyper::Response;
type Error = hyper::Error;
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 {
Some(match *request.method() {
// Start fetching content
Method::Get => {
trace!(target: "dapps", "Fetching content from: {:?}", url);
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_at,
)),
}
},
// or return error
_ => FetchState::Error(ContentHandler::error(
StatusCode::MethodNotAllowed,
"Method Not Allowed",
"Only <code>GET</code> requests are allowed.",
None,
self.embeddable_at,
)),
})
} else { None };
fn poll(&mut self) -> futures::Poll<Self::Item, Self::Error> {
loop {
trace!(target: "dapps", "Polling status: {:?}", self.status);
self.status = match mem::replace(&mut self.status, FetchState::Empty) {
FetchState::Error(error) => {
return Ok(futures::Async::Ready(error.into()));
},
FetchState::Streaming(response) => {
return Ok(futures::Async::Ready(response));
},
any => any,
};
let status = match self.status {
// Request may time out
FetchState::InProgress(_) if self.fetch_control.is_deadline_reached() => {
trace!(target: "dapps", "Fetching dapp failed because of timeout.");
FetchState::Error(self.errors.timeout_error())
},
FetchState::InProgress(ref mut receiver) => {
// Check if there is a response
trace!(target: "dapps", "Polling streaming response.");
try_ready!(receiver.poll().map_err(|err| {
warn!(target: "dapps", "Error while fetching response: {:?}", err);
hyper::Error::Timeout
}))
},
FetchState::Done(_, ref mut response) => {
return response.poll()
},
FetchState::Empty => panic!("Future polled twice."),
_ => unreachable!(),
};
trace!(target: "dapps", "New status: {:?}", status);
if let Some(status) = status {
self.fetch_control.set_status(&status);
self.status = status;
}
Next::read()
}
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.deadline < Instant::now() => {
trace!(target: "dapps", "Fetching dapp failed because of timeout.");
let timeout = ContentHandler::error(
StatusCode::GatewayTimeout,
"Download Timeout",
&format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT),
None,
self.embeddable_at,
);
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 {
// 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_at,
))
},
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_at,
);
(Some(FetchState::Error(error)), Next::write())
},
// wait some more
_ => (None, Next::wait())
}
},
FetchState::Error(ref mut handler) => (None, handler.on_request_readable(decoder)),
_ => (None, Next::write()),
};
if let Some(status) = status {
self.fetch_control.set_status(&status);
self.status = status;
}
next
}
fn on_response(&mut self, res: &mut server::Response) -> Next {
match self.status {
FetchState::Done(_, _, ref mut handler) => handler.on_response(res),
FetchState::Error(ref mut handler) => handler.on_response(res),
_ => Next::end(),
}
}
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::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,106 +16,63 @@
//! Hyper handlers implementations.
mod content;
mod auth;
mod echo;
mod fetch;
mod reader;
mod content;
mod redirect;
mod streaming;
mod fetch;
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::reader::Reader;
pub use self::content::ContentHandler;
pub use self::redirect::Redirection;
pub use self::streaming::StreamingHandler;
pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl};
use std::iter;
use itertools::Itertools;
use hyper::header;
use {apps, address, Embeddable};
use url::Url;
use hyper::{server, header, net, uri};
use signer_address;
/// Adds security-related headers to the Response.
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Embeddable, allow_js_eval: bool) {
headers.set_raw("X-XSS-Protection", "1; mode=block");
headers.set_raw("X-Content-Type-Options", "nosniff");
pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option<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 {
headers.set_raw("X-Frame-Options", "SAMEORIGIN");
if let Some(port) = embeddable_at {
headers.set_raw(
"X-Frame-Options",
vec![format!("ALLOW-FROM http://{}", signer_address(port)).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", String::new()
// Restrict everything to the same origin by default.
+ "default-src 'self';"
// Allow connecting to WS servers and HTTP(S) servers.
// We could be more restrictive and allow only RPC server URL.
+ "connect-src http: https: ws: wss:;"
// Allow framing any content from HTTP(S).
// Again we could only allow embedding from RPC server URL.
// (deprecated)
+ "frame-src 'self' http: https:;"
// Allow framing and web workers from HTTP(S).
+ "child-src 'self' http: https:;"
// 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)
+ "img-src 'self' 'unsafe-inline' data: blob: http: https:;"
// Allow style from data: blob: and HTTPS.
+ "style-src 'self' 'unsafe-inline' data: blob: https:;"
// Allow fonts from data: and HTTPS.
+ "font-src 'self' data: https:;"
// Disallow objects
+ "object-src 'none';"
// Allow scripts
+ {
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();
let eval = if allow_js_eval { " 'unsafe-eval'" } else { "" };
&format!(
"script-src 'self' {}{};",
script_src,
eval
)
}
// Same restrictions as script-src with additional
// blob: that is required for camera access (worker)
+ "worker-src 'self' https: blob:;"
// Run in sandbox mode (although it's not fully safe since we allow same-origin and script)
+ "sandbox allow-same-origin allow-forms allow-modals allow-popups allow-presentation allow-scripts;"
// Disallow submitting forms from any dapps
+ "form-action 'none';"
// Never allow mixed content
+ "block-all-mixed-content;"
// 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';"),
}
);
}
/// Extracts URL part from the Request.
pub fn extract_url(req: &server::Request<net::HttpStream>) -> Option<Url> {
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) => {
// Attempt to prepend the Host header (mandatory in HTTP/1.1)
let url_string = match req.headers().get::<header::Host>() {
Some(ref host) => {
format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path)
},
None => return None,
};
match Url::parse(&url_string) {
Ok(url) => Some(url),
_ => None,
}
},
_ => None,
}
}

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/>.
//! A chunk-producing io::Read wrapper.
use std::io::{self, Read};
use futures::{self, sink, Sink, Future};
use futures::sync::mpsc;
use hyper;
type Sender = mpsc::Sender<Result<hyper::Chunk, hyper::Error>>;
const MAX_CHUNK_SIZE: usize = 32 * 1024;
/// A Reader is essentially a stream of `hyper::Chunks`.
/// The chunks are read from given `io::Read` instance.
///
/// Unfortunately `hyper` doesn't allow you to pass `Stream`
/// directly to the response, so you need to create
/// a `Body::pair()` and send over chunks using `sink::Send`.
/// Also `Chunks` need to take `Vec` by value, so we need
/// to allocate it for each chunk being sent.
pub struct Reader<R: io::Read> {
buffer: [u8; MAX_CHUNK_SIZE],
content: io::BufReader<R>,
sending: sink::Send<Sender>,
}
impl<R: io::Read> Reader<R> {
pub fn pair(content: R, initial: Vec<u8>) -> (Self, hyper::Body) {
let (tx, rx) = hyper::Body::pair();
let reader = Reader {
buffer: [0; MAX_CHUNK_SIZE],
content: io::BufReader::new(content),
sending: tx.send(Ok(initial.into())),
};
(reader, rx)
}
}
impl<R: io::Read> Future for Reader<R> {
type Item = ();
type Error = ();
fn poll(&mut self) -> futures::Poll<Self::Item, Self::Error> {
loop {
let next = try_ready!(self.sending.poll().map_err(|err| {
warn!(target: "dapps", "Unable to send next chunk: {:?}", err);
}));
self.sending = match self.content.read(&mut self.buffer) {
Ok(0) => return Ok(futures::Async::Ready(())),
Ok(read) => next.send(Ok(self.buffer[..read].to_vec().into())),
Err(err) => next.send(Err(hyper::Error::Io(err))),
}
}
}
}

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,7 +16,9 @@
//! HTTP Redirection hyper handler
use hyper::{self, header, StatusCode};
use hyper::{header, server, Decoder, Encoder, Next};
use hyper::net::HttpStream;
use hyper::status::StatusCode;
#[derive(Clone)]
pub struct Redirection {
@@ -24,18 +26,36 @@ pub struct Redirection {
}
impl Redirection {
pub fn new<T: Into<String>>(url: T) -> Self {
pub fn new(url: &str) -> Self {
Redirection {
to_url: url.into()
to_url: url.to_owned()
}
}
pub fn boxed(url: &str) -> Box<Self> {
Box::new(Self::new(url))
}
}
impl Into<hyper::Response> for Redirection {
fn into(self) -> hyper::Response {
impl server::Handler<HttpStream> for Redirection {
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 {
// Don't use `MovedPermanently` here to prevent browser from caching the redirections.
hyper::Response::new()
.with_status(StatusCode::Found)
.with_header(header::Location::new(self.to_url))
res.set_status(StatusCode::Found);
res.headers_mut().set(header::Location(self.to_url.to_owned()));
Next::write()
}
fn on_response_writable(&mut self, _encoder: &mut Encoder<HttpStream>) -> Next {
Next::end()
}
}

View File

@@ -1,58 +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;
use hyper::{self, header, mime, StatusCode};
use handlers::{add_security_headers, Reader};
use Embeddable;
pub struct StreamingHandler<R> {
initial: Vec<u8>,
content: R,
status: StatusCode,
mimetype: mime::Mime,
safe_to_embed_on: Embeddable,
}
impl<R: io::Read> StreamingHandler<R> {
pub fn new(content: R, status: StatusCode, mimetype: mime::Mime, safe_to_embed_on: Embeddable) -> Self {
StreamingHandler {
initial: Vec::new(),
content,
status,
mimetype,
safe_to_embed_on,
}
}
pub fn set_initial_content(&mut self, content: &str) {
self.initial = content.as_bytes().to_vec();
}
pub fn into_response(self) -> (Reader<R>, hyper::Response) {
let (reader, body) = Reader::pair(self.content, self.initial);
let mut res = hyper::Response::new()
.with_status(self.status)
.with_header(header::ContentType(self.mimetype))
.with_body(body);
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on, false);
(reader, 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
@@ -15,307 +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", plugin(clippy))]
extern crate base32;
extern crate futures_cpupool;
extern crate itertools;
extern crate linked_hash_map;
extern crate mime_guess;
extern crate parking_lot;
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 unicase;
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 ethcore_bigint as bigint;
extern crate ethcore_bytes as bytes;
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_ui;
extern crate keccak_hash as hash;
extern crate parity_version;
#[macro_use]
extern crate futures;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
extern crate mime;
#[cfg(test)]
extern crate env_logger;
#[cfg(test)]
extern crate ethcore_devtools as devtools;
#[cfg(test)]
extern crate jsonrpc_core;
#[cfg(test)]
extern crate parity_reactor;
extern crate env_logger;
mod endpoint;
mod apps;
mod page;
mod router;
mod handlers;
mod rpc;
mod api;
mod proxypac;
mod web;
mod url;
#[cfg(test)]
mod tests;
pub use self::apps::urlhint::ContractClient;
use std::sync::{Arc, Mutex};
use std::net::SocketAddr;
use std::collections::HashMap;
use std::mem;
use std::path::PathBuf;
use std::sync::Arc;
use futures_cpupool::CpuPool;
use jsonrpc_http_server::{self as http, hyper, Origin};
use parking_lot::RwLock;
use fetch::Fetch;
use node_health::NodeHealth;
use jsonrpc_core::{IoHandler, IoDelegate};
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
use ethcore_rpc::Extendable;
pub use hash_fetch::urlhint::ContractClient;
pub use node_health::SyncStatus;
use self::apps::{HOME_PAGE, DAPPS_DOMAIN};
/// 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.
#[derive(Default, Clone)]
pub struct Endpoints {
local_endpoints: Arc<RwLock<Vec<String>>>,
endpoints: Arc<RwLock<endpoint::Endpoints>>,
dapps_path: PathBuf,
embeddable: Option<ParentFrameSettings>,
pool: Option<CpuPool>,
/// Webapps HTTP+RPC server build.
pub struct ServerBuilder {
dapps_path: String,
handler: Arc<IoHandler>,
registrar: Arc<ContractClient>,
sync_status: Arc<SyncStatus>,
signer_port: Option<u16>,
}
impl Endpoints {
/// Returns a current list of app endpoints.
pub fn list(&self) -> Vec<apps::App> {
self.endpoints.read().iter().filter_map(|(ref k, ref e)| {
e.info().map(|ref info| info.with_id(k))
}).collect()
impl Extendable for ServerBuilder {
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>) {
self.handler.add_delegate(delegate);
}
}
/// Check for any changes in the local dapps folder and update.
pub fn refresh_local_dapps(&self) {
let pool = match self.pool.as_ref() {
None => return,
Some(pool) => pool,
};
let new_local = apps::fs::local_endpoints(&self.dapps_path, self.embeddable.clone(), pool.clone());
let old_local = mem::replace(&mut *self.local_endpoints.write(), new_local.keys().cloned().collect());
let (_, to_remove): (_, Vec<_>) = old_local
.into_iter()
.partition(|k| new_local.contains_key(&k.clone()));
let mut endpoints = self.endpoints.write();
// remove the dead dapps
for k in to_remove {
endpoints.remove(&k);
}
// new dapps to be added
for (k, v) in new_local {
if !endpoints.contains_key(&k) {
endpoints.insert(k, v);
}
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_port: None,
}
}
}
/// Dapps server as `jsonrpc-http-server` request middleware.
pub struct Middleware {
endpoints: Endpoints,
router: router::Router,
}
impl Middleware {
/// Get local endpoints handle.
pub fn endpoints(&self) -> &Endpoints {
&self.endpoints
/// Change default sync status.
pub fn with_sync_status(&mut self, status: Arc<SyncStatus>) {
self.sync_status = status;
}
/// Creates new middleware for UI server.
pub fn ui<F: Fetch>(
pool: CpuPool,
health: NodeHealth,
dapps_domain: &str,
/// Change default signer port.
pub fn with_signer_port(&mut self, signer_port: Option<u16>) {
self.signer_port = signer_port;
}
/// 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_port.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_port.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_port: Option<u16>) -> Vec<String> {
match signer_port {
Some(port) => vec![
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
format!("http://{}", signer_address(port)),
],
None => vec![],
}
}
fn start_http<A: Authorization + 'static>(
addr: &SocketAddr,
hosts: Option<Vec<String>>,
authorization: A,
handler: Arc<IoHandler>,
dapps_path: String,
signer_port: Option<u16>,
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(),
fetch.clone(),
pool.clone(),
).embeddable_on(None).allow_dapps(false));
let special = {
let mut special = special_endpoints(
pool.clone(),
health,
content_fetcher.clone(),
);
special.insert(router::SpecialEndpoint::Home, Some(apps::ui(pool.clone())));
special
};
let router = router::Router::new(
content_fetcher,
None,
special,
None,
dapps_domain.to_owned(),
);
) -> 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_port));
let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_port.clone()));
let cors_domains = Self::cors_domains(signer_port);
Middleware {
endpoints: Default::default(),
router: router,
}
}
/// Creates new Dapps server middleware.
pub fn dapps<F: Fetch>(
pool: CpuPool,
health: NodeHealth,
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,
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(),
fetch.clone(),
pool.clone(),
).embeddable_on(embeddable.clone()).allow_dapps(true));
let (local_endpoints, endpoints) = apps::all_endpoints(
dapps_path.clone(),
extra_dapps,
dapps_domain,
embeddable.clone(),
web_proxy_tokens,
fetch.clone(),
pool.clone(),
);
let endpoints = Endpoints {
endpoints: Arc::new(RwLock::new(endpoints)),
dapps_path,
local_endpoints: Arc::new(RwLock::new(local_endpoints)),
embeddable: embeddable.clone(),
pool: Some(pool.clone()),
};
let special = {
let mut special = special_endpoints(
pool.clone(),
health,
content_fetcher.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_port.clone(),
content_fetcher.clone(),
endpoints.clone(),
special.clone(),
authorization.clone(),
hosts.clone(),
))
.map(|(l, srv)| {
Middleware {
endpoints,
router,
::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::Request) -> http::RequestMiddlewareAction {
self.router.on_request(req)
}
}
fn special_endpoints(
pool: CpuPool,
health: NodeHealth,
content_fetcher: Arc<apps::fetcher::Fetcher>,
) -> 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(pool)));
special.insert(router::SpecialEndpoint::Api, Some(api::RestApi::new(
content_fetcher,
health,
)));
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 signer_address(port: u16) -> String {
format!("127.0.0.1:{}", port)
}
#[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(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
@@ -14,149 +14,142 @@
// 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;
use futures::future;
use futures_cpupool::CpuPool;
use hyper::mime::{self, Mime};
use itertools::Itertools;
use parity_dapps::{WebApp, Info};
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Request, Response};
use page::{handler, PageCache};
use Embeddable;
use std::sync::Arc;
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use parity_dapps::{WebApp, File, Info};
pub struct Dapp<T: WebApp + 'static> {
/// futures cpu pool
pool: CpuPool,
pub struct PageEndpoint<T : WebApp + 'static> {
/// Content of the files
app: T,
pub app: Arc<T>,
/// 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_at_port: Option<u16>,
info: EndpointInfo,
fallback_to_index_html: bool,
}
impl<T: WebApp + 'static> Dapp<T> {
/// Creates new `Dapp` for builtin (compile time) Dapp.
pub fn new(pool: CpuPool, allow_js_eval: bool, app: T) -> Self {
let mut info = EndpointInfo::from(app.info());
info.allow_js_eval = Some(allow_js_eval);
Dapp {
pool,
app,
safe_to_embed_on: None,
info,
fallback_to_index_html: false,
}
}
/// Creates a new `Dapp` for builtin (compile time) Dapp.
/// Instead of returning 404 this endpoint will always server index.html.
pub fn with_fallback_to_index(pool: CpuPool, app: T) -> Self {
impl<T: WebApp + 'static> PageEndpoint<T> {
/// Creates new `PageEndpoint` for builtin (compile time) Dapp.
pub fn new(app: T) -> Self {
let info = app.info();
Dapp {
pool,
app,
safe_to_embed_on: None,
PageEndpoint {
app: Arc::new(app),
prefix: None,
safe_to_embed_at_port: None,
info: EndpointInfo::from(info),
fallback_to_index_html: true,
}
}
/// Creates new `Dapp` which can be safely used in iframe
/// Create new `PageEndpoint` and specify prefix that should be removed before looking for a file.
/// It's used only for special endpoints (i.e. `/parity-utils/`)
/// So `/parity-utils/inject.js` will be resolved to `/inject.js` is prefix is set.
pub fn with_prefix(app: T, prefix: String) -> Self {
let info = app.info();
PageEndpoint {
app: Arc::new(app),
prefix: Some(prefix),
safe_to_embed_at_port: None,
info: EndpointInfo::from(info),
}
}
/// 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(pool: CpuPool, allow_js_eval: bool, app: T, address: Embeddable) -> Self {
let mut info = EndpointInfo::from(app.info());
info.allow_js_eval = Some(allow_js_eval);
Dapp {
pool,
app,
safe_to_embed_on: address,
info,
fallback_to_index_html: false,
pub fn new_safe_to_embed(app: T, port: Option<u16>) -> Self {
let info = app.info();
PageEndpoint {
app: Arc::new(app),
prefix: None,
safe_to_embed_at_port: port,
info: EndpointInfo::from(info),
}
}
}
impl<T: WebApp> Endpoint for Dapp<T> {
impl<T: WebApp> Endpoint for PageEndpoint<T> {
fn info(&self) -> Option<&EndpointInfo> {
Some(&self.info)
}
fn respond(&self, path: EndpointPath, _req: Request) -> Response {
trace!(target: "dapps", "Builtin file path: {:?}", path);
let file_path = if path.has_no_params() {
"index.html".to_owned()
} else {
path.app_params.into_iter().filter(|x| !x.is_empty()).join("/")
};
trace!(target: "dapps", "Builtin file: {:?}", file_path);
let file = {
let file = |path| self.app.file(path).map(|file| {
let content_type = match file.content_type.parse() {
Ok(mime) => mime,
Err(_) => {
warn!(target: "dapps", "invalid MIME type: {}", file.content_type);
mime::TEXT_HTML
},
};
BuiltinFile {
content_type,
content: io::Cursor::new(file.content),
}
});
let res = file(&file_path);
if self.fallback_to_index_html {
res.or_else(|| file("index.html"))
} else {
res
}
};
let (reader, response) = handler::PageHandler {
file,
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
Box::new(handler::PageHandler {
app: BuiltinDapp::new(self.app.clone()),
prefix: self.prefix.clone(),
path: path,
file: handler::ServedFile::new(self.safe_to_embed_at_port.clone()),
cache: PageCache::Disabled,
safe_to_embed_on: self.safe_to_embed_on.clone(),
allow_js_eval: self.info.allow_js_eval.clone().unwrap_or(false),
}.into_response();
self.pool.spawn(reader).forget();
Box::new(future::ok(response))
safe_to_embed_at_port: self.safe_to_embed_at_port.clone(),
})
}
}
impl From<Info> for EndpointInfo {
fn from(info: Info) -> Self {
EndpointInfo {
id: None,
name: info.name.into(),
description: info.description.into(),
author: info.author.into(),
icon_url: info.icon_url.into(),
local_url: None,
version: info.version.into(),
allow_js_eval: None,
}
}
}
struct BuiltinFile {
content_type: Mime,
content: io::Cursor<&'static [u8]>,
struct BuiltinDapp<T: WebApp + 'static> {
app: Arc<T>,
}
impl handler::DappFile for BuiltinFile {
type Reader = io::Cursor<&'static [u8]>;
fn content_type(&self) -> &Mime {
&self.content_type
}
fn into_reader(self) -> Self::Reader {
self.content
impl<T: WebApp + 'static> BuiltinDapp<T> {
fn new(app: Arc<T>) -> Self {
BuiltinDapp {
app: app,
}
}
}
impl<T: WebApp + 'static> handler::Dapp for BuiltinDapp<T> {
type DappFile = BuiltinDappFile<T>;
fn file(&self, path: &str) -> Option<Self::DappFile> {
self.app.file(path).map(|_| {
BuiltinDappFile {
app: self.app.clone(),
path: path.into(),
write_pos: 0,
}
})
}
}
struct BuiltinDappFile<T: WebApp + 'static> {
app: Arc<T>,
path: String,
write_pos: usize,
}
impl<T: WebApp + 'static> BuiltinDappFile<T> {
fn file(&self) -> &File {
self.app.file(&self.path).expect("Check is done when structure is created.")
}
}
impl<T: WebApp + 'static> handler::DappFile for BuiltinDappFile<T> {
fn content_type(&self) -> &str {
self.file().content_type
}
fn is_drained(&self) -> bool {
self.write_pos == self.file().content.len()
}
fn next_chunk(&mut self) -> &[u8] {
&self.file().content[self.write_pos..]
}
fn bytes_written(&mut self, bytes: usize) {
self.write_pos += bytes;
}
}

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,26 +14,61 @@
// 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;
use std::time::{Duration, SystemTime};
use hyper::{self, header, StatusCode};
use hyper::mime::{self, Mime};
use std::io::Write;
use time::{self, Duration};
use apps;
use handlers::{Reader, ContentHandler, add_security_headers};
use {Embeddable};
use hyper::header;
use hyper::server;
use hyper::uri::RequestUri;
use hyper::net::HttpStream;
use hyper::status::StatusCode;
use hyper::{Decoder, Encoder, Next};
use endpoint::EndpointPath;
use handlers::{ContentHandler, add_security_headers};
/// Represents a file that can be sent to client.
/// Implementation should keep track of bytes already sent internally.
pub trait DappFile {
/// A reader type returned by this file.
type Reader: io::Read;
pub trait DappFile: Send {
/// Returns a content-type of this file.
fn content_type(&self) -> &Mime;
fn content_type(&self) -> &str;
/// Convert this file into io::Read instance.
fn into_reader(self) -> Self::Reader where Self: Sized;
/// Checks if all bytes from that file were written.
fn is_drained(&self) -> bool;
/// Fetch next chunk to write to the client.
fn next_chunk(&mut self) -> &[u8];
/// How many files have been written to the client.
fn bytes_written(&mut self, bytes: usize);
}
/// Dapp as a (dynamic) set of files.
pub trait Dapp: Send + 'static {
/// File type
type DappFile: DappFile;
/// Returns file under given path.
fn file(&self, path: &str) -> Option<Self::DappFile>;
}
/// Currently served by `PageHandler` file
pub enum ServedFile<T: Dapp> {
/// File from dapp
File(T::DappFile),
/// Error (404)
Error(ContentHandler),
}
impl<T: Dapp> ServedFile<T> {
pub fn new(embeddable_at: Option<u16>) -> Self {
ServedFile::Error(ContentHandler::error(
StatusCode::NotFound,
"404 Not Found",
"Requested dapp resource was not found.",
None,
embeddable_at,
))
}
}
/// Defines what cache headers should be appended to returned resources.
@@ -52,65 +87,177 @@ impl Default for PageCache {
/// A handler for a single webapp.
/// Resolves correct paths and serves as a plumbing code between
/// hyper server and dapp.
pub struct PageHandler<T: DappFile> {
/// File currently being served
pub file: Option<T>,
pub struct PageHandler<T: Dapp> {
/// A Dapp.
pub app: T,
/// 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_at_port: Option<u16>,
/// Cache settings for this page.
pub cache: PageCache,
/// Allow JS unsafe-eval.
pub allow_js_eval: bool,
}
impl<T: DappFile> PageHandler<T> {
pub fn into_response(self) -> (Option<Reader<T::Reader>>, hyper::Response) {
let file = match self.file {
None => return (None, ContentHandler::error(
StatusCode::NotFound,
"File not found",
"Requested file has not been found.",
None,
self.safe_to_embed_on,
).into()),
Some(file) => file,
};
impl<T: Dapp> PageHandler<T> {
fn extract_path(&self, path: &str) -> String {
let app_id = &self.path.app_id;
let prefix = "/".to_owned() + self.prefix.as_ref().unwrap_or(app_id);
let prefix_with_slash = prefix.clone() + "/";
let query_pos = path.find('?').unwrap_or_else(|| path.len());
let mut res = hyper::Response::new()
.with_status(StatusCode::Ok);
// headers
{
let mut headers = res.headers_mut();
if let PageCache::Enabled = self.cache {
let validity_secs = 365u32 * 24 * 3600;
let validity = Duration::from_secs(validity_secs as u64);
headers.set(header::CacheControl(vec![
header::CacheDirective::Public,
header::CacheDirective::MaxAge(validity_secs),
]));
headers.set(header::Expires(header::HttpDate::from(SystemTime::now() + validity)));
// Index file support
match path == "/" || path == &prefix || path == &prefix_with_slash {
true => "index.html".to_owned(),
false => if path.starts_with(&prefix_with_slash) {
path[prefix_with_slash.len()..query_pos].to_owned()
} else if path.starts_with("/") {
path[1..query_pos].to_owned()
} else {
path[0..query_pos].to_owned()
}
headers.set(header::ContentType(file.content_type().to_owned()));
add_security_headers(&mut headers, self.safe_to_embed_on, self.allow_js_eval);
}
let initial_content = if file.content_type().to_owned() == mime::TEXT_HTML {
let content = &format!(
r#"<script src="/{}/inject.js"></script>"#,
apps::UTILS_PATH,
);
content.as_bytes().to_vec()
} else {
Vec::new()
};
let (reader, body) = Reader::pair(file.into_reader(), initial_content);
res.set_body(body);
(Some(reader), res)
}
}
impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
fn on_request(&mut self, req: server::Request<HttpStream>) -> Next {
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_at_port.clone()), |f| ServedFile::File(f));
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 {
match self.file {
ServedFile::File(ref f) => {
res.set_status(StatusCode::Ok);
if let PageCache::Enabled = self.cache {
let mut headers = res.headers_mut();
let validity = Duration::days(365);
headers.set(header::CacheControl(vec![
header::CacheDirective::Public,
header::CacheDirective::MaxAge(validity.num_seconds() as u32),
]));
headers.set(header::Expires(header::HttpDate(time::now() + validity)));
}
match f.content_type().parse() {
Ok(mime) => res.headers_mut().set(header::ContentType(mime)),
Err(()) => debug!(target: "dapps", "invalid MIME type: {}", f.content_type()),
}
// Security headers:
add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port);
Next::write()
},
ServedFile::Error(ref mut handler) => {
handler.on_response(res)
}
}
}
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
match self.file {
ServedFile::Error(ref mut handler) => handler.on_response_writable(encoder),
ServedFile::File(ref f) if f.is_drained() => Next::end(),
ServedFile::File(ref mut f) => match encoder.write(f.next_chunk()) {
Ok(bytes) => {
f.bytes_written(bytes);
Next::write()
},
Err(e) => match e.kind() {
::std::io::ErrorKind::WouldBlock => Next::write(),
_ => Next::end(),
},
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
pub struct TestWebAppFile;
impl DappFile for TestWebAppFile {
fn content_type(&self) -> &str {
unimplemented!()
}
fn is_drained(&self) -> bool {
unimplemented!()
}
fn next_chunk(&mut self) -> &[u8] {
unimplemented!()
}
fn bytes_written(&mut self, _bytes: usize) {
unimplemented!()
}
}
#[derive(Default)]
pub struct TestWebapp;
impl Dapp for TestWebapp {
type DappFile = TestWebAppFile;
fn file(&self, _path: &str) -> Option<Self::DappFile> {
None
}
}
}
#[test]
fn should_extract_path_with_appid() {
// given
let path1 = "/";
let path2= "/test.css";
let path3 = "/app/myfile.txt";
let path4 = "/app/myfile.txt?query=123";
let page_handler = PageHandler {
app: test::TestWebapp,
prefix: None,
path: EndpointPath {
app_id: "app".to_owned(),
host: "".to_owned(),
port: 8080,
using_dapps_domains: true,
},
file: ServedFile::new(None),
cache: Default::default(),
safe_to_embed_at_port: None,
};
// when
let res1 = page_handler.extract_path(path1);
let res2 = page_handler.extract_path(path2);
let res3 = page_handler.extract_path(path3);
let res4 = page_handler.extract_path(path4);
// then
assert_eq!(&res1, "index.html");
assert_eq!(&res2, "test.css");
assert_eq!(&res3, "myfile.txt");
assert_eq!(&res4, "myfile.txt");
}

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,134 +15,149 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use mime_guess;
use std::{fs, fmt};
use std::io::{Seek, Read, SeekFrom};
use std::fs;
use std::path::{Path, PathBuf};
use futures::{future};
use futures_cpupool::CpuPool;
use page::handler::{self, PageCache};
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Request, Response};
use hyper::mime::Mime;
use Embeddable;
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
#[derive(Clone)]
pub struct Dapp {
pool: CpuPool,
#[derive(Debug, Clone)]
pub struct LocalPageEndpoint {
path: PathBuf,
mime: Option<Mime>,
mime: Option<String>,
info: Option<EndpointInfo>,
cache: PageCache,
embeddable_on: Embeddable,
embeddable_at: Option<u16>,
}
impl fmt::Debug for Dapp {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Dapp")
.field("path", &self.path)
.field("mime", &self.mime)
.field("info", &self.info)
.field("cache", &self.cache)
.field("embeddable_on", &self.embeddable_on)
.finish()
}
}
impl Dapp {
pub fn new(pool: CpuPool, path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Embeddable) -> Self {
Dapp {
pool,
path,
impl LocalPageEndpoint {
pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_at: Option<u16>) -> Self {
LocalPageEndpoint {
path: path,
mime: None,
info: Some(info),
cache,
embeddable_on,
cache: cache,
embeddable_at: embeddable_at,
}
}
pub fn single_file(pool: CpuPool, path: PathBuf, mime: Mime, cache: PageCache) -> Self {
Dapp {
pool,
path,
pub fn single_file(path: PathBuf, mime: String, cache: PageCache) -> Self {
LocalPageEndpoint {
path: path,
mime: Some(mime),
info: None,
cache,
embeddable_on: None,
cache: cache,
embeddable_at: None,
}
}
pub fn path(&self) -> PathBuf {
self.path.clone()
}
fn get_file(&self, path: &EndpointPath) -> Option<LocalFile> {
if let Some(ref mime) = self.mime {
return LocalFile::from_path(&self.path, mime.to_owned());
}
let mut file_path = self.path.to_owned();
if path.has_no_params() {
file_path.push("index.html");
} else {
for part in &path.app_params {
file_path.push(part);
}
}
let mime = mime_guess::guess_mime_type(&file_path);
LocalFile::from_path(&file_path, mime)
}
pub fn to_response(&self, path: &EndpointPath) -> Response {
let (reader, response) = handler::PageHandler {
file: self.get_file(path),
cache: self.cache,
safe_to_embed_on: self.embeddable_on.clone(),
allow_js_eval: self.info.as_ref().and_then(|x| x.allow_js_eval).unwrap_or(false),
}.into_response();
self.pool.spawn(reader).forget();
Box::new(future::ok(response))
}
}
impl Endpoint for Dapp {
impl Endpoint for LocalPageEndpoint {
fn info(&self) -> Option<&EndpointInfo> {
self.info.as_ref()
}
fn respond(&self, path: EndpointPath, _req: Request) -> Response {
self.to_response(&path)
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
if let Some(ref mime) = self.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_at_port: self.embeddable_at,
cache: self.cache,
})
} else {
Box::new(handler::PageHandler {
app: LocalDapp { path: self.path.clone() },
prefix: None,
path: path,
file: handler::ServedFile::new(None),
safe_to_embed_at_port: self.embeddable_at,
cache: self.cache,
})
}
}
}
struct LocalSingleFile {
path: PathBuf,
mime: String,
}
impl handler::Dapp for LocalSingleFile {
type DappFile = LocalFile;
fn file(&self, _path: &str) -> Option<Self::DappFile> {
LocalFile::from_path(&self.path, Some(&self.mime))
}
}
struct LocalDapp {
path: PathBuf,
}
impl handler::Dapp for LocalDapp {
type DappFile = LocalFile;
fn file(&self, file_path: &str) -> Option<Self::DappFile> {
let mut path = self.path.clone();
for part in file_path.split('/') {
path.push(part);
}
LocalFile::from_path(&path, None)
}
}
struct LocalFile {
content_type: Mime,
content_type: String,
buffer: [u8; 4096],
file: fs::File,
len: u64,
pos: u64,
}
impl LocalFile {
fn from_path<P: AsRef<Path>>(path: P, content_type: Mime) -> Option<Self> {
trace!(target: "dapps", "Local file: {:?}", path.as_ref());
fn from_path<P: AsRef<Path>>(path: P, mime: Option<&str>) -> Option<Self> {
// Check if file exists
fs::File::open(&path).ok().map(|file| {
let content_type = mime.map(|mime| mime.to_owned())
.unwrap_or_else(|| mime_guess::guess_mime_type(path).to_string());
let len = file.metadata().ok().map_or(0, |meta| meta.len());
LocalFile {
content_type,
file,
content_type: content_type,
buffer: [0; 4096],
file: file,
pos: 0,
len: len,
}
})
}
}
impl handler::DappFile for LocalFile {
type Reader = fs::File;
fn content_type(&self) -> &Mime {
fn content_type(&self) -> &str {
&self.content_type
}
fn into_reader(self) -> Self::Reader {
self.file
fn is_drained(&self) -> bool {
self.pos == self.len
}
fn next_chunk(&mut self) -> &[u8] {
let _ = self.file.seek(SeekFrom::Start(self.pos));
if let Ok(n) = self.file.read(&mut self.buffer) {
&self.buffer[0..n]
} else {
&self.buffer[0..0]
}
}
fn bytes_written(&mut self, bytes: usize) {
self.pos += bytes as u64;
}
}

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,9 +15,11 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
pub mod builtin;
pub mod local;
mod builtin;
mod local;
mod handler;
pub use self::local::LocalPageEndpoint;
pub use self::builtin::PageEndpoint;
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
@@ -16,40 +16,38 @@
//! Serving ProxyPac file
use apps::HOME_PAGE;
use endpoint::{Endpoint, Request, Response, EndpointPath};
use futures::future;
use endpoint::{Endpoint, Handler, EndpointPath};
use handlers::ContentHandler;
use hyper::mime;
use {address, Embeddable};
use apps::{HOME_PAGE, DAPPS_DOMAIN};
use signer_address;
pub struct ProxyPac {
embeddable: Embeddable,
dapps_domain: String,
signer_port: Option<u16>,
}
impl ProxyPac {
pub fn boxed(embeddable: Embeddable, dapps_domain: String) -> Box<Endpoint> {
Box::new(ProxyPac { embeddable, dapps_domain })
pub fn boxed(signer_port: Option<u16>) -> Box<Endpoint> {
Box::new(ProxyPac {
signer_port: signer_port
})
}
}
impl Endpoint for ProxyPac {
fn respond(&self, path: EndpointPath, _req: Request) -> Response {
let ui = self.embeddable
.as_ref()
.map(|ref parent| address(&parent.host, parent.port))
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
let signer = self.signer_port
.map(signer_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}";
}}
@@ -57,11 +55,9 @@ 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(future::ok(
ContentHandler::ok(content, mime::TEXT_JAVASCRIPT).into()
))
Box::new(ContentHandler::ok(content, mime!(Application/Javascript)))
}
}

View File

@@ -1,407 +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::sync::Arc;
use std::collections::HashMap;
use futures::future;
use hyper::{self, header, Uri};
use jsonrpc_http_server as http;
use apps;
use apps::fetcher::Fetcher;
use endpoint::{self, Endpoint, EndpointPath};
use Endpoints;
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,
}
enum Response {
Some(endpoint::Response),
None(hyper::Request),
}
/// An endpoint router.
/// Dispatches the request to particular Endpoint by requested uri/path.
pub struct Router {
endpoints: Option<Endpoints>,
fetch: Arc<Fetcher>,
special: HashMap<SpecialEndpoint, Option<Box<Endpoint>>>,
embeddable_on: Embeddable,
dapps_domain: String,
}
impl Router {
fn resolve_request(&self, req: hyper::Request, refresh_dapps: bool) -> (bool, Response) {
// Choose proper handler depending on path / domain
let endpoint = extract_endpoint(req.uri(), req.headers().get(), &self.dapps_domain);
let referer = extract_referer_endpoint(&req, &self.dapps_domain);
let is_utils = endpoint.1 == SpecialEndpoint::Utils;
let is_get_request = *req.method() == hyper::Method::Get;
let is_head_request = *req.method() == hyper::Method::Head;
let has_dapp = |dapp: &str| self.endpoints
.as_ref()
.map_or(false, |endpoints| endpoints.endpoints.read().contains_key(dapp));
trace!(target: "dapps", "Routing request to {:?}. Details: {:?}", req.uri(), req);
debug!(target: "dapps", "Handling endpoint request: {:?}, referer: {:?}", endpoint, referer);
(is_utils, match (endpoint.0, endpoint.1, referer) {
// Handle invalid web requests that we can recover from
(ref path, SpecialEndpoint::None, Some(ref referer))
if referer.app_id == apps::WEB_PATH
&& has_dapp(apps::WEB_PATH)
&& !is_web_endpoint(path)
=>
{
let token = referer.app_params.get(0).map(String::as_str).unwrap_or("");
let requested = req.uri().path();
let query = req.uri().query().map_or_else(String::new, |query| format!("?{}", query));
let redirect_url = format!("/{}/{}{}{}", apps::WEB_PATH, token, requested, query);
trace!(target: "dapps", "Redirecting to correct web request: {:?}", redirect_url);
Response::Some(Box::new(future::ok(
handlers::Redirection::new(redirect_url).into()
)))
},
// First check special endpoints
(ref path, ref endpoint, _) if self.special.contains_key(endpoint) => {
trace!(target: "dapps", "Resolving to special endpoint.");
let special = self.special.get(endpoint).expect("special known to contain key; qed");
match *special {
Some(ref special) => Response::Some(special.respond(path.clone().unwrap_or_default(), req)),
None => Response::None(req),
}
},
// Then delegate to dapp
(Some(ref path), _, _) if has_dapp(&path.app_id) => {
trace!(target: "dapps", "Resolving to local/builtin dapp.");
Response::Some(self.endpoints
.as_ref()
.expect("endpoints known to be set; qed")
.endpoints
.read()
.get(&path.app_id)
.expect("endpoints known to contain key; qed")
.respond(path.clone(), req))
},
// Try to resolve and fetch the dapp
(Some(ref path), _, _) if self.fetch.contains(&path.app_id) => {
trace!(target: "dapps", "Resolving to fetchable content.");
Response::Some(self.fetch.respond(path.clone(), req))
},
// 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.");
if refresh_dapps {
debug!(target: "dapps", "Refreshing dapps and re-trying.");
self.endpoints.as_ref().map(|endpoints| endpoints.refresh_local_dapps());
return self.resolve_request(req, false);
} else {
Response::Some(Box::new(future::ok(handlers::ContentHandler::error(
hyper::StatusCode::NotFound,
"404 Not Found",
"Requested content was not found.",
None,
self.embeddable_on.clone(),
).into())))
}
},
// Any other GET|HEAD requests to home page.
_ if (is_get_request || is_head_request) && self.special.contains_key(&SpecialEndpoint::Home) => {
trace!(target: "dapps", "Resolving to home page.");
let special = self.special.get(&SpecialEndpoint::Home).expect("special known to contain key; qed");
match *special {
Some(ref special) => {
let mut endpoint = EndpointPath::default();
endpoint.app_params = req.uri().path().split('/').map(str::to_owned).collect();
Response::Some(special.respond(endpoint, req))
},
None => Response::None(req),
}
},
// RPC by default
_ => {
trace!(target: "dapps", "Resolving to RPC call.");
Response::None(req)
}
})
}
}
impl http::RequestMiddleware for Router {
fn on_request(&self, req: hyper::Request) -> http::RequestMiddlewareAction {
let is_origin_set = req.headers().get::<header::Origin>().is_some();
let (is_utils, response) = self.resolve_request(req, self.endpoints.is_some());
match response {
Response::Some(response) => http::RequestMiddlewareAction::Respond {
should_validate_hosts: !is_utils,
response,
},
Response::None(request) => http::RequestMiddlewareAction::Proceed {
should_continue_on_invalid_cors: !is_origin_set,
request,
},
}
}
}
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: &hyper::Request, dapps_domain: &str) -> Option<EndpointPath> {
let referer = req.headers().get::<header::Referer>();
let url = referer.and_then(|referer| referer.parse().ok());
url.and_then(|url| {
extract_url_referer_endpoint(&url, dapps_domain).or_else(|| {
extract_endpoint(&url, None, dapps_domain).0
})
})
}
fn extract_url_referer_endpoint(url: &Uri, dapps_domain: &str) -> Option<EndpointPath> {
let query = url.query();
match query {
Some(query) if query.starts_with(apps::URL_REFERER) => {
let scheme = url.scheme().unwrap_or("http");
let host = url.host().unwrap_or("unknown");
let port = default_port(url, None);
let referer_url = format!("{}://{}:{}/{}", scheme, host, port, &query[apps::URL_REFERER.len()..]);
debug!(target: "dapps", "Recovering referer from query parameter: {}", referer_url);
if let Some(referer_url) = referer_url.parse().ok() {
extract_endpoint(&referer_url, None, dapps_domain).0
} else {
None
}
},
_ => None,
}
}
fn extract_endpoint(url: &Uri, extra_host: Option<&header::Host>, dapps_domain: &str) -> (Option<EndpointPath>, SpecialEndpoint) {
fn special_endpoint(path: &[&str]) -> SpecialEndpoint {
if path.len() <= 1 {
return SpecialEndpoint::None;
}
match 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,
}
}
let port = default_port(url, extra_host.as_ref().and_then(|h| h.port()));
let host = url.host().or_else(|| extra_host.as_ref().map(|h| h.hostname()));
let query = url.query().map(str::to_owned);
let mut path_segments = url.path().split('/').skip(1).collect::<Vec<_>>();
trace!(
target: "dapps",
"Extracting endpoint from: {:?} (dapps: {}). Got host {:?}:{} with path {:?}",
url, dapps_domain, host, port, path_segments
);
match host {
Some(host) if host.ends_with(dapps_domain) => {
let id = &host[0..(host.len() - dapps_domain.len())];
let special = special_endpoint(&path_segments);
// remove special endpoint id from params
if special != SpecialEndpoint::None {
path_segments.remove(0);
}
let (app_id, app_params) = if let Some(split) = id.rfind('.') {
let (params, id) = id.split_at(split);
path_segments.insert(0, params);
(id[1..].to_owned(), path_segments)
} else {
(id.to_owned(), path_segments)
};
(Some(EndpointPath {
app_id,
app_params: app_params.into_iter().map(Into::into).collect(),
query,
host: host.to_owned(),
port,
using_dapps_domains: true,
}), special)
},
Some(host) if path_segments.len() > 1 => {
let special = special_endpoint(&path_segments);
let id = path_segments.remove(0);
(Some(EndpointPath {
app_id: id.to_owned(),
app_params: path_segments.into_iter().map(Into::into).collect(),
query,
host: host.to_owned(),
port,
using_dapps_domains: false,
}), special)
},
_ => (None, special_endpoint(&path_segments)),
}
}
fn default_port(url: &Uri, extra_port: Option<u16>) -> u16 {
let scheme = url.scheme().unwrap_or("http");
url.port().or(extra_port).unwrap_or_else(|| match scheme {
"http" => 80,
"https" => 443,
_ => 80,
})
}
#[cfg(test)]
mod tests {
use super::{SpecialEndpoint, EndpointPath, extract_endpoint};
#[test]
fn should_extract_endpoint() {
let dapps_domain = ".web3.site";
// With path prefix
assert_eq!(
extract_endpoint(&"http://localhost:8080/status/index.html?q=1".parse().unwrap(), None, dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["index.html".to_owned()],
query: Some("q=1".into()),
host: "localhost".to_owned(),
port: 8080,
using_dapps_domains: false,
}), SpecialEndpoint::None)
);
// With path prefix
assert_eq!(
extract_endpoint(&"http://localhost:8080/rpc/".parse().unwrap(), None, dapps_domain),
(Some(EndpointPath {
app_id: "rpc".to_owned(),
app_params: vec!["".to_owned()],
query: None,
host: "localhost".to_owned(),
port: 8080,
using_dapps_domains: false,
}), SpecialEndpoint::Rpc)
);
assert_eq!(
extract_endpoint(&"http://my.status.web3.site/parity-utils/inject.js".parse().unwrap(), None, dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["my".into(), "inject.js".into()],
query: None,
host: "my.status.web3.site".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Utils)
);
assert_eq!(
extract_endpoint(&"http://my.status.web3.site/inject.js".parse().unwrap(), None, dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["my".into(), "inject.js".into()],
query: None,
host: "my.status.web3.site".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::None)
);
// By Subdomain
assert_eq!(
extract_endpoint(&"http://status.web3.site/test.html".parse().unwrap(), None, dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["test.html".to_owned()],
query: None,
host: "status.web3.site".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::None)
);
// RPC by subdomain
assert_eq!(
extract_endpoint(&"http://my.status.web3.site/rpc/".parse().unwrap(), None, dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["my".into(), "".into()],
query: None,
host: "my.status.web3.site".to_owned(),
port: 80,
using_dapps_domains: true,
}), SpecialEndpoint::Rpc)
);
// API by subdomain
assert_eq!(
extract_endpoint(&"http://my.status.web3.site/api/".parse().unwrap(), None, dapps_domain),
(Some(EndpointPath {
app_id: "status".to_owned(),
app_params: vec!["my".into(), "".into()],
query: None,
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 signer_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_port: Option<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_port,
))
},
// Redirect any other GET request to signer.
_ if *req.method() == hyper::Method::Get => {
if let Some(port) = self.signer_port {
trace!(target: "dapps", "Redirecting to signer interface.");
Redirection::boxed(&format!("http://{}", signer_address(port)))
} 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_port,
))
}
},
// 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_port: Option<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_port: signer_port,
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
@@ -49,7 +72,6 @@ fn should_handle_ping() {
"\
POST /api/ping HTTP/1.1\r\n\
Host: home.parity\r\n\
Content-Type: application/json\r\n\
Connection: close\r\n\
\r\n\
{}
@@ -57,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);
}
@@ -80,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,
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,15 +25,15 @@ 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!(registrar.calls.lock().len(), 4);
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,499 +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!(
response1.body.contains(r#"18
<h1>Hello Gavcoin!</h1>
0
"#),
"Expected Gavcoin body: {}",
response1.body
);
response2.assert_status("HTTP/1.1 200 OK");
assert_security_headers_for_embed(&response2.headers);
assert_eq!(
response2.body,
r#"EA
{
"id": "9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a",
"name": "Gavcoin",
"description": "Gavcoin",
"version": "1.0.0",
"author": "",
"iconUrl": "icon.png",
"localUrl": null,
"allowJsEval": false
}
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();
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_port(Some(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_port(Some(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,133 +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 parking_lot::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 = Box<Future<Item = fetch::Response, Error = fetch::Error> + Send>;
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();
});
Box::new(rx.map_err(|_| fetch::Error::Aborted))
}
fn process_and_forget<F, I, E>(&self, f: F) where
F: Future<Item=I, Error=E> + Send + 'static,
I: Send + 'static,
E: Send + 'static,
{
// Spawn the task in a separate thread.
thread::spawn(|| {
let _ = f.wait();
});
}
}

View File

@@ -1,297 +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, io, 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 parity_reactor::Remote;
use devtools::http_client;
use hash_fetch::urlhint::ContractClient;
use fetch::{Fetch, Client as FetchClient};
use node_health::{NodeHealth, TimeChecker, CpuPool};
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) -> (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 mut builder = ServerBuilder::new(&dapps_path, registrar.clone());
builder.signer_address = Some(("127.0.0.1".into(), SIGNER_PORT));
let server = process(builder).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).0
}
pub fn serve_hosts(hosts: Option<Vec<String>>) -> Server {
let hosts = hosts.map(|hosts| hosts.into_iter().map(Into::into).collect());
init_server(|mut builder| {
builder.allowed_hosts = hosts.into();
builder
}, Default::default()).0
}
pub fn serve_with_registrar() -> (Server, Arc<FakeRegistrar>) {
init_server(|builder| builder, Default::default())
}
pub fn serve_with_registrar_and_sync() -> (Server, Arc<FakeRegistrar>) {
init_server(|mut builder| {
builder.sync_status = Arc::new(FakeSync(true));
builder
}, Default::default())
}
pub fn serve_with_registrar_and_fetch() -> (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());
(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 |mut builder| {
builder.web_proxy_tokens = Arc::new(move |token| {
if &token == web_token { Some(domain.into()) } else { None }
});
builder.fetch(f.clone())
}, Default::default());
(server, fetch)
}
pub fn serve() -> Server {
init_server(|builder| builder, Default::default()).0
}
pub fn serve_ui() -> Server {
init_server(|mut builder| {
builder.serve_ui = true;
builder
}, Default::default()).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>,
fetch: Option<T>,
serve_ui: bool,
}
impl ServerBuilder {
/// Construct new dapps server
pub fn new<P: AsRef<Path>>(dapps_path: P, registrar: Arc<ContractClient>) -> 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,
fetch: None,
serve_ui: false,
}
}
}
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,
fetch: Some(fetch),
serve_ui: self.serve_ui,
}
}
/// 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) -> io::Result<Server> {
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,
Remote::new_sync(),
fetch,
self.serve_ui,
)
}
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,
serve_ui: bool,
) -> io::Result<Server> {
let health = NodeHealth::new(
sync_status.clone(),
TimeChecker::new::<String>(&[], CpuPool::new(1)),
remote.clone(),
);
let pool = ::futures_cpupool::CpuPool::new(1);
let middleware = if serve_ui {
Middleware::ui(
pool,
health,
DAPPS_DOMAIN.into(),
registrar,
sync_status,
fetch,
)
} else {
Middleware::dapps(
pool,
health,
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(|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")
.address()
}
}
impl Drop for Server {
fn drop(&mut self) {
self.server.take().unwrap().close()
}
}

View File

@@ -1,76 +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 bigint::hash::H256;
use bytes::{Bytes, ToPretty};
use hash_fetch::urlhint::{ContractClient, BoxFuture};
use parking_lot::Mutex;
use rustc_hex::FromHex;
use util::Address;
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) -> 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,91 +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 tests::helpers::{serve_ui, request, assert_security_headers};
#[test]
fn should_serve_home_js() {
// given
let server = serve_ui();
// when
let response = request(server,
"\
GET /inject.js HTTP/1.1\r\n\
Host: 127.0.0.1:8080\r\n\
Connection: close\r\n\
\r\n\
{}
"
);
// then
response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Content-Type", "application/javascript");
assert_eq!(response.body.contains("function(){"), true, "Expected function in: {}", response.body);
assert_security_headers(&response.headers);
}
#[test]
fn should_serve_home() {
// given
let server = serve_ui();
// 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
response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Content-Type", "text/html");
assert_security_headers(&response.headers);
}
#[test]
fn should_inject_js() {
// given
let server = serve_ui();
// 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
response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Content-Type", "text/html");
assert_eq!(
response.body.contains(r#"/inject.js"></script>"#),
true,
"Expected inject script tag in: {}",
response.body
);
assert_security_headers(&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
@@ -19,9 +19,8 @@
mod helpers;
mod api;
mod authorization;
mod fetch;
mod home;
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,8 +200,8 @@ fn should_serve_utils() {
);
// then
response.assert_status("HTTP/1.1 200 OK");
response.assert_header("Content-Type", "application/javascript");
assert_eq!(response.body.contains("function(){"), true, "Expected function in: {}", response.body);
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
@@ -33,8 +33,8 @@ fn should_reject_invalid_host() {
);
// then
response.assert_status("HTTP/1.1 403 Forbidden");
assert!(response.body.contains("Provided Host header is not whitelisted."), response.body);
assert_eq!(response.status, "HTTP/1.1 403 Forbidden".to_owned());
assert!(response.body.contains("Current Host Is Disallowed"), response.body);
}
#[test]
@@ -54,7 +54,7 @@ fn should_allow_valid_host() {
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
}
#[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\
{}
@@ -74,7 +74,7 @@ fn should_serve_dapps_domains() {
);
// then
response.assert_status("HTTP/1.1 200 OK");
assert_eq!(response.status, "HTTP/1.1 200 OK".to_owned());
}
#[test]
@@ -95,5 +95,33 @@ fn should_allow_parity_utils_even_on_invalid_domain() {
);
// then
response.assert_status("HTTP/1.1 200 OK");
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
);
}

145
dapps/src/url.rs Normal file
View File

@@ -0,0 +1,145 @@
// 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/HTTPS URL type. Based on URL type from Iron library.
use url_lib::{self};
pub use url_lib::Host;
/// HTTP/HTTPS URL type for Iron.
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Url {
/// Raw url of url
pub raw: url_lib::Url,
/// The host field of the URL, probably a domain.
pub host: Host,
/// The connection port.
pub port: u16,
/// The URL path, the resource to be accessed.
///
/// A *non-empty* vector encoding the parts of the URL path.
/// Empty entries of `""` correspond to trailing slashes.
pub path: Vec<String>,
/// The URL username field, from the userinfo section of the URL.
///
/// `None` if the `@` character was not part of the input OR
/// if a blank username was provided.
/// Otherwise, a non-empty string.
pub username: Option<String>,
/// The URL password field, from the userinfo section of the URL.
///
/// `None` if the `@` character was not part of the input OR
/// if a blank password was provided.
/// Otherwise, a non-empty string.
pub password: Option<String>,
}
impl Url {
/// Create a URL from a string.
///
/// The input must be a valid URL with a special scheme for this to succeed.
///
/// HTTP and HTTPS are special schemes.
///
/// See: http://url.spec.whatwg.org/#special-scheme
pub fn parse(input: &str) -> Result<Url, String> {
// Parse the string using rust-url, then convert.
match url_lib::Url::parse(input) {
Ok(raw_url) => Url::from_generic_url(raw_url),
Err(e) => Err(format!("{}", e))
}
}
/// Create a `Url` from a `rust-url` `Url`.
pub fn from_generic_url(raw_url: url_lib::Url) -> Result<Url, String> {
// Map empty usernames to None.
let username = match raw_url.username() {
"" => None,
username => Some(username.to_owned())
};
// Map empty passwords to None.
let password = match raw_url.password() {
Some(password) if !password.is_empty() => Some(password.to_owned()),
_ => None,
};
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();
Ok(Url {
port: port,
host: host,
path: path,
raw: raw_url,
username: username,
password: password,
})
}
}
#[cfg(test)]
mod test {
use super::Url;
#[test]
fn test_default_port() {
assert_eq!(Url::parse("http://example.com/wow").unwrap().port, 80u16);
assert_eq!(Url::parse("https://example.com/wow").unwrap().port, 443u16);
}
#[test]
fn test_explicit_port() {
assert_eq!(Url::parse("http://localhost:3097").unwrap().port, 3097u16);
}
#[test]
fn test_empty_username() {
assert!(Url::parse("http://@example.com").unwrap().username.is_none());
assert!(Url::parse("http://:password@example.com").unwrap().username.is_none());
}
#[test]
fn test_not_empty_username() {
let user = Url::parse("http://john:pass@example.com").unwrap().username;
assert_eq!(user.unwrap(), "john");
let user = Url::parse("http://john:@example.com").unwrap().username;
assert_eq!(user.unwrap(), "john");
}
#[test]
fn test_empty_password() {
assert!(Url::parse("http://michael@example.com").unwrap().password.is_none());
assert!(Url::parse("http://:@example.com").unwrap().password.is_none());
}
#[test]
fn test_not_empty_password() {
let pass = Url::parse("http://michael:pass@example.com").unwrap().password;
assert_eq!(pass.unwrap(), "pass");
let pass = Url::parse("http://:pass@example.com").unwrap().password;
assert_eq!(pass.unwrap(), "pass");
}
}

View File

@@ -1,166 +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 base32;
use fetch::{self, Fetch};
use hyper::{mime, StatusCode};
use apps;
use endpoint::{Endpoint, EndpointPath, Request, Response};
use futures::future;
use handlers::{
ContentFetcherHandler, ContentHandler, ContentValidator, ValidatorResponse,
StreamingHandler,
};
use {Embeddable, WebProxyTokens};
pub struct Web<F> {
embeddable_on: Embeddable,
web_proxy_tokens: Arc<WebProxyTokens>,
fetch: F,
}
impl<F: Fetch> Web<F> {
pub fn boxed(
embeddable_on: Embeddable,
web_proxy_tokens: Arc<WebProxyTokens>,
fetch: F,
) -> Box<Endpoint> {
Box::new(Web {
embeddable_on,
web_proxy_tokens,
fetch,
})
}
fn extract_target_url(&self, path: &EndpointPath) -> Result<String, ContentHandler> {
let token_and_url = 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(|| ContentHandler::error(
StatusCode::BadRequest,
"Invalid parameter",
"Couldn't parse given parameter:",
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(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(ContentHandler::error(
StatusCode::BadRequest, "Invalid Protocol", "Invalid protocol used.", None, self.embeddable_on.clone()
));
}
};
if !target_url.starts_with(&*domain) {
return Err(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);
}
// Skip the token
let query = path.query.as_ref().map_or_else(String::new, |query| format!("?{}", query));
let path = path.app_params[1..].join("/");
Ok(format!("{}{}{}", target_url, path, query))
}
}
impl<F: Fetch> Endpoint for Web<F> {
fn respond(&self, path: EndpointPath, req: Request) -> Response {
// First extract the URL (reject invalid URLs)
let target_url = match self.extract_target_url(&path) {
Ok(url) => url,
Err(response) => {
return Box::new(future::ok(response.into()));
}
};
let token = path.app_params.get(0)
.expect("`target_url` is valid; app_params is not empty;qed")
.to_owned();
Box::new(ContentFetcherHandler::new(
req.method(),
&target_url,
path,
WebInstaller {
embeddable_on: self.embeddable_on.clone(),
token,
},
self.embeddable_on.clone(),
self.fetch.clone(),
))
}
}
struct WebInstaller {
embeddable_on: Embeddable,
token: String,
}
impl ContentValidator for WebInstaller {
type Error = String;
fn validate_and_install(self, response: fetch::Response) -> Result<ValidatorResponse, String> {
let status = response.status();
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,
);
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.token,
));
}
Ok(ValidatorResponse::Streaming(handler))
}
}

View File

@@ -1,21 +1,18 @@
[package]
description = "Ethcore Parity UI"
homepage = "http://parity.io"
homepage = "http://ethcore.io"
license = "GPL-3.0"
name = "parity-ui"
version = "1.9.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 }
parity-ui-old-dev = { path = "../../js-old", optional = true }
# This is managed by the js/scripts/release.sh script on CI - keep it in a single line
parity-ui-old-precompiled = { git = "https://github.com/js-dist-paritytech/parity-stable-1-9-v1.git", optional = true }
parity-ui-precompiled = { git = "https://github.com/js-dist-paritytech/parity-stable-1-9-shell.git", optional = true }
parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", optional = true, branch = "beta" }
[features]
no-precompiled-js = ["parity-ui-dev", "parity-ui-old-dev"]
use-precompiled-js = ["parity-ui-precompiled", "parity-ui-old-precompiled"]
no-precompiled-js = ["parity-ui-dev"]
use-precompiled-js = ["parity-ui-precompiled"]

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
@@ -29,18 +29,5 @@ mod inner {
pub use self::parity_ui_precompiled::*;
}
#[cfg(feature = "parity-ui-old-dev")]
pub mod old {
extern crate parity_ui_old_dev;
pub use self::parity_ui_old_dev::*;
}
#[cfg(feature = "parity-ui-old-precompiled")]
pub mod old {
extern crate parity_ui_old_precompiled;
pub use self::parity_ui_old_precompiled::*;
}
pub use self::inner::*;

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"]

38
db/build.rs Normal file
View File

@@ -0,0 +1,38 @@
// 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_codegen as codegen;
use std::env;
use std::path::Path;
pub fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
// 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());
}
});
}
}

20
db/src/lib.rs Normal file
View File

@@ -0,0 +1,20 @@
// 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/>.
//! Database ipc service
#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
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());
}
}

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.9.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,9 +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/>.
use std::thread;
use std::time::Duration;
use std::io::{self, Read, Write};
use std::io::{Read, Write};
use std::str::{self, Lines};
use std::net::{TcpStream, SocketAddr};
@@ -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,45 +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 = Vec::new();
loop {
let mut chunk = [0; 32 *1024];
match req.read(&mut chunk) {
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => break,
Err(err) => panic!("Unable to read response: {:?}", err),
Ok(0) => break,
Ok(read) => response.extend_from_slice(&chunk[..read]),
}
}
let mut response = String::new();
let _ = req.read_to_string(&mut response);
let response = String::from_utf8_lossy(&response).into_owned();
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);
@@ -111,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
@@ -125,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

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