Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84ded6f43c | ||
|
|
eafc1b153d | ||
|
|
53b0862096 |
@@ -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
|
||||
@@ -9,8 +9,3 @@ trim_trailing_whitespace=true
|
||||
max_line_length=120
|
||||
insert_final_newline=true
|
||||
|
||||
[.travis.yml]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
tab_width=8
|
||||
end_of_line=lf
|
||||
|
||||
84
.github/CODE_OF_CONDUCT.md
vendored
84
.github/CODE_OF_CONDUCT.md
vendored
@@ -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 people’s 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 someone’s 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 venues–online and in-person–as 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/)
|
||||
33
.github/CONTRIBUTING.md
vendored
33
.github/CONTRIBUTING.md
vendored
@@ -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.
|
||||
13
.github/ISSUE_TEMPLATE.md
vendored
13
.github/ISSUE_TEMPLATE.md
vendored
@@ -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._
|
||||
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -38,7 +30,3 @@ node_modules
|
||||
|
||||
# Build artifacts
|
||||
out/
|
||||
|
||||
.vscode
|
||||
|
||||
/parity.*
|
||||
|
||||
269
.gitlab-ci.yml
269
.gitlab-ci.yml
@@ -1,269 +0,0 @@
|
||||
stages:
|
||||
- test
|
||||
- js-build
|
||||
- push-release
|
||||
- build
|
||||
variables:
|
||||
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
|
||||
untracked: true
|
||||
linux-stable:
|
||||
stage: build
|
||||
image: parity/rust:gitlab-ci
|
||||
only:
|
||||
- stable
|
||||
- beta
|
||||
- tags
|
||||
- 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
|
||||
- scripts/gitlab-build.sh x86_64-unknown-linux-gnu x86_64-unknown-linux-gnu amd64 gcc g++
|
||||
tags:
|
||||
- rust-stable
|
||||
artifacts:
|
||||
paths:
|
||||
- parity.zip
|
||||
name: "stable-x86_64-unknown-linux-gnu_parity"
|
||||
linux-stable-debian:
|
||||
stage: build
|
||||
image: parity/rust-debian:gitlab-ci
|
||||
only:
|
||||
- stable
|
||||
- beta
|
||||
- tags
|
||||
- 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++
|
||||
tags:
|
||||
- rust-debian
|
||||
artifacts:
|
||||
paths:
|
||||
- parity.zip
|
||||
name: "stable-x86_64-unknown-debian-gnu_parity"
|
||||
linux-centos:
|
||||
stage: build
|
||||
image: parity/rust-centos:gitlab-ci
|
||||
only:
|
||||
- stable
|
||||
- beta
|
||||
- tags
|
||||
- triggers
|
||||
script:
|
||||
- scripts/gitlab-build.sh x86_64-unknown-centos-gnu x86_64-unknown-linux-gnu x86_64 gcc g++
|
||||
tags:
|
||||
- rust-centos
|
||||
artifacts:
|
||||
paths:
|
||||
- parity.zip
|
||||
name: "x86_64-unknown-centos-gnu_parity"
|
||||
linux-i686:
|
||||
stage: build
|
||||
image: parity/rust-i686:gitlab-ci
|
||||
only:
|
||||
- stable
|
||||
- beta
|
||||
- tags
|
||||
- triggers
|
||||
script:
|
||||
- scripts/gitlab-build.sh i686-unknown-linux-gnu i686-unknown-linux-gnu i386 gcc g++
|
||||
tags:
|
||||
- rust-i686
|
||||
artifacts:
|
||||
paths:
|
||||
- parity.zip
|
||||
name: "i686-unknown-linux-gnu"
|
||||
linux-armv7:
|
||||
stage: build
|
||||
image: parity/rust-armv7:gitlab-ci
|
||||
only:
|
||||
- stable
|
||||
- beta
|
||||
- tags
|
||||
- triggers
|
||||
script:
|
||||
- scripts/gitlab-build.sh armv7-unknown-linux-gnueabihf armv7-unknown-linux-gnueabihf armhf arm-linux-gnueabihf-gcc arm-linux-gnueabihf-g++
|
||||
tags:
|
||||
- rust-arm
|
||||
artifacts:
|
||||
paths:
|
||||
- parity.zip
|
||||
name: "armv7_unknown_linux_gnueabihf_parity"
|
||||
linux-arm:
|
||||
stage: build
|
||||
image: parity/rust-arm:gitlab-ci
|
||||
only:
|
||||
- stable
|
||||
- beta
|
||||
- tags
|
||||
- triggers
|
||||
script:
|
||||
- scripts/gitlab-build.sh arm-unknown-linux-gnueabihf arm-unknown-linux-gnueabihf armhf arm-linux-gnueabihf-gcc arm-linux-gnueabihf-g++
|
||||
tags:
|
||||
- rust-arm
|
||||
artifacts:
|
||||
paths:
|
||||
- parity.zip
|
||||
name: "arm-unknown-linux-gnueabihf_parity"
|
||||
linux-aarch64:
|
||||
stage: build
|
||||
image: parity/rust-arm64:gitlab-ci
|
||||
only:
|
||||
- stable
|
||||
- beta
|
||||
- tags
|
||||
- triggers
|
||||
script:
|
||||
- scripts/gitlab-build.sh aarch64-unknown-linux-gnu aarch64-unknown-linux-gnu arm64 aarch64-linux-gnu-gcc aarch64-linux-gnu-g++
|
||||
tags:
|
||||
- rust-arm
|
||||
artifacts:
|
||||
paths:
|
||||
- parity.zip
|
||||
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++
|
||||
tags:
|
||||
- rust-stable
|
||||
artifacts:
|
||||
paths:
|
||||
- snap/parity.zip
|
||||
name: "stable-x86_64-unknown-snap-gnu_parity"
|
||||
allow_failure: true
|
||||
darwin:
|
||||
stage: build
|
||||
only:
|
||||
- stable
|
||||
- beta
|
||||
- tags
|
||||
- triggers
|
||||
script:
|
||||
- scripts/gitlab-build.sh x86_64-apple-darwin x86_64-apple-darwin macos gcc g++
|
||||
tags:
|
||||
- osx
|
||||
artifacts:
|
||||
paths:
|
||||
- parity.zip
|
||||
name: "x86_64-apple-darwin_parity"
|
||||
windows:
|
||||
cache:
|
||||
key: "%CI_BUILD_STAGE%-%CI_BUILD_REF_NAME%"
|
||||
untracked: true
|
||||
stage: build
|
||||
only:
|
||||
- stable
|
||||
- beta
|
||||
- tags
|
||||
- triggers
|
||||
script:
|
||||
- sh scripts/gitlab-build.sh x86_64-pc-windows-msvc x86_64-pc-windows-msvc installer "" "" ""
|
||||
tags:
|
||||
- rust-windows
|
||||
artifacts:
|
||||
paths:
|
||||
- parity.zip
|
||||
name: "x86_64-pc-windows-msvc_parity"
|
||||
docker-build:
|
||||
stage: build
|
||||
only:
|
||||
- stable
|
||||
- beta
|
||||
- tags
|
||||
- triggers
|
||||
before_script:
|
||||
- docker info
|
||||
script:
|
||||
- DOCKER_TAG=$CI_BUILD_REF_NAME
|
||||
- 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:
|
||||
stage: test
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- scripts/gitlab-test.sh test-coverage
|
||||
tags:
|
||||
- kcov
|
||||
allow_failure: true
|
||||
test-rust-stable:
|
||||
stage: test
|
||||
image: parity/rust:gitlab-ci
|
||||
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
|
||||
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-release:
|
||||
stage: js-build
|
||||
only:
|
||||
- master
|
||||
- stable
|
||||
- beta
|
||||
- tags
|
||||
- triggers
|
||||
image: parity/rust:gitlab-ci
|
||||
script:
|
||||
- scripts/gitlab-test.sh js-release
|
||||
tags:
|
||||
- javascript
|
||||
push-release:
|
||||
stage: push-release
|
||||
only:
|
||||
- tags
|
||||
- triggers
|
||||
image: parity/rust:gitlab-ci
|
||||
script:
|
||||
- rustup default stable
|
||||
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1337/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
|
||||
- curl --data "secret=$RELEASES_SECRET" http://update.parity.io:1338/push-release/$CI_BUILD_REF_NAME/$CI_BUILD_REF
|
||||
tags:
|
||||
- curl
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -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
|
||||
|
||||
108
.travis.yml
Normal file
108
.travis.yml
Normal file
@@ -0,0 +1,108 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
language: rust
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^beta-.*$/
|
||||
- /^stable-.*$/
|
||||
- /^beta$/
|
||||
- /^stable$/
|
||||
git:
|
||||
depth: 3
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
include:
|
||||
- rust: stable
|
||||
env: FEATURES="--features travis-beta" RUN_TESTS="true"
|
||||
# - rust: beta
|
||||
# env: FEATURES="--features travis-beta" RUN_TESTS="true"
|
||||
- rust: stable
|
||||
env: FEATURES="--features travis-beta" RUN_BUILD="true"
|
||||
- rust: beta
|
||||
env: FEATURES="--features travis-beta" RUN_BUILD="true"
|
||||
- rust: stable
|
||||
env: FEATURES="--features travis-beta" RUN_COVERAGE="true"
|
||||
# - rust: nightly
|
||||
# env: FEATURES="--features travis-nightly" RUN_BENCHES="true"
|
||||
- rust: nightly
|
||||
env: FEATURES="--features travis-nightly" RUN_TESTS="true"
|
||||
env:
|
||||
global:
|
||||
# GH_TOKEN
|
||||
- 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=
|
||||
- TARGETS="-p ethkey -p ethstore -p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity -p ethjson -p ethcore-dapps -p ethcore-signer"
|
||||
- ARCHIVE_SUFFIX="-${TRAVIS_OS_NAME}-${TRAVIS_TAG}"
|
||||
- KCOV_FEATURES=""
|
||||
- KCOV_CMD="./kcov-master/tmp/usr/local/bin/kcov --exclude-pattern /usr/,/.cargo,/root/.multirust,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests,ethstore/tests target/kcov"
|
||||
- RUN_TESTS="false"
|
||||
- RUN_COVERAGE="false"
|
||||
- RUN_BUILD="false"
|
||||
- RUN_BENCHES="false"
|
||||
- RUST_BACKTRACE="1"
|
||||
cache:
|
||||
apt: true
|
||||
directories:
|
||||
- $TRAVIS_BUILD_DIR/target
|
||||
- $HOME/.cargo
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libcurl4-openssl-dev
|
||||
- libelf-dev
|
||||
- libdw-dev
|
||||
|
||||
script:
|
||||
- if [ "$RUN_TESTS" = "true" ]; then cargo test --release --verbose ${FEATURES} ${TARGETS}; fi
|
||||
- if [ "$RUN_BENCHES" = "true" ]; then cargo bench --no-run ${FEATURES} ${TARGETS}; fi
|
||||
- if [ "$RUN_BUILD" = "true" ]; then cargo build --release --verbose ${FEATURES}; fi
|
||||
- if [ "$RUN_BUILD" = "true" ]; then tar cvzf parity${ARCHIVE_SUFFIX}.tar.gz -C target/release parity; fi
|
||||
|
||||
after_success: |
|
||||
[ "$RUN_COVERAGE" = "true" ] &&
|
||||
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
|
||||
tar xzf master.tar.gz && mkdir kcov-master/build && cd kcov-master/build && cmake .. && make && make install DESTDIR=../tmp && cd ../.. &&
|
||||
cargo test --no-run ${KCOV_FEATURES} ${TARGETS} &&
|
||||
$KCOV_CMD target/debug/deps/ethkey-* &&
|
||||
$KCOV_CMD target/debug/deps/ethstore-* &&
|
||||
$KCOV_CMD target/debug/deps/ethcore_util-* &&
|
||||
$KCOV_CMD target/debug/deps/ethash-* &&
|
||||
$KCOV_CMD target/debug/deps/ethcore-* &&
|
||||
$KCOV_CMD target/debug/deps/ethsync-* &&
|
||||
$KCOV_CMD target/debug/deps/ethcore_rpc-* &&
|
||||
$KCOV_CMD target/debug/deps/ethcore_dapps-* &&
|
||||
$KCOV_CMD target/debug/deps/ethcore_signer-* &&
|
||||
$KCOV_CMD target/debug/deps/ethjson-* &&
|
||||
$KCOV_CMD target/debug/parity-* &&
|
||||
[ $TRAVIS_BRANCH = master ] &&
|
||||
[ $TRAVIS_PULL_REQUEST = false ] &&
|
||||
[ $TRAVIS_RUST_VERSION = stable ] &&
|
||||
cargo doc --no-deps --verbose ${KCOV_FEATURES} ${TARGETS} &&
|
||||
echo '<meta http-equiv=refresh content=0;url=ethcore/index.html>' > target/doc/index.html &&
|
||||
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
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: "t+oGT/4lsy7IScw5s86Dpntl5Nyck4qG6nhHwMScc6FYzwLldgwgJaafL8Ej+HG+b7nFLriN+Snoa4YQ5o74X5ZlSWubVREOYQlL/fq7vcPB0DwAZ0Jufq1QW2R1M+3SwwF1eAwTv2W3G7A2K7dxjCVvENcy/gdxnZ36NeUPsqaCC9UcI2Yc7+4jyQwvx6ZfBvQeu+HbKENA0eUNs2ZQOID/1IPy0LJBvSyxAQYsysXdjTzGdNu4+Iba20E8uWYe4fAbgz+gwGarXg1L6D6gKyMlWkViqWjvXWBuDJJqMQZ3rw41AwZOoh3mKd2Lc0l6l4oZcEqPuob0yKTNjz1tuJy9xKTC2F2bDzsvUgk1IRfMK5ukXXXS09ZCZWuA9/GtnsqJ1xGTiwX+DhQzpVBHaBiseSNlYE1YN/3jNyGY+iSts1qut+1BwE7swmcTLsAPoAy8Ue+f7ErNoCg1lm71vq7VO2DLn7x2NqHyHUEuJ+7olDHSdE84G7d9otDRu/+TfMOw7GXwTaha6yJRInuNsnj4CFMLNVvYACzCC2idB7f7nUZoSFi9jf18S9fCMPVmazMrFj4g95HWrVHkjpV5zRTeUdTWw6DJl6pC9HFqORHdCvLv4Rc4dm5r3CmOcAQ0ZuiccV2oKzw4/Wic96daae8M5f5KSQ/WTr+h0wXZKp0="
|
||||
skip_cleanup: true
|
||||
file: parity${ARCHIVE_SUFFIX}.tar.gz
|
||||
on:
|
||||
tags: true
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://hooks.slack.com/services/${SLACK_WEBHOOK}
|
||||
on_success: always
|
||||
on_failure: always
|
||||
on_start: never
|
||||
|
||||
notifications:
|
||||
slack:
|
||||
rooms:
|
||||
- ethcore:4EGxt9WP6AS7uX4JKXSfR9vi#chatops
|
||||
371
CHANGELOG.md
371
CHANGELOG.md
@@ -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)
|
||||
3717
Cargo.lock
generated
3717
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
140
Cargo.toml
140
Cargo.toml
@@ -1,82 +1,40 @@
|
||||
[package]
|
||||
description = "Parity Ethereum client"
|
||||
description = "Ethcore client."
|
||||
name = "parity"
|
||||
version = "1.9.2"
|
||||
version = "1.2.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"
|
||||
syntex = "*"
|
||||
ethcore-ipc-codegen = { path = "ipc/codegen" }
|
||||
|
||||
[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"
|
||||
ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" }
|
||||
fdlimit = { path = "util/fdlimit" }
|
||||
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"
|
||||
isatty = "0.1"
|
||||
toml = "0.4"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde_derive = "1.0"
|
||||
app_dirs = "1.1.1"
|
||||
futures = "0.1"
|
||||
futures-cpupool = "0.1"
|
||||
fdlimit = "0.1"
|
||||
ws2_32-sys = "0.2"
|
||||
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
|
||||
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.9" }
|
||||
ethsync = { path = "sync" }
|
||||
rpassword = "0.2.1"
|
||||
clippy = { version = "0.0.77", optional = true}
|
||||
ethcore = { path = "ethcore" }
|
||||
ethcore-util = { path = "util" }
|
||||
ethcore-bytes = { path = "util/bytes" }
|
||||
ethcore-bigint = { path = "util/bigint" }
|
||||
ethcore-io = { path = "util/io" }
|
||||
ethsync = { path = "sync" }
|
||||
ethcore-devtools = { path = "devtools" }
|
||||
ethcore-light = { path = "ethcore/light" }
|
||||
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-rpc = { path = "rpc", optional = true }
|
||||
ethcore-signer = { path = "signer", optional = true }
|
||||
ethcore-dapps = { path = "dapps", optional = true }
|
||||
semver = "0.2"
|
||||
ethcore-ipc-nano = { path = "ipc/nano" }
|
||||
ethcore-ipc = { path = "ipc/rpc" }
|
||||
json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" }
|
||||
ansi_term = "0.7"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.2"
|
||||
@@ -84,47 +42,23 @@ winapi = "0.2"
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
daemonize = "0.2"
|
||||
|
||||
[dependencies.hyper]
|
||||
version = "0.8"
|
||||
default-features = false
|
||||
|
||||
[features]
|
||||
default = ["ui-precompiled"]
|
||||
ui = [
|
||||
"ui-enabled",
|
||||
"parity-dapps/ui",
|
||||
]
|
||||
ui-precompiled = [
|
||||
"ui-enabled",
|
||||
"parity-dapps/ui-precompiled",
|
||||
]
|
||||
ui-enabled = ["dapps"]
|
||||
dapps = ["parity-dapps"]
|
||||
jit = ["ethcore/jit"]
|
||||
json-tests = ["ethcore/json-tests"]
|
||||
test-heavy = ["ethcore/test-heavy"]
|
||||
evm-debug = ["ethcore/evm-debug"]
|
||||
evm-debug-tests = ["ethcore/evm-debug-tests"]
|
||||
slow-blocks = ["ethcore/slow-blocks"]
|
||||
secretstore = ["ethcore-secretstore"]
|
||||
final = ["parity-version/final"]
|
||||
default = ["rpc", "dapps", "ethcore-signer"]
|
||||
rpc = ["ethcore-rpc"]
|
||||
dapps = ["ethcore-dapps"]
|
||||
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev",
|
||||
"ethcore-dapps/dev", "ethcore-signer/dev"]
|
||||
travis-beta = ["ethcore/json-tests"]
|
||||
travis-nightly = ["ethcore/json-tests", "dev"]
|
||||
|
||||
[[bin]]
|
||||
path = "parity/main.rs"
|
||||
name = "parity"
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
debug = false
|
||||
debug = true
|
||||
lto = false
|
||||
panic = "abort"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"chainspec",
|
||||
"dapps/js-glue",
|
||||
"ethcore/wasm/run",
|
||||
"ethkey/cli",
|
||||
"ethstore/cli",
|
||||
"evmbin",
|
||||
"transaction-pool",
|
||||
"whisper",
|
||||
]
|
||||
|
||||
135
README.md
135
README.md
@@ -1,95 +1,77 @@
|
||||
# [Parity](https://parity.io/) - fast, light, and robust Ethereum client
|
||||
# [Parity](https://ethcore.io/parity.html)
|
||||
### Fast, light, and robust Ethereum implementation
|
||||
|
||||
[](https://gitlab.parity.io/parity/parity/commits/master)
|
||||
[](https://build.snapcraft.io/user/paritytech/parity)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
[![Build Status][travis-image]][travis-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Join the chat at https://gitter.im/trogdoro/xiki][gitter-image]][gitter-url] [![GPLv3][license-image]][license-url]
|
||||
|
||||
- [Download the latest release here.](https://github.com/paritytech/parity/releases/latest)
|
||||
[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]: http://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
||||
### Join the chat!
|
||||
|
||||
Get in touch with us on Gitter:
|
||||
[](https://gitter.im/paritytech/parity)
|
||||
[](https://gitter.im/paritytech/parity.js)
|
||||
[](https://gitter.im/paritytech/parity/miners)
|
||||
[](https://gitter.im/paritytech/parity-poa)
|
||||
|
||||
Or join our community on Matrix:
|
||||
[](https://riot.im/app/#/group/+parity:matrix.parity.io)
|
||||
|
||||
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.
|
||||
[Internal Documentation](http://ethcore.github.io/parity/ethcore/index.html)
|
||||
|
||||
----
|
||||
|
||||
## 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'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:
|
||||
By default, Parity will run a JSONRPC server on `127.0.0.1:8545`. This is fully configurable and supports a number
|
||||
of RPC APIs.
|
||||
|
||||
- create and manage your Ethereum accounts;
|
||||
- manage your Ether and any Ethereum tokens;
|
||||
- create and register your own tokens;
|
||||
- and much more.
|
||||
Parity also runs a server for running decentralized apps, or "Dapps", on `http://127.0.0.1:8080`.
|
||||
This includes a few useful Dapps, including Ethereum Wallet, Maker OTC, and a node status page.
|
||||
In a near-future release, it will be easy to install Dapps and use them through this web interface.
|
||||
|
||||
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.
|
||||
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!
|
||||
|
||||
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!
|
||||
|
||||
**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.1. 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,52 +80,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
|
||||
|
||||
To get started, just run
|
||||
```bash
|
||||
$ ./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
|
||||
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.
|
||||
and parity will begin syncing the Ethereum blockchain.
|
||||
|
||||
54
SECURITY.md
54
SECURITY.md
@@ -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.
|
||||
29
appveyor.yml
Normal file
29
appveyor.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
install:
|
||||
- git submodule update --init --recursive
|
||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.9.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.9.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
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- cargo test --verbose --release
|
||||
|
||||
after_test:
|
||||
- cargo build --verbose --release
|
||||
- makensis.exe nsis\installer.nsi
|
||||
|
||||
artifacts:
|
||||
- path: nsis\installer.exe
|
||||
name: Windows Installer (x86_64)
|
||||
|
||||
cache:
|
||||
- target
|
||||
- C:\users\appveyor\.cargo -> appveyor.yml
|
||||
38
build.rs
38
build.rs
@@ -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,21 +15,35 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate rustc_version;
|
||||
extern crate syntex;
|
||||
extern crate ethcore_ipc_codegen as codegen;
|
||||
|
||||
const MIN_RUSTC_VERSION: &'static str = "1.15.1";
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
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!("
|
||||
if let Channel::Nightly = version_meta().channel {
|
||||
println!("cargo:rustc-cfg=nightly");
|
||||
}
|
||||
|
||||
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:
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
|
||||
rustup update stable
|
||||
// ipc pass
|
||||
{
|
||||
let src = Path::new("parity/hypervisor/service.rs.in");
|
||||
let dst = Path::new(&out_dir).join("hypervisor_service_ipc.rs");
|
||||
let mut registry = syntex::Registry::new();
|
||||
codegen::register(&mut registry);
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
|
||||
and try building Parity again.
|
||||
|
||||
", is, required));
|
||||
// serialization pass
|
||||
{
|
||||
let src = Path::new(&out_dir).join("hypervisor_service_ipc.rs");
|
||||
let dst = Path::new(&out_dir).join("hypervisor_service_cg.rs");
|
||||
let mut registry = syntex::Registry::new();
|
||||
codegen::register(&mut registry);
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
43
cov.sh
Executable file
43
cov.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
# Installing KCOV under ubuntu
|
||||
# https://users.rust-lang.org/t/tutorial-how-to-collect-test-coverages-for-rust-project/650#
|
||||
### Install deps
|
||||
# $ sudo apt-get install libcurl4-openssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev
|
||||
#
|
||||
### Compile kcov
|
||||
# $ wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && tar xf master.tar.gz
|
||||
# $ cd kcov-master && mkdir build && cd build
|
||||
# $ cmake .. && make && sudo make install
|
||||
|
||||
### Running coverage
|
||||
if ! type kcov > /dev/null; then
|
||||
echo "Install kcov first (details inside this file). Aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cargo test \
|
||||
-p ethkey \
|
||||
-p ethstore \
|
||||
-p ethash \
|
||||
-p ethcore-util \
|
||||
-p ethcore \
|
||||
-p ethsync \
|
||||
-p ethcore-rpc \
|
||||
-p parity \
|
||||
-p ethcore-signer \
|
||||
-p ethcore-dapps \
|
||||
--no-run || exit $?
|
||||
rm -rf target/coverage
|
||||
mkdir -p target/coverage
|
||||
|
||||
EXCLUDE="~/.multirust,rocksdb,secp256k1,src/tests,util/json-tests,util/src/network/tests,sync/src/tests,ethcore/src/tests,ethcore/src/evm/tests,ethstore/tests"
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethkey-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethstore-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethash-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_util-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethsync-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_rpc-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_signer-*
|
||||
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_dapps-*
|
||||
xdg-open target/coverage/index.html
|
||||
@@ -1,48 +1,41 @@
|
||||
[package]
|
||||
description = "Parity Dapps crate"
|
||||
name = "parity-dapps"
|
||||
version = "1.9.0"
|
||||
name = "ethcore-dapps"
|
||||
version = "1.2.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"
|
||||
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"
|
||||
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" }
|
||||
|
||||
jsonrpc-core = "2.0"
|
||||
jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.git" }
|
||||
hyper = { default-features = false, git = "https://github.com/ethcore/hyper" }
|
||||
url = "1.0"
|
||||
rustc-serialize = "0.3"
|
||||
serde = "0.7.0"
|
||||
serde_json = "0.7.0"
|
||||
serde_macros = { version = "0.7.0", optional = true }
|
||||
ethcore-rpc = { path = "../rpc" }
|
||||
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" }
|
||||
parity-dapps = { git = "https://github.com/ethcore/parity-dapps-rs.git", version = "0.3" }
|
||||
# List of apps
|
||||
parity-dapps-status = { git = "https://github.com/ethcore/parity-dapps-status-rs.git", version = "0.5.0" }
|
||||
parity-dapps-builtins = { git = "https://github.com/ethcore/parity-dapps-builtins-rs.git", version = "0.5.0" }
|
||||
parity-dapps-wallet = { git = "https://github.com/ethcore/parity-dapps-wallet-rs.git", version = "0.6.0", optional = true }
|
||||
parity-dapps-dao = { git = "https://github.com/ethcore/parity-dapps-dao-rs.git", version = "0.4.0", optional = true }
|
||||
parity-dapps-makerotc = { git = "https://github.com/ethcore/parity-dapps-makerotc-rs.git", version = "0.3.0", optional = true }
|
||||
mime_guess = { version = "1.6.1" }
|
||||
clippy = { version = "0.0.77", optional = true}
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.4"
|
||||
ethcore-devtools = { path = "../devtools" }
|
||||
[build-dependencies]
|
||||
serde_codegen = { version = "0.7.0", optional = true }
|
||||
syntex = "*"
|
||||
|
||||
[features]
|
||||
ui = ["parity-ui/no-precompiled-js"]
|
||||
ui-precompiled = ["parity-ui/use-precompiled-js"]
|
||||
default = ["serde_codegen", "extra-dapps"]
|
||||
extra-dapps = ["parity-dapps-wallet"]
|
||||
nightly = ["serde_macros"]
|
||||
dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"]
|
||||
|
||||
45
dapps/build.rs
Normal file
45
dapps/build.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
// 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 syntex;
|
||||
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/mod.rs.in");
|
||||
let dst = Path::new(&out_dir).join("mod.rs");
|
||||
|
||||
let mut registry = syntex::Registry::new();
|
||||
|
||||
serde_codegen::register(&mut registry);
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde_macros")]
|
||||
mod inner {
|
||||
pub fn main() {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
inner::main();
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
[package]
|
||||
description = "Base Package for all Parity built-in dapps"
|
||||
name = "parity-dapps-glue"
|
||||
version = "1.9.1"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
quasi_codegen = { version = "0.32", optional = true }
|
||||
syntex = { version = "0.58", 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 }
|
||||
|
||||
[features]
|
||||
default = ["with-syntex"]
|
||||
nightly = ["quasi_macros"]
|
||||
with-syntex = ["quasi/with-syntex", "quasi_codegen", "quasi_codegen/with-syntex", "syntex", "syntex_syntax"]
|
||||
use-precompiled-js = []
|
||||
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# Parity Dapps (JS-glue)
|
||||
|
||||
Code generator to simplify creating a built-in Parity Dapp
|
||||
|
||||
# How to create new builtin Dapp.
|
||||
1. Clone this repository.
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/paritytech/parity.git
|
||||
```
|
||||
|
||||
1. Create a new directory for your Dapp. (`./myapp`)
|
||||
|
||||
```bash
|
||||
$ mkdir -p ./parity/dapps/myapp/src/web
|
||||
```
|
||||
|
||||
1. Copy your frontend files to `./dapps/myapp/src/web` (bundled ones)
|
||||
|
||||
```bash
|
||||
$ cp -r ./myapp-src/* ./parity/dapps/myapp/src/web
|
||||
```
|
||||
|
||||
1. Instead of creating `web3` in your app. Load (as the first script tag in `head`):
|
||||
|
||||
```html
|
||||
<script src="/parity-utils/inject.js"></script>
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/paritytech/parity-ui.git
|
||||
$ cd ./parity-ui/
|
||||
$ cp ./home/Cargo.toml ../parity/dapps/myapp/Cargo.toml
|
||||
$ cp ./home/build.rs ../parity/dapps/myapp/build.rs
|
||||
$ cp ./home/src/lib.rs ../parity/dapps/myapp/src/lib.rs
|
||||
$ cp ./home/src/lib.rs.in ../parity/dapps/myapp/src/lib.rs.in
|
||||
# And edit the details of your app
|
||||
$ vim ../parity/dapps/myapp/Cargo.toml # Edit the details
|
||||
$ vim ./parity/dapps/myapp/src/lib.rs.in # Edit the details
|
||||
```
|
||||
# How to include your Dapp into `Parity`?
|
||||
1. Edit `dapps/Cargo.toml` and add dependency to your application (it can be optional)
|
||||
|
||||
```toml
|
||||
# Use git repo and version
|
||||
parity-dapps-myapp = { path="./myapp" }
|
||||
```
|
||||
|
||||
1. Edit `dapps/src/apps.rs` and add your application to `all_pages` (if it's optional you need to specify two functions - see `parity-dapps-wallet` example)
|
||||
|
||||
1. Compile parity.
|
||||
|
||||
```bash
|
||||
$ cargo build --release # While inside `parity`
|
||||
```
|
||||
|
||||
1. Commit the results.
|
||||
|
||||
```bash
|
||||
$ git add myapp && git commit -am "My first Parity Dapp".
|
||||
```
|
||||
@@ -1,65 +0,0 @@
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
pub mod inner {
|
||||
use syntex;
|
||||
use codegen;
|
||||
use syntax::{ast, fold};
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
fn strip_attributes(krate: ast::Crate) -> ast::Crate {
|
||||
/// Helper folder that strips the serde attributes after the extensions have been expanded.
|
||||
struct StripAttributeFolder;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Some(attr)
|
||||
}
|
||||
|
||||
fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac {
|
||||
fold::noop_fold_mac(mac, self)
|
||||
}
|
||||
}
|
||||
|
||||
fold::Folder::fold_crate(&mut StripAttributeFolder, krate)
|
||||
}
|
||||
|
||||
pub fn register(reg: &mut syntex::Registry) {
|
||||
reg.add_attr("feature(custom_derive)");
|
||||
reg.add_attr("feature(custom_attribute)");
|
||||
|
||||
reg.add_decorator("derive_WebAppFiles", codegen::expand_webapp_implementation);
|
||||
reg.add_post_expansion_pass(strip_attributes);
|
||||
}
|
||||
|
||||
pub fn generate() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let mut registry = syntex::Registry::new();
|
||||
register(&mut registry);
|
||||
|
||||
let src = Path::new("src/lib.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.rs");
|
||||
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "with-syntex"))]
|
||||
pub mod inner {
|
||||
use codegen;
|
||||
|
||||
pub fn register(reg: &mut rustc_plugin::Registry) {
|
||||
reg.register_syntax_extension(
|
||||
syntax::parse::token::intern("derive_WebAppFiles"),
|
||||
syntax::ext::base::MultiDecorator(
|
||||
Box::new(codegen::expand_webapp_implementation)));
|
||||
|
||||
reg.register_attribute("webapp".to_owned(), AttributeType::Normal);
|
||||
}
|
||||
|
||||
pub fn generate() {}
|
||||
}
|
||||
@@ -1,194 +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/>.
|
||||
|
||||
extern crate aster;
|
||||
extern crate glob;
|
||||
extern crate mime_guess;
|
||||
|
||||
use self::mime_guess::guess_mime_type;
|
||||
use std::path::{self, Path, PathBuf};
|
||||
use std::ops::Deref;
|
||||
|
||||
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;
|
||||
|
||||
pub fn expand_webapp_implementation(
|
||||
cx: &mut ExtCtxt,
|
||||
span: Span,
|
||||
meta_item: &MetaItem,
|
||||
annotatable: &Annotatable,
|
||||
push: &mut FnMut(Annotatable)
|
||||
) {
|
||||
let item = match *annotatable {
|
||||
Annotatable::Item(ref item) => item,
|
||||
_ => {
|
||||
cx.span_err(meta_item.span, "`#[derive(WebAppFiles)]` may only be applied to struct implementations");
|
||||
return;
|
||||
},
|
||||
};
|
||||
let builder = aster::AstBuilder::new().span(span);
|
||||
implement_webapp(cx, &builder, item, push);
|
||||
}
|
||||
|
||||
fn implement_webapp(cx: &ExtCtxt, builder: &aster::AstBuilder, item: &Item, push: &mut FnMut(Annotatable)) {
|
||||
let static_files_dir = extract_path(cx, item);
|
||||
|
||||
let src = Path::new("src");
|
||||
let static_files = {
|
||||
let mut buf = src.to_path_buf();
|
||||
buf.push(static_files_dir.deref());
|
||||
buf
|
||||
};
|
||||
|
||||
let search_location = {
|
||||
let mut buf = static_files.to_path_buf();
|
||||
buf.push("**");
|
||||
buf.push("*");
|
||||
buf
|
||||
};
|
||||
|
||||
let files = glob::glob(search_location.to_str().expect("Valid UTF8 path"))
|
||||
.expect("The sources directory is missing.")
|
||||
.collect::<Result<Vec<PathBuf>, glob::GlobError>>()
|
||||
.expect("There should be no error when reading a list of files.");
|
||||
|
||||
let statements = files
|
||||
.iter()
|
||||
.filter(|path_buf| path_buf.is_file())
|
||||
.map(|path_buf| {
|
||||
let path = path_buf.as_path();
|
||||
let filename = path.file_name().and_then(|s| s.to_str()).expect("Only UTF8 paths.");
|
||||
let mime_type = guess_mime_type(filename).to_string();
|
||||
let file_path = as_uri(path.strip_prefix(&static_files).ok().expect("Prefix is always there, cause it's absolute path;qed"));
|
||||
let file_path_in_source = path.to_str().expect("Only UTF8 paths.");
|
||||
|
||||
let path_lit = builder.expr().str(file_path.as_str());
|
||||
let mime_lit = builder.expr().str(mime_type.as_str());
|
||||
let web_path_lit = builder.expr().str(file_path_in_source);
|
||||
let separator_lit = builder.expr().str(path::MAIN_SEPARATOR.to_string().as_str());
|
||||
let concat_id = builder.id("concat!");
|
||||
let env_id = builder.id("env!");
|
||||
let macro_id = builder.id("include_bytes!");
|
||||
|
||||
let content = quote_expr!(
|
||||
cx,
|
||||
$macro_id($concat_id($env_id("CARGO_MANIFEST_DIR"), $separator_lit, $web_path_lit))
|
||||
);
|
||||
quote_stmt!(
|
||||
cx,
|
||||
files.insert($path_lit, File { path: $path_lit, content_type: $mime_lit, content: $content });
|
||||
).expect("The statement is always ok, because it just uses literals.")
|
||||
}).collect::<Vec<ast::Stmt>>();
|
||||
|
||||
let type_name = item.ident;
|
||||
|
||||
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
|
||||
files
|
||||
}
|
||||
}
|
||||
).unwrap();
|
||||
|
||||
push(Annotatable::Item(files_impl));
|
||||
}
|
||||
|
||||
fn extract_path(cx: &ExtCtxt, item: &Item) -> String {
|
||||
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) {
|
||||
return s.deref().to_owned();
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default
|
||||
"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> {
|
||||
match lit.node {
|
||||
ast::LitKind::Str(ref s, _) => Some(s.clone().as_str()),
|
||||
_ => {
|
||||
cx.span_err(
|
||||
lit.span,
|
||||
&format!("webapp annotation path must be a string, not `{}`",
|
||||
lit_to_string(lit)
|
||||
)
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_uri(path: &Path) -> String {
|
||||
let mut s = String::new();
|
||||
for component in path.iter() {
|
||||
s.push_str(component.to_str().expect("Only UTF-8 filenames are supported."));
|
||||
s.push('/');
|
||||
}
|
||||
s[0..s.len()-1].into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_convert_path_separators_on_all_platforms() {
|
||||
// given
|
||||
let p = {
|
||||
let mut p = PathBuf::new();
|
||||
p.push("web");
|
||||
p.push("src");
|
||||
p.push("index.html");
|
||||
p
|
||||
};
|
||||
|
||||
// when
|
||||
let path = as_uri(&p);
|
||||
|
||||
// then
|
||||
assert_eq!(path, "web/src/index.html".to_owned());
|
||||
}
|
||||
@@ -1,89 +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/>.
|
||||
|
||||
#![cfg_attr(feature="use-precompiled-js", allow(dead_code))]
|
||||
#![cfg_attr(feature="use-precompiled-js", allow(unused_imports))]
|
||||
|
||||
use std::fmt;
|
||||
use std::process::Command;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
mod platform {
|
||||
use std::process::Command;
|
||||
|
||||
pub static NPM_CMD: &'static str = "npm";
|
||||
pub fn handle_cmd(cmd: &mut Command) -> &mut Command {
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod platform {
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
pub static NPM_CMD: &'static str = "cmd.exe";
|
||||
// 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 {
|
||||
cmd.stdin(Stdio::null())
|
||||
.arg("/c")
|
||||
.arg("npm.cmd")
|
||||
}
|
||||
}
|
||||
|
||||
fn die<T : fmt::Debug>(s: &'static str, e: T) -> ! {
|
||||
panic!("Error: {}: {:?}", s, e);
|
||||
}
|
||||
|
||||
#[cfg(feature = "use-precompiled-js")]
|
||||
pub fn test(_path: &str) {
|
||||
}
|
||||
#[cfg(feature = "use-precompiled-js")]
|
||||
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))
|
||||
.arg("install")
|
||||
.arg("--no-progress")
|
||||
.current_dir(path)
|
||||
.status()
|
||||
.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))
|
||||
.arg("run")
|
||||
.arg("build")
|
||||
.env("NODE_ENV", "production")
|
||||
.env("BUILD_DEST", dest)
|
||||
.current_dir(path)
|
||||
.status()
|
||||
.unwrap_or_else(|e| die("Building JS code", e));
|
||||
assert!(child.success(), "There was an error build JS code.");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "use-precompiled-js"))]
|
||||
pub fn test(path: &str) {
|
||||
let child = Command::new(platform::NPM_CMD)
|
||||
.arg("run")
|
||||
.arg("test")
|
||||
.current_dir(path)
|
||||
.status()
|
||||
.unwrap_or_else(|e| die("Running test command", e));
|
||||
assert!(child.success(), "There was an error while running JS tests.");
|
||||
}
|
||||
@@ -1,46 +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/>.
|
||||
|
||||
extern crate quasi;
|
||||
|
||||
mod codegen;
|
||||
mod build;
|
||||
pub mod js;
|
||||
pub use build::inner::generate;
|
||||
|
||||
use std::default::Default;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct File {
|
||||
pub path: &'static str,
|
||||
pub content: &'static [u8],
|
||||
// TODO: use strongly-typed MIME.
|
||||
pub content_type: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Info {
|
||||
pub name: &'static str,
|
||||
pub version: &'static str,
|
||||
pub author: &'static str,
|
||||
pub description: &'static str,
|
||||
pub icon_url: &'static str,
|
||||
}
|
||||
|
||||
pub trait WebApp : Default + Send + Sync {
|
||||
fn file(&self, path: &str) -> Option<&File>;
|
||||
fn info(&self) -> Info;
|
||||
}
|
||||
@@ -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" }
|
||||
@@ -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})
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
@@ -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,55 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
use endpoint::{Endpoint, Endpoints, EndpointInfo, Handler, EndpointPath};
|
||||
|
||||
use hyper::{Method, StatusCode};
|
||||
use api::response::as_json;
|
||||
|
||||
use api::response;
|
||||
use apps::fetcher::Fetcher;
|
||||
use endpoint::{Endpoint, Request, Response, EndpointPath};
|
||||
use futures::{future, Future};
|
||||
use node_health::{NodeHealth, HealthStatus};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RestApi {
|
||||
fetcher: Arc<Fetcher>,
|
||||
health: NodeHealth,
|
||||
endpoints: Arc<Endpoints>,
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
#[derive(Debug, PartialEq, 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,
|
||||
}
|
||||
|
||||
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())),
|
||||
impl App {
|
||||
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 RestApi {
|
||||
pub fn new(
|
||||
fetcher: Arc<Fetcher>,
|
||||
health: NodeHealth,
|
||||
) -> Box<Endpoint> {
|
||||
pub fn new(endpoints: Arc<Endpoints>) -> Box<Endpoint> {
|
||||
Box::new(RestApi {
|
||||
fetcher,
|
||||
health,
|
||||
endpoints: endpoints
|
||||
})
|
||||
}
|
||||
|
||||
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 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
|
||||
};
|
||||
|
||||
Ok(response::as_json(status, &health).into())
|
||||
})
|
||||
)
|
||||
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_handler(&self, _path: EndpointPath) -> Box<Handler> {
|
||||
as_json(&self.list_apps())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,13 @@
|
||||
|
||||
//! REST API
|
||||
|
||||
mod api;
|
||||
mod response;
|
||||
mod types;
|
||||
#![warn(missing_docs)]
|
||||
#![cfg_attr(feature="nightly", feature(custom_derive, custom_attribute, plugin))]
|
||||
#![cfg_attr(feature="nightly", plugin(serde_macros, clippy))]
|
||||
|
||||
#[cfg(feature = "serde_macros")]
|
||||
include!("mod.rs.in");
|
||||
|
||||
#[cfg(not(feature = "serde_macros"))]
|
||||
include!(concat!(env!("OUT_DIR"), "/mod.rs"));
|
||||
|
||||
pub use self::api::RestApi;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,8 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import LocaleStore from './store';
|
||||
mod api;
|
||||
mod response;
|
||||
|
||||
export {
|
||||
LocaleStore
|
||||
};
|
||||
pub use self::api::RestApi;
|
||||
pub use self::api::App;
|
||||
@@ -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,8 @@
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
use hyper::{self, mime, StatusCode};
|
||||
use endpoint::{ContentHandler, Handler};
|
||||
|
||||
use handlers::{ContentHandler, EchoHandler};
|
||||
|
||||
pub fn empty() -> hyper::Response {
|
||||
ContentHandler::ok("".into(), mime::TEXT_PLAIN).into()
|
||||
}
|
||||
|
||||
pub fn as_json<T: Serialize>(status: StatusCode, val: &T) -> hyper::Response {
|
||||
let json = serde_json::to_string(val)
|
||||
.expect("serialization to string is infallible; qed");
|
||||
ContentHandler::new(status, json, mime::APPLICATION_JSON).into()
|
||||
}
|
||||
|
||||
pub fn ping(req: hyper::Request) -> hyper::Response {
|
||||
EchoHandler::new(req).into()
|
||||
}
|
||||
|
||||
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 as_json<T : Serialize>(val: &T) -> Box<Handler> {
|
||||
Box::new(ContentHandler::new(serde_json::to_string(val).unwrap(), "application/json".to_owned()))
|
||||
}
|
||||
|
||||
@@ -1,27 +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 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,
|
||||
}
|
||||
@@ -1,59 +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 endpoint::EndpointInfo;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
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,
|
||||
#[serde(rename="localUrl")]
|
||||
pub local_url: Option<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(),
|
||||
local_url: info.local_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,
|
||||
local_url: self.local_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +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.
|
||||
|
||||
use std::fs;
|
||||
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use page::local;
|
||||
use handlers::FetchControl;
|
||||
|
||||
pub enum ContentStatus {
|
||||
Fetching(FetchControl),
|
||||
Ready(local::Dapp),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ContentCache {
|
||||
cache: LinkedHashMap<String, ContentStatus>,
|
||||
}
|
||||
|
||||
impl ContentCache {
|
||||
pub fn insert(&mut self, content_id: String, status: ContentStatus) -> Option<ContentStatus> {
|
||||
self.cache.insert(content_id, status)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, content_id: &str) -> Option<ContentStatus> {
|
||||
self.cache.remove(content_id)
|
||||
}
|
||||
|
||||
pub fn get(&mut self, content_id: &str) -> Option<&mut ContentStatus> {
|
||||
self.cache.get_refresh(content_id)
|
||||
}
|
||||
|
||||
pub fn clear_garbage(&mut self, expected_size: usize) -> Vec<(String, ContentStatus)> {
|
||||
let len = self.cache.len();
|
||||
|
||||
if len <= expected_size {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut removed = Vec::with_capacity(len - expected_size);
|
||||
|
||||
while self.cache.len() > expected_size {
|
||||
let entry = self.cache.pop_front().expect("expected_size bounded at 0, len is greater; qed");
|
||||
|
||||
match entry.1 {
|
||||
ContentStatus::Fetching(ref fetch) => {
|
||||
trace!(target: "dapps", "Aborting {} because of limit.", entry.0);
|
||||
// Mark as aborted
|
||||
fetch.abort()
|
||||
},
|
||||
ContentStatus::Ready(ref endpoint) => {
|
||||
trace!(target: "dapps", "Removing {} because of limit.", entry.0);
|
||||
// Remove path (dir or file)
|
||||
let res = fs::remove_dir_all(&endpoint.path()).or_else(|_| fs::remove_file(&endpoint.path()));
|
||||
if let Err(e) = res {
|
||||
warn!(target: "dapps", "Unable to remove dapp/content from cache: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removed.push(entry);
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.cache.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn only_keys(data: Vec<(String, ContentStatus)>) -> Vec<String> {
|
||||
data.into_iter().map(|x| x.0).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_remove_least_recently_used() {
|
||||
// given
|
||||
let mut cache = ContentCache::default();
|
||||
cache.insert("a".into(), ContentStatus::Fetching(Default::default()));
|
||||
cache.insert("b".into(), ContentStatus::Fetching(Default::default()));
|
||||
cache.insert("c".into(), ContentStatus::Fetching(Default::default()));
|
||||
|
||||
// when
|
||||
let res = cache.clear_garbage(2);
|
||||
|
||||
// then
|
||||
assert_eq!(cache.len(), 2);
|
||||
assert_eq!(only_keys(res), vec!["a"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_update_lru_if_accessed() {
|
||||
// given
|
||||
let mut cache = ContentCache::default();
|
||||
cache.insert("a".into(), ContentStatus::Fetching(Default::default()));
|
||||
cache.insert("b".into(), ContentStatus::Fetching(Default::default()));
|
||||
cache.insert("c".into(), ContentStatus::Fetching(Default::default()));
|
||||
|
||||
// when
|
||||
cache.get("a");
|
||||
let res = cache.clear_garbage(2);
|
||||
|
||||
// then
|
||||
assert_eq!(cache.len(), 2);
|
||||
assert_eq!(only_keys(res), vec!["b"]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 = 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)
|
||||
}
|
||||
}
|
||||
@@ -1,339 +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 {
|
||||
name: "fake".into(),
|
||||
description: "".into(),
|
||||
version: "".into(),
|
||||
author: "".into(),
|
||||
icon_url: "".into(),
|
||||
local_url: Some("".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);
|
||||
}
|
||||
}
|
||||
@@ -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,14 @@
|
||||
// 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 serde_json;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use futures_cpupool::CpuPool;
|
||||
|
||||
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
|
||||
use endpoint::{Endpoint, EndpointInfo};
|
||||
use page::{local, PageCache};
|
||||
use Embeddable;
|
||||
use std::path::PathBuf;
|
||||
use page::LocalPageEndpoint;
|
||||
use endpoint::{Endpoints, EndpointInfo};
|
||||
use api::App;
|
||||
|
||||
struct LocalDapp {
|
||||
id: String,
|
||||
@@ -32,87 +29,17 @@ struct LocalDapp {
|
||||
info: EndpointInfo,
|
||||
}
|
||||
|
||||
/// Tries to find and read manifest file in given `path` to extract `EndpointInfo`
|
||||
/// If manifest is not found sensible default `EndpointInfo` is returned based on given `name`.
|
||||
fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo {
|
||||
path.push(MANIFEST_FILENAME);
|
||||
|
||||
fs::File::open(path.clone())
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
.and_then(|mut f| {
|
||||
// Reat file
|
||||
let mut s = String::new();
|
||||
f.read_to_string(&mut s).map_err(|e| format!("{:?}", e))?;
|
||||
// Try to deserialize manifest
|
||||
deserialize_manifest(s)
|
||||
})
|
||||
.map(Into::into)
|
||||
.unwrap_or_else(|e| {
|
||||
warn!(target: "dapps", "Cannot read manifest file at: {:?}. Error: {:?}", path, e);
|
||||
|
||||
EndpointInfo {
|
||||
name: name.into(),
|
||||
description: name.into(),
|
||||
version: "0.0.0".into(),
|
||||
author: "?".into(),
|
||||
icon_url: "icon.png".into(),
|
||||
local_url: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
@@ -133,6 +60,57 @@ 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.json");
|
||||
|
||||
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
|
||||
serde_json::from_str::<App>(&s).map_err(|e| format!("{:?}", e))
|
||||
})
|
||||
.map(|app| EndpointInfo {
|
||||
name: app.name,
|
||||
description: app.description,
|
||||
version: app.version,
|
||||
author: app.author,
|
||||
icon_url: app.icon_url,
|
||||
})
|
||||
.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) -> 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))
|
||||
);
|
||||
}
|
||||
pages
|
||||
}
|
||||
|
||||
@@ -1,29 +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 serde_json;
|
||||
pub use apps::App as Manifest;
|
||||
|
||||
pub const MANIFEST_FILENAME: &'static str = "manifest.json";
|
||||
|
||||
pub fn deserialize_manifest(manifest: String) -> Result<Manifest, String> {
|
||||
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> {
|
||||
serde_json::to_string_pretty(manifest).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
@@ -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,87 +14,74 @@
|
||||
// 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;
|
||||
pub mod fetcher;
|
||||
pub mod manifest;
|
||||
mod fs;
|
||||
|
||||
pub use self::app::App;
|
||||
extern crate parity_dapps_status;
|
||||
extern crate parity_dapps_builtins;
|
||||
|
||||
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, parity_ui::App::default()))
|
||||
pub fn main_page() -> &'static str {
|
||||
"/home/"
|
||||
}
|
||||
|
||||
pub fn ui(pool: CpuPool) -> Box<Endpoint> {
|
||||
Box::new(page::builtin::Dapp::with_fallback_to_index(pool, parity_ui::App::default()))
|
||||
pub fn utils() -> Box<Endpoint> {
|
||||
Box::new(PageEndpoint::with_prefix(parity_dapps_builtins::App::default(), UTILS_PATH.to_owned()))
|
||||
}
|
||||
|
||||
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) -> 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);
|
||||
// Home page needs to be safe embed
|
||||
// because we use Cross-Origin LocalStorage.
|
||||
// TODO [ToDr] Account naming should be moved to parity.
|
||||
pages.insert("home".into(), Box::new(
|
||||
PageEndpoint::new_safe_to_embed(parity_dapps_builtins::App::default())
|
||||
));
|
||||
pages.insert("proxy".into(), ProxyPac::boxed());
|
||||
insert::<parity_dapps_status::App>(&mut pages, "parity");
|
||||
insert::<parity_dapps_status::App>(&mut pages, "status");
|
||||
|
||||
// NOTE [ToDr] Dapps will be currently embeded on 8180
|
||||
insert::<parity_ui::App>(&mut pages, "ui", Embeddable::Yes(embeddable.clone()), pool.clone());
|
||||
// old version
|
||||
insert::<parity_ui::old::App>(&mut pages, "v1", Embeddable::Yes(embeddable.clone()), pool.clone());
|
||||
// Optional dapps
|
||||
wallet_page(&mut pages);
|
||||
daodapp_page(&mut pages);
|
||||
makerotc_page(&mut pages);
|
||||
|
||||
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) {
|
||||
pages.insert(id.to_owned(), Box::new(match embed_at {
|
||||
Embeddable::Yes(address) => page::builtin::Dapp::new_safe_to_embed(pool, T::default(), address),
|
||||
Embeddable::No => page::builtin::Dapp::new(pool, T::default()),
|
||||
}));
|
||||
#[cfg(feature = "parity-dapps-wallet")]
|
||||
fn wallet_page(pages: &mut Endpoints) {
|
||||
extern crate parity_dapps_wallet;
|
||||
insert::<parity_dapps_wallet::App>(pages, "wallet");
|
||||
}
|
||||
#[cfg(not(feature = "parity-dapps-wallet"))]
|
||||
fn wallet_page(_pages: &mut Endpoints) {}
|
||||
|
||||
enum Embeddable {
|
||||
Yes(Option<ParentFrameSettings>),
|
||||
#[allow(dead_code)]
|
||||
No,
|
||||
#[cfg(feature = "parity-dapps-dao")]
|
||||
fn daodapp_page(pages: &mut Endpoints) {
|
||||
extern crate parity_dapps_dao;
|
||||
insert::<parity_dapps_dao::App>(pages, "dao");
|
||||
}
|
||||
#[cfg(not(feature = "parity-dapps-dao"))]
|
||||
fn daodapp_page(_pages: &mut Endpoints) {}
|
||||
|
||||
#[cfg(feature = "parity-dapps-makerotc")]
|
||||
fn makerotc_page(pages: &mut Endpoints) {
|
||||
extern crate parity_dapps_makerotc;
|
||||
insert::<parity_dapps_makerotc::App>(pages, "makerotc");
|
||||
}
|
||||
#[cfg(not(feature = "parity-dapps-makerotc"))]
|
||||
fn makerotc_page(_pages: &mut Endpoints) {}
|
||||
|
||||
fn insert<T : WebApp + Default + 'static>(pages: &mut Endpoints, id: &str) {
|
||||
pages.insert(id.to_owned(), Box::new(PageEndpoint::new(T::default())));
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -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,25 +16,18 @@
|
||||
|
||||
//! URL Endpoint traits
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use hyper::status::StatusCode;
|
||||
use hyper::{header, server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
|
||||
use futures::Future;
|
||||
use hyper;
|
||||
use std::io::Write;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[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)]
|
||||
@@ -44,15 +37,63 @@ pub struct EndpointInfo {
|
||||
pub version: String,
|
||||
pub author: String,
|
||||
pub icon_url: String,
|
||||
pub local_url: Option<String>,
|
||||
}
|
||||
|
||||
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 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<server::Handler<HttpStream>>;
|
||||
}
|
||||
|
||||
pub type Endpoints = BTreeMap<String, Box<Endpoint>>;
|
||||
pub type Handler = server::Handler<HttpStream>;
|
||||
|
||||
pub struct ContentHandler {
|
||||
content: String,
|
||||
mimetype: String,
|
||||
write_pos: usize,
|
||||
}
|
||||
|
||||
impl ContentHandler {
|
||||
pub fn new(content: String, mimetype: String) -> Self {
|
||||
ContentHandler {
|
||||
content: content,
|
||||
mimetype: mimetype,
|
||||
write_pos: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Handler<HttpStream> for ContentHandler {
|
||||
fn on_request(&mut self, _request: server::Request) -> 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::Ok);
|
||||
res.headers_mut().set(header::ContentType(self.mimetype.parse().unwrap()));
|
||||
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()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>{title}</title>
|
||||
<link rel="stylesheet" href="/parity-utils/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="parity-navbar">
|
||||
</div>
|
||||
<div class="parity-box">
|
||||
<h1>{title}</h1>
|
||||
<h3>{message}</h3>
|
||||
<p><code>{details}</code></p>
|
||||
</div>
|
||||
<div class="parity-status">
|
||||
<small>{version}</small>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,88 +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/>.
|
||||
|
||||
//! Simple Content Handler
|
||||
|
||||
use hyper::{self, mime, header};
|
||||
use hyper::StatusCode;
|
||||
|
||||
use parity_version::version;
|
||||
|
||||
use handlers::add_security_headers;
|
||||
use Embeddable;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContentHandler {
|
||||
code: StatusCode,
|
||||
content: String,
|
||||
mimetype: mime::Mime,
|
||||
safe_to_embed_on: Embeddable,
|
||||
}
|
||||
|
||||
impl ContentHandler {
|
||||
pub fn ok(content: String, mimetype: mime::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 error(
|
||||
code: StatusCode,
|
||||
title: &str,
|
||||
message: &str,
|
||||
details: Option<&str>,
|
||||
embeddable_on: Embeddable,
|
||||
) -> Self {
|
||||
Self::html(code, format!(
|
||||
include_str!("../error_tpl.html"),
|
||||
title=title,
|
||||
message=message,
|
||||
details=details.unwrap_or_else(|| ""),
|
||||
version=version(),
|
||||
), embeddable_on)
|
||||
}
|
||||
|
||||
pub fn new(code: StatusCode, content: String, mimetype: mime::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 {
|
||||
ContentHandler {
|
||||
code,
|
||||
content,
|
||||
mimetype,
|
||||
safe_to_embed_on,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
res
|
||||
}
|
||||
}
|
||||
@@ -1,46 +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/>.
|
||||
|
||||
//! Echo Handler
|
||||
|
||||
use hyper::{self, header};
|
||||
|
||||
use handlers::add_security_headers;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EchoHandler {
|
||||
request: hyper::Request,
|
||||
}
|
||||
|
||||
impl EchoHandler {
|
||||
pub fn new(request: hyper::Request) -> Self {
|
||||
EchoHandler {
|
||||
request,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
add_security_headers(res.headers_mut(), None);
|
||||
res
|
||||
}
|
||||
}
|
||||
@@ -1,369 +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/>.
|
||||
|
||||
//! Hyper Server Handler that fetches a file during a request (proxy).
|
||||
|
||||
use std::{fmt, mem};
|
||||
use std::sync::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 endpoint::{self, EndpointPath};
|
||||
use handlers::{ContentHandler, StreamingHandler};
|
||||
use page::local;
|
||||
use {Embeddable};
|
||||
|
||||
const FETCH_TIMEOUT: u64 = 300;
|
||||
|
||||
pub enum ValidatorResponse {
|
||||
Local(local::Dapp),
|
||||
Streaming(StreamingHandler<fetch::Response>),
|
||||
}
|
||||
|
||||
pub trait ContentValidator: Sized + Send + 'static {
|
||||
type Error: fmt::Debug + fmt::Display;
|
||||
|
||||
fn validate_and_install(self, fetch::Response) -> Result<ValidatorResponse, Self::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FetchControl {
|
||||
abort: Arc<AtomicBool>,
|
||||
listeners: Arc<Mutex<Vec<oneshot::Sender<WaitResult>>>>,
|
||||
deadline: Instant,
|
||||
}
|
||||
|
||||
impl Default for FetchControl {
|
||||
fn default() -> Self {
|
||||
FetchControl {
|
||||
abort: Arc::new(AtomicBool::new(false)),
|
||||
listeners: Arc::new(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) {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum WaitState {
|
||||
Waiting(oneshot::Receiver<WaitResult>),
|
||||
Done(endpoint::Response),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum WaitResult {
|
||||
Error(ContentHandler),
|
||||
Done(local::Dapp),
|
||||
NonAwaitable,
|
||||
}
|
||||
|
||||
pub struct WaitingHandler {
|
||||
path: EndpointPath,
|
||||
state: WaitState,
|
||||
}
|
||||
|
||||
impl Future for WaitingHandler {
|
||||
type Item = hyper::Response;
|
||||
type Error = hyper::Error;
|
||||
|
||||
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));
|
||||
|
||||
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()
|
||||
},
|
||||
};
|
||||
|
||||
self.state = new_state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
status: FetchState,
|
||||
errors: Errors,
|
||||
}
|
||||
|
||||
impl ContentFetcherHandler {
|
||||
pub fn fetch_control(&self) -> FetchControl {
|
||||
self.fetch_control.clone()
|
||||
}
|
||||
|
||||
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 };
|
||||
|
||||
// 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()),
|
||||
};
|
||||
|
||||
ContentFetcherHandler {
|
||||
fetch_control,
|
||||
status,
|
||||
errors,
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
// make sure to run within fetch thread pool.
|
||||
fetch.process(future)
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for ContentFetcherHandler {
|
||||
type Item = hyper::Response;
|
||||
type Error = hyper::Error;
|
||||
|
||||
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);
|
||||
self.fetch_control.set_status(&status);
|
||||
self.status = status;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +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/>.
|
||||
|
||||
//! Hyper handlers implementations.
|
||||
|
||||
mod content;
|
||||
mod echo;
|
||||
mod fetch;
|
||||
mod reader;
|
||||
mod redirect;
|
||||
mod streaming;
|
||||
|
||||
pub use self::content::ContentHandler;
|
||||
pub use self::echo::EchoHandler;
|
||||
pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl, ValidatorResponse};
|
||||
pub use self::reader::Reader;
|
||||
pub use self::redirect::Redirection;
|
||||
pub use self::streaming::StreamingHandler;
|
||||
|
||||
use std::iter;
|
||||
use itertools::Itertools;
|
||||
use hyper::header;
|
||||
use {apps, address, Embeddable};
|
||||
|
||||
/// Adds security-related headers to the Response.
|
||||
pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Embeddable) {
|
||||
headers.set_raw("X-XSS-Protection", "1; mode=block");
|
||||
headers.set_raw("X-Content-Type-Options", "nosniff");
|
||||
|
||||
// Embedding header:
|
||||
if let None = embeddable_on {
|
||||
headers.set_raw("X-Frame-Options", "SAMEORIGIN");
|
||||
}
|
||||
|
||||
// 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();
|
||||
&format!(
|
||||
"script-src 'self' {};",
|
||||
script_src
|
||||
)
|
||||
}
|
||||
// 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';"),
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -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))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +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/>.
|
||||
|
||||
//! HTTP Redirection hyper handler
|
||||
|
||||
use hyper::{self, header, StatusCode};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Redirection {
|
||||
to_url: String
|
||||
}
|
||||
|
||||
impl Redirection {
|
||||
pub fn new<T: Into<String>>(url: T) -> Self {
|
||||
Redirection {
|
||||
to_url: url.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<hyper::Response> for Redirection {
|
||||
fn into(self) -> hyper::Response {
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
(reader, res)
|
||||
}
|
||||
}
|
||||
389
dapps/src/lib.rs
389
dapps/src/lib.rs
@@ -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,156 @@
|
||||
// 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 serde;
|
||||
extern crate serde_json;
|
||||
extern crate unicase;
|
||||
extern crate zip;
|
||||
|
||||
extern crate jsonrpc_http_server;
|
||||
|
||||
extern crate ethcore_util as util;
|
||||
extern crate ethcore_bigint as bigint;
|
||||
extern crate ethcore_bytes as bytes;
|
||||
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;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate env_logger;
|
||||
#[cfg(test)]
|
||||
extern crate ethcore_devtools as devtools;
|
||||
#[cfg(test)]
|
||||
extern crate url;
|
||||
extern crate hyper;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate jsonrpc_core;
|
||||
#[cfg(test)]
|
||||
extern crate parity_reactor;
|
||||
extern crate jsonrpc_http_server;
|
||||
extern crate parity_dapps;
|
||||
extern crate ethcore_rpc;
|
||||
extern crate mime_guess;
|
||||
|
||||
mod endpoint;
|
||||
mod apps;
|
||||
mod page;
|
||||
mod router;
|
||||
mod handlers;
|
||||
mod rpc;
|
||||
mod api;
|
||||
mod proxypac;
|
||||
mod web;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
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 jsonrpc_core::{IoHandler, IoDelegate};
|
||||
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
|
||||
use ethcore_rpc::Extendable;
|
||||
|
||||
use fetch::Fetch;
|
||||
use node_health::NodeHealth;
|
||||
static DAPPS_DOMAIN : &'static str = ".parity";
|
||||
|
||||
pub use hash_fetch::urlhint::ContractClient;
|
||||
pub use node_health::SyncStatus;
|
||||
|
||||
|
||||
/// Validates Web Proxy tokens
|
||||
pub trait WebProxyTokens: Send + Sync {
|
||||
/// Should return a domain allowed to be accessed by this token or `None` if the token is not valid
|
||||
fn domain(&self, token: &str) -> Option<Origin>;
|
||||
/// Webapps HTTP+RPC server build.
|
||||
pub struct ServerBuilder {
|
||||
dapps_path: String,
|
||||
handler: Arc<IoHandler>,
|
||||
}
|
||||
|
||||
impl<F> WebProxyTokens for F where F: Fn(String) -> Option<Origin> + Send + Sync {
|
||||
fn domain(&self, token: &str) -> Option<Origin> { self(token.to_owned()) }
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
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| apps::App::from_info(k, info))
|
||||
}).collect()
|
||||
impl Extendable for ServerBuilder {
|
||||
fn add_delegate<D: Send + Sync + 'static>(&self, delegate: IoDelegate<D>) {
|
||||
self.handler.add_delegate(delegate);
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) -> Self {
|
||||
ServerBuilder {
|
||||
dapps_path: dapps_path,
|
||||
handler: Arc::new(IoHandler::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// Asynchronously start server with no authentication,
|
||||
/// returns result with `Server` handle on success or an error.
|
||||
pub fn start_unsecure_http(&self, addr: &SocketAddr) -> Result<Server, ServerError> {
|
||||
Server::start_http(addr, NoAuth, self.handler.clone(), self.dapps_path.clone())
|
||||
}
|
||||
|
||||
/// Creates new middleware for UI server.
|
||||
pub fn ui<F: Fetch>(
|
||||
pool: CpuPool,
|
||||
health: NodeHealth,
|
||||
dapps_domain: &str,
|
||||
registrar: Arc<ContractClient>,
|
||||
sync_status: Arc<SyncStatus>,
|
||||
fetch: F,
|
||||
) -> Self {
|
||||
let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(
|
||||
hash_fetch::urlhint::URLHintContract::new(registrar),
|
||||
sync_status.clone(),
|
||||
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())));
|
||||
/// 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, username: &str, password: &str) -> Result<Server, ServerError> {
|
||||
Server::start_http(addr, HttpBasicAuth::single_user(username, password), self.handler.clone(), self.dapps_path.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Webapps HTTP server.
|
||||
pub struct Server {
|
||||
server: Option<hyper::server::Listening>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
fn start_http<A: Authorization + 'static>(addr: &SocketAddr, authorization: A, handler: Arc<IoHandler>, dapps_path: String) -> Result<Server, ServerError> {
|
||||
let panic_handler = Arc::new(Mutex::new(None));
|
||||
let authorization = Arc::new(authorization);
|
||||
let endpoints = Arc::new(apps::all_endpoints(dapps_path));
|
||||
let special = Arc::new({
|
||||
let mut special = HashMap::new();
|
||||
special.insert(router::SpecialEndpoint::Rpc, rpc::rpc(handler, panic_handler.clone()));
|
||||
special.insert(router::SpecialEndpoint::Api, api::RestApi::new(endpoints.clone()));
|
||||
special.insert(router::SpecialEndpoint::Utils, apps::utils());
|
||||
special
|
||||
};
|
||||
let router = router::Router::new(
|
||||
content_fetcher,
|
||||
None,
|
||||
special,
|
||||
None,
|
||||
dapps_domain.to_owned(),
|
||||
);
|
||||
});
|
||||
|
||||
Middleware {
|
||||
endpoints: Default::default(),
|
||||
router: router,
|
||||
}
|
||||
try!(hyper::Server::http(addr))
|
||||
.handle(move |_| router::Router::new(
|
||||
apps::main_page(),
|
||||
endpoints.clone(),
|
||||
special.clone(),
|
||||
authorization.clone(),
|
||||
))
|
||||
.map(|l| Server {
|
||||
server: Some(l),
|
||||
panic_handler: panic_handler,
|
||||
})
|
||||
.map_err(ServerError::from)
|
||||
}
|
||||
|
||||
/// 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()),
|
||||
};
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
|
||||
let special = {
|
||||
let mut special = special_endpoints(
|
||||
pool.clone(),
|
||||
health,
|
||||
content_fetcher.clone(),
|
||||
);
|
||||
special.insert(
|
||||
router::SpecialEndpoint::Home,
|
||||
Some(apps::ui_redirection(embeddable.clone())),
|
||||
);
|
||||
special
|
||||
};
|
||||
impl Drop for Server {
|
||||
fn drop(&mut self) {
|
||||
self.server.take().unwrap().close()
|
||||
}
|
||||
}
|
||||
|
||||
let router = router::Router::new(
|
||||
content_fetcher,
|
||||
Some(endpoints.clone()),
|
||||
special,
|
||||
embeddable,
|
||||
dapps_domain.to_owned(),
|
||||
);
|
||||
/// Webapp Server startup error
|
||||
#[derive(Debug)]
|
||||
pub enum ServerError {
|
||||
/// Wrapped `std::io::Error`
|
||||
IoError(std::io::Error),
|
||||
/// Other `hyper` error
|
||||
Other(hyper::error::Error),
|
||||
}
|
||||
|
||||
Middleware {
|
||||
endpoints,
|
||||
router,
|
||||
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 {
|
||||
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,
|
||||
}
|
||||
|
||||
@@ -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,114 +14,74 @@
|
||||
// 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 page::handler;
|
||||
use std::sync::Arc;
|
||||
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
|
||||
use parity_dapps::{WebApp, File, Info};
|
||||
|
||||
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Request, Response};
|
||||
use page::{handler, PageCache};
|
||||
use Embeddable;
|
||||
|
||||
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: bool,
|
||||
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, 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: false,
|
||||
info: EndpointInfo::from(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 {
|
||||
/// 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();
|
||||
Dapp {
|
||||
pool,
|
||||
app,
|
||||
safe_to_embed_on: None,
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: Some(prefix),
|
||||
safe_to_embed: false,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new `Dapp` which can be safely used in iframe
|
||||
/// 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, app: T, address: Embeddable) -> Self {
|
||||
pub fn new_safe_to_embed(app: T) -> Self {
|
||||
let info = app.info();
|
||||
Dapp {
|
||||
pool,
|
||||
app,
|
||||
safe_to_embed_on: address,
|
||||
PageEndpoint {
|
||||
app: Arc::new(app),
|
||||
prefix: None,
|
||||
safe_to_embed: true,
|
||||
info: EndpointInfo::from(info),
|
||||
fallback_to_index_html: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
cache: PageCache::Disabled,
|
||||
safe_to_embed_on: self.safe_to_embed_on.clone(),
|
||||
}.into_response();
|
||||
|
||||
self.pool.spawn(reader).forget();
|
||||
|
||||
Box::new(future::ok(response))
|
||||
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: None,
|
||||
safe_to_embed: self.safe_to_embed,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,26 +92,63 @@ impl From<Info> for EndpointInfo {
|
||||
description: info.description.into(),
|
||||
author: info.author.into(),
|
||||
icon_url: info.icon_url.into(),
|
||||
local_url: None,
|
||||
version: info.version.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,101 +14,194 @@
|
||||
// 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 apps;
|
||||
use handlers::{Reader, ContentHandler, add_security_headers};
|
||||
use {Embeddable};
|
||||
use std::io::Write;
|
||||
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;
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// Defines what cache headers should be appended to returned resources.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum PageCache {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
/// Dapp as a (dynamic) set of files.
|
||||
pub trait Dapp: Send + 'static {
|
||||
/// File type
|
||||
type DappFile: DappFile;
|
||||
|
||||
impl Default for PageCache {
|
||||
fn default() -> Self {
|
||||
PageCache::Disabled
|
||||
}
|
||||
/// Returns file under given path.
|
||||
fn file(&self, path: &str) -> Option<Self::DappFile>;
|
||||
}
|
||||
|
||||
/// 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: Option<T::DappFile>,
|
||||
/// 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,
|
||||
/// Cache settings for this page.
|
||||
pub cache: PageCache,
|
||||
pub safe_to_embed: 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);
|
||||
}
|
||||
|
||||
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) -> 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,
|
||||
};
|
||||
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 {
|
||||
if let Some(ref f) = self.file {
|
||||
res.set_status(StatusCode::Ok);
|
||||
res.headers_mut().set(header::ContentType(f.content_type().parse().unwrap()));
|
||||
if !self.safe_to_embed {
|
||||
res.headers_mut().set_raw("X-Frame-Options", vec![b"SAMEORIGIN".to_vec()]);
|
||||
}
|
||||
Next::write()
|
||||
} else {
|
||||
res.set_status(StatusCode::NotFound);
|
||||
Next::write()
|
||||
}
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
match self.file {
|
||||
None => Next::end(),
|
||||
Some(ref f) if f.is_drained() => Next::end(),
|
||||
Some(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
|
||||
},
|
||||
file: None,
|
||||
safe_to_embed: true,
|
||||
};
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
@@ -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,133 +15,104 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use mime_guess;
|
||||
use std::{fs, fmt};
|
||||
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 std::io::{Seek, Read, SeekFrom};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use page::handler;
|
||||
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Dapp {
|
||||
pool: CpuPool,
|
||||
pub struct LocalPageEndpoint {
|
||||
path: PathBuf,
|
||||
mime: Option<Mime>,
|
||||
info: Option<EndpointInfo>,
|
||||
cache: PageCache,
|
||||
embeddable_on: Embeddable,
|
||||
info: EndpointInfo,
|
||||
}
|
||||
|
||||
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 LocalPageEndpoint {
|
||||
pub fn new(path: PathBuf, info: EndpointInfo) -> Self {
|
||||
LocalPageEndpoint {
|
||||
path: path,
|
||||
info: info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dapp {
|
||||
pub fn new(pool: CpuPool, path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Embeddable) -> Self {
|
||||
Dapp {
|
||||
pool,
|
||||
path,
|
||||
mime: None,
|
||||
info: Some(info),
|
||||
cache,
|
||||
embeddable_on,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn single_file(pool: CpuPool, path: PathBuf, mime: Mime, cache: PageCache) -> Self {
|
||||
Dapp {
|
||||
pool,
|
||||
path,
|
||||
mime: Some(mime),
|
||||
info: None,
|
||||
cache,
|
||||
embeddable_on: 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(),
|
||||
}.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()
|
||||
Some(&self.info)
|
||||
}
|
||||
|
||||
fn respond(&self, path: EndpointPath, _req: Request) -> Response {
|
||||
self.to_response(&path)
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
Box::new(handler::PageHandler {
|
||||
app: LocalDapp::new(self.path.clone()),
|
||||
prefix: None,
|
||||
path: path,
|
||||
file: None,
|
||||
safe_to_embed: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalFile {
|
||||
content_type: Mime,
|
||||
file: fs::File,
|
||||
struct LocalDapp {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl LocalFile {
|
||||
fn from_path<P: AsRef<Path>>(path: P, content_type: Mime) -> Option<Self> {
|
||||
trace!(target: "dapps", "Local file: {:?}", path.as_ref());
|
||||
impl LocalDapp {
|
||||
fn new(path: PathBuf) -> Self {
|
||||
LocalDapp {
|
||||
path: path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
// Check if file exists
|
||||
fs::File::open(&path).ok().map(|file| {
|
||||
fs::File::open(path.clone()).ok().map(|file| {
|
||||
let content_type = mime_guess::guess_mime_type(path);
|
||||
let len = file.metadata().ok().map_or(0, |meta| meta.len());
|
||||
LocalFile {
|
||||
content_type,
|
||||
file,
|
||||
content_type: content_type.to_string(),
|
||||
buffer: [0; 4096],
|
||||
file: file,
|
||||
pos: 0,
|
||||
len: len,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl handler::DappFile for LocalFile {
|
||||
type Reader = fs::File;
|
||||
struct LocalFile {
|
||||
content_type: String,
|
||||
buffer: [u8; 4096],
|
||||
file: fs::File,
|
||||
len: u64,
|
||||
pos: u64,
|
||||
}
|
||||
|
||||
fn content_type(&self) -> &Mime {
|
||||
impl handler::DappFile for LocalFile {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,10 @@
|
||||
// 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::handler::PageCache;
|
||||
pub use self::local::LocalPageEndpoint;
|
||||
pub use self::builtin::PageEndpoint;
|
||||
|
||||
|
||||
@@ -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,52 +16,32 @@
|
||||
|
||||
//! Serving ProxyPac file
|
||||
|
||||
use apps::HOME_PAGE;
|
||||
use endpoint::{Endpoint, Request, Response, EndpointPath};
|
||||
use futures::future;
|
||||
use handlers::ContentHandler;
|
||||
use hyper::mime;
|
||||
use {address, Embeddable};
|
||||
use endpoint::{Endpoint, Handler, ContentHandler, EndpointPath};
|
||||
use apps::DAPPS_DOMAIN;
|
||||
|
||||
pub struct ProxyPac {
|
||||
embeddable: Embeddable,
|
||||
dapps_domain: String,
|
||||
}
|
||||
pub struct ProxyPac;
|
||||
|
||||
impl ProxyPac {
|
||||
pub fn boxed(embeddable: Embeddable, dapps_domain: String) -> Box<Endpoint> {
|
||||
Box::new(ProxyPac { embeddable, dapps_domain })
|
||||
pub fn boxed() -> Box<Endpoint> {
|
||||
Box::new(ProxyPac)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
.unwrap_or_else(|| format!("{}:{}", path.host, path.port));
|
||||
|
||||
fn to_handler(&self, path: EndpointPath) -> Box<Handler> {
|
||||
let content = format!(
|
||||
r#"
|
||||
function FindProxyForURL(url, host) {{
|
||||
if (shExpMatch(host, "{0}.{1}"))
|
||||
if (shExpMatch(host, "*{0}"))
|
||||
{{
|
||||
return "PROXY {4}";
|
||||
}}
|
||||
|
||||
if (shExpMatch(host, "*.{1}"))
|
||||
{{
|
||||
return "PROXY {2}:{3}";
|
||||
return "PROXY {1}:{2}";
|
||||
}}
|
||||
|
||||
return "DIRECT";
|
||||
}}
|
||||
"#,
|
||||
HOME_PAGE, self.dapps_domain, path.host, path.port, ui);
|
||||
|
||||
Box::new(future::ok(
|
||||
ContentHandler::ok(content, mime::TEXT_JAVASCRIPT).into()
|
||||
))
|
||||
DAPPS_DOMAIN, path.host, path.port);
|
||||
Box::new(ContentHandler::new(content, "application/javascript".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
161
dapps/src/router/auth.rs
Normal file
161
dapps/src/router/auth.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
// 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::io::Write;
|
||||
use std::collections::HashMap;
|
||||
use hyper::{header, server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
/// Authorization result
|
||||
pub enum Authorized {
|
||||
/// Authorization was successful.
|
||||
Yes,
|
||||
/// Unsuccessful authorization. Handler for further work is returned.
|
||||
No(Box<server::Handler<HttpStream>>),
|
||||
}
|
||||
|
||||
/// Authorization interface
|
||||
pub trait Authorization : Send + Sync {
|
||||
/// Checks if authorization is valid.
|
||||
fn is_authorized(&self, req: &server::Request)-> 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)-> Authorized {
|
||||
Authorized::Yes
|
||||
}
|
||||
}
|
||||
|
||||
impl Authorization for HttpBasicAuth {
|
||||
fn is_authorized(&self, req: &server::Request) -> Authorized {
|
||||
let auth = self.check_auth(&req);
|
||||
|
||||
match auth {
|
||||
Access::Denied => {
|
||||
Authorized::No(Box::new(UnauthorizedHandler { write_pos: 0 }))
|
||||
},
|
||||
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) -> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UnauthorizedHandler {
|
||||
write_pos: usize,
|
||||
}
|
||||
|
||||
impl server::Handler<HttpStream> for UnauthorizedHandler {
|
||||
fn on_request(&mut self, _request: server::Request) -> 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);
|
||||
Next::write()
|
||||
}
|
||||
|
||||
fn on_response_writable(&mut self, encoder: &mut Encoder<HttpStream>) -> Next {
|
||||
let response = "Unauthorized".as_bytes();
|
||||
|
||||
if self.write_pos == response.len() {
|
||||
return Next::end();
|
||||
}
|
||||
|
||||
match encoder.write(&response[self.write_pos..]) {
|
||||
Ok(bytes) => {
|
||||
self.write_pos += bytes;
|
||||
Next::write()
|
||||
},
|
||||
Err(e) => match e.kind() {
|
||||
::std::io::ErrorKind::WouldBlock => Next::write(),
|
||||
_ => Next::end()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AuthRequiredHandler;
|
||||
|
||||
impl server::Handler<HttpStream> for AuthRequiredHandler {
|
||||
fn on_request(&mut self, _request: server::Request) -> 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()
|
||||
}
|
||||
}
|
||||
255
dapps/src/router/mod.rs
Normal file
255
dapps/src/router/mod.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
// 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.
|
||||
|
||||
mod url;
|
||||
mod redirect;
|
||||
pub mod auth;
|
||||
|
||||
use DAPPS_DOMAIN;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
use url::Host;
|
||||
use hyper;
|
||||
use hyper::{server, uri, header};
|
||||
use hyper::{Next, Encoder, Decoder};
|
||||
use hyper::net::HttpStream;
|
||||
use apps;
|
||||
use endpoint::{Endpoint, Endpoints, EndpointPath};
|
||||
use self::url::Url;
|
||||
use self::auth::{Authorization, Authorized};
|
||||
use self::redirect::Redirection;
|
||||
|
||||
/// 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> {
|
||||
main_page: &'static str,
|
||||
endpoints: Arc<Endpoints>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
authorization: Arc<A>,
|
||||
handler: Box<server::Handler<HttpStream>>,
|
||||
}
|
||||
|
||||
impl<A: Authorization + 'static> server::Handler<HttpStream> for Router<A> {
|
||||
|
||||
fn on_request(&mut self, req: server::Request) -> Next {
|
||||
// Check authorization
|
||||
let auth = self.authorization.is_authorized(&req);
|
||||
|
||||
// Choose proper handler depending on path / domain
|
||||
self.handler = match auth {
|
||||
Authorized::No(handler) => handler,
|
||||
Authorized::Yes => {
|
||||
let url = extract_url(&req);
|
||||
let endpoint = extract_endpoint(&url);
|
||||
|
||||
match endpoint {
|
||||
// First check special endpoints
|
||||
(ref path, ref endpoint) if self.special.contains_key(endpoint) => {
|
||||
self.special.get(endpoint).unwrap().to_handler(path.clone().unwrap_or_default())
|
||||
},
|
||||
// Then delegate to dapp
|
||||
(Some(ref path), _) if self.endpoints.contains_key(&path.app_id) => {
|
||||
self.endpoints.get(&path.app_id).unwrap().to_handler(path.clone())
|
||||
},
|
||||
// Redirection to main page
|
||||
_ if *req.method() == hyper::method::Method::Get => {
|
||||
Redirection::new(self.main_page)
|
||||
},
|
||||
// RPC by default
|
||||
_ => {
|
||||
self.special.get(&SpecialEndpoint::Rpc).unwrap().to_handler(EndpointPath::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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(
|
||||
main_page: &'static str,
|
||||
endpoints: Arc<Endpoints>,
|
||||
special: Arc<HashMap<SpecialEndpoint, Box<Endpoint>>>,
|
||||
authorization: Arc<A>) -> Self {
|
||||
|
||||
let handler = special.get(&SpecialEndpoint::Rpc).unwrap().to_handler(EndpointPath::default());
|
||||
Router {
|
||||
main_page: main_page,
|
||||
endpoints: endpoints,
|
||||
special: special,
|
||||
authorization: authorization,
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_url(req: &server::Request) -> 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,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}), 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,
|
||||
}), 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,
|
||||
}), 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,
|
||||
}), 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,
|
||||
}), 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,
|
||||
}), 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,
|
||||
}), 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,
|
||||
}), SpecialEndpoint::Api)
|
||||
);
|
||||
}
|
||||
55
dapps/src/router/redirect.rs
Normal file
55
dapps/src/router/redirect.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
// 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 Redirection hyper handler
|
||||
|
||||
use hyper::{header, server, Decoder, Encoder, Next};
|
||||
use hyper::net::HttpStream;
|
||||
use hyper::status::StatusCode;
|
||||
|
||||
pub struct Redirection {
|
||||
to_url: &'static str
|
||||
}
|
||||
|
||||
impl Redirection {
|
||||
pub fn new(url: &'static str) -> Box<Self> {
|
||||
Box::new(Redirection {
|
||||
to_url: url
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl server::Handler<HttpStream> for Redirection {
|
||||
fn on_request(&mut self, _request: server::Request) -> 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::MovedPermanently);
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
145
dapps/src/router/url.rs
Normal file
145
dapps/src/router/url.rs
Normal 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::Host;
|
||||
use url::{self};
|
||||
|
||||
/// HTTP/HTTPS URL type for Iron.
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub struct Url {
|
||||
/// Raw url of url
|
||||
pub raw: url::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::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::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");
|
||||
}
|
||||
}
|
||||
41
dapps/src/rpc.rs
Normal file
41
dapps/src/rpc.rs
Normal 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/>.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
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: vec![AccessControlAllowOrigin::Null],
|
||||
})
|
||||
}
|
||||
|
||||
struct RpcEndpoint {
|
||||
handler: Arc<IoHandler>,
|
||||
panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>,
|
||||
cors_domain: Vec<AccessControlAllowOrigin>,
|
||||
}
|
||||
|
||||
impl Endpoint for RpcEndpoint {
|
||||
fn to_handler(&self, _path: EndpointPath) -> Box<Handler> {
|
||||
let panic_handler = PanicHandler { handler: self.panic_handler.clone() };
|
||||
Box::new(ServerHandler::new(self.handler.clone(), self.cors_domain.clone(), panic_handler))
|
||||
}
|
||||
}
|
||||
@@ -1,86 +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, serve_with_registrar, request, assert_security_headers};
|
||||
|
||||
#[test]
|
||||
fn should_return_error() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /api/empty 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 404 Not Found");
|
||||
response.assert_header("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_handle_ping() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
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\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Content-Type", "application/json");
|
||||
assert_eq!(response.body, "0\n\n".to_owned());
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_try_to_resolve_dapp() {
|
||||
// given
|
||||
let (server, registrar) = serve_with_registrar();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /api/content/1472a9e190620cdf6b31f383373e45efcfe869a820c91f9ccd7eb9fb45e4985d HTTP/1.1\r\n\
|
||||
Host: home.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(), 2);
|
||||
assert_security_headers(&response.headers);
|
||||
}
|
||||
@@ -1,544 +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 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,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn should_resolve_dapp() {
|
||||
// given
|
||||
let (server, registrar) = serve_with_registrar();
|
||||
|
||||
// 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 404 Not Found");
|
||||
assert_eq!(registrar.calls.lock().len(), 4);
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_503_when_syncing_but_should_make_the_calls() {
|
||||
// given
|
||||
let (server, registrar) = serve_with_registrar_and_sync();
|
||||
|
||||
// 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\
|
||||
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("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#"D2
|
||||
{
|
||||
"id": "9c94e154dab8acf859b30ee80fc828fb1d38359d938751b65db71d460588d82a",
|
||||
"name": "Gavcoin",
|
||||
"description": "Gavcoin",
|
||||
"version": "1.0.0",
|
||||
"author": "",
|
||||
"iconUrl": "icon.png",
|
||||
"localUrl": null
|
||||
}
|
||||
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();
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,207 +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, request, assert_security_headers, assert_security_headers_for_embed};
|
||||
|
||||
#[test]
|
||||
fn should_redirect_to_home() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// 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 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.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_redirect_to_home_when_trailing_slash_is_missing() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /app 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 302 Found");
|
||||
assert_eq!(response.headers.get(0).unwrap(), "Location: http://127.0.0.1:18180");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_display_404_on_invalid_dapp() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /invaliddapp/ 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 404 Not Found");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_display_404_on_invalid_dapp_with_domain() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: invaliddapp.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 404 Not Found");
|
||||
assert_security_headers_for_embed(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_rpc() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST / HTTP/1.1\r\n\
|
||||
Host: 127.0.0.1:8080\r\n\
|
||||
Connection: close\r\n\
|
||||
Content-Type: application/json\r\n
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// 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}"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_rpc_at_slash_rpc() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
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
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// 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}"#));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_serve_proxy_pac() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /proxy/proxy.pac 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");
|
||||
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_security_headers(&response.headers);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_utils() {
|
||||
// given
|
||||
let server = serve();
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /parity-utils/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);
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -1,99 +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_hosts, request};
|
||||
|
||||
#[test]
|
||||
fn should_reject_invalid_host() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// 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 403 Forbidden");
|
||||
assert!(response.body.contains("Provided Host header is not whitelisted."), response.body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_allow_valid_host() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /ui/ 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");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_serve_dapps_domains() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: ui.web3.site\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
}
|
||||
|
||||
#[test]
|
||||
// NOTE [todr] This is required for error pages to be styled properly.
|
||||
fn should_allow_parity_utils_even_on_invalid_domain() {
|
||||
// given
|
||||
let server = serve_hosts(Some(vec!["localhost:8080".into()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
GET /parity-utils/styles.css 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");
|
||||
}
|
||||
166
dapps/src/web.rs
166
dapps/src/web.rs
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
description = "Ethcore Parity UI"
|
||||
homepage = "http://parity.io"
|
||||
license = "GPL-3.0"
|
||||
name = "parity-ui"
|
||||
version = "1.9.0"
|
||||
authors = ["Parity Technologies <admin@parity.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-beta-1-9-v1.git", optional = true }
|
||||
parity-ui-precompiled = { git = "https://github.com/js-dist-paritytech/parity-beta-1-9-shell.git", optional = true }
|
||||
|
||||
[features]
|
||||
no-precompiled-js = ["parity-ui-dev", "parity-ui-old-dev"]
|
||||
use-precompiled-js = ["parity-ui-precompiled", "parity-ui-old-precompiled"]
|
||||
26
db/Cargo.toml
Normal file
26
db/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
description = "Ethcore Database"
|
||||
homepage = "http://ethcore.io"
|
||||
license = "GPL-3.0"
|
||||
name = "ethcore-db"
|
||||
version = "1.2.0"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
syntex = "*"
|
||||
ethcore-ipc-codegen = { path = "../ipc/codegen" }
|
||||
|
||||
[dependencies]
|
||||
clippy = { version = "0.0.77", 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"]
|
||||
@@ -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,28 +14,30 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Eth PUB-SUB rpc interface.
|
||||
extern crate syntex;
|
||||
extern crate ethcore_ipc_codegen as codegen;
|
||||
|
||||
use jsonrpc_core::Result;
|
||||
use jsonrpc_macros::Trailing;
|
||||
use jsonrpc_macros::pubsub::Subscriber;
|
||||
use jsonrpc_pubsub::SubscriptionId;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
use v1::types::pubsub;
|
||||
pub fn main() {
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
|
||||
build_rpc_trait! {
|
||||
/// Eth PUB-SUB rpc interface.
|
||||
pub trait EthPubSub {
|
||||
type Metadata;
|
||||
// ipc pass
|
||||
{
|
||||
let src = Path::new("src/lib.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.intermediate.rs.in");
|
||||
let mut registry = syntex::Registry::new();
|
||||
codegen::register(&mut registry);
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
|
||||
#[pubsub(name = "eth_subscription")] {
|
||||
/// Subscribe to Eth subscription.
|
||||
#[rpc(name = "eth_subscribe")]
|
||||
fn subscribe(&self, Self::Metadata, Subscriber<pubsub::Result>, pubsub::Kind, Trailing<pubsub::Params>);
|
||||
|
||||
/// Unsubscribe from existing Eth subscription.
|
||||
#[rpc(name = "eth_unsubscribe")]
|
||||
fn unsubscribe(&self, SubscriptionId) -> Result<bool>;
|
||||
}
|
||||
// binary serialization pass
|
||||
{
|
||||
let src = Path::new(&out_dir).join("lib.intermediate.rs.in");
|
||||
let dst = Path::new(&out_dir).join("lib.rs");
|
||||
let mut registry = syntex::Registry::new();
|
||||
codegen::register(&mut registry);
|
||||
registry.expand("", &src, &dst).unwrap();
|
||||
}
|
||||
}
|
||||
573
db/src/database.rs
Normal file
573
db/src/database.rs
Normal file
@@ -0,0 +1,573 @@
|
||||
// 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};
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(s: String) -> Error {
|
||||
Error::RocksDb(s)
|
||||
}
|
||||
}
|
||||
|
||||
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: &Vec<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().unwrap();
|
||||
let db_lock = self.db.read().unwrap();
|
||||
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().unwrap();
|
||||
let db_lock = self.db.read().unwrap();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Ipc)]
|
||||
impl DatabaseService for Database {
|
||||
fn open(&self, config: DatabaseConfig, path: String) -> Result<(), Error> {
|
||||
let mut db = self.db.write().unwrap();
|
||||
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().unwrap();
|
||||
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().unwrap();
|
||||
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().unwrap();
|
||||
cache_lock.remove(key.to_vec());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&self, transaction: DBTransaction) -> Result<(), Error> {
|
||||
let mut cache_lock = self.write_cache.write().unwrap();
|
||||
|
||||
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().unwrap().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().unwrap();
|
||||
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().unwrap();
|
||||
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().unwrap();
|
||||
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().unwrap();
|
||||
let db = try!(db_lock.as_ref().ok_or(Error::IsClosed));
|
||||
|
||||
let mut iterators = self.iterators.write().unwrap();
|
||||
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().unwrap();
|
||||
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().unwrap();
|
||||
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::init_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::init_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::init_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::init_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::init_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());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#[cfg(feature = "with-syntex")]
|
||||
//! Database ipc service
|
||||
|
||||
#![allow(dead_code, unused_assignments, unused_variables)] // codegen issues
|
||||
include!(concat!(env!("OUT_DIR"), "/lib.rs"));
|
||||
|
||||
#[cfg(not(feature = "with-syntex"))]
|
||||
include!("lib.rs.in");
|
||||
|
||||
89
db/src/lib.rs.in
Normal file
89
db/src/lib.rs.in
Normal 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::init_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::init_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();
|
||||
}
|
||||
});
|
||||
}
|
||||
132
db/src/traits.rs
Normal file
132
db/src/traits.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
// 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::mem;
|
||||
use ipc::binary::BinaryConvertError;
|
||||
use std::collections::VecDeque;
|
||||
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,
|
||||
}
|
||||
|
||||
/// Database configuration
|
||||
#[derive(Binary)]
|
||||
pub struct DatabaseConfig {
|
||||
/// Optional prefix size in bytes. Allows lookup by partial key.
|
||||
pub prefix_size: Option<usize>,
|
||||
/// write cache length
|
||||
pub cache: usize,
|
||||
}
|
||||
|
||||
impl Default for DatabaseConfig {
|
||||
fn default() -> DatabaseConfig {
|
||||
DatabaseConfig {
|
||||
prefix_size: None,
|
||||
cache: DEFAULT_CACHE_LEN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseConfig {
|
||||
fn with_prefix(prefix: usize) -> DatabaseConfig {
|
||||
DatabaseConfig {
|
||||
prefix_size: Some(prefix),
|
||||
cache: DEFAULT_CACHE_LEN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DatabaseService : Sized {
|
||||
/// Opens database in the specified path
|
||||
fn open(&self, config: DatabaseConfig, path: String) -> Result<(), Error>;
|
||||
|
||||
/// Opens database in the specified path with the default config
|
||||
fn open_default(&self, path: String) -> Result<(), Error>;
|
||||
|
||||
/// Closes database
|
||||
fn close(&self) -> Result<(), Error>;
|
||||
|
||||
/// Insert a key-value pair in the transaction. Any existing value value will be overwritten.
|
||||
fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Error>;
|
||||
|
||||
/// Delete value by key.
|
||||
fn delete(&self, key: &[u8]) -> Result<(), Error>;
|
||||
|
||||
/// Get value by key.
|
||||
fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error>;
|
||||
|
||||
/// Get value by partial key. Prefix size should match configured prefix size.
|
||||
fn get_by_prefix(&self, prefix: &[u8]) -> Result<Option<Vec<u8>>, Error>;
|
||||
|
||||
/// Check if there is anything in the database.
|
||||
fn is_empty(&self) -> Result<bool, Error>;
|
||||
|
||||
/// Get handle to iterate through keys
|
||||
fn iter(&self) -> Result<IteratorHandle, Error>;
|
||||
|
||||
/// Next key-value for the the given iterator
|
||||
fn iter_next(&self, iterator: IteratorHandle) -> Option<KeyValue>;
|
||||
|
||||
/// Dispose iteration that is no longer needed
|
||||
fn dispose_iter(&self, handle: IteratorHandle) -> Result<(), Error>;
|
||||
|
||||
/// Write client transaction
|
||||
fn write(&self, transaction: DBTransaction) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
#[derive(Binary)]
|
||||
pub struct DBTransaction {
|
||||
pub writes: RefCell<Vec<KeyValue>>,
|
||||
pub removes: RefCell<Vec<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl DBTransaction {
|
||||
pub fn new() -> DBTransaction {
|
||||
DBTransaction {
|
||||
writes: RefCell::new(Vec::new()),
|
||||
removes: RefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put(&self, key: &[u8], value: &[u8]) {
|
||||
let mut brw = self.writes.borrow_mut();
|
||||
brw.push(KeyValue { key: key.to_vec(), value: value.to_vec() });
|
||||
}
|
||||
|
||||
pub fn delete(&self, key: &[u8]) {
|
||||
let mut brw = self.removes.borrow_mut();
|
||||
brw.push(key.to_vec());
|
||||
}
|
||||
}
|
||||
@@ -1,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.2.0"
|
||||
authors = ["Ethcore <admin@ethcore.io>"]
|
||||
|
||||
[dependencies]
|
||||
rand = "0.3"
|
||||
|
||||
@@ -1,132 +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::thread;
|
||||
use std::time::Duration;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::str::{self, Lines};
|
||||
use std::net::{TcpStream, SocketAddr};
|
||||
|
||||
pub struct Response {
|
||||
pub status: String,
|
||||
pub headers: Vec<String>,
|
||||
pub headers_raw: String,
|
||||
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 {
|
||||
let line = lines.next();
|
||||
match line {
|
||||
None => break,
|
||||
Some("") if !all => break,
|
||||
Some(v) => {
|
||||
block.push_str(v);
|
||||
block.push_str("\n");
|
||||
},
|
||||
}
|
||||
}
|
||||
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();
|
||||
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 response = String::from_utf8_lossy(&response).into_owned();
|
||||
let mut lines = response.lines();
|
||||
let status = lines.next().expect("Expected a response").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);
|
||||
|
||||
Response {
|
||||
status: status,
|
||||
headers: headers,
|
||||
headers_raw: headers_raw,
|
||||
body: body,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if all required security headers are present
|
||||
pub fn assert_security_headers_present(headers: &[String], port: Option<u16>) {
|
||||
if let None = port {
|
||||
assert!(
|
||||
headers.iter().find(|header| header.as_str() == "X-Frame-Options: SAMEORIGIN").is_some(),
|
||||
"X-Frame-Options: SAMEORIGIN missing: {:?}", headers
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
headers.iter().find(|header| header.as_str() == "X-XSS-Protection: 1; mode=block").is_some(),
|
||||
"X-XSS-Protection missing: {:?}", headers
|
||||
);
|
||||
assert!(
|
||||
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
|
||||
)
|
||||
}
|
||||
@@ -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,10 +19,9 @@
|
||||
|
||||
extern crate rand;
|
||||
|
||||
mod random_path;
|
||||
mod test_socket;
|
||||
mod stop_guard;
|
||||
pub mod http_client;
|
||||
pub mod random_path;
|
||||
pub mod test_socket;
|
||||
pub mod stop_guard;
|
||||
|
||||
pub use random_path::*;
|
||||
pub use test_socket::*;
|
||||
|
||||
@@ -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,12 +19,10 @@
|
||||
use std::path::*;
|
||||
use std::fs;
|
||||
use std::env;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use rand::random;
|
||||
|
||||
pub struct RandomTempPath {
|
||||
path: PathBuf,
|
||||
pub panic_on_drop_failure: bool,
|
||||
path: PathBuf
|
||||
}
|
||||
|
||||
pub fn random_filename() -> String {
|
||||
@@ -40,8 +38,7 @@ impl RandomTempPath {
|
||||
let mut dir = env::temp_dir();
|
||||
dir.push(random_filename());
|
||||
RandomTempPath {
|
||||
path: dir.clone(),
|
||||
panic_on_drop_failure: true,
|
||||
path: dir.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +47,7 @@ impl RandomTempPath {
|
||||
dir.push(random_filename());
|
||||
fs::create_dir_all(dir.as_path()).unwrap();
|
||||
RandomTempPath {
|
||||
path: dir.clone(),
|
||||
panic_on_drop_failure: true,
|
||||
path: dir.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,59 +66,14 @@ impl RandomTempPath {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Path> for RandomTempPath {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.as_path()
|
||||
}
|
||||
}
|
||||
impl Deref for RandomTempPath {
|
||||
type Target = Path;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_path()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RandomTempPath {
|
||||
fn drop(&mut self) {
|
||||
if let Err(_) = fs::remove_dir_all(&self) {
|
||||
if let Err(e) = fs::remove_file(&self) {
|
||||
if self.panic_on_drop_failure {
|
||||
panic!("Failed to remove temp directory. Here's what prevented this from happening: ({})", e);
|
||||
}
|
||||
}
|
||||
if let Err(e) = fs::remove_dir_all(self.as_path()) {
|
||||
panic!("Failed to remove temp directory. Here's what prevented this from happening: ({})", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GuardedTempResult<T> {
|
||||
pub result: Option<T>,
|
||||
pub _temp: RandomTempPath,
|
||||
}
|
||||
|
||||
impl<T> GuardedTempResult<T> {
|
||||
pub fn reference(&self) -> &T {
|
||||
self.result.as_ref().unwrap()
|
||||
}
|
||||
|
||||
pub fn reference_mut(&mut self) -> &mut T {
|
||||
self.result.as_mut().unwrap()
|
||||
}
|
||||
|
||||
pub fn take(&mut self) -> T {
|
||||
self.result.take().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for GuardedTempResult<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &T { self.result.as_ref().unwrap() }
|
||||
}
|
||||
|
||||
impl<T> DerefMut for GuardedTempResult<T> {
|
||||
fn deref_mut(&mut self) -> &mut T { self.result.as_mut().unwrap() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creates_dir() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -62,7 +62,6 @@ impl TestSocket {
|
||||
impl Read for TestSocket {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||
let end_position = cmp::min(self.read_buffer.len(), self.cursor+buf.len());
|
||||
if self.cursor > end_position { return Ok(0) }
|
||||
let len = cmp::max(end_position - self.cursor, 0);
|
||||
match len {
|
||||
0 => Ok(0),
|
||||
@@ -70,7 +69,7 @@ impl Read for TestSocket {
|
||||
for i in self.cursor..end_position {
|
||||
buf[i-self.cursor] = self.read_buffer[i];
|
||||
}
|
||||
self.cursor = end_position;
|
||||
self.cursor = self.cursor + buf.len();
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
|
||||
14
doc.sh
Executable file
14
doc.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# generate documentation only for partiy and ethcore libraries
|
||||
|
||||
cargo doc --no-deps --verbose \
|
||||
-p ethkey \
|
||||
-p ethstore \
|
||||
-p ethash \
|
||||
-p ethcore-util \
|
||||
-p ethcore \
|
||||
-p ethsync \
|
||||
-p ethcore-rpc \
|
||||
-p ethcore-signer \
|
||||
-p ethcore-dapps \
|
||||
-p parity \
|
||||
@@ -1,3 +0,0 @@
|
||||
Usage
|
||||
|
||||
```docker build -f docker/ubuntu/Dockerfile --tag ethcore/parity:branch_or_tag_name .```
|
||||
@@ -1,36 +1,25 @@
|
||||
FROM centos:latest
|
||||
WORKDIR /build
|
||||
|
||||
# install tools and dependencies
|
||||
RUN yum -y update&& \
|
||||
yum install -y git make gcc-c++ gcc file binutils
|
||||
|
||||
yum install -y git make gcc-c++ gcc file
|
||||
# install rustup
|
||||
RUN curl -sSf https://static.rust-lang.org/rustup.sh -o rustup.sh &&\
|
||||
ls&&\
|
||||
sh rustup.sh --disable-sudo
|
||||
|
||||
ls&&\
|
||||
sh rustup.sh -s -- --disable-sudo
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# set compiler
|
||||
ENV CXX g++
|
||||
ENV CC gcc
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && \
|
||||
cargo -V && \
|
||||
gcc -v &&\
|
||||
g++ -v
|
||||
|
||||
# build parity
|
||||
ADD . /build/parity
|
||||
RUN cd parity&&\
|
||||
cargo build --release --verbose && \
|
||||
# git clone parity
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity&&\
|
||||
ls -a&&\
|
||||
cargo build --release --verbose && \
|
||||
ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity
|
||||
|
||||
file /build/parity/target/release/parity && \
|
||||
RUN file /build/parity/target/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/release/parity"]
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
MAINTAINER Parity Technologies <devops@parity.io>
|
||||
WORKDIR /build
|
||||
#ENV for build TAG
|
||||
ARG BUILD_TAG
|
||||
ENV BUILD_TAG ${BUILD_TAG:-master}
|
||||
RUN echo "Build tag:" $BUILD_TAG
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --force-yes --no-install-recommends \
|
||||
# make
|
||||
build-essential \
|
||||
# add-apt-repository
|
||||
software-properties-common \
|
||||
make \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
g++ \
|
||||
gcc \
|
||||
libc6 \
|
||||
libc6-dev \
|
||||
binutils \
|
||||
file \
|
||||
openssl \
|
||||
libssl-dev \
|
||||
libudev-dev \
|
||||
pkg-config \
|
||||
dpkg-dev \
|
||||
# evmjit dependencies
|
||||
zlib1g-dev \
|
||||
libedit-dev \
|
||||
libudev-dev &&\
|
||||
# cmake and llvm ppa's. then update ppa's
|
||||
add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \
|
||||
add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \
|
||||
apt-get update && \
|
||||
apt-get install -y --force-yes cmake llvm-3.7-dev && \
|
||||
# install evmjit
|
||||
git clone https://github.com/debris/evmjit && \
|
||||
cd evmjit && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. && make && make install && cd && \
|
||||
# install rustup
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y && \
|
||||
# rustup directory
|
||||
PATH=/root/.cargo/bin:$PATH && \
|
||||
# show backtraces
|
||||
RUST_BACKTRACE=1 && \
|
||||
# build parity
|
||||
cd /build&&git clone https://github.com/paritytech/parity && \
|
||||
cd parity && \
|
||||
git pull&& \
|
||||
git checkout $BUILD_TAG && \
|
||||
cargo build --verbose --release --features final && \
|
||||
#ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity && \
|
||||
file /build/parity/target/release/parity&&mkdir -p /parity&& cp /build/parity/target/release/parity /parity&&\
|
||||
#cleanup Docker image
|
||||
rm -rf /root/.cargo&&rm -rf /root/.multirust&&rm -rf /root/.rustup&&rm -rf /build&&\
|
||||
apt-get purge -y \
|
||||
# make
|
||||
build-essential \
|
||||
# add-apt-repository
|
||||
software-properties-common \
|
||||
make \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
g++ \
|
||||
gcc \
|
||||
binutils \
|
||||
file \
|
||||
pkg-config \
|
||||
dpkg-dev \
|
||||
# evmjit dependencies
|
||||
zlib1g-dev \
|
||||
libedit-dev \
|
||||
cmake llvm-3.7-dev&&\
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
# setup ENTRYPOINT
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/parity/parity"]
|
||||
@@ -1,45 +0,0 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get -y update && \
|
||||
apt-get install -y --force-yes --no-install-recommends \
|
||||
curl git make g++ gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \
|
||||
libc6-arm64-cross libc6-dev-arm64-cross wget file ca-certificates \
|
||||
binutils-aarch64-linux-gnu \
|
||||
&& \
|
||||
apt-get clean
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
|
||||
# rustup directory
|
||||
ENV PATH /root/.cargo/bin:$PATH
|
||||
|
||||
ENV RUST_TARGETS="aarch64-unknown-linux-gnu"
|
||||
|
||||
# multirust add arm--linux-gnuabhf toolchain
|
||||
RUN rustup target add aarch64-unknown-linux-gnu
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && cargo -V
|
||||
|
||||
# build parity
|
||||
ADD . /build/parity
|
||||
RUN cd parity && \
|
||||
mkdir -p .cargo && \
|
||||
echo '[target.aarch64-unknown-linux-gnu]\n\
|
||||
linker = "aarch64-linux-gnu-gcc"\n'\
|
||||
>>.cargo/config && \
|
||||
cat .cargo/config && \
|
||||
cargo build --target aarch64-unknown-linux-gnu --release --verbose && \
|
||||
ls /build/parity/target/aarch64-unknown-linux-gnu/release/parity && \
|
||||
/usr/bin/aarch64-linux-gnu-strip /build/parity/target/aarch64-unknown-linux-gnu/release/parity
|
||||
|
||||
RUN file /build/parity/target/aarch64-unknown-linux-gnu/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/aarch64-unknown-linux-gnu/release/parity"]
|
||||
@@ -1,13 +1,12 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get -y update && \
|
||||
apt-get install -y --force-yes --no-install-recommends \
|
||||
curl git make g++ gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf \
|
||||
libc6-dev-armhf-cross wget file ca-certificates \
|
||||
binutils-arm-linux-gnueabihf \
|
||||
&& \
|
||||
apt-get install -y --force-yes --no-install-recommends \
|
||||
curl git make g++ gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf \
|
||||
libc6-dev-armhf-cross wget file ca-certificates \
|
||||
binutils-arm-linux-gnueabihf \
|
||||
&& \
|
||||
apt-get clean
|
||||
|
||||
# install rustup
|
||||
@@ -19,27 +18,33 @@ ENV PATH /root/.cargo/bin:$PATH
|
||||
ENV RUST_TARGETS="arm-unknown-linux-gnueabihf"
|
||||
|
||||
# multirust add arm--linux-gnuabhf toolchain
|
||||
RUN rustup target add armv7-unknown-linux-gnueabihf
|
||||
RUN rustup target add stable arm-unknown-linux-gnueabihf
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && cargo -V
|
||||
|
||||
# set compilers
|
||||
ENV CXX arm-linux-gnueabihf-g++
|
||||
ENV CC arm-linux-gnueabihf-gcc
|
||||
# build parity
|
||||
ADD . /build/parity
|
||||
RUN cd parity && \
|
||||
mkdir -p .cargo && \
|
||||
echo '[target.armv7-unknown-linux-gnueabihf]\n\
|
||||
linker = "arm-linux-gnueabihf-gcc"\n'\
|
||||
>>.cargo/config && \
|
||||
cat .cargo/config && \
|
||||
cargo build --target armv7-unknown-linux-gnueabihf --release --verbose && \
|
||||
ls /build/parity/target/armv7-unknown-linux-gnueabihf/release/parity && \
|
||||
/usr/bin/arm-linux-gnueabihf-strip /build/parity/target/armv7-unknown-linux-gnueabihf/release/parity
|
||||
|
||||
RUN file /build/parity/target/armv7-unknown-linux-gnueabihf/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/armv7-unknown-linux-gnueabihf/release/parity"]
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
git checkout master && \
|
||||
wget https://github.com/nix-rust/nix/archive/v0.5.0.tar.gz && \
|
||||
tar -xf v0.5.0.tar.gz && \
|
||||
rm -rf v0.5.0.tar.gz && \
|
||||
wget https://github.com/thkaw/mio/archive/v0.5.x.tar.gz && \
|
||||
tar -xf v0.5.x.tar.gz && \
|
||||
rm -rf v0.5.x.tar.gz && \
|
||||
mkdir -p .cargo && \
|
||||
echo 'paths = ["nix-0.5.0","mio-0.5.x"]\n\
|
||||
[target.arm-unknown-linux-gnueabihf]\n\
|
||||
linker = "arm-linux-gnueabihf-gcc"\n'\
|
||||
>>.cargo/config && \
|
||||
cat .cargo/config && \
|
||||
rustc -vV && \
|
||||
cargo -V && \
|
||||
cargo build --target arm-unknown-linux-gnueabihf --release --verbose && \
|
||||
ls /build/parity/target/arm-unknown-linux-gnueabihf/release/parity && \
|
||||
file /build/parity/target/arm-unknown-linux-gnueabihf/release/parity && \
|
||||
/usr/bin/arm-linux-gnueabihf-strip /build/parity/target/arm-unknown-linux-gnueabihf/release/parity
|
||||
RUN file /build/parity/target/arm-unknown-linux-gnueabihf/release/parity
|
||||
|
||||
37
docker/ubuntu-dev/Dockerfile
Normal file
37
docker/ubuntu-dev/Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
FROM ubuntu:14.04
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
# make
|
||||
build-essential \
|
||||
# add-apt-repository
|
||||
software-properties-common \
|
||||
curl \
|
||||
g++ \
|
||||
wget \
|
||||
git \
|
||||
# evmjit dependencies
|
||||
zlib1g-dev \
|
||||
libedit-dev
|
||||
|
||||
# cmake, llvm and rocksdb ppas. then update ppas
|
||||
RUN add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \
|
||||
add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \
|
||||
apt-get update && \
|
||||
apt-get install -y --force-yes cmake llvm-3.7-dev
|
||||
|
||||
# install evmjit
|
||||
RUN git clone https://github.com/debris/evmjit && \
|
||||
cd evmjit && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. && make && make install && cd
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
|
||||
# rustup directory
|
||||
ENV PATH /root/.cargo/bin:$PATH
|
||||
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
@@ -1,34 +1,31 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
# make
|
||||
build-essential \
|
||||
# add-apt-repository
|
||||
software-properties-common \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
g++ \
|
||||
binutils \
|
||||
file \
|
||||
# evmjit dependencies
|
||||
zlib1g-dev \
|
||||
libedit-dev
|
||||
apt-get install -y \
|
||||
# make
|
||||
build-essential \
|
||||
# add-apt-repository
|
||||
software-properties-common \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
g++ \
|
||||
# evmjit dependencies
|
||||
zlib1g-dev \
|
||||
libedit-dev
|
||||
|
||||
# cmake and llvm ppas. then update ppas
|
||||
# cmake, llvm and rocksdb ppas. then update ppas
|
||||
RUN add-apt-repository -y "ppa:george-edison55/cmake-3.x" && \
|
||||
add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \
|
||||
apt-get update && \
|
||||
apt-get install -y --force-yes cmake llvm-3.7-dev
|
||||
add-apt-repository "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.7 main" && \
|
||||
apt-get update && \
|
||||
apt-get install -y --force-yes cmake llvm-3.7-dev
|
||||
|
||||
# install evmjit
|
||||
RUN git clone https://github.com/debris/evmjit && \
|
||||
cd evmjit && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. && make && make install && cd
|
||||
cd evmjit && \
|
||||
mkdir build && cd build && \
|
||||
cmake .. && make && make install && cd
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
@@ -39,20 +36,7 @@ ENV PATH /root/.cargo/bin:$PATH
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && \
|
||||
cargo -V && \
|
||||
gcc -v &&\
|
||||
g++ -v
|
||||
|
||||
# build parity
|
||||
ADD . /build/parity
|
||||
RUN cd parity && \
|
||||
cargo build --release --features ethcore/jit --verbose && \
|
||||
ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity
|
||||
|
||||
RUN file /build/parity/target/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/release/parity"]
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
cargo build --release --features ethcore/jit
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
FROM ubuntu:14.04
|
||||
WORKDIR /build
|
||||
|
||||
# install tools and dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
g++ \
|
||||
build-essential \
|
||||
curl \
|
||||
git \
|
||||
file \
|
||||
binutils \
|
||||
libssl-dev \
|
||||
pkg-config \
|
||||
libudev-dev
|
||||
apt-get install -y \
|
||||
g++ \
|
||||
curl \
|
||||
git \
|
||||
make
|
||||
|
||||
# install rustup
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
@@ -23,20 +17,7 @@ ENV PATH /root/.cargo/bin:$PATH
|
||||
# show backtraces
|
||||
ENV RUST_BACKTRACE 1
|
||||
|
||||
# show tools
|
||||
RUN rustc -vV && \
|
||||
cargo -V && \
|
||||
gcc -v &&\
|
||||
g++ -v
|
||||
|
||||
# build parity
|
||||
ADD . /build/parity
|
||||
RUN cd parity && \
|
||||
cargo build --release --verbose && \
|
||||
ls /build/parity/target/release/parity && \
|
||||
strip /build/parity/target/release/parity
|
||||
|
||||
RUN file /build/parity/target/release/parity
|
||||
|
||||
EXPOSE 8080 8545 8180
|
||||
ENTRYPOINT ["/build/parity/target/release/parity"]
|
||||
RUN git clone https://github.com/ethcore/parity && \
|
||||
cd parity && \
|
||||
cargo build --release
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user