1use crate::commands;
2use crate::core::{find_repo_root, Object, ObjectHash};
3use crate::response::{CommandResponse, ServeResponse};
4use crate::storage::ObjectStore;
5use std::collections::{HashMap, HashSet};
6use std::io::{BufRead, Write};
7use std::net::{IpAddr, TcpListener};
8use std::sync::{Arc, Mutex};
9use std::time::Instant;
10use tiny_http::{Header, Method, Response, Server, StatusCode};
11
12const MAX_BODY_SIZE: usize = 1_048_576;
14
15const RATE_LIMIT_MAX_REQUESTS: u32 = 100;
17
18const RATE_LIMIT_WINDOW_SECS: u64 = 60;
20
21pub(crate) struct RateLimiter {
23 clients: HashMap<IpAddr, (Instant, u32)>,
24}
25
26impl RateLimiter {
27 pub(crate) fn new() -> Self {
28 RateLimiter {
29 clients: HashMap::new(),
30 }
31 }
32
33 pub(crate) fn check(&mut self, ip: IpAddr) -> bool {
36 let now = Instant::now();
37 let window = std::time::Duration::from_secs(RATE_LIMIT_WINDOW_SECS);
38
39 let entry = self.clients.entry(ip).or_insert((now, 0));
40 if now.duration_since(entry.0) >= window {
41 *entry = (now, 1);
43 true
44 } else if entry.1 < RATE_LIMIT_MAX_REQUESTS {
45 entry.1 += 1;
46 true
47 } else {
48 false
49 }
50 }
51}
52
53pub fn execute(port: u16, token: Option<String>) -> Result<ServeResponse, crate::errors::LitError> {
54 let repo_root = find_repo_root()?;
55 execute_at(port, token, repo_root)
56}
57
58pub fn execute_at(
61 port: u16,
62 token: Option<String>,
63 repo_root: std::path::PathBuf,
64) -> Result<ServeResponse, crate::errors::LitError> {
65 let bind_addr = format!("127.0.0.1:{}", port);
66 let server = Server::http(&bind_addr)
67 .map_err(|e| format!("Failed to start server on {}: {}", bind_addr, e))?;
68
69 eprintln!("Lit API server listening on http://{}", bind_addr);
70 eprintln!("Repository: {}", repo_root.display());
71 if token.is_some() {
72 eprintln!("Authentication: Bearer token required");
73 }
74 eprintln!("Press Ctrl+C to stop");
75
76 let mut rate_limiter = RateLimiter::new();
77
78 for mut request in server.incoming_requests() {
79 if let Some(ip) = request.remote_addr().map(|a| a.ip()) {
81 if !rate_limiter.check(ip) {
82 let body = r#"{"status":"error","error":{"message":"Rate limit exceeded"}}"#;
83 let resp = Response::from_string(body)
84 .with_status_code(StatusCode(429))
85 .with_header(json_content_type());
86 let _ = request.respond(resp);
87 continue;
88 }
89 }
90
91 if let Some(ref expected_token) = token {
93 let auth_header = request.headers().iter().find(|h| {
94 let name = h.field.as_str().to_string();
95 name.eq_ignore_ascii_case("authorization")
96 });
97
98 let authorized = match auth_header {
99 Some(h) => {
100 let val = h.value.as_str();
101 val.starts_with("Bearer ")
102 && subtle::ConstantTimeEq::ct_eq(
103 &val.as_bytes()[7..],
104 expected_token.as_bytes(),
105 )
106 .into()
107 }
108 None => false,
109 };
110
111 if !authorized {
112 let body = r#"{"status":"error","error":{"message":"Unauthorized"}}"#;
113 let resp = Response::from_string(body)
114 .with_status_code(StatusCode(401))
115 .with_header(json_content_type());
116 let _ = request.respond(resp);
117 continue;
118 }
119 }
120
121 let method = request.method().clone();
122 let url = request.url().to_string();
123
124 let body_str = read_body(&mut request).unwrap_or_default();
126 let result = route_request(method, &url, &body_str, &repo_root);
127
128 match result {
129 Ok((status, body)) => {
130 let resp = Response::from_string(body)
131 .with_status_code(StatusCode(status))
132 .with_header(json_content_type());
133 let _ = request.respond(resp);
134 }
135 Err(e) => {
136 eprintln!("API error: {}", e.internal_message());
138 let body = serde_json::json!({
139 "status": "error",
140 "error": {"message": e.user_message()}
141 })
142 .to_string();
143 let resp = Response::from_string(body)
144 .with_status_code(StatusCode(500))
145 .with_header(json_content_type());
146 let _ = request.respond(resp);
147 }
148 }
149 }
150
151 Ok(ServeResponse {
152 message: "Server stopped".to_string(),
153 })
154}
155
156pub fn execute_stdio() -> Result<ServeResponse, crate::errors::LitError> {
160 let repo_root = find_repo_root()?;
161 let stdin = std::io::stdin();
162 let stdout = std::io::stdout();
163 let reader = stdin.lock();
164 let mut writer = stdout.lock();
165
166 for line in reader.lines() {
167 let line = match line {
168 Ok(l) => l,
169 Err(_) => break, };
171 let line = line.trim().to_string();
172 if line.is_empty() {
173 continue;
174 }
175
176 let req: serde_json::Value = match serde_json::from_str(&line) {
177 Ok(v) => v,
178 Err(e) => {
179 let resp =
180 serde_json::json!({"status": 400, "body": format!("Invalid JSON: {}", e)});
181 let _ = writeln!(writer, "{}", resp);
182 let _ = writer.flush();
183 continue;
184 }
185 };
186
187 let method_str = req.get("method").and_then(|v| v.as_str()).unwrap_or("GET");
188 let path = req.get("path").and_then(|v| v.as_str()).unwrap_or("/");
189 let body = req.get("body").and_then(|v| v.as_str()).unwrap_or("");
190
191 let method = match method_str.to_uppercase().as_str() {
192 "GET" => Method::Get,
193 "POST" => Method::Post,
194 "PUT" => Method::Put,
195 "DELETE" => Method::Delete,
196 _ => Method::Get,
197 };
198
199 let (status, response_body) = match route_request(method, path, body, &repo_root) {
200 Ok((s, b)) => (s, b),
201 Err(e) => {
202 eprintln!("Stdio API error: {}", e.internal_message());
204 let err_body = serde_json::json!({
205 "status": "error",
206 "error": {"message": e.user_message()}
207 })
208 .to_string();
209 (500, err_body)
210 }
211 };
212
213 let resp = serde_json::json!({"status": status, "body": response_body});
214 let _ = writeln!(writer, "{}", resp);
215 let _ = writer.flush();
216 }
217
218 Ok(ServeResponse {
219 message: "Stdio server stopped".to_string(),
220 })
221}
222
223pub fn execute_daemon(port: u16) -> Result<ServeResponse, crate::errors::LitError> {
227 let repo_root = find_repo_root()?;
228 let bind_addr = format!("127.0.0.1:{}", port);
230 let listener = TcpListener::bind(&bind_addr)
231 .map_err(|e| format!("Failed to bind lit:// daemon on {}: {}", bind_addr, e))?;
232
233 eprintln!("Lit daemon listening on lit://127.0.0.1:{}", port);
234 eprintln!("Repository: {}", repo_root.display());
235 eprintln!("Press Ctrl+C to stop");
236
237 let rate_limiter = Arc::new(Mutex::new(RateLimiter::new()));
238
239 for stream in listener.incoming() {
240 let stream = match stream {
241 Ok(s) => s,
242 Err(e) => {
243 eprintln!("Accept error: {}", e);
244 continue;
245 }
246 };
247
248 if let Ok(addr) = stream.peer_addr() {
250 if let Ok(mut rl) = rate_limiter.lock() {
251 if !rl.check(addr.ip()) {
252 continue;
254 }
255 }
256 }
257
258 let repo = repo_root.clone();
259 std::thread::spawn(move || {
260 handle_daemon_connection(stream, &repo);
261 });
262 }
263
264 Ok(ServeResponse {
265 message: "Daemon stopped".to_string(),
266 })
267}
268
269fn handle_daemon_connection(stream: std::net::TcpStream, repo_root: &std::path::Path) {
271 let reader_stream = match stream.try_clone() {
272 Ok(s) => s,
273 Err(_) => return,
274 };
275 let reader = std::io::BufReader::new(reader_stream);
276 let mut writer = std::io::BufWriter::new(stream);
277
278 for line in reader.lines() {
279 let line = match line {
280 Ok(l) => l,
281 Err(_) => break,
282 };
283 let line = line.trim().to_string();
284 if line.is_empty() {
285 continue;
286 }
287
288 let req: serde_json::Value = match serde_json::from_str(&line) {
289 Ok(v) => v,
290 Err(e) => {
291 let resp =
292 serde_json::json!({"status": 400, "body": format!("Invalid JSON: {}", e)});
293 let _ = writeln!(writer, "{}", resp);
294 let _ = writer.flush();
295 continue;
296 }
297 };
298
299 let method_str = req.get("method").and_then(|v| v.as_str()).unwrap_or("GET");
300 let path = req.get("path").and_then(|v| v.as_str()).unwrap_or("/");
301 let body = req.get("body").and_then(|v| v.as_str()).unwrap_or("");
302
303 let method = match method_str.to_uppercase().as_str() {
304 "GET" => Method::Get,
305 "POST" => Method::Post,
306 "PUT" => Method::Put,
307 "DELETE" => Method::Delete,
308 _ => Method::Get,
309 };
310
311 let (status, response_body) = match route_request(method, path, body, repo_root) {
312 Ok((s, b)) => (s, b),
313 Err(e) => {
314 eprintln!("Daemon API error: {}", e.internal_message());
316 let err_body = serde_json::json!({
317 "status": "error",
318 "error": {"message": e.user_message()}
319 })
320 .to_string();
321 (500, err_body)
322 }
323 };
324
325 let resp = serde_json::json!({"status": status, "body": response_body});
326 if writeln!(writer, "{}", resp).is_err() {
327 break;
328 }
329 if writer.flush().is_err() {
330 break;
331 }
332 }
333}
334
335fn json_content_type() -> Header {
336 Header::from_bytes("Content-Type", "application/json").unwrap()
337}
338
339fn read_body(request: &mut tiny_http::Request) -> Result<String, crate::errors::LitError> {
340 let content_length = request.body_length().unwrap_or(0);
341 if content_length > MAX_BODY_SIZE {
342 return Err("Request body too large".into());
343 }
344 let mut body = String::new();
345 request
346 .as_reader()
347 .read_to_string(&mut body)
348 .map_err(|e| format!("Failed to read request body: {}", e))?;
349 Ok(body)
350}
351
352fn route_request(
353 method: Method,
354 url: &str,
355 body: &str,
356 repo_root: &std::path::Path,
357) -> Result<(u16, String), crate::errors::LitError> {
358 let path = url.split('?').next().unwrap_or(url);
359
360 match (method, path) {
361 (Method::Get, "/api/v1") | (Method::Get, "/api/v1/") => {
363 let info = serde_json::json!({
364 "name": "lit",
365 "version": env!("CARGO_PKG_VERSION"),
366 "api_version": "v1",
367 "endpoints": [
368 "GET /api/v1/status",
369 "GET /api/v1/log?count=N",
370 "GET /api/v1/branches",
371 "GET /api/v1/diff?staged=bool",
372 "GET /api/v1/show/:ref",
373 "GET /api/v1/tags",
374 "GET /api/v1/remotes",
375 "GET /api/v1/config",
376 "GET /api/v1/search?q=query&messages=bool",
377 "GET /api/v1/verify",
378 "GET /api/v1/ontology",
379 "POST /api/v1/add",
380 "POST /api/v1/commit",
381 "POST /api/v1/snapshot",
382 "POST /api/v1/checkout",
383 "POST /api/v1/merge",
384 "POST /api/v1/branch",
385 ]
386 });
387 Ok((200, serde_json::to_string_pretty(&info).unwrap()))
388 }
389
390 (Method::Get, "/api/v1/status") => {
392 let resp = commands::status::execute()?;
393 Ok((200, resp.to_json_output()))
394 }
395
396 (Method::Get, "/api/v1/log") => {
397 let count = parse_query_param(url, "count")
398 .and_then(|s| s.parse::<usize>().ok())
399 .unwrap_or(10);
400 let oneline = parse_query_param(url, "oneline")
401 .map(|s| s == "true")
402 .unwrap_or(false);
403 let resp = commands::log::execute(count, oneline)?;
404 Ok((200, resp.to_json_output()))
405 }
406
407 (Method::Get, "/api/v1/branches") => {
408 let resp = commands::branch::execute(None, false, true)?;
409 Ok((200, resp.to_json_output()))
410 }
411
412 (Method::Get, "/api/v1/diff") => {
413 let staged = parse_query_param(url, "staged")
414 .map(|s| s == "true")
415 .unwrap_or(false);
416 let stat = parse_query_param(url, "stat")
417 .map(|s| s == "true")
418 .unwrap_or(false);
419 let resp = commands::diff::execute(staged, stat, false, None, None)?;
420 Ok((200, resp.to_json_output()))
421 }
422
423 (Method::Get, p) if p.starts_with("/api/v1/show/") => {
424 let object = &p["/api/v1/show/".len()..];
425 if object.is_empty() {
426 return Ok((400, r#"{"status":"error","error":{"message":"Missing object ref"}}"#.to_string()));
427 }
428 if !is_valid_ref(object) {
429 return Ok((400, r#"{"status":"error","error":{"message":"Invalid object ref"}}"#.to_string()));
430 }
431 let resp = commands::show::execute(object.to_string())?;
432 Ok((200, resp.to_json_output()))
433 }
434
435 (Method::Get, "/api/v1/tags") => {
436 let resp = commands::tag::execute(None, None, false, false, false, false, true, None)?;
437 Ok((200, resp.to_json_output()))
438 }
439
440 (Method::Get, "/api/v1/remotes") => {
441 let resp = commands::remote::execute(Some(crate::RemoteCommands::List { verbose: true }))?;
442 Ok((200, resp.to_json_output()))
443 }
444
445 (Method::Get, "/api/v1/config") => {
446 let resp = commands::config::execute(Some(crate::ConfigCommands::Show))?;
447 Ok((200, resp.to_json_output()))
448 }
449
450 (Method::Get, "/api/v1/search") => {
451 let query = parse_query_param(url, "q").unwrap_or_default();
452 if query.is_empty() {
453 return Ok((400, r#"{"status":"error","error":{"message":"Missing query parameter 'q'"}}"#.to_string()));
454 }
455 let messages = parse_query_param(url, "messages")
456 .map(|s| s == "true")
457 .unwrap_or(false);
458 let metadata = parse_query_param(url, "metadata");
459 let max = parse_query_param(url, "max")
460 .and_then(|s| s.parse::<usize>().ok())
461 .unwrap_or(100);
462 let resp = commands::search::execute(query, messages, metadata, max)?;
463 Ok((200, resp.to_json_output()))
464 }
465
466 (Method::Get, "/api/v1/verify") => {
467 let resp = commands::verify::execute()?;
468 Ok((200, resp.to_json_output()))
469 }
470
471 (Method::Get, "/api/v1/ontology") => {
472 let resp = crate::ontology::get_ontology();
473 Ok((200, serde_json::to_string_pretty(&resp).unwrap()))
474 }
475
476 (Method::Post, "/api/v1/add") => {
478 let payload: serde_json::Value = serde_json::from_str(body)
479 .map_err(|e| format!("Invalid JSON: {}", e))?;
480 let files: Vec<String> = payload
481 .get("files")
482 .and_then(|v| serde_json::from_value(v.clone()).ok())
483 .unwrap_or_default();
484 if files.is_empty() {
485 return Ok((400, r#"{"status":"error","error":{"message":"Missing 'files' array"}}"#.to_string()));
486 }
487 let resp = commands::add::execute(files)?;
488 Ok((200, resp.to_json_output()))
489 }
490
491 (Method::Post, "/api/v1/commit") => {
492 let payload: serde_json::Value = serde_json::from_str(body)
493 .map_err(|e| format!("Invalid JSON: {}", e))?;
494 let message = payload
495 .get("message")
496 .and_then(|v| v.as_str())
497 .ok_or("Missing 'message' field")?
498 .to_string();
499 let author = payload
500 .get("author")
501 .and_then(|v| v.as_str())
502 .map(|s| s.to_string());
503 let resp = commands::commit::execute(message, author)?;
504 Ok((200, resp.to_json_output()))
505 }
506
507 (Method::Post, "/api/v1/snapshot") => {
508 let payload: serde_json::Value = serde_json::from_str(body)
509 .map_err(|e| format!("Invalid JSON: {}", e))?;
510 let message = payload
511 .get("message")
512 .and_then(|v| v.as_str())
513 .ok_or("Missing 'message' field")?
514 .to_string();
515 let author = payload
516 .get("author")
517 .and_then(|v| v.as_str())
518 .map(|s| s.to_string());
519 let metadata = payload.get("metadata").cloned();
520 let resp = commands::snapshot::execute(message, author, metadata)?;
521 Ok((200, resp.to_json_output()))
522 }
523
524 (Method::Post, "/api/v1/checkout") => {
525 let payload: serde_json::Value = serde_json::from_str(body)
526 .map_err(|e| format!("Invalid JSON: {}", e))?;
527 let target = payload
528 .get("target")
529 .and_then(|v| v.as_str())
530 .ok_or("Missing 'target' field")?
531 .to_string();
532 let create = payload
533 .get("create")
534 .and_then(|v| v.as_bool())
535 .unwrap_or(false);
536 let resp = commands::checkout::execute(target, create)?;
537 Ok((200, resp.to_json_output()))
538 }
539
540 (Method::Post, "/api/v1/merge") => {
541 let payload: serde_json::Value = serde_json::from_str(body)
542 .map_err(|e| format!("Invalid JSON: {}", e))?;
543 let branch = payload
544 .get("branch")
545 .and_then(|v| v.as_str())
546 .ok_or("Missing 'branch' field")?
547 .to_string();
548 let strategy = payload
549 .get("strategy")
550 .and_then(|v| v.as_str())
551 .map(|s| s.to_string());
552 let resp = commands::merge::execute(branch, strategy)?;
553 Ok((200, resp.to_json_output()))
554 }
555
556 (Method::Post, "/api/v1/branch") => {
557 let payload: serde_json::Value = serde_json::from_str(body)
558 .map_err(|e| format!("Invalid JSON: {}", e))?;
559 let name = payload
560 .get("name")
561 .and_then(|v| v.as_str())
562 .map(|s| s.to_string());
563 let delete = payload
564 .get("delete")
565 .and_then(|v| v.as_bool())
566 .unwrap_or(false);
567 let resp = commands::branch::execute(name, delete, false)?;
568 Ok((200, resp.to_json_output()))
569 }
570
571 (Method::Get, "/api/v1/transport/refs") => {
574 let kind = parse_query_param(url, "kind").unwrap_or_else(|| "all".to_string());
575 let mut refs = Vec::new();
576 if kind == "all" || kind == "heads" {
577 if let Ok(head_refs) = crate::core::refs::list_refs(repo_root, "heads") {
578 for r in head_refs {
579 refs.push(serde_json::json!({"kind": "heads", "name": r.name, "hash": r.hash}));
580 }
581 }
582 }
583 if kind == "all" || kind == "tags" {
584 if let Ok(tag_refs) = crate::core::refs::list_refs(repo_root, "tags") {
585 for r in tag_refs {
586 refs.push(serde_json::json!({"kind": "tags", "name": r.name, "hash": r.hash}));
587 }
588 }
589 }
590 Ok((200, serde_json::json!({"refs": refs}).to_string()))
591 }
592
593 (Method::Get, "/api/v1/transport/head") => {
595 let head = std::fs::read_to_string(repo_root.join(".lit").join("HEAD"))
596 .map_err(|e| format!("Failed to read HEAD: {}", e))?;
597 Ok((200, serde_json::json!({"head": head.trim()}).to_string()))
598 }
599
600 (Method::Get, p) if p.starts_with("/api/v1/transport/refs/heads/") => {
602 let branch = &p["/api/v1/transport/refs/heads/".len()..];
603 if !is_valid_ref(branch) {
604 return Ok((400, r#"{"status":"error","error":{"message":"Invalid branch name"}}"#.to_string()));
605 }
606 let hash = crate::core::refs::read_ref(repo_root, &format!("heads/{}", branch))?;
607 Ok((200, serde_json::json!({"branch": branch, "hash": hash}).to_string()))
608 }
609
610 (Method::Get, p) if p.starts_with("/api/v1/transport/objects/") && p.ends_with("/exists") => {
612 let hash_str = &p["/api/v1/transport/objects/".len()..p.len() - "/exists".len()];
613 if !is_valid_hex_hash(hash_str) {
614 return Ok((400, r#"{"status":"error","error":{"message":"Invalid object hash"}}"#.to_string()));
615 }
616 let store = ObjectStore::new(repo_root);
617 let exists = store.exists(&ObjectHash::from_hex(hash_str.to_string()));
618 Ok((200, serde_json::json!({"hash": hash_str, "exists": exists}).to_string()))
619 }
620
621 (Method::Get, p) if p.starts_with("/api/v1/transport/objects/") => {
623 let hash_str = &p["/api/v1/transport/objects/".len()..];
624 if !is_valid_hex_hash(hash_str) {
625 return Ok((400, r#"{"status":"error","error":{"message":"Invalid object hash"}}"#.to_string()));
626 }
627 let store = ObjectStore::new(repo_root);
628 let hash = ObjectHash::from_hex(hash_str.to_string());
629 let obj = store.read(&hash)?;
630 let data = obj.to_bytes();
631 use std::io::Write as _;
632 let mut encoder = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::fast());
633 encoder.write_all(&data).map_err(|e| format!("Compress error: {}", e))?;
634 let compressed = encoder.finish().map_err(|e| format!("Compress error: {}", e))?;
635 let b64 = base64_encode(&compressed);
636 Ok((200, serde_json::json!({"hash": hash_str, "data": b64}).to_string()))
637 }
638
639 (Method::Post, "/api/v1/transport/objects") => {
641 let store = ObjectStore::new(repo_root);
642 let payload: serde_json::Value = serde_json::from_str(body)
643 .map_err(|e| format!("Invalid JSON: {}", e))?;
644 let objects = payload.get("objects")
645 .and_then(|v| v.as_array())
646 .ok_or("Missing 'objects' array")?;
647 let mut written = 0;
648 for entry in objects {
649 let b64_data = entry.get("data")
650 .and_then(|v| v.as_str())
651 .ok_or("Missing 'data' field in object entry")?;
652 let compressed = base64_decode(b64_data)?;
653 use std::io::Read as _;
654 let mut decoder = flate2::read::ZlibDecoder::new(&compressed[..]);
655 let mut raw = Vec::new();
656 decoder.read_to_end(&mut raw)
657 .map_err(|e| format!("Decompress error: {}", e))?;
658 let obj = Object::from_bytes(&raw)?;
659 store.write(&obj)?;
660 written += 1;
661 }
662 Ok((200, serde_json::json!({"written": written}).to_string()))
663 }
664
665 (Method::Put, p) if p.starts_with("/api/v1/transport/refs/heads/") => {
667 let branch = &p["/api/v1/transport/refs/heads/".len()..];
668 if !is_valid_ref(branch) {
669 return Ok((400, r#"{"status":"error","error":{"message":"Invalid branch name"}}"#.to_string()));
670 }
671 let payload: serde_json::Value = serde_json::from_str(body)
672 .map_err(|e| format!("Invalid JSON: {}", e))?;
673 let hash = payload.get("hash")
674 .and_then(|v| v.as_str())
675 .ok_or("Missing 'hash' field")?;
676 let force = payload.get("force")
677 .and_then(|v| v.as_bool())
678 .unwrap_or(false);
679
680 if !force {
682 if let Ok(current) = crate::core::refs::read_ref(repo_root, &format!("heads/{}", branch)) {
683 let store = ObjectStore::new(repo_root);
684 let old_hash = ObjectHash::from_hex(current);
685 let new_hash = ObjectHash::from_hex(hash.to_string());
686 let is_ff = crate::core::merge::is_ancestor(&store, &old_hash, &new_hash)?;
687 if !is_ff {
688 return Ok((409, serde_json::json!({
689 "status": "error",
690 "error": {"message": "Non-fast-forward update rejected. Use force=true."}
691 }).to_string()));
692 }
693 }
694 }
695
696 crate::core::refs::write_ref(repo_root, &format!("heads/{}", branch), hash)?;
697 Ok((200, serde_json::json!({"branch": branch, "hash": hash, "updated": true}).to_string()))
698 }
699
700 (Method::Post, "/api/v1/transport/negotiate") => {
702 let store = ObjectStore::new(repo_root);
703 let payload: serde_json::Value = serde_json::from_str(body)
704 .map_err(|e| format!("Invalid JSON: {}", e))?;
705 let wants: Vec<String> = payload.get("wants")
706 .and_then(|v| serde_json::from_value(v.clone()).ok())
707 .unwrap_or_default();
708 let haves: Vec<String> = payload.get("haves")
709 .and_then(|v| serde_json::from_value(v.clone()).ok())
710 .unwrap_or_default();
711 let known: HashSet<String> = haves.into_iter().collect();
712 let mut all_needed = Vec::new();
713 for want in &wants {
714 let hash = ObjectHash::from_hex(want.clone());
715 let needed = crate::network::transport::walk_commit_graph(&store, &hash, &known)?;
716 for h in needed {
717 let s = h.as_str().to_string();
718 if !all_needed.contains(&s) {
719 all_needed.push(s);
720 }
721 }
722 }
723 Ok((200, serde_json::json!({"needed": all_needed}).to_string()))
724 }
725
726 _ => Ok((
727 404,
728 r#"{"status":"error","error":{"message":"Not found. GET /api/v1 for available endpoints."}}"#
729 .to_string(),
730 )),
731 }
732}
733
734fn url_decode(s: &str) -> String {
735 let mut result = Vec::new();
736 let bytes = s.as_bytes();
737 let mut i = 0;
738 while i < bytes.len() {
739 if bytes[i] == b'%' && i + 2 < bytes.len() {
740 if let Ok(byte) =
741 u8::from_str_radix(std::str::from_utf8(&bytes[i + 1..i + 3]).unwrap_or(""), 16)
742 {
743 result.push(byte);
744 i += 3;
745 continue;
746 }
747 }
748 if bytes[i] == b'+' {
749 result.push(b' ');
750 } else {
751 result.push(bytes[i]);
752 }
753 i += 1;
754 }
755 String::from_utf8(result).unwrap_or_default()
756}
757
758fn parse_query_param(url: &str, key: &str) -> Option<String> {
759 let query = url.split('?').nth(1)?;
760 for pair in query.split('&') {
761 let mut parts = pair.splitn(2, '=');
762 if let (Some(k), Some(v)) = (parts.next(), parts.next()) {
763 if k == key {
764 return Some(url_decode(v));
765 }
766 }
767 }
768 None
769}
770
771fn is_valid_ref(name: &str) -> bool {
774 if name.is_empty() || name.len() > 256 {
775 return false;
776 }
777 if name.contains("..") || name.contains("//") || name.starts_with('/') || name.ends_with('/') {
778 return false;
779 }
780 name.chars()
781 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' || c == '/')
782}
783
784fn is_valid_hex_hash(s: &str) -> bool {
786 !s.is_empty() && s.len() <= 192 && s.chars().all(|c| c.is_ascii_hexdigit())
787}
788
789fn base64_encode(data: &[u8]) -> String {
791 const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
792 let mut out = String::with_capacity(data.len().div_ceil(3) * 4);
793 for chunk in data.chunks(3) {
794 let b0 = chunk[0] as u32;
795 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
796 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
797 let triple = (b0 << 16) | (b1 << 8) | b2;
798 out.push(CHARS[((triple >> 18) & 0x3F) as usize] as char);
799 out.push(CHARS[((triple >> 12) & 0x3F) as usize] as char);
800 if chunk.len() > 1 {
801 out.push(CHARS[((triple >> 6) & 0x3F) as usize] as char);
802 } else {
803 out.push('=');
804 }
805 if chunk.len() > 2 {
806 out.push(CHARS[(triple & 0x3F) as usize] as char);
807 } else {
808 out.push('=');
809 }
810 }
811 out
812}
813
814fn base64_decode(input: &str) -> Result<Vec<u8>, crate::errors::LitError> {
816 fn val(c: u8) -> Result<u32, crate::errors::LitError> {
817 match c {
818 b'A'..=b'Z' => Ok((c - b'A') as u32),
819 b'a'..=b'z' => Ok((c - b'a' + 26) as u32),
820 b'0'..=b'9' => Ok((c - b'0' + 52) as u32),
821 b'+' => Ok(62),
822 b'/' => Ok(63),
823 b'=' => Ok(0),
824 _ => Err(format!("Invalid base64 character: {}", c as char).into()),
825 }
826 }
827 let bytes: Vec<u8> = input.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
828 let mut out = Vec::with_capacity(bytes.len() * 3 / 4);
829 for chunk in bytes.chunks(4) {
830 if chunk.len() < 4 {
831 return Err("Invalid base64 length".into());
832 }
833 let a = val(chunk[0])?;
834 let b = val(chunk[1])?;
835 let c = val(chunk[2])?;
836 let d = val(chunk[3])?;
837 let triple = (a << 18) | (b << 12) | (c << 6) | d;
838 out.push(((triple >> 16) & 0xFF) as u8);
839 if chunk[2] != b'=' {
840 out.push(((triple >> 8) & 0xFF) as u8);
841 }
842 if chunk[3] != b'=' {
843 out.push((triple & 0xFF) as u8);
844 }
845 }
846 Ok(out)
847}