aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Rutger Broekhoff2024-01-24 16:17:20 +0100
committerLibravatar Rutger Broekhoff2024-01-24 16:17:20 +0100
commit2bba6b5341fd341de2b282e43f9b62f281ccf40e (patch)
treef9db8a9b31d5840883766d29aea8cb34f771be08
parentf5a460434b2a02ecc3c03ced6ee91df800824695 (diff)
downloadgitolfs3-2bba6b5341fd341de2b282e43f9b62f281ccf40e.tar.gz
gitolfs3-2bba6b5341fd341de2b282e43f9b62f281ccf40e.zip
An attempt at implementing a shell
-rw-r--r--rs/Cargo.lock4
-rw-r--r--rs/Cargo.toml1
-rw-r--r--rs/server/src/main.rs11
-rw-r--r--rs/shell/Cargo.toml6
-rw-r--r--rs/shell/src/main.rs138
5 files changed, 156 insertions, 4 deletions
diff --git a/rs/Cargo.lock b/rs/Cargo.lock
index ed8409a..afc8c7b 100644
--- a/rs/Cargo.lock
+++ b/rs/Cargo.lock
@@ -1661,6 +1661,10 @@ dependencies = [
1661] 1661]
1662 1662
1663[[package]] 1663[[package]]
1664name = "shell"
1665version = "0.1.0"
1666
1667[[package]]
1664name = "signal-hook-registry" 1668name = "signal-hook-registry"
1665version = "1.4.1" 1669version = "1.4.1"
1666source = "registry+https://github.com/rust-lang/crates.io-index" 1670source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/rs/Cargo.toml b/rs/Cargo.toml
index ec75eb3..6439e6b 100644
--- a/rs/Cargo.toml
+++ b/rs/Cargo.toml
@@ -4,4 +4,5 @@ members = [
4 "common", 4 "common",
5 "git-lfs-authenticate", 5 "git-lfs-authenticate",
6 "server", 6 "server",
7 "shell",
7] 8]
diff --git a/rs/server/src/main.rs b/rs/server/src/main.rs
index 880cb46..76e447d 100644
--- a/rs/server/src/main.rs
+++ b/rs/server/src/main.rs
@@ -116,7 +116,8 @@ struct Env {
116} 116}
117 117
118fn require_env(name: &str) -> Result<String, String> { 118fn require_env(name: &str) -> Result<String, String> {
119 std::env::var(name).map_err(|_| format!("environment variable {name} should be defined and valid")) 119 std::env::var(name)
120 .map_err(|_| format!("environment variable {name} should be defined and valid"))
120} 121}
121 122
122impl Env { 123impl Env {
@@ -130,7 +131,8 @@ impl Env {
130 key_path: require_env("GITOLFS3_KEY_PATH")?, 131 key_path: require_env("GITOLFS3_KEY_PATH")?,
131 listen_host: require_env("GITOLFS3_LISTEN_HOST")?, 132 listen_host: require_env("GITOLFS3_LISTEN_HOST")?,
132 listen_port: require_env("GITOLFS3_LISTEN_PORT")?, 133 listen_port: require_env("GITOLFS3_LISTEN_PORT")?,
133 trusted_forwarded_hosts: std::env::var("GITOLFS3_TRUSTED_FORWARDED_HOSTS").unwrap_or_default(), 134 trusted_forwarded_hosts: std::env::var("GITOLFS3_TRUSTED_FORWARDED_HOSTS")
135 .unwrap_or_default(),
134 }) 136 })
135 } 137 }
136} 138}
@@ -170,7 +172,7 @@ async fn main() -> ExitCode {
170 Err(e) => { 172 Err(e) => {
171 println!("Failed to create S3 client: {e}"); 173 println!("Failed to create S3 client: {e}");
172 return ExitCode::FAILURE; 174 return ExitCode::FAILURE;
173 }, 175 }
174 }; 176 };
175 let key = match common::load_key(&env.key_path) { 177 let key = match common::load_key(&env.key_path) {
176 Ok(key) => key, 178 Ok(key) => key,
@@ -180,7 +182,8 @@ async fn main() -> ExitCode {
180 } 182 }
181 }; 183 };
182 184
183 let trusted_forwarded_hosts: HashSet<String> = env.trusted_forwarded_hosts 185 let trusted_forwarded_hosts: HashSet<String> = env
186 .trusted_forwarded_hosts
184 .split(',') 187 .split(',')
185 .map(|s| s.to_owned()) 188 .map(|s| s.to_owned())
186 .filter(|s| !s.is_empty()) 189 .filter(|s| !s.is_empty())
diff --git a/rs/shell/Cargo.toml b/rs/shell/Cargo.toml
new file mode 100644
index 0000000..0dcb6d6
--- /dev/null
+++ b/rs/shell/Cargo.toml
@@ -0,0 +1,6 @@
1[package]
2name = "shell"
3version = "0.1.0"
4edition = "2021"
5
6[dependencies]
diff --git a/rs/shell/src/main.rs b/rs/shell/src/main.rs
new file mode 100644
index 0000000..cbe01e7
--- /dev/null
+++ b/rs/shell/src/main.rs
@@ -0,0 +1,138 @@
1use std::{process::ExitCode, os::unix::process::CommandExt};
2
3fn parse_sq(s: &str) -> Option<(String, &str)> {
4 #[derive(PartialEq, Eq)]
5 enum SqState {
6 Quoted,
7 Unquoted { may_escape: bool },
8 UnquotedEscaped,
9 }
10
11 let mut result = String::new();
12 let mut state = SqState::Unquoted { may_escape: false };
13 let mut remaining = "";
14 for (i, c) in s.char_indices() {
15 match state {
16 SqState::Unquoted { may_escape: false} => {
17 if c != '\'' {
18 return None;
19 }
20 state = SqState::Quoted
21 }
22 SqState::Quoted => {
23 if c == '\'' {
24 state = SqState::Unquoted { may_escape: true };
25 continue;
26 }
27 result.push(c);
28 }
29 SqState::Unquoted { may_escape: true }=> {
30 if is_posix_space(c) {
31 remaining = &s[i..];
32 break;
33 }
34 if c != '\\' {
35 return None;
36 }
37 state = SqState::UnquotedEscaped;
38 }
39 SqState::UnquotedEscaped => {
40 if c != '\\' && c != '!' {
41 return None;
42 }
43 result.push(c);
44 state = SqState::Unquoted { may_escape: false };
45 }
46 }
47 }
48
49 if state != (SqState::Unquoted { may_escape: true }) {
50 return None
51 }
52 Some((result, remaining))
53}
54
55fn parse_cmd(mut cmd: &str) -> Option<Vec<String>> {
56 let mut args = Vec::<String>::new();
57
58 cmd = cmd.trim_matches(is_posix_space);
59 while cmd != "" {
60 if cmd.starts_with('\'') {
61 let (arg, remaining) = parse_sq(cmd)?;
62 args.push(arg);
63 cmd = remaining.trim_start_matches(is_posix_space);
64 } else if let Some((arg, remaining)) = cmd.split_once(is_posix_space) {
65 args.push(arg.to_owned());
66 cmd = remaining.trim_start_matches(is_posix_space);
67 } else {
68 args.push(cmd.to_owned());
69 cmd = "";
70 }
71 }
72
73 Some(args)
74}
75
76fn is_posix_space(c: char) -> bool {
77 // Form feed: 0x0c
78 // Vertical tab: 0x0b
79 c == ' ' || c == '\x0c' || c == '\n' || c == '\r' || c == '\t' || c == '\x0b'
80}
81
82fn main() -> ExitCode {
83 let mut args = std::env::args().skip(1);
84 if args.next() != Some("-c".to_string()) {
85 eprintln!("Expected usage: shell -c <argument>");
86 return ExitCode::FAILURE;
87 }
88 let Some(cmd) = args.next() else {
89 eprintln!("Missing argument for argument '-c'");
90 return ExitCode::FAILURE;
91 };
92 if args.next() != None {
93 eprintln!("Too many arguments passed");
94 return ExitCode::FAILURE;
95 }
96
97 let Some(mut cmd) = parse_cmd(&cmd) else {
98 eprintln!("Bad command");
99 return ExitCode::FAILURE;
100 };
101
102 let Some(mut program) = cmd.drain(0..1).next() else {
103 eprintln!("Bad command");
104 return ExitCode::FAILURE;
105 };
106 if program == "git" {
107 let Some(subcommand) = cmd.drain(0..1).next() else {
108 eprintln!("Bad command");
109 return ExitCode::FAILURE;
110 };
111 program.push('-');
112 program.push_str(&subcommand);
113 }
114
115 let mut args = Vec::new();
116
117 let git_cmds = ["receive-pack", "upload-archive", "upload-pack"];
118 if git_cmds.contains(&program.as_str()) {
119 if cmd.len() != 1 {
120 eprintln!("Bad command");
121 return ExitCode::FAILURE;
122 }
123 let repository = cmd[0].trim_start_matches('/');
124 args.push(repository);
125 } else if program == "git-lfs-authenticate" {
126 if cmd.len() != 2 {
127 eprintln!("Bad command");
128 return ExitCode::FAILURE;
129 }
130 let repository = cmd[0].trim_start_matches('/');
131 args.push(repository);
132 args.push(&cmd[1]);
133 }
134
135 let e = std::process::Command::new(program).args(args).exec();
136 eprintln!("Error: {e}");
137 ExitCode::FAILURE
138}