├── tests
├── env
│ ├── test.1
│ └── .gitignore
├── cors
│ ├── test.yaml
│ ├── README.md
│ └── index.html
├── go.mod
├── go.sum
└── structs.go
├── .dockerignore
├── TODO.md
├── profiler
├── .gitignore
├── environment
│ ├── test_ws4sqlite.yaml
│ └── test.yaml
├── stress_sqliterg.sh
├── stress_ws4sqlite.sh
└── Profile.java
├── Dockerfile.binaries
├── .gitignore
├── Dockerfile
├── CHANGELOG.md
├── RELEASE_CHECKLIST.md
├── CONTRIBUTING.md
├── Cargo.toml
├── src
├── commandline.rs
├── auth.rs
├── req_res.rs
├── db_config.rs
├── main.rs
├── commons.rs
├── backup.rs
├── main_config.rs
├── macros.rs
└── logic.rs
├── Makefile
├── db_conf.template.yaml
├── CODE_OF_CONDUCT.md
├── README.md
├── LICENSE
└── Cargo.lock
/tests/env/test.1:
--------------------------------------------------------------------------------
1 | 1
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | target
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | - Comments
2 |
--------------------------------------------------------------------------------
/tests/env/.gitignore:
--------------------------------------------------------------------------------
1 | *.db*
2 | *.yaml
3 | backup/
--------------------------------------------------------------------------------
/profiler/.gitignore:
--------------------------------------------------------------------------------
1 | environment/*db*
2 | *.class
3 | ws4sqlite*
--------------------------------------------------------------------------------
/tests/cors/test.yaml:
--------------------------------------------------------------------------------
1 | corsOrigin: "*"
2 | auth:
3 | mode: HTTP_BASIC
4 | byCredentials:
5 | - user: user
6 | password: ciao
7 |
--------------------------------------------------------------------------------
/tests/go.mod:
--------------------------------------------------------------------------------
1 | module sqliterg_tests
2 |
3 | go 1.21
4 |
5 | require (
6 | github.com/stretchr/testify v1.8.4
7 | gopkg.in/yaml.v3 v3.0.1
8 | )
9 |
10 | require (
11 | github.com/davecgh/go-spew v1.1.1 // indirect
12 | github.com/pmezard/go-difflib v1.0.0 // indirect
13 | )
14 |
--------------------------------------------------------------------------------
/profiler/environment/test_ws4sqlite.yaml:
--------------------------------------------------------------------------------
1 | auth:
2 | mode: INLINE
3 | byQuery: SELECT 1 FROM AUTH WHERE USER = :user AND PASS = :password
4 | corsOrigin: "*"
5 | initStatements:
6 | - CREATE TABLE IF NOT EXISTS TBL (ID INT, VAL TEXT)
7 | - CREATE TABLE IF NOT EXISTS AUTH (USER TEXT, PASS TEXT)
8 | - DELETE FROM AUTH
9 | - INSERT INTO AUTH VALUES ('myUser', 'ciao')
--------------------------------------------------------------------------------
/tests/cors/README.md:
--------------------------------------------------------------------------------
1 | To test CORS, setup two sqliterg like this (suppose to be in project's base dir):
2 |
3 | ```bash
4 | sqliterg --mem-db test::tests/cors/test.yaml &
5 | sqliterg --serve-dir tests/cors/ --port 12322 --index-file index.html &
6 | ```
7 |
8 | Then visit `http://localhost:12322` with a browser; in the network debugger you should find that the OPTIONS call (preflight) and the POST call are successful.
--------------------------------------------------------------------------------
/Dockerfile.binaries:
--------------------------------------------------------------------------------
1 | # Used by make docker-zbuild-linux. See BUILDING.md
2 |
3 | FROM rust:latest as build
4 |
5 | RUN apt-get update
6 | RUN apt-get full-upgrade -y
7 | RUN apt-get install -y zip
8 | # RUN cargo install -f cross
9 |
10 | WORKDIR /abc
11 | COPY . .
12 |
13 | RUN make build-static-nostatic
14 |
15 | # Now copy it into our base image.
16 | FROM scratch AS export
17 | COPY --from=build /abc/bin/* .
--------------------------------------------------------------------------------
/profiler/environment/test.yaml:
--------------------------------------------------------------------------------
1 | auth:
2 | mode: INLINE
3 | byQuery: SELECT 1 FROM AUTH WHERE USER = :user AND PASS = :password
4 | corsOrigin: "*"
5 | macros:
6 | - id: M1
7 | statements:
8 | - CREATE TABLE IF NOT EXISTS TBL (ID INT, VAL TEXT)
9 | - CREATE TABLE IF NOT EXISTS AUTH (USER TEXT, PASS TEXT)
10 | - DELETE FROM AUTH
11 | - INSERT INTO AUTH VALUES ('myUser', 'ciao')
12 | execution:
13 | onCreate: true
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Mine
2 |
3 | clippy_results.txt
4 | bin/
5 |
6 | # Generated by Cargo
7 | # will have compiled files and executables
8 | debug/
9 | target/
10 |
11 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
12 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
13 | # Cargo.lock
14 |
15 | # These are backup files generated by rustfmt
16 | **/*.rs.bk
17 |
18 | # MSVC Windows builds of rustc generate these, which store debugging information
19 | *.pdb
20 |
--------------------------------------------------------------------------------
/profiler/stress_sqliterg.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd "$(dirname "$0")"
4 |
5 | URL="http://localhost:12321/test"
6 | REQUESTS=20000
7 |
8 | mkdir environment/backup
9 | rm -f environment/*.db*
10 |
11 | pkill -x ws4sqlite
12 | pkill -x sqliterg
13 |
14 | cd ..
15 | cargo build --release
16 | cd profiler
17 | ../target/release/sqliterg --db environment/test.db &
18 |
19 | javac Profile.java
20 |
21 | sleep 1
22 |
23 | echo -n "Elapsed seconds in sqliterg: "
24 | java -cp ./ Profile $REQUESTS $URL $REQ
25 |
26 | rm Profile.class
27 |
28 | pkill -x sqliterg
29 | rm -f environment/*.db*
30 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:latest as build
2 |
3 | RUN apk add --no-cache rust cargo sqlite-libs sqlite-dev sed
4 |
5 | COPY . /build
6 | WORKDIR /build
7 |
8 | RUN cp Cargo.toml Cargo.toml.orig
9 | RUN sed 's/^rusqlite.*$/rusqlite = { version = "~0", features = ["serde_json", "load_extension"] }/' Cargo.toml.orig > Cargo.toml
10 |
11 | RUN ["cargo", "build", "--release"]
12 |
13 | # Now copy it into our base image.
14 | FROM alpine:latest
15 |
16 | COPY --from=build /build/target/release/sqliterg /
17 |
18 | # TODO why libgcc?
19 | RUN apk add --no-cache sqlite-libs libgcc
20 |
21 | EXPOSE 12321
22 | VOLUME /data
23 |
24 | CMD ["/sqliterg"]
25 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # v0.18.0 - 4 December 2023
2 |
3 | - [Issue #1] Implement positional parameters for SQL;
4 | - [Issue #2] Consider loading of sqlite extension on startup. (or load_extension statements in macro);
5 | - Library updates.
6 |
7 | # v0.17.1 - 3 October 2023
8 |
9 | - Aligned to [the documentation](https://docs.sqliterg.dev) (for the first time);
10 | - For backup and macros web services, the `Content-Type` header is not needed;
11 | - At authentication failures, wait 1 second;
12 | - Check for non-empty statement list in macros;
13 | - Check that the backup directory is not the same as the database file;
14 | - Library updates.
15 |
16 | # v0.17.0 - 1 October 2023
17 |
18 | First version
19 |
--------------------------------------------------------------------------------
/RELEASE_CHECKLIST.md:
--------------------------------------------------------------------------------
1 | - `make update`
2 | - `make clean`
3 | - `make test` [~5min]
4 | - Git: commit ("Pre-release commit") + push
5 | - Git(flow): Release start + push the branch
6 | - Update the version number
7 | - Update the changelog
8 | - Git: commit ("Version labeling & changelog") + push
9 | - `make docker` [33min]
10 | - `make docker-zbuild-linux` [1h]
11 | - Compile on macos
12 | - checkout the release branch
13 | - `make build-macos` [17min]
14 | - Compile on windows
15 | - checkout the release branch
16 | - `cargo build --release` [7min]
17 | - Zip to a filename like `sqliterg-v0.x.y-win-x86_64-bundled.zip`
18 | - Git(flow): Release finish
19 | - Assemble the release on Github
20 | - `make clean` (again)
21 |
--------------------------------------------------------------------------------
/profiler/stress_ws4sqlite.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | URL="http://localhost:12321/test_ws4sqlite"
4 | REQUESTS=20000
5 |
6 | cd "$(dirname "$0")"
7 |
8 | rm -f environment/*.db*
9 | rm -f ws4sqlite*
10 |
11 | pkill -x ws4sqlite
12 | pkill -x sqliterg
13 |
14 | wget -q https://github.com/proofrock/ws4sqlite/releases/download/v0.15.1/ws4sqlite-v0.15.1-linux-amd64.tar.gz
15 | tar xzf ws4sqlite-v0.15.0-linux-amd64.tar.gz &> /dev/null
16 |
17 | ./ws4sqlite --db environment/test_ws4sqlite.db &
18 |
19 | javac Profile.java
20 |
21 | sleep 1
22 |
23 | echo -n "Elapsed seconds in ws4sqlite: "
24 | java -cp ./ Profile $REQUESTS $URL $REQ
25 |
26 | rm Profile.class
27 |
28 | pkill -x ws4sqlite
29 | rm -f ws4sqlite*
30 | rm -f environment/*.db*
31 |
--------------------------------------------------------------------------------
/tests/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
6 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
10 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
11 |
--------------------------------------------------------------------------------
/tests/cors/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Pull Request Process
9 |
10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a
11 | build.
12 | 2. Update the README.md with details of changes to the interface, this includes new environment
13 | variables, exposed ports, useful file locations and container parameters.
14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this
15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
17 | do not have permission to do that, you may request the second reviewer to merge it for you.
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | authors = ["Germano Rizzo "]
3 | description = "A SQLite remote gateway - query SQLite via HTTP"
4 | name = "sqliterg"
5 | version = "0.18.0"
6 | edition = "2021"
7 | license = "Apache-2.0"
8 |
9 | [dependencies]
10 | actix-cors = "~0"
11 | actix-files = "~0"
12 | actix-web = "~4"
13 | actix-web-httpauth = "~0"
14 | chrono = "~0"
15 | clap = { version = "~4", features = [ "derive" ] }
16 | eyre = "~0"
17 | hex = "~0"
18 | ring = "~0"
19 | # rusqlite = { git = "https://github.com/rusqlite/rusqlite", features = ["serde_json", "load_extension"] }
20 | rusqlite = { version = "~0", features = ["bundled", "serde_json", "load_extension" ] }
21 | # rusqlite = { version = "~0", features = ["serde_json", "load_extension"] }
22 | serde = { version = "~1", features = ["derive"] }
23 | serde_derive = "~1"
24 | serde_json = "~1"
25 | serde_yaml = "~0"
26 | shellexpand = "~3"
27 |
28 | [profile.dev]
29 | opt-level = 0
30 | overflow-checks = true
31 | strip = false
32 | lto = false
33 |
34 | [profile.release]
35 | opt-level = 3
36 | overflow-checks = false
37 | strip = true
38 | lto = true
39 |
40 | [[bin]]
41 | name = "sqliterg"
42 | path = "src/main.rs"
--------------------------------------------------------------------------------
/src/commandline.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023-, Germano Rizzo
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use clap::Parser;
16 |
17 | use crate::commons::{assert, is_dir, resolve_tilde};
18 |
19 | #[derive(Debug, Parser)]
20 | #[command(author, version, about, long_about = None)]
21 | #[command(
22 | help_template = "{name} {version}\n {about-section}\n {usage-heading} {usage}\n {all-args} {tab}"
23 | )]
24 | pub struct AppConfig {
25 | #[arg(
26 | long,
27 | value_name = "HOST",
28 | default_value = "0.0.0.0",
29 | help = "The host to bind"
30 | )]
31 | pub bind_host: String,
32 | #[arg(long, value_name = "DB_PATH", help = "Repeatable; paths of file-based databases [format: \"dbFilePath[::configFilePath]\"]", num_args = 0..)]
33 | pub db: Vec,
34 | #[arg(long, value_name = "MEM_DB", help = "Repeatable; config for memory-based databases [format: \"ID[::configFilePath]\"]", num_args = 0..)]
35 | pub mem_db: Vec,
36 | #[arg(
37 | short,
38 | long,
39 | value_name = "PORT",
40 | default_value = "12321",
41 | help = "Port for the web service"
42 | )]
43 | pub port: u16,
44 | #[arg(
45 | long,
46 | value_name = "DIR",
47 | help = "A directory to serve with builtin HTTP server"
48 | )]
49 | pub serve_dir: Option,
50 | #[arg(
51 | long,
52 | value_name = "FILE",
53 | help = "If --serve-dir is configured, the file to treat as index.",
54 | default_value = "index.html"
55 | )]
56 | pub index_file: String,
57 | }
58 |
59 | pub fn parse_cli() -> AppConfig {
60 | let mut ret = AppConfig::parse();
61 |
62 | assert(
63 | ret.db.len() + ret.mem_db.len() > 0 || ret.serve_dir.is_some(),
64 | "no database and no dir to serve specified".to_string(),
65 | );
66 |
67 | if let Some(sd) = ret.serve_dir {
68 | let sd = resolve_tilde(&sd);
69 | assert(
70 | is_dir(&sd),
71 | format!("directory to serve does not exist: {}", sd),
72 | );
73 | ret.serve_dir = Some(sd.to_owned());
74 | }
75 |
76 | ret
77 | }
78 |
--------------------------------------------------------------------------------
/src/auth.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023-, Germano Rizzo
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use std::ops::DerefMut;
16 |
17 | use actix_web_httpauth::headers::authorization::{Authorization, Basic};
18 | use rusqlite::named_params;
19 |
20 | use crate::{
21 | commons::{equal_case_insensitive, sha256},
22 | db_config::Auth,
23 | db_config::{AuthMode, Credentials},
24 | req_res::ReqCredentials,
25 | MUTEXES,
26 | };
27 |
28 | /// Given the provided password and the expected ones (unhashed and hashed), returns if
29 | /// the passwords match. All three passwords may be Options.
30 | pub fn process_creds(
31 | given_password: &Option,
32 | password: &Option,
33 | hashed_password: &Option,
34 | ) -> bool {
35 | match (given_password, password, hashed_password) {
36 | (_, None, None) => true,
37 | (Some(gp), Some(p), _) => gp == p,
38 | (Some(gp), None, Some(hp)) => equal_case_insensitive(hp, &sha256(gp)),
39 | _ => false,
40 | }
41 | }
42 |
43 | fn auth_by_credentials(user: String, password: String, creds: &Vec) -> bool {
44 | for c in creds {
45 | // TODO hash table lookup? I don't expect the credentials list to grow very much, so it may be overkill (and use memory)
46 | if equal_case_insensitive(&user, &c.user) {
47 | return process_creds(&Some(password), &c.password, &c.hashed_password);
48 | }
49 | }
50 | false
51 | }
52 |
53 | fn auth_by_query(user: String, password: String, query: &str, db_name: &str) -> bool {
54 | let db_lock = MUTEXES.get().unwrap().get(db_name).unwrap();
55 | let mut db_lock_guard = db_lock.lock().unwrap();
56 | let conn = db_lock_guard.deref_mut();
57 |
58 | let res = conn.query_row(
59 | query,
60 | named_params! {":user": user, ":password":password},
61 | |_| Ok(()),
62 | );
63 | res.is_ok()
64 | }
65 |
66 | pub fn process_auth(
67 | auth_config: &Auth,
68 | auth_inline: &Option,
69 | auth_header: &Option>,
70 | db_name: &str,
71 | ) -> bool {
72 | let (user, password) = match auth_config.mode {
73 | AuthMode::HttpBasic => match auth_header {
74 | Some(auth_header) => (
75 | auth_header.as_ref().user_id().to_string(),
76 | auth_header.as_ref().password().unwrap().to_string(),
77 | ),
78 | None => return false,
79 | },
80 | AuthMode::Inline => match auth_inline {
81 | Some(auth_inline) => (auth_inline.user.to_owned(), auth_inline.password.to_owned()),
82 | None => return false,
83 | },
84 | };
85 |
86 | match &auth_config.by_credentials {
87 | Some(creds) => auth_by_credentials(user, password, creds),
88 | None => match &auth_config.by_query {
89 | Some(query) => auth_by_query(user, password, query, db_name),
90 | None => false,
91 | },
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/profiler/Profile.java:
--------------------------------------------------------------------------------
1 | import java.io.InputStream;
2 | import java.io.UnsupportedEncodingException;
3 | import java.net.HttpURLConnection;
4 | import java.net.URL;
5 | import java.util.concurrent.CountDownLatch;
6 | import java.util.concurrent.ExecutorService;
7 | import java.util.concurrent.Executors;
8 |
9 | public class Profile {
10 | private static final int NUM_THREADS = 8;
11 | private static final String JSON = "{\"credentials\":{\"user\":\"myUser\",\"password\":\"ciao\"},\"transaction\":[{\"statement\":\"DELETE FROM TBL\"},{\"query\":\"SELECT * FROM TBL\"},{\"statement\":\"INSERT INTO TBL (ID, VAL) VALUES (:id, :val)\",\"values\":{\"id\":0,\"val\":\"zero\"}},{\"statement\":\"INSERT INTO TBL (ID, VAL) VALUES (:id, :val)\",\"valuesBatch\":[{\"id\":1,\"val\":\"uno\"},{\"id\":2,\"val\":\"due\"}]},{\"noFail\":true,\"statement\":\"INSERT INTO TBL (ID, VAL) VALUES (:id, :val, 1)\",\"valuesBatch\":[{\"id\":1,\"val\":\"uno\"},{\"id\":2,\"val\":\"due\"}]},{\"statement\":\"INSERT INTO TBL (ID, VAL) VALUES (:id, :val)\",\"valuesBatch\":[{\"id\":3,\"val\":\"tre\"}]},{\"query\":\"SELECT * FROM TBL WHERE ID=:id\",\"values\":{\"id\":1}},{\"statement\":\"DELETE FROM TBL\"}]}";
12 | private static byte[] JSON_BYTES;
13 | private static int JSON_LEN;
14 |
15 | static {
16 | try {
17 | JSON_BYTES = JSON.getBytes("utf-8");
18 | JSON_LEN = JSON_BYTES.length;
19 | } catch (UnsupportedEncodingException e) {
20 | e.printStackTrace();
21 | System.exit(1);
22 | }
23 | }
24 |
25 | private static int numRequests;
26 | private static String urlToCall;
27 |
28 | private static ExecutorService threadPool = Executors.newFixedThreadPool(NUM_THREADS);
29 |
30 | public static void main(String[] args) throws Exception {
31 | numRequests = Integer.parseInt(args[0]);
32 | urlToCall = args[1];
33 |
34 | var cdl = new CountDownLatch(numRequests);
35 |
36 | var start = System.currentTimeMillis();
37 |
38 | for (int i = 0; i < numRequests; i++) {
39 | threadPool.execute(() -> {
40 | performHttpRequest();
41 | cdl.countDown();
42 | });
43 | }
44 | cdl.await();
45 |
46 | System.out.println((System.currentTimeMillis() - start) / 1000.0);
47 |
48 | threadPool.shutdown();
49 | }
50 |
51 | private static void performHttpRequest() {
52 | try {
53 | var url = new URL(urlToCall);
54 | var connection = (HttpURLConnection) url.openConnection();
55 | connection.setRequestMethod("POST");
56 | connection.setRequestProperty("Content-Type", "application/json"); // Set Content-Type header
57 | connection.setDoOutput(true);
58 |
59 | try (var os = connection.getOutputStream()) {
60 | os.write(JSON_BYTES, 0, JSON_LEN);
61 | }
62 |
63 | var ret = connection.getResponseCode();
64 | if (ret != 200) {
65 | try (InputStream errorStream = connection.getErrorStream()) {
66 | byte[] buffer = new byte[1024];
67 | int bytesRead;
68 | while ((bytesRead = errorStream.read(buffer)) != -1) {
69 | System.err.write(buffer, 0, bytesRead);
70 | }
71 | }
72 | try (InputStream errorStream = connection.getInputStream()) {
73 | byte[] buffer = new byte[1024];
74 | int bytesRead;
75 | while ((bytesRead = errorStream.read(buffer)) != -1) {
76 | System.err.write(buffer, 0, bytesRead);
77 | }
78 | }
79 | }
80 | connection.disconnect();
81 | } catch (Exception e) {
82 | e.printStackTrace();
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test
2 |
3 | clean:
4 | cargo clean
5 | rm -rf bin
6 | - docker image prune -af
7 | - docker builder prune -af
8 | rm -f profiler/*.class
9 |
10 | update:
11 | cargo update
12 | cd tests && go get -u
13 | cd tests && go mod tidy
14 |
15 | lint:
16 | cargo clippy 2> clippy_results.txt
17 |
18 | profile:
19 | bash profiler/stress_sqliterg.sh
20 | bash profiler/stress_ws4sqlite.sh
21 |
22 | test:
23 | - pkill sqliterg
24 | make build-debug
25 | cd tests; go test -v -timeout 5m
26 |
27 | test-short:
28 | - pkill sqliterg
29 | make build-debug
30 | cd tests; go test -v -timeout 1m -short
31 |
32 | build-debug:
33 | cargo build
34 |
35 | build:
36 | cargo build --release
37 |
38 | build-static-nostatic:
39 | rm -rf bin
40 | - mkdir bin
41 | bash -c "RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --target `uname -m`-unknown-linux-gnu"
42 | bash -c "tar czf bin/sqliterg-v0.18.0-linux-`uname -m`-static-bundled.tar.gz -C target/`uname -m`-unknown-linux-gnu/release/ sqliterg"
43 | cp Cargo.toml Cargo.toml.orig
44 | sed 's/^rusqlite.*$$/rusqlite = { version = "~0", features = [\"serde_json\", \"load_extension\"] }/' Cargo.toml.orig > Cargo.toml
45 | bash -c "cargo build --release --target `uname -m`-unknown-linux-gnu"
46 | bash -c "tar czf bin/sqliterg-v0.18.0-linux-`uname -m`-dynamic.tar.gz -C target/`uname -m`-unknown-linux-gnu/release/ sqliterg"
47 | mv Cargo.toml.orig Cargo.toml
48 |
49 | build-macos:
50 | rm -rf bin
51 | - mkdir bin
52 | cargo build --release
53 | tar czf bin/sqliterg-v0.18.0-macos-x86_64-bundled.tar.gz -C target/release/ sqliterg
54 | cargo build --release --target aarch64-apple-darwin
55 | tar czf bin/sqliterg-v0.18.0-macos-aarch64-bundled.tar.gz -C target/aarch64-apple-darwin/release/ sqliterg
56 | cp Cargo.toml Cargo.toml.orig
57 | sed 's/^rusqlite.*$$/rusqlite = { version = "~0", features = [\"serde_json\", "load_extension"] }/' Cargo.toml.orig > Cargo.toml
58 | cargo build --release
59 | tar czf bin/sqliterg-v0.18.0-macos-x86_64-dynamic.tar.gz -C target/release/ sqliterg
60 | cargo build --release --target aarch64-apple-darwin
61 | tar czf bin/sqliterg-v0.18.0-macos-aarch64-dynamic.tar.gz -C target/aarch64-apple-darwin/release/ sqliterg
62 | mv Cargo.toml.orig Cargo.toml
63 |
64 | docker:
65 | docker run --privileged --rm tonistiigi/binfmt --install arm64
66 | docker buildx build --no-cache --platform linux/amd64 -t germanorizzo/sqliterg:v0.18.0-x86_64 --push .
67 | docker buildx build --no-cache --platform linux/arm64 -t germanorizzo/sqliterg:v0.18.0-aarch64 --push .
68 | - docker manifest rm germanorizzo/sqliterg:v0.18.0
69 | docker manifest create germanorizzo/sqliterg:v0.18.0 germanorizzo/sqliterg:v0.18.0-x86_64 germanorizzo/sqliterg:v0.18.0-aarch64
70 | docker manifest push germanorizzo/sqliterg:v0.18.0
71 | - docker manifest rm germanorizzo/sqliterg:latest
72 | docker manifest create germanorizzo/sqliterg:latest germanorizzo/sqliterg:v0.18.0-x86_64 germanorizzo/sqliterg:v0.18.0-aarch64
73 | docker manifest push germanorizzo/sqliterg:latest
74 |
75 | docker-edge:
76 | # in Cargo.toml, set 'version = "0.x.999"' where x is the current minor
77 | docker run --privileged --rm tonistiigi/binfmt --install arm64,arm
78 | docker buildx build --no-cache --platform linux/amd64 -t germanorizzo/sqliterg:edge --push .
79 |
80 | docker-zbuild-linux:
81 | - mkdir bin
82 | docker run --privileged --rm tonistiigi/binfmt --install arm64,arm
83 | docker buildx build --no-cache --platform linux/amd64 -f Dockerfile.binaries --target export -t tmp_binaries_build . --output bin
84 | docker buildx build --no-cache --platform linux/arm64 -f Dockerfile.binaries --target export -t tmp_binaries_build . --output bin
85 | # Doesn't work. armv7-unknown-linux-gnueabihf must be used. Anyway, for now ARMv7 is out of scope.
86 | # docker buildx build --no-cache --platform linux/arm/v7 -f Dockerfile.binaries --target export -t tmp_binaries_build . --output bin
87 |
88 |
--------------------------------------------------------------------------------
/src/req_res.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023-, Germano Rizzo
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use crate::commons::default_as_false;
16 | use actix_web::{
17 | body::BoxBody,
18 | http::{header::ContentType, StatusCode},
19 | HttpRequest, HttpResponse, Responder,
20 | };
21 | use serde::{Deserialize, Serialize};
22 |
23 | use serde_json::Value as JsonValue;
24 |
25 | #[derive(Debug, Deserialize)]
26 | pub struct ReqCredentials {
27 | pub user: String,
28 | pub password: String,
29 | }
30 |
31 | #[derive(Debug, Deserialize)]
32 | pub struct ReqTransactionItem {
33 | #[serde(rename = "noFail")]
34 | #[serde(default = "default_as_false")]
35 | pub no_fail: bool,
36 | pub query: Option,
37 | pub statement: Option,
38 | pub values: Option,
39 | #[serde(rename = "valuesBatch")]
40 | pub values_batch: Option>,
41 | }
42 |
43 | #[derive(Debug, Deserialize)]
44 | pub struct Request {
45 | pub credentials: Option,
46 | pub transaction: Vec,
47 | }
48 |
49 | #[derive(Debug, Serialize)]
50 | pub struct ResponseItem {
51 | pub success: bool,
52 | #[serde(skip_serializing_if = "Option::is_none")]
53 | pub error: Option,
54 | #[serde(rename = "resultSet")]
55 | #[serde(skip_serializing_if = "Option::is_none")]
56 | pub result_set: Option>,
57 | #[serde(rename = "rowsUpdated")]
58 | #[serde(skip_serializing_if = "Option::is_none")]
59 | pub rows_updated: Option,
60 | #[serde(rename = "rowsUpdatedBatch")]
61 | #[serde(skip_serializing_if = "Option::is_none")]
62 | pub rows_updated_batch: Option>,
63 | }
64 |
65 | #[derive(Debug, Serialize)]
66 | pub struct Response {
67 | #[serde(skip_serializing_if = "Option::is_none")]
68 | pub results: Option>,
69 | #[serde(rename = "reqIdx")]
70 | #[serde(skip_serializing_if = "Option::is_none")]
71 | pub req_idx: Option,
72 | #[serde(skip_serializing_if = "Option::is_none")]
73 | pub message: Option,
74 | #[serde(skip_serializing)]
75 | pub status_code: u16,
76 | #[serde(skip_serializing)]
77 | pub success: bool,
78 | }
79 |
80 | impl Responder for Response {
81 | type Body = BoxBody;
82 |
83 | fn respond_to(self, _req: &HttpRequest) -> HttpResponse {
84 | let body = serde_json::to_string(&self).unwrap();
85 |
86 | HttpResponse::Ok()
87 | .status(StatusCode::from_u16(self.status_code).unwrap())
88 | .content_type(ContentType::json())
89 | .body(body)
90 | }
91 | }
92 |
93 | impl Response {
94 | pub fn new_ok(results: Vec) -> Response {
95 | Response {
96 | results: Some(results),
97 | req_idx: None,
98 | message: None,
99 | status_code: 200,
100 | success: true,
101 | }
102 | }
103 |
104 | pub fn new_err(status_code: u16, req_idx: isize, msg: String) -> Response {
105 | Response {
106 | results: None,
107 | req_idx: Some(req_idx),
108 | message: Some(msg),
109 | status_code,
110 | success: false,
111 | }
112 | }
113 | }
114 |
115 | #[derive(Deserialize)]
116 | pub struct Token {
117 | pub token: Option,
118 | }
119 |
--------------------------------------------------------------------------------
/src/db_config.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023-, Germano Rizzo
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use eyre::Result;
16 | use std::fs::File;
17 | use std::io::Read;
18 |
19 | use crate::commons::{default_as_false, default_as_zero};
20 |
21 | #[derive(Debug, Deserialize, Clone)]
22 | pub enum AuthMode {
23 | #[serde(rename = "HTTP_BASIC")]
24 | HttpBasic,
25 | #[serde(rename = "INLINE")]
26 | Inline,
27 | }
28 |
29 | fn default_401() -> u16 {
30 | 401
31 | }
32 |
33 | #[derive(Debug, Deserialize, Clone)]
34 | pub struct Auth {
35 | #[serde(rename = "authErrorCode")]
36 | #[serde(default = "default_401")]
37 | pub auth_error_code: u16,
38 | pub mode: AuthMode,
39 | #[serde(rename = "byQuery")]
40 | pub by_query: Option,
41 | #[serde(rename = "byCredentials")]
42 | pub by_credentials: Option>,
43 | }
44 |
45 | #[derive(Debug, Deserialize, Clone)]
46 | pub struct Credentials {
47 | pub user: String,
48 | pub password: Option,
49 | #[serde(rename = "hashedPassword")]
50 | pub hashed_password: Option,
51 | }
52 |
53 | #[derive(Debug, Deserialize, Clone)]
54 | pub struct StoredStatement {
55 | pub id: String,
56 | pub sql: String,
57 | }
58 |
59 | #[derive(Debug, Deserialize, Clone)]
60 | pub struct ExecutionWebService {
61 | #[serde(rename = "authErrorCode")]
62 | #[serde(default = "default_401")]
63 | pub auth_error_code: u16,
64 | #[serde(rename = "authToken")]
65 | pub auth_token: Option,
66 | #[serde(rename = "hashedAuthToken")]
67 | pub hashed_auth_token: Option,
68 | }
69 |
70 | #[derive(Debug, Deserialize, Clone)]
71 | pub struct ExecutionMode {
72 | #[serde(rename = "onCreate")]
73 | #[serde(default = "default_as_false")]
74 | pub on_create: bool,
75 | #[serde(rename = "onStartup")]
76 | #[serde(default = "default_as_false")]
77 | pub on_startup: bool,
78 | #[serde(default = "default_as_zero")]
79 | pub period: i32,
80 | #[serde(rename = "webService")]
81 | pub web_service: Option,
82 | }
83 |
84 | #[derive(Debug, Deserialize, Clone)]
85 | pub struct Macro {
86 | pub id: String,
87 | #[serde(rename = "disableTransaction")]
88 | #[serde(default = "default_as_false")]
89 | pub disable_transaction: bool,
90 | pub statements: Vec,
91 | pub execution: ExecutionMode,
92 | }
93 |
94 | #[derive(Debug, Deserialize, Clone)]
95 | pub struct Backup {
96 | #[serde(rename = "backupDir")]
97 | pub backup_dir: String,
98 | #[serde(rename = "numFiles")]
99 | pub num_files: usize,
100 | pub execution: ExecutionMode,
101 | }
102 |
103 | #[derive(Debug, Default, Deserialize, Clone)]
104 | pub struct DbConfig {
105 | pub auth: Option,
106 | #[serde(rename = "journalMode")]
107 | pub journal_mode: Option,
108 | #[serde(rename = "readOnly")]
109 | #[serde(default = "default_as_false")]
110 | pub read_only: bool,
111 | #[serde(rename = "corsOrigin")]
112 | pub cors_origin: Option,
113 | #[serde(rename = "useOnlyStoredStatements")]
114 | #[serde(default = "default_as_false")]
115 | pub use_only_stored_statements: bool,
116 | #[serde(rename = "storedStatements")]
117 | pub stored_statements: Option>,
118 | pub macros: Option>,
119 | pub backup: Option,
120 | }
121 |
122 | pub fn parse_dbconf(filename: &String) -> Result {
123 | let mut file = File::open(filename)?;
124 | let mut content = String::new();
125 | file.read_to_string(&mut content)?;
126 |
127 | let ret = serde_yaml::from_str(&content)?;
128 |
129 | Ok(ret)
130 | }
131 |
--------------------------------------------------------------------------------
/tests/structs.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2022-, Germano Rizzo
3 |
4 | Permission to use, copy, modify, and/or distribute this software for any
5 | purpose with or without fee is hereby granted, provided that the above
6 | copyright notice and this permission notice appear in all copies.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 | */
16 |
17 | package main
18 |
19 | // These are for parsing the config file (from YAML)
20 |
21 | type credentialsCfg struct {
22 | User string `yaml:"user,omitempty"`
23 | Password string `yaml:"password,omitempty"`
24 | HashedPassword string `yaml:"hashedPassword,omitempty"`
25 | }
26 |
27 | type authr struct {
28 | AuthErrorCode *int `yaml:"authErrorCode,omitempty"`
29 | Mode string `yaml:"mode,omitempty"` // 'INLINE' or 'HTTP_BASIC'
30 | CustomErrorCode *int `yaml:"customErrorCode,omitempty"`
31 | ByQuery string `yaml:"byQuery,omitempty"`
32 | ByCredentials []credentialsCfg `yaml:"byCredentials,omitempty"`
33 | }
34 |
35 | type storedStatement struct {
36 | Id string `yaml:"id,omitempty"`
37 | Sql string `yaml:"sql,omitempty"`
38 | }
39 |
40 | type webService struct {
41 | AuthErrorCode *int `yaml:"authErrorCode,omitempty"`
42 | AuthToken *string `yaml:"authToken,omitempty"`
43 | HashedAuthToken *string `yaml:"hashedAuthToken,omitempty"`
44 | }
45 |
46 | type execution struct {
47 | OnCreate *bool `yaml:"onCreate,omitempty"`
48 | OnStartup *bool `yaml:"onStartup,omitempty"`
49 | Period *uint `yaml:"period,omitempty"`
50 | WebService *webService `yaml:"webService,omitempty"`
51 | }
52 |
53 | type macro struct {
54 | Id string `yaml:"id,omitempty"`
55 | DisableTransaction *bool `yaml:"disableTransaction,omitempty"`
56 | Statements []string `yaml:"statements,omitempty"`
57 | Execution execution `yaml:"execution,omitempty"`
58 | }
59 |
60 | type backup struct {
61 | BackupDir string `yaml:"backupDir,omitempty"`
62 | NumFiles uint `yaml:"numFiles,omitempty"`
63 | Execution execution `yaml:"execution,omitempty"`
64 | }
65 |
66 | type db struct {
67 | Auth *authr `yaml:"auth,omitempty"`
68 | ReadOnly bool `yaml:"readOnly,omitempty"`
69 | CORSOrigin string `yaml:"corsOrigin,omitempty"`
70 | UseOnlyStoredStatements bool `yaml:"useOnlyStoredStatements,omitempty"`
71 | JournalMode string `yaml:"journalMode,omitempty"`
72 | StoredStatement []storedStatement `yaml:"storedStatements,omitempty"`
73 | Macros []macro `yaml:"macros,omitempty"`
74 | Backup backup `yaml:"backup,omitempty"`
75 | }
76 |
77 | // These are for parsing the request (from JSON)
78 |
79 | type credentials struct {
80 | User string `json:"user,omitempty"`
81 | Password string `json:"password,omitempty"`
82 | }
83 |
84 | type requestItem struct {
85 | Query string `json:"query,omitempty"`
86 | Statement string `json:"statement,omitempty"`
87 | NoFail bool `json:"noFail,omitempty"`
88 | Values interface{} `json:"values,omitempty"`
89 | ValuesBatch interface{} `json:"valuesBatch,omitempty"`
90 | }
91 |
92 | type request struct {
93 | Credentials *credentials `json:"credentials,omitempty"`
94 | Transaction []requestItem `json:"transaction,omitempty"`
95 | }
96 |
97 | // These are for generating the response
98 |
99 | type responseItem struct {
100 | Success bool `json:"success"`
101 | RowsUpdated *int `json:"rowsUpdated,omitempty"`
102 | RowsUpdatedBatch []int `json:"rowsUpdatedBatch,omitempty"`
103 | ResultSet []map[string]interface{} `json:"resultSet"`
104 | Error string `json:"error,omitempty"`
105 | }
106 |
107 | type response struct {
108 | Results []responseItem `json:"results"`
109 | }
110 |
--------------------------------------------------------------------------------
/db_conf.template.yaml:
--------------------------------------------------------------------------------
1 | # Main endpoint for requests is http://:/
2 |
3 | # If present, "auth" defines the authentication for this database
4 | auth:
5 | # Optional, by default 401. The error HTTP code to be returned if auth fails.
6 | authErrorCode: 499
7 | # Mandatory. Defines how the credentials are passed to the server.
8 | # "INLINE" means that credentials are passed in the request
9 | # "HTTP_BASIC" uses Basic Authentication (via the "Authorization: Basic" header)
10 | mode: INLINE
11 | # Only one among "byQuery" and "byCredentials" must be specified.
12 | # This query validates credentials against a query in the database, it must have
13 | # two parameters named ":user" and ":password" and if the credentials are valid must
14 | # return (at least) one row.
15 | byQuery: SELECT 1 FROM AUTH WHERE USER = :user AND PASS = :password
16 | # This is a list of valid credentials, "statically" specified. "user" is case-insensitive
17 | # while either a plaintext "password" or a SHA-256 hashed "hashedPassword" must be supplied.
18 | byCredentials:
19 | - user: myUser1
20 | password: ciao
21 | - user: myUser2
22 | hashedPassword: b133a0c0e9bee3be20163d2ad31d6248db292aa6dcb1ee087a2aa50e0fc75ae2 # "ciao"
23 | # Journal mode for the database. Optional, default is "WAL". Not validated to accommodate for
24 | # future versions of SQLite. From SQLite docs: DELETE | TRUNCATE | PERSIST | MEMORY | WAL | OFF
25 | journalMode: WAL
26 | # Database is read-only. This is set after startup macros or backup are performed, so they can
27 | # still modify the database. It's implemented using the query_only PRAGMA.
28 | readOnly: false
29 | # Instruct the web server to povide the CORS header and preflight system as needed.
30 | corsOrigin: "*"
31 | # A "map" of statements (or queries) that can be called from request or macros using '^'. If I
32 | # want to use Q1, for example, I should specify '"query": "^Q1"' in the request.
33 | storedStatements:
34 | - id: Q1
35 | sql: SELECT * FROM TBL
36 | - id: Q2
37 | sql: CREATE TABLE IF NOT EXISTS AUTH (USER TEXT, PASS TEXT)
38 | # If set, only a Stored Statement can be used in the requests. Useful to avoid SQL injection.
39 | useOnlyStoredStatements: false
40 | # A "map" of macros, that are named groups of statements (not queries) that can be run at db
41 | # creation, at startup, every /n/ minutes, or via a web request.
42 | macros:
43 | # ID of the macro
44 | - id: M1
45 | # Allow to execute out of a transaction, e.g. for VACUUM
46 | disableTransaction: false
47 | # Which statements it must execute (in a transaction). Stored Statements references can be used.
48 | statements:
49 | - CREATE TABLE IF NOT EXISTS TBL (ID INT, VAL TEXT)
50 | - ^Q2
51 | # Control of execution. Mandatory. All the contents have defaults meaning "disabled".
52 | execution:
53 | # Executes if the database is created (file wasn't present or in-memory)
54 | onCreate: false
55 | # Executes at each startup. Implies onCreate; if both are specified the macro will be executed once.
56 | onStartup: false
57 | # Executes every /n/ minutes. First execution is /n/ minutes after startup.
58 | period: 1 # in minutes, <= 0: never
59 | # Exposes an endpoint to execute the macro. A token-based for of authentication is mandatory.
60 | # Endpoint is http://://macro/
61 | webService:
62 | # Optional, by default 401. The error HTTP code to be returned if auth fails.
63 | authErrorCode: 499
64 | # Either a plaintext "authToken" or a SHA-256 hashed "hashedAuthToken" must be supplied.
65 | authToken: ciao
66 | hashedAuthToken: b133a0c0e9bee3be20163d2ad31d6248db292aa6dcb1ee087a2aa50e0fc75ae2
67 | # Optional. Configuration of the backups. Backups can be run at startup, every /n/ minutes,
68 | # or via a web request.
69 | backup:
70 | # Directory for the backups. Must exist; mandatory config.
71 | backupDir: backups/
72 | # Keeps only the last /n/ backup files. Mandatory.
73 | numFiles: 3
74 | # Control of execution. Mandatory. All the contents have defaults meaning "disabled".
75 | execution:
76 | # Executes if the database is created (file wasn't present or in-memory)
77 | onCreate: false
78 | # Executes at each startup. Implies onCreate; if both are specified the macro will be executed once.
79 | onStartup: false
80 | # Executes every /n/ minutes. First execution is /n/ minutes after startup.
81 | period: 1 # in minutes, <= 0: never
82 | # Exposes an endpoint to execute the backup. A token-based for of authentication is mandatory.
83 | # Endpoint is http://://backup
84 | webService:
85 | # Optional, by default 401. The error HTTP code to be returned if auth fails.
86 | authErrorCode: 499
87 | # Either a plaintext "authToken" or a SHA-256 hashed "hashedAuthToken" must be supplied.
88 | authToken: ciao
89 | hashedAuthToken: b133a0c0e9bee3be20163d2ad31d6248db292aa6dcb1ee087a2aa50e0fc75ae2
90 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023-, Germano Rizzo
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | #[macro_use]
16 | extern crate serde_derive;
17 | #[macro_use]
18 | extern crate eyre;
19 |
20 | use std::{
21 | collections::HashMap,
22 | ops::Deref,
23 | sync::{Mutex, OnceLock},
24 | };
25 |
26 | use actix_cors::Cors;
27 | use actix_files::Files;
28 | use actix_web::{
29 | guard,
30 | web::{route, scope, Data},
31 | App, HttpServer, Scope,
32 | };
33 | use rusqlite::Connection;
34 |
35 | pub mod auth;
36 | mod backup;
37 | pub mod commandline;
38 | pub mod commons;
39 | pub mod db_config;
40 | mod logic;
41 | mod macros;
42 | pub mod main_config;
43 | pub mod req_res;
44 |
45 | use crate::{commandline::parse_cli, db_config::AuthMode, main_config::compose_db_map};
46 |
47 | pub const CURRENT_PROTO_VERSION: u8 = 1;
48 |
49 | pub static MUTEXES: OnceLock>> = OnceLock::new();
50 |
51 | fn get_sqlite_version() -> String {
52 | let conn: Connection = Connection::open_in_memory().unwrap();
53 | conn.query_row("SELECT sqlite_version()", [], |row| row.get(0))
54 | .unwrap()
55 | }
56 |
57 | #[actix_web::main]
58 | async fn main() -> std::io::Result<()> {
59 | println!(
60 | "{} v{}. based on SQLite v{}\n",
61 | env!("CARGO_PKG_NAME"),
62 | env!("CARGO_PKG_VERSION"),
63 | get_sqlite_version()
64 | );
65 |
66 | let cli = parse_cli();
67 |
68 | // side effect of compose_db_map: populate MUTEXES
69 | // aborts on error
70 | let db_map = compose_db_map(&cli);
71 |
72 | if let Some(sd) = &cli.serve_dir {
73 | println!("- serving directory: {}", sd);
74 | println!(" - with index file: {}", &cli.index_file);
75 | };
76 |
77 | let app_lambda = move || {
78 | let dir = cli.serve_dir.to_owned();
79 | let index_file = cli.index_file.to_owned();
80 | let mut a = App::new();
81 | for (db_name, db_conf) in db_map.iter() {
82 | let scop: Scope = scope(format!("/{}", db_name.to_owned()).deref())
83 | .app_data(Data::new(db_name.to_owned()))
84 | .app_data(Data::new(db_conf.to_owned()))
85 | .route(
86 | "",
87 | route()
88 | .guard(guard::Post())
89 | .guard(guard::Header("content-type", "application/json"))
90 | .to(logic::handler),
91 | )
92 | .route(
93 | "/macro/{macro_name}",
94 | route()
95 | .guard(guard::Any(guard::Get()).or(guard::Post()))
96 | .to(macros::handler),
97 | )
98 | .route(
99 | "/backup",
100 | route()
101 | .guard(guard::Any(guard::Get()).or(guard::Post()))
102 | .to(backup::handler),
103 | );
104 |
105 | match &db_conf.conf.cors_origin {
106 | Some(orig) => {
107 | let mut cors = Cors::default()
108 | .allowed_methods(vec!["POST"])
109 | .allowed_header("content-type");
110 | if db_conf.conf.auth.is_some()
111 | && matches!(
112 | db_conf.conf.auth.as_ref().unwrap().mode,
113 | AuthMode::HttpBasic
114 | )
115 | {
116 | cors = cors.allowed_header("authorization");
117 | }
118 | if orig == "*" {
119 | cors = cors.allow_any_origin();
120 | } else {
121 | cors = cors.allowed_origin(orig);
122 | }
123 | a = a.service(scop.wrap(cors))
124 | }
125 | None => a = a.service(scop),
126 | }
127 | }
128 |
129 | if let Some(dir) = dir {
130 | a = a.service(Files::new("/", dir).index_file(index_file));
131 | };
132 |
133 | a
134 | };
135 |
136 | let bind_addr = format!("{}:{}", cli.bind_host, cli.port);
137 | println!("- Listening on {}", &bind_addr);
138 | HttpServer::new(app_lambda).bind(bind_addr)?.run().await
139 | }
140 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | https://github.com/proofrock/sqliterg/issues.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > **Hey! This project is archived. I decided to switch back the development on [ws4sqlite](https://github.com/proofrock/ws4sqlite), for reasons expressed in [this discussion](https://github.com/proofrock/ws4sqlite/discussions/44). If you'll want to hack on it, though, I'll be more than glad to assist!**
2 |
3 | # 🌿 Introduction
4 |
5 | > *This is a rewrite in Rust of [ws4sqlite](https://github.com/proofrock/ws4sqlite), 30-50% faster, 10x less memory used, more flexible in respect to sqlite support. It is not a direct rewrite, more like a "sane" (I hope) redesign. You can read more about what's changed and how to migrate [here](https://docs.sqliterg.dev/features/migrating-from-ws4sqlite).*
6 |
7 | **sqliterg** is a server-side application that, applied to one or more SQLite files, allows to perform SQL queries and statements on them via REST (or better, JSON over HTTP).
8 |
9 | Full docs are available [here](https://docs.sqliterg.dev/) and a [tutorial](https://docs.sqliterg.dev/tutorial) too.
10 |
11 | Possible use cases are the ones where remote access to a sqlite db is useful/needed, for example a data layer for a remote application, possibly serverless or even called from a web page ([*after security considerations*](https://docs.sqliterg.dev/security) of course).
12 |
13 | As a quick example, after launching:
14 |
15 | ```bash
16 | sqliterg --db mydatabase.db
17 | ```
18 |
19 | It's possible to make a POST call to `http://localhost:12321/mydatabase`, e.g. with the following body:
20 |
21 | ```json
22 | {
23 | "transaction": [
24 | {
25 | "statement": "INSERT INTO TEST_TABLE (ID, VAL, VAL2) VALUES (:id, :val, :val2)",
26 | "values": { "id": 1, "val": "hello", "val2": null }
27 | },
28 | {
29 | "query": "SELECT * FROM TEST_TABLE"
30 | }
31 | ]
32 | }
33 | ```
34 |
35 | Obtaining an answer of:
36 |
37 | ```json
38 | {
39 | "results": [
40 | {
41 | "success": true,
42 | "rowsUpdated": 1
43 | },
44 | {
45 | "success": true,
46 | "resultSet": [
47 | { "ID": 1, "VAL": "hello", "VAL2": null }
48 | ]
49 | }
50 | ]
51 | }
52 | ```
53 |
54 | # 🎞️ Features
55 |
56 | - A [**single executable file**](https://docs.sqliterg.dev/documentation/installation) (written in Rust);
57 | - Can be [built](https://docs.sqliterg.dev/building-and-testing#supported-platforms) either against the system's SQLite or embedding one;
58 | - HTTP/JSON access;
59 | - Directly call `sqliterg` on a database (as above), many options available using a YAML companion file;
60 | - [**In-memory DBs**](https://docs.sqliterg.dev/documentation/running#file-based-and-in-memory) are supported;
61 | - Serving of [**multiple databases**](https://docs.sqliterg.dev/documentation/configuration-file) in the same server instance;
62 | - Named or positional parameters in SQL are supported;
63 | - [**Batching**](https://docs.sqliterg.dev/documentation/requests#batch-parameter-values-for-a-statement) of multiple value sets for a single statement;
64 | - All queries of a call are executed in a [**transaction**](https://docs.sqliterg.dev/documentation/requests);
65 | - For each query/statement, specify if a failure should rollback the whole transaction, or the failure is [**limited**](https://docs.sqliterg.dev/documentation/errors#managed-errors) to that query;
66 | - "[**Stored Statements**](https://docs.sqliterg.dev/documentation/stored-statements)": define SQL in the server, and call it from the client;
67 | - "[**Macros**](https://docs.sqliterg.dev/documentation/macros)": lists of statements that can be executed at db creation, at startup, periodically or calling a web service;
68 | - [**Backups**](https://docs.sqliterg.dev/documentation/backup), rotated and also runnable at db creation, at startup, periodically or calling a web service;
69 | - [**CORS**](https://docs.sqliterg.dev/documentation/configuration-file#corsorigin) mode, configurable per-db;
70 | - [**Journal Mode**](https://docs.sqliterg.dev/documentation/configuration-file#journalmode) (e.g. WAL) can be configured;
71 | - [**Embedded web server**](https://docs.sqliterg.dev/documentation/web-server) to directly serve web pages that can access `sqliterg` without CORS;
72 | - [**Quite fast**](https://docs.sqliterg.dev/features/performances)!
73 | - Comprehensive [**test suite**](https://docs.sqliterg.dev/building-and-testing#testing);
74 | - [**Docker images**](https://docs.sqliterg.dev/documentation/installation/docker), for x86_64 and arm64;
75 | - Binaries are provided with a bundled SQLite "inside" them, or linked against the system's installed SQLite.
76 |
77 | ### Security Features
78 |
79 | * [**Authentication**](https://docs.sqliterg.dev/security#authentication) can be configured
80 | * on the client, either using HTTP Basic Authentication or specifying the credentials in the request;
81 | * on the server, either by specifying credentials (also with hashed passwords) or providing a query to look them up in the db itself;
82 | * customizable `Not Authorized` error code (if `401` is not optimal);
83 | * A database can be opened in [**read-only mode**](https://docs.sqliterg.dev/security#read-only-databases) (only queries will be allowed);
84 | * It's possible to enforce using [**only stored statements**](https://docs.sqliterg.dev/security#stored-statements-to-prevent-sql-injection), to avoid some forms of SQL injection and receiving SQL from the client altogether;
85 | * [**CORS/Allowed Origin**](https://docs.sqliterg.dev/security#cors-allowed-origin) can be configured and enforced;
86 | * It's possible to [**bind**](https://docs.sqliterg.dev/security#binding-to-a-network-interface) to a network interface, to limit access.
87 |
88 | Some design choices:
89 |
90 | * Very thin layer over SQLite. Errors and type translation, for example, are those provided by the SQLite driver;
91 | * Doesn't include HTTPS, as this can be done easily (and much more securely) with a [reverse proxy](https://docs.sqliterg.dev/security#use-a-reverse-proxy-if-going-on-the-internet).
92 |
93 | # 🥇 Credits
94 |
95 | Kindly supported by [JetBrains for Open Source development](https://www.jetbrains.com/community/opensource/?utm_campaign=opensource&utm_content=approved&utm_medium=email&utm_source=newsletter&utm_term=jblogo#support).
96 |
--------------------------------------------------------------------------------
/src/commons.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023-, Germano Rizzo
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use chrono::{Datelike, Local, Timelike};
16 | use eyre::Result;
17 | use ring::digest::{Context, SHA256};
18 | use std::{
19 | borrow::Borrow,
20 | collections::HashMap,
21 | fs::{read_dir, remove_file},
22 | ops::Deref,
23 | path::Path,
24 | process::exit,
25 | };
26 |
27 | // General utils
28 |
29 | pub fn abort(str: String) -> ! {
30 | eprintln!("FATAL: {}", str);
31 | exit(1);
32 | }
33 |
34 | // https://github.com/serde-rs/serde/issues/1030#issuecomment-522278006
35 | pub fn default_as_false() -> bool {
36 | false
37 | }
38 |
39 | pub fn default_as_true() -> bool {
40 | true
41 | }
42 |
43 | pub fn default_as_zero() -> i32 {
44 | 0
45 | }
46 |
47 | pub fn file_exists(path: &str) -> bool {
48 | let path = Path::new(path);
49 | Path::new(path).exists()
50 | }
51 |
52 | pub fn is_dir(path: &String) -> bool {
53 | let path = Path::new(path);
54 | Path::new(path).is_dir()
55 | }
56 |
57 | pub fn is_file_in_directory(file_path: &str, dir_path: &str) -> bool {
58 | let file_path = Path::new(file_path);
59 | let dir_path = Path::new(dir_path);
60 |
61 | file_path.starts_with(dir_path)
62 | }
63 |
64 | pub fn sha256(input: &String) -> String {
65 | let digest = {
66 | let mut context = Context::new(&SHA256);
67 | context.update(input.as_bytes()); // UTF-8
68 | context.finish()
69 | };
70 |
71 | hex::encode(digest.as_ref())
72 | }
73 |
74 | pub fn equal_case_insensitive(s1: &str, s2: &str) -> bool {
75 | s1.to_lowercase() == s2.to_lowercase()
76 | }
77 |
78 | pub fn now() -> String {
79 | let current_datetime = Local::now();
80 |
81 | let year = current_datetime.year();
82 | let month = current_datetime.month();
83 | let day = current_datetime.day();
84 | let hour = current_datetime.hour();
85 | let minute = current_datetime.minute();
86 |
87 | format!("{:04}{:02}{:02}-{:02}{:02}", year, month, day, hour, minute)
88 | }
89 |
90 | pub fn delete_old_files(dir: &str, files_to_keep: usize) -> Result<()> {
91 | let path = Path::new(dir);
92 | let dir = path.parent().map(|parent| parent.to_path_buf()).unwrap();
93 |
94 | let mut entries: Vec<_> = read_dir(dir)?.filter_map(|entry| entry.ok()).collect();
95 |
96 | entries.sort_by(|a, b| {
97 | let a_meta = a.metadata().unwrap();
98 | let b_meta = b.metadata().unwrap();
99 | a_meta.modified().unwrap().cmp(&b_meta.modified().unwrap())
100 | });
101 |
102 | let num_entries = entries.len();
103 |
104 | if num_entries > files_to_keep {
105 | for entry in entries.iter().take(num_entries - files_to_keep) {
106 | if entry.path().is_file() {
107 | remove_file(entry.path())?;
108 | }
109 | }
110 | }
111 |
112 | Ok(())
113 | }
114 |
115 | pub fn check_stored_stmt<'a>(
116 | sql: &'a String,
117 | stored_statements: &'a HashMap,
118 | use_only_stored_statements: bool,
119 | ) -> Result<&'a String> {
120 | match sql.strip_prefix('^') {
121 | Some(s) => match stored_statements.get(&s.to_string()) {
122 | Some(s) => Ok(s),
123 | None => Err(eyre!("Stored statement '{}' not found", sql)),
124 | },
125 | None => {
126 | if use_only_stored_statements {
127 | Err(eyre!(
128 | "UseOnlyStoredStatement set but a stored statement wasn't used"
129 | ))
130 | } else {
131 | Ok(sql)
132 | }
133 | }
134 | }
135 | }
136 |
137 | pub fn resolve_tilde(p: &String) -> String {
138 | shellexpand::tilde(p).into_owned()
139 | }
140 |
141 | pub fn split_on_first_double_colon(input: &str) -> (String, String) {
142 | let mut parts = input.splitn(2, "::");
143 | let first_part = parts.next().unwrap_or_default().to_string();
144 | let second_part = parts.next().unwrap_or_default().to_string();
145 |
146 | (first_part, second_part)
147 | }
148 |
149 | pub fn if_abort_eyre(result: eyre::Result) -> T {
150 | result.unwrap_or_else(|e| abort(e.to_string()))
151 | }
152 |
153 | pub fn if_abort_rusqlite(result: rusqlite::Result) -> T {
154 | result.unwrap_or_else(|e| abort(e.to_string()))
155 | }
156 |
157 | pub fn assert(condition: bool, msg: String) {
158 | if !condition {
159 | abort(msg);
160 | }
161 | }
162 |
163 | // Utils to convert serde structs to slices accepted by rusqlite as named params
164 | // adapted from serde-rusqlite, https://github.com/twistedfall/serde_rusqlite/blob/master/LICENSE
165 |
166 | pub struct NamedParamsContainer(Vec<(String, Box)>);
167 |
168 | impl NamedParamsContainer {
169 | pub fn slice(&self) -> Vec<(&str, &dyn rusqlite::types::ToSql)> {
170 | self.0
171 | .iter()
172 | .map(|el| (el.0.deref(), el.1.borrow()))
173 | .collect()
174 | }
175 | }
176 |
177 | impl From)>> for NamedParamsContainer {
178 | fn from(src: Vec<(String, Box)>) -> Self {
179 | Self(src)
180 | }
181 | }
182 |
183 | pub struct PositionalParamsContainer(Vec>);
184 |
185 | impl PositionalParamsContainer {
186 | pub fn slice(&self) -> Vec<&dyn rusqlite::types::ToSql> {
187 | self.0.iter().map(|el| (el.borrow())).collect()
188 | }
189 | }
190 |
191 | impl From>> for PositionalParamsContainer {
192 | fn from(src: Vec>) -> Self {
193 | Self(src)
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/backup.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023-, Germano Rizzo
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use std::{ops::DerefMut, path::Path as SysPath, time::Duration};
16 |
17 | use actix_web::{
18 | rt::{
19 | spawn,
20 | time::{interval_at, sleep, Instant},
21 | },
22 | web, Responder,
23 | };
24 | use rusqlite::Connection;
25 |
26 | use crate::{
27 | auth::process_creds,
28 | commons::{abort, delete_old_files, file_exists},
29 | db_config::Backup,
30 | main_config::Db,
31 | req_res::{Response, Token},
32 | MUTEXES,
33 | };
34 |
35 | fn gen_bkp_file(directory: &str, filepath: &str) -> String {
36 | let path = SysPath::new(directory);
37 | let fpath = SysPath::new(filepath);
38 | let base_name = fpath.file_stem().unwrap().to_str().unwrap();
39 | let extension = fpath.extension();
40 | let intermission = crate::commons::now();
41 |
42 | let new_file_name = match extension {
43 | Some(e) => format!("{}_{}.{}", base_name, intermission, e.to_str().unwrap()),
44 | None => format!("{}_{}", base_name, intermission),
45 | };
46 | let new_file_path = path.join(new_file_name);
47 |
48 | new_file_path.into_os_string().into_string().unwrap()
49 | }
50 |
51 | fn do_backup(bkp_dir: &str, num_files: usize, db_path: &str, conn: &Connection) -> Response {
52 | if !file_exists(bkp_dir) {
53 | return Response::new_err(404, -1, format!("Backup dir '{}' not found", bkp_dir));
54 | }
55 |
56 | let file = gen_bkp_file(bkp_dir, db_path);
57 | if file_exists(&file) {
58 | Response::new_err(409, -1, format!("File '{}' already exists", file))
59 | } else {
60 | match conn.execute("VACUUM INTO ?1", [&file]) {
61 | Ok(_) => match delete_old_files(&file, num_files) {
62 | Ok(_) => Response::new_ok(vec![]),
63 | Err(e) => Response::new_err(
64 | 500,
65 | -1,
66 | format!("Database backed up but error in deleting old files: {}", e),
67 | ),
68 | },
69 | Err(e) => Response::new_err(500, -1, e.to_string()),
70 | }
71 | }
72 | }
73 |
74 | pub async fn handler(
75 | db_conf: web::Data,
76 | db_name: web::Data,
77 | token: web::Query,
78 | ) -> impl Responder {
79 | let db_name = db_name.to_string();
80 | match &db_conf.conf.backup {
81 | Some(bkp) => match &bkp.execution.web_service {
82 | Some(bkp_ws) => {
83 | if !process_creds(&token.token, &bkp_ws.auth_token, &bkp_ws.hashed_auth_token) {
84 | sleep(Duration::from_millis(1000)).await;
85 |
86 | return Response::new_err(
87 | bkp_ws.auth_error_code,
88 | -1,
89 | format!("In database '{}', backup: token mismatch", db_name),
90 | );
91 | }
92 |
93 | let db_lock = MUTEXES.get().unwrap().get(&db_name).unwrap();
94 | let mut db_lock_guard = db_lock.lock().unwrap();
95 | let conn = db_lock_guard.deref_mut();
96 |
97 | do_backup(&bkp.backup_dir, bkp.num_files, &db_conf.path, conn)
98 | }
99 | None => Response::new_err(
100 | 404,
101 | -1,
102 | format!(
103 | "Database '{}' doesn't have a backup.execution.webService node",
104 | db_name
105 | ),
106 | ),
107 | },
108 | None => Response::new_err(
109 | 404,
110 | -1,
111 | format!("Database '{}' doesn't have a backup node", db_name),
112 | ),
113 | }
114 | }
115 |
116 | pub fn bootstrap_backup(
117 | is_new_db: bool,
118 | bkp: &Backup,
119 | db_name: &String,
120 | db_path: &str,
121 | conn: &Connection,
122 | ) {
123 | let bkp = bkp.to_owned();
124 | let bex = &bkp.execution;
125 | if bex.on_startup || (is_new_db && bex.on_create) {
126 | let res = do_backup(&bkp.backup_dir, bkp.num_files, db_path, conn);
127 | if !res.success {
128 | abort(format!(
129 | "Backup of database '{}': {}",
130 | db_name,
131 | res.message.unwrap()
132 | ));
133 | }
134 | }
135 | println!(" - backup configured");
136 | if bex.on_create {
137 | println!(" - performed on database creation");
138 | }
139 | if bex.on_startup {
140 | println!(" - performed on server startup");
141 | }
142 | if bex.period > 0 {
143 | println!(" - performed periodically");
144 | }
145 | if bex.web_service.is_some() {
146 | println!(" - callable via web service");
147 | }
148 | }
149 |
150 | pub fn periodic_backup(bkp: &Backup, db_name: String, db_path: String) {
151 | let bkp = bkp.to_owned();
152 | let period = bkp.execution.period;
153 | let bkp_dir = bkp.backup_dir;
154 | let num_files = bkp.num_files;
155 | if period > 0 {
156 | let period = period as u64 * 60;
157 | spawn(async move {
158 | let p = Duration::from_secs(period);
159 | let first_start = Instant::now().checked_add(p).unwrap();
160 | let mut interval = interval_at(first_start, p);
161 |
162 | loop {
163 | interval.tick().await;
164 |
165 | if !file_exists(&bkp_dir) {
166 | eprintln!("Backup dir '{}' not found", bkp_dir);
167 | return;
168 | }
169 |
170 | let file = gen_bkp_file(&bkp_dir, &db_path);
171 | if file_exists(&file) {
172 | eprintln!("File '{}' already exists", file);
173 | } else {
174 | let db_lock = MUTEXES.get().unwrap().get(&db_name).unwrap();
175 | let mut db_lock_guard = db_lock.lock().unwrap();
176 | let conn = db_lock_guard.deref_mut();
177 |
178 | let res = do_backup(&bkp_dir, num_files, &db_path, conn);
179 | if !res.success {
180 | eprintln!("Backing up '{}': {}", db_name, res.message.unwrap())
181 | }
182 | }
183 | }
184 | });
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/main_config.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023-, Germano Rizzo
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use std::fs::remove_file;
16 | use std::sync::Mutex;
17 | use std::{collections::HashMap, path::Path};
18 |
19 | use rusqlite::Connection;
20 |
21 | use crate::backup::{bootstrap_backup, periodic_backup};
22 | use crate::commandline::AppConfig;
23 | use crate::commons::{
24 | abort, assert, file_exists, if_abort_rusqlite, is_dir, is_file_in_directory, resolve_tilde,
25 | split_on_first_double_colon,
26 | };
27 | use crate::db_config::{parse_dbconf, DbConfig, Macro};
28 | use crate::macros::{bootstrap_db_macros, count_macros, periodic_macro, resolve_macros};
29 | use crate::MUTEXES;
30 |
31 | #[derive(Debug, Clone)]
32 | pub struct Db {
33 | pub is_mem: bool,
34 | pub path: String,
35 | pub conf: DbConfig,
36 |
37 | // calculated
38 | pub stored_statements: HashMap,
39 | pub macros: HashMap,
40 | }
41 |
42 | fn split_path(path: &str) -> (String, String, String) {
43 | // returns (db_path, yaml, db_name)
44 | let (mut db_path, mut yaml) = split_on_first_double_colon(path);
45 | db_path = resolve_tilde(&db_path);
46 | let path = Path::new(&db_path);
47 | if yaml.is_empty() {
48 | let file_stem = path.file_stem().unwrap().to_str().unwrap();
49 | let yaml_file_name = format!("{file_stem}.yaml");
50 | let yaml_path = path.with_file_name(yaml_file_name);
51 | yaml = yaml_path.to_str().unwrap().to_string();
52 | }
53 | let yaml = resolve_tilde(&yaml);
54 |
55 | let db_name = path.file_stem().unwrap().to_str().unwrap().to_string();
56 |
57 | (db_path, yaml, db_name)
58 | }
59 |
60 | fn compose_single_db(
61 | yaml: &String,
62 | conn_string: &String,
63 | db_name: &String,
64 | db_path: &String, // simple name if in-mem
65 | is_new_db: bool,
66 | is_mem: bool,
67 | ) -> (Db, Connection) {
68 | println!("- Database '{}'", db_name);
69 |
70 | if is_mem {
71 | println!(" - in-memory database");
72 | } else {
73 | println!(" - from file '{}'", db_path);
74 | if is_new_db {
75 | println!(" - file not present, it will be created");
76 | }
77 | }
78 |
79 | let mut dbconf = if yaml.is_empty() || !file_exists(yaml) {
80 | println!(" - companion file not found: assuming defaults");
81 | DbConfig::default()
82 | } else {
83 | println!(" - parsing companion file '{}'", yaml);
84 | parse_dbconf(yaml).unwrap_or_else(|e| abort(format!("parsing YAML {}: {}", yaml, e)))
85 | };
86 |
87 | if let Some(orig) = &dbconf.cors_origin {
88 | println!(" - allowed CORS origin: {}", orig);
89 | }
90 |
91 | if let Some(b) = &mut dbconf.backup {
92 | assert(
93 | b.num_files > 0,
94 | "backup: num_files must be 1 or more".to_string(),
95 | );
96 | let bd = resolve_tilde(&b.backup_dir);
97 | assert(
98 | is_dir(&bd),
99 | format!("backup directory does not exist: {}", bd),
100 | );
101 | b.backup_dir = bd;
102 | }
103 |
104 | if let Some(a) = &dbconf.auth {
105 | assert(
106 | a.by_credentials.is_none() != a.by_query.is_none(),
107 | "auth: exactly one among by_credentials and by_query must be specified".to_string(),
108 | );
109 | if let Some(vc) = &a.by_credentials {
110 | for c in vc {
111 | assert(
112 | c.password.is_some() || c.hashed_password.is_some(),
113 | format!(
114 | "auth: user '{}': password or hashedPassword must be specified",
115 | &c.user
116 | ),
117 | );
118 | }
119 | }
120 | println!(" - authentication set up");
121 | }
122 |
123 | let stored_statements: HashMap = dbconf
124 | .to_owned()
125 | .stored_statements
126 | .map(|ss| {
127 | ss.iter()
128 | .map(|el| (el.id.to_owned(), el.sql.to_owned()))
129 | .collect()
130 | })
131 | .unwrap_or_default();
132 |
133 | if !stored_statements.is_empty() {
134 | println!(
135 | " - {} stored statements configured",
136 | stored_statements.len()
137 | );
138 | if dbconf.use_only_stored_statements {
139 | println!(" - allowing only stored statements for requests")
140 | }
141 | }
142 |
143 | let macros: HashMap = resolve_macros(&mut dbconf, &stored_statements);
144 |
145 | for macr in macros.values() {
146 | assert(
147 | !macr.statements.is_empty(),
148 | format!("Macro '{}' does not have any statement", macr.id),
149 | );
150 | }
151 |
152 | if !macros.is_empty() {
153 | println!(" - {} macro(s) configured", macros.len());
154 | let count = count_macros(macros.to_owned());
155 | if count[0] > 0 {
156 | println!(" - {} applied on database creation", count[0]);
157 | }
158 | if count[1] > 0 {
159 | println!(" - {} applied on server startup", count[1]);
160 | }
161 | if count[2] > 0 {
162 | println!(" - {} applied periodically", count[2]);
163 | }
164 | if count[3] > 0 {
165 | println!(" - {} callable via web service", count[3]);
166 | }
167 | }
168 |
169 | let mut conn = if_abort_rusqlite(Connection::open(conn_string));
170 |
171 | let res = bootstrap_db_macros(is_new_db, &dbconf, db_name, &mut conn);
172 | if res.is_err() {
173 | let _ = conn.close();
174 | if !is_mem && is_new_db {
175 | let _ = remove_file(Path::new(db_path));
176 | }
177 | abort(res.err().unwrap().to_string());
178 | }
179 |
180 | for macr in macros.values() {
181 | periodic_macro(macr.to_owned(), db_name.to_owned());
182 | }
183 |
184 | if let Some(backup) = dbconf.to_owned().backup {
185 | if !is_mem {
186 | assert(
187 | !is_file_in_directory(db_path, &backup.backup_dir),
188 | format!(
189 | "Backup config for '{}': backup dir cannot be the same as db file dir",
190 | db_name
191 | ),
192 | );
193 | }
194 |
195 | bootstrap_backup(is_new_db, &backup, db_name, db_path, &conn);
196 |
197 | periodic_backup(&backup, db_name.to_owned(), conn_string.to_owned());
198 | }
199 |
200 | if dbconf.read_only {
201 | if_abort_rusqlite(conn.execute("PRAGMA query_only = true", []));
202 | println!(" - read-only");
203 | }
204 |
205 | let jm = dbconf.journal_mode.to_owned().unwrap_or("WAL".to_string());
206 | if_abort_rusqlite(conn.query_row(&format!("PRAGMA journal_mode = {}", jm), [], |_| Ok(())));
207 | println!(" - journal mode: {}", jm);
208 |
209 | let db_conf = Db {
210 | is_mem,
211 | path: conn_string.to_owned(),
212 | conf: dbconf,
213 | stored_statements,
214 | macros,
215 | };
216 | (db_conf, conn)
217 | }
218 |
219 | fn check_db_name(db_name: &String, db_map: &HashMap) {
220 | assert(
221 | !db_map.contains_key(db_name),
222 | format!("database '{}' already defined", db_name),
223 | );
224 | }
225 |
226 | pub fn compose_db_map(cl: &AppConfig) -> HashMap {
227 | let mut db_map = HashMap::new();
228 | let mut mutexes = HashMap::new();
229 | for db_path in &cl.db {
230 | let (db_path, yaml, db_name) = split_path(db_path);
231 | check_db_name(&db_name, &db_map);
232 |
233 | let is_new_db = !file_exists(&db_path);
234 |
235 | let (db_cfg, conn) =
236 | compose_single_db(&yaml, &db_path, &db_name, &db_path, is_new_db, false);
237 |
238 | db_map.insert(db_name.to_owned(), db_cfg);
239 | mutexes.insert(db_name.to_owned(), Mutex::new(conn));
240 | }
241 | for db in &cl.mem_db {
242 | let (db_name, yaml) = split_on_first_double_colon(db);
243 | check_db_name(&db_name, &db_map);
244 |
245 | let yaml = resolve_tilde(&yaml);
246 | let conn_string = format!("file:{}?mode=memory", db_name);
247 |
248 | let (db_cfg, conn) = compose_single_db(&yaml, &conn_string, &db_name, &db_name, true, true);
249 |
250 | db_map.insert(db_name.to_owned(), db_cfg);
251 | mutexes.insert(db_name.to_owned(), Mutex::new(conn));
252 | }
253 | let _ = MUTEXES.set(mutexes);
254 | db_map
255 | }
256 |
--------------------------------------------------------------------------------
/src/macros.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023-, Germano Rizzo
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use std::{collections::HashMap, ops::DerefMut, time::Duration};
16 |
17 | use actix_web::{
18 | rt::{
19 | spawn,
20 | time::{interval_at, sleep, Instant},
21 | },
22 | web::{self, Path},
23 | Responder,
24 | };
25 | use eyre::Result;
26 | use rusqlite::Connection;
27 |
28 | use crate::{
29 | auth::process_creds,
30 | commons::{check_stored_stmt, if_abort_eyre},
31 | db_config::{DbConfig, Macro},
32 | main_config::Db,
33 | req_res::{Response, ResponseItem, Token},
34 | MUTEXES,
35 | };
36 |
37 | /// Parses the macro list and substitutes the references to stored statements with the target sql
38 | pub fn resolve_macros(
39 | dbconf: &mut DbConfig,
40 | stored_statements: &HashMap,
41 | ) -> HashMap {
42 | let mut ret: HashMap = HashMap::new();
43 | if let Some(ms) = &mut dbconf.macros {
44 | for macr in ms {
45 | let mut statements: Vec = vec![];
46 | #[allow(clippy::unnecessary_to_owned)]
47 | for statement in macr.statements.to_owned() {
48 | let statement =
49 | if_abort_eyre(check_stored_stmt(&statement, stored_statements, false));
50 | statements.push(statement.to_owned());
51 | }
52 | macr.statements = statements;
53 | ret.insert(macr.id.to_owned(), macr.to_owned());
54 | }
55 | }
56 | ret
57 | }
58 |
59 | fn exec_macro_single_notrx(macr: &Macro, conn: &mut Connection) -> Response {
60 | let mut ret = vec![];
61 | for (i, statement) in macr.statements.iter().enumerate() {
62 | let changed_rows = conn.execute(statement, []);
63 | match changed_rows {
64 | Ok(cr) => {
65 | ret.push(ResponseItem {
66 | success: true,
67 | error: None,
68 | result_set: None,
69 | rows_updated: Some(cr),
70 | rows_updated_batch: None,
71 | });
72 | }
73 | Err(e) => {
74 | return Response::new_err(500, i as isize, e.to_string());
75 | }
76 | }
77 | }
78 |
79 | Response::new_ok(ret)
80 | }
81 |
82 | fn exec_macro_single(macr: &Macro, conn: &mut Connection) -> Response {
83 | if macr.disable_transaction {
84 | return exec_macro_single_notrx(macr, conn);
85 | }
86 |
87 | let tx = match conn.transaction() {
88 | Ok(tx) => tx,
89 | Err(_) => {
90 | return Response::new_err(
91 | 500,
92 | -1,
93 | format!("Transaction open failed for macro '{}'", macr.id),
94 | )
95 | }
96 | };
97 |
98 | let mut ret = vec![];
99 | for (i, statement) in macr.statements.iter().enumerate() {
100 | let changed_rows = tx.execute(statement, []);
101 | match changed_rows {
102 | Ok(cr) => {
103 | ret.push(ResponseItem {
104 | success: true,
105 | error: None,
106 | result_set: None,
107 | rows_updated: Some(cr),
108 | rows_updated_batch: None,
109 | });
110 | }
111 | Err(e) => {
112 | let _ = tx.rollback();
113 | return Response::new_err(500, i as isize, e.to_string());
114 | }
115 | }
116 | }
117 |
118 | match tx.commit() {
119 | Ok(_) => Response::new_ok(ret),
120 | Err(_) => Response::new_err(500, -1, format!("Commit failed for macro '{}'", macr.id)),
121 | }
122 | }
123 |
124 | pub fn bootstrap_db_macros(
125 | is_new_db: bool,
126 | db_conf: &DbConfig,
127 | db_name: &String,
128 | conn: &mut Connection,
129 | ) -> Result<()> {
130 | match &db_conf.macros {
131 | Some(macros) => {
132 | for macr in macros {
133 | if macr.execution.on_startup || (is_new_db && macr.execution.on_create) {
134 | let res = exec_macro_single(macr, conn);
135 | if !res.success {
136 | return Result::Err(eyre!(
137 | "In macro '{}' of db '{}', index {}: {}",
138 | macr.id,
139 | db_name,
140 | res.req_idx.unwrap_or(-1),
141 | res.message.unwrap_or("unknown error".to_string())
142 | ));
143 | };
144 | }
145 | }
146 | }
147 | None => (),
148 | }
149 |
150 | Result::Ok(())
151 | }
152 |
153 | pub async fn handler(
154 | db_conf: web::Data,
155 | db_name: web::Data,
156 | macro_name: Path,
157 | token: web::Query,
158 | ) -> impl Responder {
159 | let db_name = db_name.to_string();
160 | let macro_name = macro_name.to_string();
161 |
162 | match db_conf.macros.get(¯o_name) {
163 | Some(macr) => match ¯.execution.web_service {
164 | Some(mex_ws) => {
165 | if !process_creds(&token.token, &mex_ws.auth_token, &mex_ws.hashed_auth_token) {
166 | sleep(Duration::from_millis(1000)).await;
167 |
168 | return Response::new_err(
169 | mex_ws.auth_error_code,
170 | -1,
171 | format!(
172 | "In database '{}', macro '{}': token mismatch",
173 | db_name, macro_name
174 | ),
175 | );
176 | }
177 |
178 | let db_lock = MUTEXES.get().unwrap().get(&db_name).unwrap();
179 | let mut db_lock_guard = db_lock.lock().unwrap();
180 | let conn = db_lock_guard.deref_mut();
181 |
182 | exec_macro_single(macr, conn)
183 | }
184 | None => Response::new_err(
185 | 404,
186 | -1,
187 | format!(
188 | "In database '{}', macro '{}' doesn't have an execution node",
189 | db_name, macro_name
190 | ),
191 | ),
192 | },
193 | None => Response::new_err(
194 | 404,
195 | -1,
196 | format!(
197 | "Database '{}' doesn't have a macro named '{}'",
198 | db_name, macro_name
199 | ),
200 | ),
201 | }
202 | }
203 |
204 | pub fn periodic_macro(macr: Macro, db_name: String) {
205 | if macr.execution.period > 0 {
206 | spawn(async move {
207 | let p = Duration::from_secs(macr.execution.period as u64 * 60);
208 | let first_start = Instant::now().checked_add(p).unwrap();
209 | let mut interval = interval_at(first_start, p);
210 |
211 | loop {
212 | interval.tick().await; // skip first execution
213 |
214 | let db_lock = MUTEXES.get().unwrap().get(&db_name).unwrap();
215 | let mut db_lock_guard = db_lock.lock().unwrap();
216 | let conn = db_lock_guard.deref_mut();
217 |
218 | let tx = match conn.transaction() {
219 | Ok(tx) => tx,
220 | Err(_) => {
221 | eprintln!(
222 | "Transaction open failed for db '{}', macro '{}'",
223 | db_name, macr.id,
224 | );
225 | return;
226 | }
227 | };
228 |
229 | for (i, statement) in macr.statements.iter().enumerate() {
230 | match tx.execute(statement, []) {
231 | Ok(_) => (),
232 | Err(e) => {
233 | let _ = tx.rollback();
234 | eprintln!(
235 | "In macro '{}' of db '{}', index {}: {}",
236 | macr.id, db_name, i, e
237 | );
238 | return;
239 | }
240 | }
241 | }
242 |
243 | match tx.commit() {
244 | Ok(_) => println!("Macro '{}' executed for db '{}'", macr.id, db_name),
245 | Err(e) => {
246 | eprintln!(
247 | "Commit failed for startup macros in db '{}': {}",
248 | db_name, e
249 | );
250 | }
251 | }
252 | }
253 | });
254 | }
255 | }
256 |
257 | pub fn count_macros(macros: HashMap) -> [usize; 4] {
258 | // return [num_on_create, num_on_startup, num_periodic, num_exposed_via_webservice]
259 | let mut ret = [0, 0, 0, 0];
260 | for macr in macros.values() {
261 | let e = ¯.execution;
262 | if e.on_create {
263 | ret[0] += 1;
264 | }
265 | if e.on_startup {
266 | ret[1] += 1;
267 | }
268 | if e.period > 0 {
269 | ret[2] += 1;
270 | }
271 | if e.web_service.is_some() {
272 | ret[3] += 1;
273 | }
274 | }
275 | ret
276 | }
277 |
--------------------------------------------------------------------------------
/src/logic.rs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2023-, Germano Rizzo
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use std::{collections::HashMap, ops::DerefMut, time::Duration};
16 |
17 | use actix_web::{http::header::Header, rt::time::sleep, web, HttpRequest, Responder};
18 | use actix_web_httpauth::headers::authorization::{Authorization, Basic};
19 | use eyre::Result;
20 | use rusqlite::{types::Value, ToSql, Transaction};
21 | use serde_json::{json, Map as JsonMap, Value as JsonValue};
22 |
23 | use crate::{
24 | auth::process_auth,
25 | commons::{check_stored_stmt, NamedParamsContainer, PositionalParamsContainer},
26 | db_config::{AuthMode, DbConfig},
27 | main_config::Db,
28 | req_res::{self, Response, ResponseItem},
29 | MUTEXES,
30 | };
31 |
32 | fn val_db2val_json(val: Value) -> JsonValue {
33 | match val {
34 | Value::Null => JsonValue::Null,
35 | Value::Integer(v) => json!(v),
36 | Value::Real(v) => json!(v),
37 | Value::Text(v) => json!(v),
38 | Value::Blob(v) => json!(v),
39 | }
40 | }
41 |
42 | fn calc_named_params(params: &JsonMap) -> NamedParamsContainer {
43 | let mut named_params: Vec<(String, Box)> = Vec::new();
44 |
45 | for (k, v) in params {
46 | let mut key: String = String::from(":");
47 | key.push_str(k);
48 | let val: Box = if v.is_string() {
49 | Box::new(v.as_str().unwrap().to_owned())
50 | } else {
51 | Box::new(v.to_owned())
52 | };
53 | named_params.push((key, val));
54 | }
55 |
56 | NamedParamsContainer::from(named_params)
57 | }
58 |
59 | fn calc_positional_params(params: &Vec) -> PositionalParamsContainer {
60 | let mut ret_params: Vec> = Vec::new();
61 |
62 | for v in params {
63 | let val: Box = if v.is_string() {
64 | Box::new(v.as_str().unwrap().to_owned())
65 | } else {
66 | Box::new(v.to_owned())
67 | };
68 | ret_params.push(val);
69 | }
70 |
71 | PositionalParamsContainer::from(ret_params)
72 | }
73 |
74 | #[allow(clippy::type_complexity)]
75 | fn do_query(
76 | tx: &Transaction,
77 | sql: &str,
78 | values: &Option,
79 | ) -> Result<(Option>, Option, Option>)> {
80 | let mut stmt = tx.prepare(sql)?;
81 | let column_names: Vec = stmt
82 | .column_names()
83 | .iter()
84 | .map(|cn| cn.to_string())
85 | .collect();
86 | let mut rows = match values {
87 | Some(p) => {
88 | // FIXME this code is repeated three times; I wish I could make a common
89 | // function but, no matter how I try, it seems not to be possible
90 | if p.is_object() {
91 | let map = p.as_object().unwrap();
92 | stmt.query(calc_named_params(map).slice().as_slice())?
93 | } else if p.is_array() {
94 | let array = p.as_array().unwrap();
95 | stmt.query(calc_positional_params(array).slice().as_slice())?
96 | } else {
97 | return Err(eyre!("Values are neither positional nor named".to_string()));
98 | }
99 | }
100 | None => stmt.query([])?,
101 | };
102 | let mut response = vec![];
103 | loop {
104 | let row = rows.next();
105 | match row {
106 | Ok(row) => match row {
107 | Some(row) => {
108 | let mut map: JsonMap = JsonMap::new();
109 | for (i, col_name) in column_names.iter().enumerate() {
110 | let value: Value = row.get_unwrap(i);
111 | map.insert(col_name.to_string(), val_db2val_json(value));
112 | }
113 | response.push(JsonValue::Object(map));
114 | }
115 | None => break,
116 | },
117 | Err(e) => return Err(eyre!(e.to_string())),
118 | }
119 | }
120 | Ok((Some(response), None, None))
121 | }
122 |
123 | #[allow(clippy::type_complexity)]
124 | fn do_statement(
125 | tx: &Transaction,
126 | sql: &str,
127 | values: &Option,
128 | values_batch: &Option>,
129 | ) -> Result<(Option>, Option, Option>)> {
130 | Ok(if values.is_none() && values_batch.is_none() {
131 | let changed_rows = tx.execute(sql, [])?;
132 | (None, Some(changed_rows), None)
133 | } else if values.is_some() {
134 | let p = values.as_ref().unwrap();
135 | let changed_rows = if p.is_object() {
136 | let map = p.as_object().unwrap();
137 | tx.execute(sql, calc_named_params(map).slice().as_slice())?
138 | } else if p.is_array() {
139 | let array = p.as_array().unwrap();
140 | tx.execute(sql, calc_positional_params(array).slice().as_slice())?
141 | } else {
142 | return Err(eyre!("Values are neither positional nor named".to_string()));
143 | };
144 |
145 | (None, Some(changed_rows), None)
146 | } else {
147 | // values_batch.is_some()
148 | let mut stmt = tx.prepare(sql)?;
149 | let mut ret = vec![];
150 | for p in values_batch.as_ref().unwrap() {
151 | let changed_rows = if p.is_object() {
152 | let map = p.as_object().unwrap();
153 | stmt.execute(calc_named_params(map).slice().as_slice())?
154 | } else if p.is_array() {
155 | let array = p.as_array().unwrap();
156 | stmt.execute(calc_positional_params(array).slice().as_slice())?
157 | } else {
158 | return Err(eyre!("Values are neither positional nor named".to_string()));
159 | };
160 |
161 | ret.push(changed_rows);
162 | }
163 | (None, None, Some(ret))
164 | })
165 | }
166 |
167 | fn process(
168 | db_name: &str,
169 | http_req: web::Json,
170 | stored_statements: &HashMap,
171 | dbconf: &DbConfig,
172 | ) -> Result {
173 | let db_lock = MUTEXES.get().unwrap().get(db_name).unwrap();
174 | let mut db_lock_guard = db_lock.lock().unwrap();
175 | let conn = db_lock_guard.deref_mut();
176 | let tx = conn.transaction()?;
177 |
178 | let mut results = vec![];
179 | let mut failed: Option<(u16, usize, String)> = None; // http code, index, error
180 |
181 | for (idx, trx_item) in http_req.transaction.iter().enumerate() {
182 | #[allow(clippy::type_complexity)]
183 | let ret: Result<
184 | (Option>, Option, Option>),
185 | (u16, String), // Error: (http code, message)
186 | > = if trx_item.query.is_some() == trx_item.statement.is_some() {
187 | Err((
188 | 400,
189 | "exactly one of 'query' and 'statement' must be provided".to_string(),
190 | ))
191 | } else if let Some(query) = &trx_item.query {
192 | match check_stored_stmt(query, stored_statements, dbconf.use_only_stored_statements) {
193 | Ok(sql) => match do_query(&tx, sql, &trx_item.values) {
194 | Ok(ok_payload) => Ok(ok_payload),
195 | Err(err) => Err((500, err.to_string())),
196 | },
197 | Err(e) => Err((409, e.to_string())),
198 | }
199 | } else if trx_item.values.is_some() && trx_item.values_batch.is_some() {
200 | Err((
201 | 400,
202 | "at most one of values and values_batch must be provided".to_string(),
203 | ))
204 | } else {
205 | let statement = trx_item.statement.as_ref().unwrap(); // always present, for the previous if's
206 | match check_stored_stmt(
207 | statement,
208 | stored_statements,
209 | dbconf.use_only_stored_statements,
210 | ) {
211 | Ok(sql) => match do_statement(&tx, sql, &trx_item.values, &trx_item.values_batch) {
212 | Ok(ok_payload) => Ok(ok_payload),
213 | Err(err) => Err((500, err.to_string())),
214 | },
215 | Err(e) => Err((409, e.to_string())),
216 | }
217 | };
218 |
219 | if !trx_item.no_fail {
220 | if let Err(err) = ret {
221 | failed = Some((err.0, idx, err.1));
222 | break;
223 | }
224 | }
225 |
226 | results.push(match ret {
227 | Ok(val) => ResponseItem {
228 | success: true,
229 | error: None,
230 | result_set: val.0,
231 | rows_updated: val.1,
232 | rows_updated_batch: val.2,
233 | },
234 | Err(err) => ResponseItem {
235 | success: false,
236 | error: Some(err.1),
237 | result_set: None,
238 | rows_updated: None,
239 | rows_updated_batch: None,
240 | },
241 | });
242 | }
243 |
244 | Ok(match failed {
245 | Some(f) => {
246 | tx.rollback()?;
247 | Response::new_err(f.0, f.1 as isize, f.2)
248 | }
249 | None => {
250 | tx.commit()?;
251 | Response::new_ok(results)
252 | }
253 | })
254 | }
255 |
256 | pub async fn handler(
257 | req: HttpRequest,
258 | body: web::Json,
259 | db_conf: web::Data,
260 | db_name: web::Data,
261 | ) -> impl Responder {
262 | if let Some(ac) = &db_conf.conf.auth {
263 | let ac_headers = if matches!(
264 | db_conf.conf.auth.as_ref().unwrap().mode,
265 | AuthMode::HttpBasic
266 | ) {
267 | match Authorization::::parse(&req) {
268 | Ok(a) => Some(a),
269 | Err(_) => None,
270 | }
271 | } else {
272 | None
273 | };
274 |
275 | if !process_auth(ac, &body.credentials, &ac_headers, &db_name) {
276 | sleep(Duration::from_millis(1000)).await;
277 |
278 | return Response::new_err(ac.auth_error_code, -1, "Authorization failed".to_string());
279 | }
280 | }
281 |
282 | process(&db_name, body, &db_conf.stored_statements, &db_conf.conf).unwrap()
283 | }
284 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright (c) 2023-, Germano Rizzo
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "actix-codec"
7 | version = "0.5.1"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8"
10 | dependencies = [
11 | "bitflags 1.3.2",
12 | "bytes",
13 | "futures-core",
14 | "futures-sink",
15 | "memchr",
16 | "pin-project-lite",
17 | "tokio",
18 | "tokio-util",
19 | "tracing",
20 | ]
21 |
22 | [[package]]
23 | name = "actix-cors"
24 | version = "0.6.4"
25 | source = "registry+https://github.com/rust-lang/crates.io-index"
26 | checksum = "b340e9cfa5b08690aae90fb61beb44e9b06f44fe3d0f93781aaa58cfba86245e"
27 | dependencies = [
28 | "actix-utils",
29 | "actix-web",
30 | "derive_more",
31 | "futures-util",
32 | "log",
33 | "once_cell",
34 | "smallvec",
35 | ]
36 |
37 | [[package]]
38 | name = "actix-files"
39 | version = "0.6.2"
40 | source = "registry+https://github.com/rust-lang/crates.io-index"
41 | checksum = "d832782fac6ca7369a70c9ee9a20554623c5e51c76e190ad151780ebea1cf689"
42 | dependencies = [
43 | "actix-http",
44 | "actix-service",
45 | "actix-utils",
46 | "actix-web",
47 | "askama_escape",
48 | "bitflags 1.3.2",
49 | "bytes",
50 | "derive_more",
51 | "futures-core",
52 | "http-range",
53 | "log",
54 | "mime",
55 | "mime_guess",
56 | "percent-encoding",
57 | "pin-project-lite",
58 | ]
59 |
60 | [[package]]
61 | name = "actix-http"
62 | version = "3.4.0"
63 | source = "registry+https://github.com/rust-lang/crates.io-index"
64 | checksum = "a92ef85799cba03f76e4f7c10f533e66d87c9a7e7055f3391f09000ad8351bc9"
65 | dependencies = [
66 | "actix-codec",
67 | "actix-rt",
68 | "actix-service",
69 | "actix-utils",
70 | "ahash",
71 | "base64",
72 | "bitflags 2.4.1",
73 | "brotli",
74 | "bytes",
75 | "bytestring",
76 | "derive_more",
77 | "encoding_rs",
78 | "flate2",
79 | "futures-core",
80 | "h2",
81 | "http",
82 | "httparse",
83 | "httpdate",
84 | "itoa",
85 | "language-tags",
86 | "local-channel",
87 | "mime",
88 | "percent-encoding",
89 | "pin-project-lite",
90 | "rand",
91 | "sha1",
92 | "smallvec",
93 | "tokio",
94 | "tokio-util",
95 | "tracing",
96 | "zstd",
97 | ]
98 |
99 | [[package]]
100 | name = "actix-macros"
101 | version = "0.2.4"
102 | source = "registry+https://github.com/rust-lang/crates.io-index"
103 | checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
104 | dependencies = [
105 | "quote",
106 | "syn 2.0.39",
107 | ]
108 |
109 | [[package]]
110 | name = "actix-router"
111 | version = "0.5.1"
112 | source = "registry+https://github.com/rust-lang/crates.io-index"
113 | checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799"
114 | dependencies = [
115 | "bytestring",
116 | "http",
117 | "regex",
118 | "serde",
119 | "tracing",
120 | ]
121 |
122 | [[package]]
123 | name = "actix-rt"
124 | version = "2.9.0"
125 | source = "registry+https://github.com/rust-lang/crates.io-index"
126 | checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d"
127 | dependencies = [
128 | "futures-core",
129 | "tokio",
130 | ]
131 |
132 | [[package]]
133 | name = "actix-server"
134 | version = "2.3.0"
135 | source = "registry+https://github.com/rust-lang/crates.io-index"
136 | checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4"
137 | dependencies = [
138 | "actix-rt",
139 | "actix-service",
140 | "actix-utils",
141 | "futures-core",
142 | "futures-util",
143 | "mio",
144 | "socket2",
145 | "tokio",
146 | "tracing",
147 | ]
148 |
149 | [[package]]
150 | name = "actix-service"
151 | version = "2.0.2"
152 | source = "registry+https://github.com/rust-lang/crates.io-index"
153 | checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a"
154 | dependencies = [
155 | "futures-core",
156 | "paste",
157 | "pin-project-lite",
158 | ]
159 |
160 | [[package]]
161 | name = "actix-utils"
162 | version = "3.0.1"
163 | source = "registry+https://github.com/rust-lang/crates.io-index"
164 | checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8"
165 | dependencies = [
166 | "local-waker",
167 | "pin-project-lite",
168 | ]
169 |
170 | [[package]]
171 | name = "actix-web"
172 | version = "4.4.0"
173 | source = "registry+https://github.com/rust-lang/crates.io-index"
174 | checksum = "0e4a5b5e29603ca8c94a77c65cf874718ceb60292c5a5c3e5f4ace041af462b9"
175 | dependencies = [
176 | "actix-codec",
177 | "actix-http",
178 | "actix-macros",
179 | "actix-router",
180 | "actix-rt",
181 | "actix-server",
182 | "actix-service",
183 | "actix-utils",
184 | "actix-web-codegen",
185 | "ahash",
186 | "bytes",
187 | "bytestring",
188 | "cfg-if",
189 | "cookie",
190 | "derive_more",
191 | "encoding_rs",
192 | "futures-core",
193 | "futures-util",
194 | "itoa",
195 | "language-tags",
196 | "log",
197 | "mime",
198 | "once_cell",
199 | "pin-project-lite",
200 | "regex",
201 | "serde",
202 | "serde_json",
203 | "serde_urlencoded",
204 | "smallvec",
205 | "socket2",
206 | "time",
207 | "url",
208 | ]
209 |
210 | [[package]]
211 | name = "actix-web-codegen"
212 | version = "4.2.2"
213 | source = "registry+https://github.com/rust-lang/crates.io-index"
214 | checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5"
215 | dependencies = [
216 | "actix-router",
217 | "proc-macro2",
218 | "quote",
219 | "syn 2.0.39",
220 | ]
221 |
222 | [[package]]
223 | name = "actix-web-httpauth"
224 | version = "0.8.1"
225 | source = "registry+https://github.com/rust-lang/crates.io-index"
226 | checksum = "1d613edf08a42ccc6864c941d30fe14e1b676a77d16f1dbadc1174d065a0a775"
227 | dependencies = [
228 | "actix-utils",
229 | "actix-web",
230 | "base64",
231 | "futures-core",
232 | "futures-util",
233 | "log",
234 | "pin-project-lite",
235 | ]
236 |
237 | [[package]]
238 | name = "addr2line"
239 | version = "0.21.0"
240 | source = "registry+https://github.com/rust-lang/crates.io-index"
241 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
242 | dependencies = [
243 | "gimli",
244 | ]
245 |
246 | [[package]]
247 | name = "adler"
248 | version = "1.0.2"
249 | source = "registry+https://github.com/rust-lang/crates.io-index"
250 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
251 |
252 | [[package]]
253 | name = "ahash"
254 | version = "0.8.6"
255 | source = "registry+https://github.com/rust-lang/crates.io-index"
256 | checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
257 | dependencies = [
258 | "cfg-if",
259 | "getrandom",
260 | "once_cell",
261 | "version_check",
262 | "zerocopy",
263 | ]
264 |
265 | [[package]]
266 | name = "aho-corasick"
267 | version = "1.1.2"
268 | source = "registry+https://github.com/rust-lang/crates.io-index"
269 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
270 | dependencies = [
271 | "memchr",
272 | ]
273 |
274 | [[package]]
275 | name = "alloc-no-stdlib"
276 | version = "2.0.4"
277 | source = "registry+https://github.com/rust-lang/crates.io-index"
278 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
279 |
280 | [[package]]
281 | name = "alloc-stdlib"
282 | version = "0.2.2"
283 | source = "registry+https://github.com/rust-lang/crates.io-index"
284 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
285 | dependencies = [
286 | "alloc-no-stdlib",
287 | ]
288 |
289 | [[package]]
290 | name = "allocator-api2"
291 | version = "0.2.16"
292 | source = "registry+https://github.com/rust-lang/crates.io-index"
293 | checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
294 |
295 | [[package]]
296 | name = "android-tzdata"
297 | version = "0.1.1"
298 | source = "registry+https://github.com/rust-lang/crates.io-index"
299 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
300 |
301 | [[package]]
302 | name = "android_system_properties"
303 | version = "0.1.5"
304 | source = "registry+https://github.com/rust-lang/crates.io-index"
305 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
306 | dependencies = [
307 | "libc",
308 | ]
309 |
310 | [[package]]
311 | name = "anstream"
312 | version = "0.6.4"
313 | source = "registry+https://github.com/rust-lang/crates.io-index"
314 | checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
315 | dependencies = [
316 | "anstyle",
317 | "anstyle-parse",
318 | "anstyle-query",
319 | "anstyle-wincon",
320 | "colorchoice",
321 | "utf8parse",
322 | ]
323 |
324 | [[package]]
325 | name = "anstyle"
326 | version = "1.0.4"
327 | source = "registry+https://github.com/rust-lang/crates.io-index"
328 | checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
329 |
330 | [[package]]
331 | name = "anstyle-parse"
332 | version = "0.2.3"
333 | source = "registry+https://github.com/rust-lang/crates.io-index"
334 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
335 | dependencies = [
336 | "utf8parse",
337 | ]
338 |
339 | [[package]]
340 | name = "anstyle-query"
341 | version = "1.0.1"
342 | source = "registry+https://github.com/rust-lang/crates.io-index"
343 | checksum = "a3a318f1f38d2418400f8209655bfd825785afd25aa30bb7ba6cc792e4596748"
344 | dependencies = [
345 | "windows-sys 0.52.0",
346 | ]
347 |
348 | [[package]]
349 | name = "anstyle-wincon"
350 | version = "3.0.2"
351 | source = "registry+https://github.com/rust-lang/crates.io-index"
352 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
353 | dependencies = [
354 | "anstyle",
355 | "windows-sys 0.52.0",
356 | ]
357 |
358 | [[package]]
359 | name = "askama_escape"
360 | version = "0.10.3"
361 | source = "registry+https://github.com/rust-lang/crates.io-index"
362 | checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
363 |
364 | [[package]]
365 | name = "autocfg"
366 | version = "1.1.0"
367 | source = "registry+https://github.com/rust-lang/crates.io-index"
368 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
369 |
370 | [[package]]
371 | name = "backtrace"
372 | version = "0.3.69"
373 | source = "registry+https://github.com/rust-lang/crates.io-index"
374 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
375 | dependencies = [
376 | "addr2line",
377 | "cc",
378 | "cfg-if",
379 | "libc",
380 | "miniz_oxide",
381 | "object",
382 | "rustc-demangle",
383 | ]
384 |
385 | [[package]]
386 | name = "base64"
387 | version = "0.21.5"
388 | source = "registry+https://github.com/rust-lang/crates.io-index"
389 | checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
390 |
391 | [[package]]
392 | name = "bitflags"
393 | version = "1.3.2"
394 | source = "registry+https://github.com/rust-lang/crates.io-index"
395 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
396 |
397 | [[package]]
398 | name = "bitflags"
399 | version = "2.4.1"
400 | source = "registry+https://github.com/rust-lang/crates.io-index"
401 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
402 |
403 | [[package]]
404 | name = "block-buffer"
405 | version = "0.10.4"
406 | source = "registry+https://github.com/rust-lang/crates.io-index"
407 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
408 | dependencies = [
409 | "generic-array",
410 | ]
411 |
412 | [[package]]
413 | name = "brotli"
414 | version = "3.4.0"
415 | source = "registry+https://github.com/rust-lang/crates.io-index"
416 | checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f"
417 | dependencies = [
418 | "alloc-no-stdlib",
419 | "alloc-stdlib",
420 | "brotli-decompressor",
421 | ]
422 |
423 | [[package]]
424 | name = "brotli-decompressor"
425 | version = "2.5.1"
426 | source = "registry+https://github.com/rust-lang/crates.io-index"
427 | checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
428 | dependencies = [
429 | "alloc-no-stdlib",
430 | "alloc-stdlib",
431 | ]
432 |
433 | [[package]]
434 | name = "bumpalo"
435 | version = "3.14.0"
436 | source = "registry+https://github.com/rust-lang/crates.io-index"
437 | checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
438 |
439 | [[package]]
440 | name = "bytes"
441 | version = "1.5.0"
442 | source = "registry+https://github.com/rust-lang/crates.io-index"
443 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
444 |
445 | [[package]]
446 | name = "bytestring"
447 | version = "1.3.1"
448 | source = "registry+https://github.com/rust-lang/crates.io-index"
449 | checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72"
450 | dependencies = [
451 | "bytes",
452 | ]
453 |
454 | [[package]]
455 | name = "cc"
456 | version = "1.0.83"
457 | source = "registry+https://github.com/rust-lang/crates.io-index"
458 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
459 | dependencies = [
460 | "jobserver",
461 | "libc",
462 | ]
463 |
464 | [[package]]
465 | name = "cfg-if"
466 | version = "1.0.0"
467 | source = "registry+https://github.com/rust-lang/crates.io-index"
468 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
469 |
470 | [[package]]
471 | name = "chrono"
472 | version = "0.4.31"
473 | source = "registry+https://github.com/rust-lang/crates.io-index"
474 | checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
475 | dependencies = [
476 | "android-tzdata",
477 | "iana-time-zone",
478 | "js-sys",
479 | "num-traits",
480 | "wasm-bindgen",
481 | "windows-targets 0.48.5",
482 | ]
483 |
484 | [[package]]
485 | name = "clap"
486 | version = "4.4.11"
487 | source = "registry+https://github.com/rust-lang/crates.io-index"
488 | checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2"
489 | dependencies = [
490 | "clap_builder",
491 | "clap_derive",
492 | ]
493 |
494 | [[package]]
495 | name = "clap_builder"
496 | version = "4.4.11"
497 | source = "registry+https://github.com/rust-lang/crates.io-index"
498 | checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb"
499 | dependencies = [
500 | "anstream",
501 | "anstyle",
502 | "clap_lex",
503 | "strsim",
504 | ]
505 |
506 | [[package]]
507 | name = "clap_derive"
508 | version = "4.4.7"
509 | source = "registry+https://github.com/rust-lang/crates.io-index"
510 | checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
511 | dependencies = [
512 | "heck",
513 | "proc-macro2",
514 | "quote",
515 | "syn 2.0.39",
516 | ]
517 |
518 | [[package]]
519 | name = "clap_lex"
520 | version = "0.6.0"
521 | source = "registry+https://github.com/rust-lang/crates.io-index"
522 | checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
523 |
524 | [[package]]
525 | name = "colorchoice"
526 | version = "1.0.0"
527 | source = "registry+https://github.com/rust-lang/crates.io-index"
528 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
529 |
530 | [[package]]
531 | name = "convert_case"
532 | version = "0.4.0"
533 | source = "registry+https://github.com/rust-lang/crates.io-index"
534 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
535 |
536 | [[package]]
537 | name = "cookie"
538 | version = "0.16.2"
539 | source = "registry+https://github.com/rust-lang/crates.io-index"
540 | checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
541 | dependencies = [
542 | "percent-encoding",
543 | "time",
544 | "version_check",
545 | ]
546 |
547 | [[package]]
548 | name = "core-foundation-sys"
549 | version = "0.8.6"
550 | source = "registry+https://github.com/rust-lang/crates.io-index"
551 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
552 |
553 | [[package]]
554 | name = "cpufeatures"
555 | version = "0.2.11"
556 | source = "registry+https://github.com/rust-lang/crates.io-index"
557 | checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
558 | dependencies = [
559 | "libc",
560 | ]
561 |
562 | [[package]]
563 | name = "crc32fast"
564 | version = "1.3.2"
565 | source = "registry+https://github.com/rust-lang/crates.io-index"
566 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
567 | dependencies = [
568 | "cfg-if",
569 | ]
570 |
571 | [[package]]
572 | name = "crypto-common"
573 | version = "0.1.6"
574 | source = "registry+https://github.com/rust-lang/crates.io-index"
575 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
576 | dependencies = [
577 | "generic-array",
578 | "typenum",
579 | ]
580 |
581 | [[package]]
582 | name = "deranged"
583 | version = "0.3.10"
584 | source = "registry+https://github.com/rust-lang/crates.io-index"
585 | checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc"
586 | dependencies = [
587 | "powerfmt",
588 | ]
589 |
590 | [[package]]
591 | name = "derive_more"
592 | version = "0.99.17"
593 | source = "registry+https://github.com/rust-lang/crates.io-index"
594 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
595 | dependencies = [
596 | "convert_case",
597 | "proc-macro2",
598 | "quote",
599 | "rustc_version",
600 | "syn 1.0.109",
601 | ]
602 |
603 | [[package]]
604 | name = "digest"
605 | version = "0.10.7"
606 | source = "registry+https://github.com/rust-lang/crates.io-index"
607 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
608 | dependencies = [
609 | "block-buffer",
610 | "crypto-common",
611 | ]
612 |
613 | [[package]]
614 | name = "dirs"
615 | version = "5.0.1"
616 | source = "registry+https://github.com/rust-lang/crates.io-index"
617 | checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
618 | dependencies = [
619 | "dirs-sys",
620 | ]
621 |
622 | [[package]]
623 | name = "dirs-sys"
624 | version = "0.4.1"
625 | source = "registry+https://github.com/rust-lang/crates.io-index"
626 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
627 | dependencies = [
628 | "libc",
629 | "option-ext",
630 | "redox_users",
631 | "windows-sys 0.48.0",
632 | ]
633 |
634 | [[package]]
635 | name = "encoding_rs"
636 | version = "0.8.33"
637 | source = "registry+https://github.com/rust-lang/crates.io-index"
638 | checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
639 | dependencies = [
640 | "cfg-if",
641 | ]
642 |
643 | [[package]]
644 | name = "equivalent"
645 | version = "1.0.1"
646 | source = "registry+https://github.com/rust-lang/crates.io-index"
647 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
648 |
649 | [[package]]
650 | name = "eyre"
651 | version = "0.6.9"
652 | source = "registry+https://github.com/rust-lang/crates.io-index"
653 | checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd"
654 | dependencies = [
655 | "indenter",
656 | "once_cell",
657 | ]
658 |
659 | [[package]]
660 | name = "fallible-iterator"
661 | version = "0.3.0"
662 | source = "registry+https://github.com/rust-lang/crates.io-index"
663 | checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
664 |
665 | [[package]]
666 | name = "fallible-streaming-iterator"
667 | version = "0.1.9"
668 | source = "registry+https://github.com/rust-lang/crates.io-index"
669 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
670 |
671 | [[package]]
672 | name = "flate2"
673 | version = "1.0.28"
674 | source = "registry+https://github.com/rust-lang/crates.io-index"
675 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
676 | dependencies = [
677 | "crc32fast",
678 | "miniz_oxide",
679 | ]
680 |
681 | [[package]]
682 | name = "fnv"
683 | version = "1.0.7"
684 | source = "registry+https://github.com/rust-lang/crates.io-index"
685 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
686 |
687 | [[package]]
688 | name = "form_urlencoded"
689 | version = "1.2.1"
690 | source = "registry+https://github.com/rust-lang/crates.io-index"
691 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
692 | dependencies = [
693 | "percent-encoding",
694 | ]
695 |
696 | [[package]]
697 | name = "futures-core"
698 | version = "0.3.29"
699 | source = "registry+https://github.com/rust-lang/crates.io-index"
700 | checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
701 |
702 | [[package]]
703 | name = "futures-sink"
704 | version = "0.3.29"
705 | source = "registry+https://github.com/rust-lang/crates.io-index"
706 | checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
707 |
708 | [[package]]
709 | name = "futures-task"
710 | version = "0.3.29"
711 | source = "registry+https://github.com/rust-lang/crates.io-index"
712 | checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
713 |
714 | [[package]]
715 | name = "futures-util"
716 | version = "0.3.29"
717 | source = "registry+https://github.com/rust-lang/crates.io-index"
718 | checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
719 | dependencies = [
720 | "futures-core",
721 | "futures-task",
722 | "pin-project-lite",
723 | "pin-utils",
724 | "slab",
725 | ]
726 |
727 | [[package]]
728 | name = "generic-array"
729 | version = "0.14.7"
730 | source = "registry+https://github.com/rust-lang/crates.io-index"
731 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
732 | dependencies = [
733 | "typenum",
734 | "version_check",
735 | ]
736 |
737 | [[package]]
738 | name = "getrandom"
739 | version = "0.2.11"
740 | source = "registry+https://github.com/rust-lang/crates.io-index"
741 | checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
742 | dependencies = [
743 | "cfg-if",
744 | "libc",
745 | "wasi",
746 | ]
747 |
748 | [[package]]
749 | name = "gimli"
750 | version = "0.28.1"
751 | source = "registry+https://github.com/rust-lang/crates.io-index"
752 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
753 |
754 | [[package]]
755 | name = "h2"
756 | version = "0.3.22"
757 | source = "registry+https://github.com/rust-lang/crates.io-index"
758 | checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
759 | dependencies = [
760 | "bytes",
761 | "fnv",
762 | "futures-core",
763 | "futures-sink",
764 | "futures-util",
765 | "http",
766 | "indexmap",
767 | "slab",
768 | "tokio",
769 | "tokio-util",
770 | "tracing",
771 | ]
772 |
773 | [[package]]
774 | name = "hashbrown"
775 | version = "0.14.3"
776 | source = "registry+https://github.com/rust-lang/crates.io-index"
777 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
778 | dependencies = [
779 | "ahash",
780 | "allocator-api2",
781 | ]
782 |
783 | [[package]]
784 | name = "hashlink"
785 | version = "0.8.4"
786 | source = "registry+https://github.com/rust-lang/crates.io-index"
787 | checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
788 | dependencies = [
789 | "hashbrown",
790 | ]
791 |
792 | [[package]]
793 | name = "heck"
794 | version = "0.4.1"
795 | source = "registry+https://github.com/rust-lang/crates.io-index"
796 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
797 |
798 | [[package]]
799 | name = "hex"
800 | version = "0.4.3"
801 | source = "registry+https://github.com/rust-lang/crates.io-index"
802 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
803 |
804 | [[package]]
805 | name = "http"
806 | version = "0.2.11"
807 | source = "registry+https://github.com/rust-lang/crates.io-index"
808 | checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
809 | dependencies = [
810 | "bytes",
811 | "fnv",
812 | "itoa",
813 | ]
814 |
815 | [[package]]
816 | name = "http-range"
817 | version = "0.1.5"
818 | source = "registry+https://github.com/rust-lang/crates.io-index"
819 | checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
820 |
821 | [[package]]
822 | name = "httparse"
823 | version = "1.8.0"
824 | source = "registry+https://github.com/rust-lang/crates.io-index"
825 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
826 |
827 | [[package]]
828 | name = "httpdate"
829 | version = "1.0.3"
830 | source = "registry+https://github.com/rust-lang/crates.io-index"
831 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
832 |
833 | [[package]]
834 | name = "iana-time-zone"
835 | version = "0.1.58"
836 | source = "registry+https://github.com/rust-lang/crates.io-index"
837 | checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
838 | dependencies = [
839 | "android_system_properties",
840 | "core-foundation-sys",
841 | "iana-time-zone-haiku",
842 | "js-sys",
843 | "wasm-bindgen",
844 | "windows-core",
845 | ]
846 |
847 | [[package]]
848 | name = "iana-time-zone-haiku"
849 | version = "0.1.2"
850 | source = "registry+https://github.com/rust-lang/crates.io-index"
851 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
852 | dependencies = [
853 | "cc",
854 | ]
855 |
856 | [[package]]
857 | name = "idna"
858 | version = "0.5.0"
859 | source = "registry+https://github.com/rust-lang/crates.io-index"
860 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
861 | dependencies = [
862 | "unicode-bidi",
863 | "unicode-normalization",
864 | ]
865 |
866 | [[package]]
867 | name = "indenter"
868 | version = "0.3.3"
869 | source = "registry+https://github.com/rust-lang/crates.io-index"
870 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
871 |
872 | [[package]]
873 | name = "indexmap"
874 | version = "2.1.0"
875 | source = "registry+https://github.com/rust-lang/crates.io-index"
876 | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
877 | dependencies = [
878 | "equivalent",
879 | "hashbrown",
880 | ]
881 |
882 | [[package]]
883 | name = "itoa"
884 | version = "1.0.9"
885 | source = "registry+https://github.com/rust-lang/crates.io-index"
886 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
887 |
888 | [[package]]
889 | name = "jobserver"
890 | version = "0.1.27"
891 | source = "registry+https://github.com/rust-lang/crates.io-index"
892 | checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
893 | dependencies = [
894 | "libc",
895 | ]
896 |
897 | [[package]]
898 | name = "js-sys"
899 | version = "0.3.66"
900 | source = "registry+https://github.com/rust-lang/crates.io-index"
901 | checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
902 | dependencies = [
903 | "wasm-bindgen",
904 | ]
905 |
906 | [[package]]
907 | name = "language-tags"
908 | version = "0.3.2"
909 | source = "registry+https://github.com/rust-lang/crates.io-index"
910 | checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
911 |
912 | [[package]]
913 | name = "libc"
914 | version = "0.2.150"
915 | source = "registry+https://github.com/rust-lang/crates.io-index"
916 | checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
917 |
918 | [[package]]
919 | name = "libredox"
920 | version = "0.0.1"
921 | source = "registry+https://github.com/rust-lang/crates.io-index"
922 | checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
923 | dependencies = [
924 | "bitflags 2.4.1",
925 | "libc",
926 | "redox_syscall",
927 | ]
928 |
929 | [[package]]
930 | name = "libsqlite3-sys"
931 | version = "0.27.0"
932 | source = "registry+https://github.com/rust-lang/crates.io-index"
933 | checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716"
934 | dependencies = [
935 | "cc",
936 | "pkg-config",
937 | "vcpkg",
938 | ]
939 |
940 | [[package]]
941 | name = "local-channel"
942 | version = "0.1.5"
943 | source = "registry+https://github.com/rust-lang/crates.io-index"
944 | checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8"
945 | dependencies = [
946 | "futures-core",
947 | "futures-sink",
948 | "local-waker",
949 | ]
950 |
951 | [[package]]
952 | name = "local-waker"
953 | version = "0.1.4"
954 | source = "registry+https://github.com/rust-lang/crates.io-index"
955 | checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487"
956 |
957 | [[package]]
958 | name = "lock_api"
959 | version = "0.4.11"
960 | source = "registry+https://github.com/rust-lang/crates.io-index"
961 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
962 | dependencies = [
963 | "autocfg",
964 | "scopeguard",
965 | ]
966 |
967 | [[package]]
968 | name = "log"
969 | version = "0.4.20"
970 | source = "registry+https://github.com/rust-lang/crates.io-index"
971 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
972 |
973 | [[package]]
974 | name = "memchr"
975 | version = "2.6.4"
976 | source = "registry+https://github.com/rust-lang/crates.io-index"
977 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
978 |
979 | [[package]]
980 | name = "mime"
981 | version = "0.3.17"
982 | source = "registry+https://github.com/rust-lang/crates.io-index"
983 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
984 |
985 | [[package]]
986 | name = "mime_guess"
987 | version = "2.0.4"
988 | source = "registry+https://github.com/rust-lang/crates.io-index"
989 | checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
990 | dependencies = [
991 | "mime",
992 | "unicase",
993 | ]
994 |
995 | [[package]]
996 | name = "miniz_oxide"
997 | version = "0.7.1"
998 | source = "registry+https://github.com/rust-lang/crates.io-index"
999 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
1000 | dependencies = [
1001 | "adler",
1002 | ]
1003 |
1004 | [[package]]
1005 | name = "mio"
1006 | version = "0.8.9"
1007 | source = "registry+https://github.com/rust-lang/crates.io-index"
1008 | checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
1009 | dependencies = [
1010 | "libc",
1011 | "log",
1012 | "wasi",
1013 | "windows-sys 0.48.0",
1014 | ]
1015 |
1016 | [[package]]
1017 | name = "num-traits"
1018 | version = "0.2.17"
1019 | source = "registry+https://github.com/rust-lang/crates.io-index"
1020 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
1021 | dependencies = [
1022 | "autocfg",
1023 | ]
1024 |
1025 | [[package]]
1026 | name = "object"
1027 | version = "0.32.1"
1028 | source = "registry+https://github.com/rust-lang/crates.io-index"
1029 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
1030 | dependencies = [
1031 | "memchr",
1032 | ]
1033 |
1034 | [[package]]
1035 | name = "once_cell"
1036 | version = "1.18.0"
1037 | source = "registry+https://github.com/rust-lang/crates.io-index"
1038 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
1039 |
1040 | [[package]]
1041 | name = "option-ext"
1042 | version = "0.2.0"
1043 | source = "registry+https://github.com/rust-lang/crates.io-index"
1044 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
1045 |
1046 | [[package]]
1047 | name = "parking_lot"
1048 | version = "0.12.1"
1049 | source = "registry+https://github.com/rust-lang/crates.io-index"
1050 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
1051 | dependencies = [
1052 | "lock_api",
1053 | "parking_lot_core",
1054 | ]
1055 |
1056 | [[package]]
1057 | name = "parking_lot_core"
1058 | version = "0.9.9"
1059 | source = "registry+https://github.com/rust-lang/crates.io-index"
1060 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
1061 | dependencies = [
1062 | "cfg-if",
1063 | "libc",
1064 | "redox_syscall",
1065 | "smallvec",
1066 | "windows-targets 0.48.5",
1067 | ]
1068 |
1069 | [[package]]
1070 | name = "paste"
1071 | version = "1.0.14"
1072 | source = "registry+https://github.com/rust-lang/crates.io-index"
1073 | checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
1074 |
1075 | [[package]]
1076 | name = "percent-encoding"
1077 | version = "2.3.1"
1078 | source = "registry+https://github.com/rust-lang/crates.io-index"
1079 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
1080 |
1081 | [[package]]
1082 | name = "pin-project-lite"
1083 | version = "0.2.13"
1084 | source = "registry+https://github.com/rust-lang/crates.io-index"
1085 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
1086 |
1087 | [[package]]
1088 | name = "pin-utils"
1089 | version = "0.1.0"
1090 | source = "registry+https://github.com/rust-lang/crates.io-index"
1091 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1092 |
1093 | [[package]]
1094 | name = "pkg-config"
1095 | version = "0.3.27"
1096 | source = "registry+https://github.com/rust-lang/crates.io-index"
1097 | checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
1098 |
1099 | [[package]]
1100 | name = "powerfmt"
1101 | version = "0.2.0"
1102 | source = "registry+https://github.com/rust-lang/crates.io-index"
1103 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
1104 |
1105 | [[package]]
1106 | name = "ppv-lite86"
1107 | version = "0.2.17"
1108 | source = "registry+https://github.com/rust-lang/crates.io-index"
1109 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
1110 |
1111 | [[package]]
1112 | name = "proc-macro2"
1113 | version = "1.0.70"
1114 | source = "registry+https://github.com/rust-lang/crates.io-index"
1115 | checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
1116 | dependencies = [
1117 | "unicode-ident",
1118 | ]
1119 |
1120 | [[package]]
1121 | name = "quote"
1122 | version = "1.0.33"
1123 | source = "registry+https://github.com/rust-lang/crates.io-index"
1124 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
1125 | dependencies = [
1126 | "proc-macro2",
1127 | ]
1128 |
1129 | [[package]]
1130 | name = "rand"
1131 | version = "0.8.5"
1132 | source = "registry+https://github.com/rust-lang/crates.io-index"
1133 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
1134 | dependencies = [
1135 | "libc",
1136 | "rand_chacha",
1137 | "rand_core",
1138 | ]
1139 |
1140 | [[package]]
1141 | name = "rand_chacha"
1142 | version = "0.3.1"
1143 | source = "registry+https://github.com/rust-lang/crates.io-index"
1144 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
1145 | dependencies = [
1146 | "ppv-lite86",
1147 | "rand_core",
1148 | ]
1149 |
1150 | [[package]]
1151 | name = "rand_core"
1152 | version = "0.6.4"
1153 | source = "registry+https://github.com/rust-lang/crates.io-index"
1154 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
1155 | dependencies = [
1156 | "getrandom",
1157 | ]
1158 |
1159 | [[package]]
1160 | name = "redox_syscall"
1161 | version = "0.4.1"
1162 | source = "registry+https://github.com/rust-lang/crates.io-index"
1163 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
1164 | dependencies = [
1165 | "bitflags 1.3.2",
1166 | ]
1167 |
1168 | [[package]]
1169 | name = "redox_users"
1170 | version = "0.4.4"
1171 | source = "registry+https://github.com/rust-lang/crates.io-index"
1172 | checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
1173 | dependencies = [
1174 | "getrandom",
1175 | "libredox",
1176 | "thiserror",
1177 | ]
1178 |
1179 | [[package]]
1180 | name = "regex"
1181 | version = "1.10.2"
1182 | source = "registry+https://github.com/rust-lang/crates.io-index"
1183 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
1184 | dependencies = [
1185 | "aho-corasick",
1186 | "memchr",
1187 | "regex-automata",
1188 | "regex-syntax",
1189 | ]
1190 |
1191 | [[package]]
1192 | name = "regex-automata"
1193 | version = "0.4.3"
1194 | source = "registry+https://github.com/rust-lang/crates.io-index"
1195 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
1196 | dependencies = [
1197 | "aho-corasick",
1198 | "memchr",
1199 | "regex-syntax",
1200 | ]
1201 |
1202 | [[package]]
1203 | name = "regex-syntax"
1204 | version = "0.8.2"
1205 | source = "registry+https://github.com/rust-lang/crates.io-index"
1206 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
1207 |
1208 | [[package]]
1209 | name = "ring"
1210 | version = "0.17.6"
1211 | source = "registry+https://github.com/rust-lang/crates.io-index"
1212 | checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866"
1213 | dependencies = [
1214 | "cc",
1215 | "getrandom",
1216 | "libc",
1217 | "spin",
1218 | "untrusted",
1219 | "windows-sys 0.48.0",
1220 | ]
1221 |
1222 | [[package]]
1223 | name = "rusqlite"
1224 | version = "0.30.0"
1225 | source = "registry+https://github.com/rust-lang/crates.io-index"
1226 | checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d"
1227 | dependencies = [
1228 | "bitflags 2.4.1",
1229 | "fallible-iterator",
1230 | "fallible-streaming-iterator",
1231 | "hashlink",
1232 | "libsqlite3-sys",
1233 | "serde_json",
1234 | "smallvec",
1235 | ]
1236 |
1237 | [[package]]
1238 | name = "rustc-demangle"
1239 | version = "0.1.23"
1240 | source = "registry+https://github.com/rust-lang/crates.io-index"
1241 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
1242 |
1243 | [[package]]
1244 | name = "rustc_version"
1245 | version = "0.4.0"
1246 | source = "registry+https://github.com/rust-lang/crates.io-index"
1247 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
1248 | dependencies = [
1249 | "semver",
1250 | ]
1251 |
1252 | [[package]]
1253 | name = "ryu"
1254 | version = "1.0.15"
1255 | source = "registry+https://github.com/rust-lang/crates.io-index"
1256 | checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
1257 |
1258 | [[package]]
1259 | name = "scopeguard"
1260 | version = "1.2.0"
1261 | source = "registry+https://github.com/rust-lang/crates.io-index"
1262 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
1263 |
1264 | [[package]]
1265 | name = "semver"
1266 | version = "1.0.20"
1267 | source = "registry+https://github.com/rust-lang/crates.io-index"
1268 | checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
1269 |
1270 | [[package]]
1271 | name = "serde"
1272 | version = "1.0.193"
1273 | source = "registry+https://github.com/rust-lang/crates.io-index"
1274 | checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
1275 | dependencies = [
1276 | "serde_derive",
1277 | ]
1278 |
1279 | [[package]]
1280 | name = "serde_derive"
1281 | version = "1.0.193"
1282 | source = "registry+https://github.com/rust-lang/crates.io-index"
1283 | checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
1284 | dependencies = [
1285 | "proc-macro2",
1286 | "quote",
1287 | "syn 2.0.39",
1288 | ]
1289 |
1290 | [[package]]
1291 | name = "serde_json"
1292 | version = "1.0.108"
1293 | source = "registry+https://github.com/rust-lang/crates.io-index"
1294 | checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
1295 | dependencies = [
1296 | "itoa",
1297 | "ryu",
1298 | "serde",
1299 | ]
1300 |
1301 | [[package]]
1302 | name = "serde_urlencoded"
1303 | version = "0.7.1"
1304 | source = "registry+https://github.com/rust-lang/crates.io-index"
1305 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
1306 | dependencies = [
1307 | "form_urlencoded",
1308 | "itoa",
1309 | "ryu",
1310 | "serde",
1311 | ]
1312 |
1313 | [[package]]
1314 | name = "serde_yaml"
1315 | version = "0.9.27"
1316 | source = "registry+https://github.com/rust-lang/crates.io-index"
1317 | checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
1318 | dependencies = [
1319 | "indexmap",
1320 | "itoa",
1321 | "ryu",
1322 | "serde",
1323 | "unsafe-libyaml",
1324 | ]
1325 |
1326 | [[package]]
1327 | name = "sha1"
1328 | version = "0.10.6"
1329 | source = "registry+https://github.com/rust-lang/crates.io-index"
1330 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
1331 | dependencies = [
1332 | "cfg-if",
1333 | "cpufeatures",
1334 | "digest",
1335 | ]
1336 |
1337 | [[package]]
1338 | name = "shellexpand"
1339 | version = "3.1.0"
1340 | source = "registry+https://github.com/rust-lang/crates.io-index"
1341 | checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
1342 | dependencies = [
1343 | "dirs",
1344 | ]
1345 |
1346 | [[package]]
1347 | name = "signal-hook-registry"
1348 | version = "1.4.1"
1349 | source = "registry+https://github.com/rust-lang/crates.io-index"
1350 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
1351 | dependencies = [
1352 | "libc",
1353 | ]
1354 |
1355 | [[package]]
1356 | name = "slab"
1357 | version = "0.4.9"
1358 | source = "registry+https://github.com/rust-lang/crates.io-index"
1359 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
1360 | dependencies = [
1361 | "autocfg",
1362 | ]
1363 |
1364 | [[package]]
1365 | name = "smallvec"
1366 | version = "1.11.2"
1367 | source = "registry+https://github.com/rust-lang/crates.io-index"
1368 | checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
1369 |
1370 | [[package]]
1371 | name = "socket2"
1372 | version = "0.5.5"
1373 | source = "registry+https://github.com/rust-lang/crates.io-index"
1374 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
1375 | dependencies = [
1376 | "libc",
1377 | "windows-sys 0.48.0",
1378 | ]
1379 |
1380 | [[package]]
1381 | name = "spin"
1382 | version = "0.9.8"
1383 | source = "registry+https://github.com/rust-lang/crates.io-index"
1384 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
1385 |
1386 | [[package]]
1387 | name = "sqliterg"
1388 | version = "0.18.0"
1389 | dependencies = [
1390 | "actix-cors",
1391 | "actix-files",
1392 | "actix-web",
1393 | "actix-web-httpauth",
1394 | "chrono",
1395 | "clap",
1396 | "eyre",
1397 | "hex",
1398 | "ring",
1399 | "rusqlite",
1400 | "serde",
1401 | "serde_derive",
1402 | "serde_json",
1403 | "serde_yaml",
1404 | "shellexpand",
1405 | ]
1406 |
1407 | [[package]]
1408 | name = "strsim"
1409 | version = "0.10.0"
1410 | source = "registry+https://github.com/rust-lang/crates.io-index"
1411 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
1412 |
1413 | [[package]]
1414 | name = "syn"
1415 | version = "1.0.109"
1416 | source = "registry+https://github.com/rust-lang/crates.io-index"
1417 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
1418 | dependencies = [
1419 | "proc-macro2",
1420 | "quote",
1421 | "unicode-ident",
1422 | ]
1423 |
1424 | [[package]]
1425 | name = "syn"
1426 | version = "2.0.39"
1427 | source = "registry+https://github.com/rust-lang/crates.io-index"
1428 | checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
1429 | dependencies = [
1430 | "proc-macro2",
1431 | "quote",
1432 | "unicode-ident",
1433 | ]
1434 |
1435 | [[package]]
1436 | name = "thiserror"
1437 | version = "1.0.50"
1438 | source = "registry+https://github.com/rust-lang/crates.io-index"
1439 | checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
1440 | dependencies = [
1441 | "thiserror-impl",
1442 | ]
1443 |
1444 | [[package]]
1445 | name = "thiserror-impl"
1446 | version = "1.0.50"
1447 | source = "registry+https://github.com/rust-lang/crates.io-index"
1448 | checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
1449 | dependencies = [
1450 | "proc-macro2",
1451 | "quote",
1452 | "syn 2.0.39",
1453 | ]
1454 |
1455 | [[package]]
1456 | name = "time"
1457 | version = "0.3.30"
1458 | source = "registry+https://github.com/rust-lang/crates.io-index"
1459 | checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
1460 | dependencies = [
1461 | "deranged",
1462 | "itoa",
1463 | "powerfmt",
1464 | "serde",
1465 | "time-core",
1466 | "time-macros",
1467 | ]
1468 |
1469 | [[package]]
1470 | name = "time-core"
1471 | version = "0.1.2"
1472 | source = "registry+https://github.com/rust-lang/crates.io-index"
1473 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
1474 |
1475 | [[package]]
1476 | name = "time-macros"
1477 | version = "0.2.15"
1478 | source = "registry+https://github.com/rust-lang/crates.io-index"
1479 | checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
1480 | dependencies = [
1481 | "time-core",
1482 | ]
1483 |
1484 | [[package]]
1485 | name = "tinyvec"
1486 | version = "1.6.0"
1487 | source = "registry+https://github.com/rust-lang/crates.io-index"
1488 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
1489 | dependencies = [
1490 | "tinyvec_macros",
1491 | ]
1492 |
1493 | [[package]]
1494 | name = "tinyvec_macros"
1495 | version = "0.1.1"
1496 | source = "registry+https://github.com/rust-lang/crates.io-index"
1497 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
1498 |
1499 | [[package]]
1500 | name = "tokio"
1501 | version = "1.34.0"
1502 | source = "registry+https://github.com/rust-lang/crates.io-index"
1503 | checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
1504 | dependencies = [
1505 | "backtrace",
1506 | "bytes",
1507 | "libc",
1508 | "mio",
1509 | "parking_lot",
1510 | "pin-project-lite",
1511 | "signal-hook-registry",
1512 | "socket2",
1513 | "windows-sys 0.48.0",
1514 | ]
1515 |
1516 | [[package]]
1517 | name = "tokio-util"
1518 | version = "0.7.10"
1519 | source = "registry+https://github.com/rust-lang/crates.io-index"
1520 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
1521 | dependencies = [
1522 | "bytes",
1523 | "futures-core",
1524 | "futures-sink",
1525 | "pin-project-lite",
1526 | "tokio",
1527 | "tracing",
1528 | ]
1529 |
1530 | [[package]]
1531 | name = "tracing"
1532 | version = "0.1.40"
1533 | source = "registry+https://github.com/rust-lang/crates.io-index"
1534 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
1535 | dependencies = [
1536 | "log",
1537 | "pin-project-lite",
1538 | "tracing-core",
1539 | ]
1540 |
1541 | [[package]]
1542 | name = "tracing-core"
1543 | version = "0.1.32"
1544 | source = "registry+https://github.com/rust-lang/crates.io-index"
1545 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
1546 | dependencies = [
1547 | "once_cell",
1548 | ]
1549 |
1550 | [[package]]
1551 | name = "typenum"
1552 | version = "1.17.0"
1553 | source = "registry+https://github.com/rust-lang/crates.io-index"
1554 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
1555 |
1556 | [[package]]
1557 | name = "unicase"
1558 | version = "2.7.0"
1559 | source = "registry+https://github.com/rust-lang/crates.io-index"
1560 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
1561 | dependencies = [
1562 | "version_check",
1563 | ]
1564 |
1565 | [[package]]
1566 | name = "unicode-bidi"
1567 | version = "0.3.13"
1568 | source = "registry+https://github.com/rust-lang/crates.io-index"
1569 | checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
1570 |
1571 | [[package]]
1572 | name = "unicode-ident"
1573 | version = "1.0.12"
1574 | source = "registry+https://github.com/rust-lang/crates.io-index"
1575 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
1576 |
1577 | [[package]]
1578 | name = "unicode-normalization"
1579 | version = "0.1.22"
1580 | source = "registry+https://github.com/rust-lang/crates.io-index"
1581 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
1582 | dependencies = [
1583 | "tinyvec",
1584 | ]
1585 |
1586 | [[package]]
1587 | name = "unsafe-libyaml"
1588 | version = "0.2.9"
1589 | source = "registry+https://github.com/rust-lang/crates.io-index"
1590 | checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
1591 |
1592 | [[package]]
1593 | name = "untrusted"
1594 | version = "0.9.0"
1595 | source = "registry+https://github.com/rust-lang/crates.io-index"
1596 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
1597 |
1598 | [[package]]
1599 | name = "url"
1600 | version = "2.5.0"
1601 | source = "registry+https://github.com/rust-lang/crates.io-index"
1602 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
1603 | dependencies = [
1604 | "form_urlencoded",
1605 | "idna",
1606 | "percent-encoding",
1607 | ]
1608 |
1609 | [[package]]
1610 | name = "utf8parse"
1611 | version = "0.2.1"
1612 | source = "registry+https://github.com/rust-lang/crates.io-index"
1613 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
1614 |
1615 | [[package]]
1616 | name = "vcpkg"
1617 | version = "0.2.15"
1618 | source = "registry+https://github.com/rust-lang/crates.io-index"
1619 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
1620 |
1621 | [[package]]
1622 | name = "version_check"
1623 | version = "0.9.4"
1624 | source = "registry+https://github.com/rust-lang/crates.io-index"
1625 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
1626 |
1627 | [[package]]
1628 | name = "wasi"
1629 | version = "0.11.0+wasi-snapshot-preview1"
1630 | source = "registry+https://github.com/rust-lang/crates.io-index"
1631 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1632 |
1633 | [[package]]
1634 | name = "wasm-bindgen"
1635 | version = "0.2.89"
1636 | source = "registry+https://github.com/rust-lang/crates.io-index"
1637 | checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
1638 | dependencies = [
1639 | "cfg-if",
1640 | "wasm-bindgen-macro",
1641 | ]
1642 |
1643 | [[package]]
1644 | name = "wasm-bindgen-backend"
1645 | version = "0.2.89"
1646 | source = "registry+https://github.com/rust-lang/crates.io-index"
1647 | checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
1648 | dependencies = [
1649 | "bumpalo",
1650 | "log",
1651 | "once_cell",
1652 | "proc-macro2",
1653 | "quote",
1654 | "syn 2.0.39",
1655 | "wasm-bindgen-shared",
1656 | ]
1657 |
1658 | [[package]]
1659 | name = "wasm-bindgen-macro"
1660 | version = "0.2.89"
1661 | source = "registry+https://github.com/rust-lang/crates.io-index"
1662 | checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
1663 | dependencies = [
1664 | "quote",
1665 | "wasm-bindgen-macro-support",
1666 | ]
1667 |
1668 | [[package]]
1669 | name = "wasm-bindgen-macro-support"
1670 | version = "0.2.89"
1671 | source = "registry+https://github.com/rust-lang/crates.io-index"
1672 | checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
1673 | dependencies = [
1674 | "proc-macro2",
1675 | "quote",
1676 | "syn 2.0.39",
1677 | "wasm-bindgen-backend",
1678 | "wasm-bindgen-shared",
1679 | ]
1680 |
1681 | [[package]]
1682 | name = "wasm-bindgen-shared"
1683 | version = "0.2.89"
1684 | source = "registry+https://github.com/rust-lang/crates.io-index"
1685 | checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
1686 |
1687 | [[package]]
1688 | name = "windows-core"
1689 | version = "0.51.1"
1690 | source = "registry+https://github.com/rust-lang/crates.io-index"
1691 | checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
1692 | dependencies = [
1693 | "windows-targets 0.48.5",
1694 | ]
1695 |
1696 | [[package]]
1697 | name = "windows-sys"
1698 | version = "0.48.0"
1699 | source = "registry+https://github.com/rust-lang/crates.io-index"
1700 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
1701 | dependencies = [
1702 | "windows-targets 0.48.5",
1703 | ]
1704 |
1705 | [[package]]
1706 | name = "windows-sys"
1707 | version = "0.52.0"
1708 | source = "registry+https://github.com/rust-lang/crates.io-index"
1709 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
1710 | dependencies = [
1711 | "windows-targets 0.52.0",
1712 | ]
1713 |
1714 | [[package]]
1715 | name = "windows-targets"
1716 | version = "0.48.5"
1717 | source = "registry+https://github.com/rust-lang/crates.io-index"
1718 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
1719 | dependencies = [
1720 | "windows_aarch64_gnullvm 0.48.5",
1721 | "windows_aarch64_msvc 0.48.5",
1722 | "windows_i686_gnu 0.48.5",
1723 | "windows_i686_msvc 0.48.5",
1724 | "windows_x86_64_gnu 0.48.5",
1725 | "windows_x86_64_gnullvm 0.48.5",
1726 | "windows_x86_64_msvc 0.48.5",
1727 | ]
1728 |
1729 | [[package]]
1730 | name = "windows-targets"
1731 | version = "0.52.0"
1732 | source = "registry+https://github.com/rust-lang/crates.io-index"
1733 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
1734 | dependencies = [
1735 | "windows_aarch64_gnullvm 0.52.0",
1736 | "windows_aarch64_msvc 0.52.0",
1737 | "windows_i686_gnu 0.52.0",
1738 | "windows_i686_msvc 0.52.0",
1739 | "windows_x86_64_gnu 0.52.0",
1740 | "windows_x86_64_gnullvm 0.52.0",
1741 | "windows_x86_64_msvc 0.52.0",
1742 | ]
1743 |
1744 | [[package]]
1745 | name = "windows_aarch64_gnullvm"
1746 | version = "0.48.5"
1747 | source = "registry+https://github.com/rust-lang/crates.io-index"
1748 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
1749 |
1750 | [[package]]
1751 | name = "windows_aarch64_gnullvm"
1752 | version = "0.52.0"
1753 | source = "registry+https://github.com/rust-lang/crates.io-index"
1754 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
1755 |
1756 | [[package]]
1757 | name = "windows_aarch64_msvc"
1758 | version = "0.48.5"
1759 | source = "registry+https://github.com/rust-lang/crates.io-index"
1760 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
1761 |
1762 | [[package]]
1763 | name = "windows_aarch64_msvc"
1764 | version = "0.52.0"
1765 | source = "registry+https://github.com/rust-lang/crates.io-index"
1766 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
1767 |
1768 | [[package]]
1769 | name = "windows_i686_gnu"
1770 | version = "0.48.5"
1771 | source = "registry+https://github.com/rust-lang/crates.io-index"
1772 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
1773 |
1774 | [[package]]
1775 | name = "windows_i686_gnu"
1776 | version = "0.52.0"
1777 | source = "registry+https://github.com/rust-lang/crates.io-index"
1778 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
1779 |
1780 | [[package]]
1781 | name = "windows_i686_msvc"
1782 | version = "0.48.5"
1783 | source = "registry+https://github.com/rust-lang/crates.io-index"
1784 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
1785 |
1786 | [[package]]
1787 | name = "windows_i686_msvc"
1788 | version = "0.52.0"
1789 | source = "registry+https://github.com/rust-lang/crates.io-index"
1790 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
1791 |
1792 | [[package]]
1793 | name = "windows_x86_64_gnu"
1794 | version = "0.48.5"
1795 | source = "registry+https://github.com/rust-lang/crates.io-index"
1796 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
1797 |
1798 | [[package]]
1799 | name = "windows_x86_64_gnu"
1800 | version = "0.52.0"
1801 | source = "registry+https://github.com/rust-lang/crates.io-index"
1802 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
1803 |
1804 | [[package]]
1805 | name = "windows_x86_64_gnullvm"
1806 | version = "0.48.5"
1807 | source = "registry+https://github.com/rust-lang/crates.io-index"
1808 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
1809 |
1810 | [[package]]
1811 | name = "windows_x86_64_gnullvm"
1812 | version = "0.52.0"
1813 | source = "registry+https://github.com/rust-lang/crates.io-index"
1814 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
1815 |
1816 | [[package]]
1817 | name = "windows_x86_64_msvc"
1818 | version = "0.48.5"
1819 | source = "registry+https://github.com/rust-lang/crates.io-index"
1820 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
1821 |
1822 | [[package]]
1823 | name = "windows_x86_64_msvc"
1824 | version = "0.52.0"
1825 | source = "registry+https://github.com/rust-lang/crates.io-index"
1826 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
1827 |
1828 | [[package]]
1829 | name = "zerocopy"
1830 | version = "0.7.28"
1831 | source = "registry+https://github.com/rust-lang/crates.io-index"
1832 | checksum = "7d6f15f7ade05d2a4935e34a457b936c23dc70a05cc1d97133dc99e7a3fe0f0e"
1833 | dependencies = [
1834 | "zerocopy-derive",
1835 | ]
1836 |
1837 | [[package]]
1838 | name = "zerocopy-derive"
1839 | version = "0.7.28"
1840 | source = "registry+https://github.com/rust-lang/crates.io-index"
1841 | checksum = "dbbad221e3f78500350ecbd7dfa4e63ef945c05f4c61cb7f4d3f84cd0bba649b"
1842 | dependencies = [
1843 | "proc-macro2",
1844 | "quote",
1845 | "syn 2.0.39",
1846 | ]
1847 |
1848 | [[package]]
1849 | name = "zstd"
1850 | version = "0.12.4"
1851 | source = "registry+https://github.com/rust-lang/crates.io-index"
1852 | checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c"
1853 | dependencies = [
1854 | "zstd-safe",
1855 | ]
1856 |
1857 | [[package]]
1858 | name = "zstd-safe"
1859 | version = "6.0.6"
1860 | source = "registry+https://github.com/rust-lang/crates.io-index"
1861 | checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581"
1862 | dependencies = [
1863 | "libc",
1864 | "zstd-sys",
1865 | ]
1866 |
1867 | [[package]]
1868 | name = "zstd-sys"
1869 | version = "2.0.9+zstd.1.5.5"
1870 | source = "registry+https://github.com/rust-lang/crates.io-index"
1871 | checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656"
1872 | dependencies = [
1873 | "cc",
1874 | "pkg-config",
1875 | ]
1876 |
--------------------------------------------------------------------------------