1use crate::git::repo;
46use crate::paths;
47use crate::publish::{create_index_line, write_to_index};
48use cargo_util::Sha256;
49use cargo_util::paths::append;
50use flate2::Compression;
51use flate2::write::GzEncoder;
52use pasetors::keys::{AsymmetricPublicKey, AsymmetricSecretKey};
53use pasetors::paserk::FormatAsPaserk;
54use pasetors::token::UntrustedToken;
55use std::collections::{BTreeMap, HashMap};
56use std::fmt;
57use std::fs::{self, File};
58use std::io::{BufRead, BufReader, Read, Write};
59use std::net::{SocketAddr, TcpListener, TcpStream};
60use std::path::{Path, PathBuf};
61use std::thread::{self, JoinHandle};
62use tar::{Builder, Header};
63use time::format_description::well_known::Rfc3339;
64use time::{Duration, OffsetDateTime};
65use url::Url;
66
67pub fn registry_path() -> PathBuf {
75 generate_path("registry")
76}
77
78pub fn api_path() -> PathBuf {
85 generate_path("api")
86}
87
88pub fn dl_path() -> PathBuf {
96 generate_path("dl")
97}
98
99pub fn alt_registry_path() -> PathBuf {
103 generate_path("alternative-registry")
104}
105
106fn alt_registry_url() -> Url {
108 generate_url("alternative-registry")
109}
110
111pub fn alt_dl_path() -> PathBuf {
115 generate_path("alternative-dl")
116}
117
118pub fn alt_api_path() -> PathBuf {
122 generate_path("alternative-api")
123}
124
125fn generate_path(name: &str) -> PathBuf {
126 paths::root().join(name)
127}
128fn generate_url(name: &str) -> Url {
129 Url::from_file_path(generate_path(name)).ok().unwrap()
130}
131
132#[derive(Clone)]
134pub enum Token {
135 Plaintext(String),
136 Keys(String, Option<String>),
137}
138
139impl Token {
140 pub fn rfc_key() -> Token {
144 Token::Keys(
145 "k3.secret.fNYVuMvBgOlljt9TDohnaYLblghqaHoQquVZwgR6X12cBFHZLFsaU3q7X3k1Zn36"
146 .to_string(),
147 Some("sub".to_string()),
148 )
149 }
150}
151
152type RequestCallback = Box<dyn Send + Fn(&Request, &HttpServer) -> Response>;
153
154pub struct RegistryBuilder {
158 alternative: Option<String>,
160 token: Option<Token>,
162 auth_required: bool,
164 http_index: bool,
166 http_api: bool,
168 api: bool,
170 configure_token: bool,
172 configure_registry: bool,
174 custom_responders: HashMap<String, RequestCallback>,
176 not_found_handler: RequestCallback,
178 delayed_index_update: usize,
180 credential_provider: Option<String>,
182}
183
184pub struct TestRegistry {
188 server: Option<HttpServerHandle>,
189 index_url: Url,
190 path: PathBuf,
191 api_url: Url,
192 dl_url: Url,
193 token: Token,
194}
195
196impl TestRegistry {
197 pub fn index_url(&self) -> &Url {
198 &self.index_url
199 }
200
201 pub fn api_url(&self) -> &Url {
202 &self.api_url
203 }
204
205 pub fn token(&self) -> &str {
206 match &self.token {
207 Token::Plaintext(s) => s,
208 Token::Keys(_, _) => panic!("registry was not configured with a plaintext token"),
209 }
210 }
211
212 pub fn key(&self) -> &str {
213 match &self.token {
214 Token::Plaintext(_) => panic!("registry was not configured with a secret key"),
215 Token::Keys(s, _) => s,
216 }
217 }
218
219 pub fn join(self) {
223 if let Some(mut server) = self.server {
224 server.stop();
225 let handle = server.handle.take().unwrap();
226 handle.join().unwrap();
227 }
228 }
229}
230
231impl RegistryBuilder {
232 #[must_use]
233 pub fn new() -> RegistryBuilder {
234 let not_found = |_req: &Request, _server: &HttpServer| -> Response {
235 Response {
236 code: 404,
237 headers: vec![],
238 body: b"not found".to_vec(),
239 }
240 };
241 RegistryBuilder {
242 alternative: None,
243 token: None,
244 auth_required: false,
245 http_api: false,
246 http_index: false,
247 api: true,
248 configure_registry: true,
249 configure_token: true,
250 custom_responders: HashMap::new(),
251 not_found_handler: Box::new(not_found),
252 delayed_index_update: 0,
253 credential_provider: None,
254 }
255 }
256
257 #[must_use]
259 pub fn add_responder<R: 'static + Send + Fn(&Request, &HttpServer) -> Response>(
260 mut self,
261 url: impl Into<String>,
262 responder: R,
263 ) -> Self {
264 self.custom_responders
265 .insert(url.into(), Box::new(responder));
266 self
267 }
268
269 #[must_use]
270 pub fn not_found_handler<R: 'static + Send + Fn(&Request, &HttpServer) -> Response>(
271 mut self,
272 responder: R,
273 ) -> Self {
274 self.not_found_handler = Box::new(responder);
275 self
276 }
277
278 #[must_use]
280 pub fn delayed_index_update(mut self, delay: usize) -> Self {
281 self.delayed_index_update = delay;
282 self
283 }
284
285 #[must_use]
287 pub fn alternative_named(mut self, alt: &str) -> Self {
288 self.alternative = Some(alt.to_string());
289 self
290 }
291
292 #[must_use]
294 pub fn alternative(self) -> Self {
295 self.alternative_named("alternative")
296 }
297
298 #[must_use]
300 pub fn no_configure_token(mut self) -> Self {
301 self.configure_token = false;
302 self
303 }
304
305 #[must_use]
307 pub fn no_configure_registry(mut self) -> Self {
308 self.configure_registry = false;
309 self
310 }
311
312 #[must_use]
314 pub fn token(mut self, token: Token) -> Self {
315 self.token = Some(token);
316 self
317 }
318
319 #[must_use]
322 pub fn auth_required(mut self) -> Self {
323 self.auth_required = true;
324 self
325 }
326
327 #[must_use]
329 pub fn http_index(mut self) -> Self {
330 self.http_index = true;
331 self
332 }
333
334 #[must_use]
336 pub fn http_api(mut self) -> Self {
337 self.http_api = true;
338 self
339 }
340
341 #[must_use]
343 pub fn no_api(mut self) -> Self {
344 self.api = false;
345 self
346 }
347
348 #[must_use]
350 pub fn credential_provider(mut self, provider: &[&str]) -> Self {
351 self.credential_provider = Some(format!("['{}']", provider.join("','")));
352 self
353 }
354
355 #[must_use]
357 pub fn build(self) -> TestRegistry {
358 let config_path = paths::cargo_home().join("config.toml");
359 t!(fs::create_dir_all(config_path.parent().unwrap()));
360 let prefix = if let Some(alternative) = &self.alternative {
361 format!("{alternative}-")
362 } else {
363 String::new()
364 };
365 let registry_path = generate_path(&format!("{prefix}registry"));
366 let index_url = generate_url(&format!("{prefix}registry"));
367 let api_url = generate_url(&format!("{prefix}api"));
368 let dl_url = generate_url(&format!("{prefix}dl"));
369 let dl_path = generate_path(&format!("{prefix}dl"));
370 let api_path = generate_path(&format!("{prefix}api"));
371 let token = self
372 .token
373 .unwrap_or_else(|| Token::Plaintext(format!("{prefix}sekrit")));
374
375 let (server, index_url, api_url, dl_url) = if !self.http_index && !self.http_api {
376 (None, index_url, api_url, dl_url)
378 } else {
379 let server = HttpServer::new(
380 registry_path.clone(),
381 dl_path,
382 api_path.clone(),
383 token.clone(),
384 self.auth_required,
385 self.custom_responders,
386 self.not_found_handler,
387 self.delayed_index_update,
388 );
389 let index_url = if self.http_index {
390 server.index_url()
391 } else {
392 index_url
393 };
394 let api_url = if self.http_api {
395 server.api_url()
396 } else {
397 api_url
398 };
399 let dl_url = server.dl_url();
400 (Some(server), index_url, api_url, dl_url)
401 };
402
403 let registry = TestRegistry {
404 api_url,
405 index_url,
406 server,
407 dl_url,
408 path: registry_path,
409 token,
410 };
411
412 if self.configure_registry {
413 if let Some(alternative) = &self.alternative {
414 append(
415 &config_path,
416 format!(
417 "
418 [registries.{alternative}]
419 index = '{}'",
420 registry.index_url
421 )
422 .as_bytes(),
423 )
424 .unwrap();
425 if let Some(p) = &self.credential_provider {
426 append(
427 &config_path,
428 &format!(
429 "
430 credential-provider = {p}
431 "
432 )
433 .as_bytes(),
434 )
435 .unwrap()
436 }
437 } else {
438 append(
439 &config_path,
440 format!(
441 "
442 [source.crates-io]
443 replace-with = 'dummy-registry'
444
445 [registries.dummy-registry]
446 index = '{}'",
447 registry.index_url
448 )
449 .as_bytes(),
450 )
451 .unwrap();
452
453 if let Some(p) = &self.credential_provider {
454 append(
455 &config_path,
456 &format!(
457 "
458 [registry]
459 credential-provider = {p}
460 "
461 )
462 .as_bytes(),
463 )
464 .unwrap()
465 }
466 }
467 }
468
469 if self.configure_token {
470 let credentials = paths::cargo_home().join("credentials.toml");
471 match ®istry.token {
472 Token::Plaintext(token) => {
473 if let Some(alternative) = &self.alternative {
474 append(
475 &credentials,
476 format!(
477 r#"
478 [registries.{alternative}]
479 token = "{token}"
480 "#
481 )
482 .as_bytes(),
483 )
484 .unwrap();
485 } else {
486 append(
487 &credentials,
488 format!(
489 r#"
490 [registry]
491 token = "{token}"
492 "#
493 )
494 .as_bytes(),
495 )
496 .unwrap();
497 }
498 }
499 Token::Keys(key, subject) => {
500 let mut out = if let Some(alternative) = &self.alternative {
501 format!("\n[registries.{alternative}]\n")
502 } else {
503 format!("\n[registry]\n")
504 };
505 out += &format!("secret-key = \"{key}\"\n");
506 if let Some(subject) = subject {
507 out += &format!("secret-key-subject = \"{subject}\"\n");
508 }
509
510 append(&credentials, out.as_bytes()).unwrap();
511 }
512 }
513 }
514
515 let auth = if self.auth_required {
516 r#","auth-required":true"#
517 } else {
518 ""
519 };
520 let api = if self.api {
521 format!(r#","api":"{}""#, registry.api_url)
522 } else {
523 String::new()
524 };
525 repo(®istry.path)
527 .file(
528 "config.json",
529 &format!(r#"{{"dl":"{}"{api}{auth}}}"#, registry.dl_url),
530 )
531 .build();
532 fs::create_dir_all(api_path.join("api/v1/crates")).unwrap();
533
534 registry
535 }
536}
537
538#[must_use]
564pub struct Package {
565 name: String,
566 vers: String,
567 deps: Vec<Dependency>,
568 files: Vec<PackageFile>,
569 yanked: bool,
570 features: FeatureMap,
571 local: bool,
572 alternative: bool,
573 invalid_index_line: bool,
574 index_line: Option<String>,
575 edition: Option<String>,
576 resolver: Option<String>,
577 proc_macro: bool,
578 links: Option<String>,
579 rust_version: Option<String>,
580 cargo_features: Vec<String>,
581 pubtime: Option<String>,
582 v: Option<u32>,
583}
584
585pub(crate) type FeatureMap = BTreeMap<String, Vec<String>>;
586
587#[derive(Clone)]
589pub struct Dependency {
590 name: String,
591 vers: String,
592 kind: String,
593 artifact: Option<String>,
594 bindep_target: Option<String>,
595 lib: bool,
596 target: Option<String>,
597 features: Vec<String>,
598 registry: Option<String>,
599 package: Option<String>,
600 optional: bool,
601 default_features: bool,
602 public: bool,
603}
604
605#[non_exhaustive]
607enum EntryData {
608 Regular(String),
609 Symlink(PathBuf),
610 Directory,
611}
612
613struct PackageFile {
615 path: String,
616 contents: EntryData,
617 mode: u32,
620 extra: bool,
623}
624
625const DEFAULT_MODE: u32 = 0o644;
626
627pub fn init() -> TestRegistry {
633 RegistryBuilder::new().build()
634}
635
636pub fn alt_init() -> TestRegistry {
640 init();
641 RegistryBuilder::new().alternative().build()
642}
643
644pub struct HttpServerHandle {
645 addr: SocketAddr,
646 handle: Option<JoinHandle<()>>,
647}
648
649impl HttpServerHandle {
650 pub fn index_url(&self) -> Url {
651 Url::parse(&format!("sparse+http://{}/index/", self.addr)).unwrap()
652 }
653
654 pub fn api_url(&self) -> Url {
655 Url::parse(&format!("http://{}/", self.addr)).unwrap()
656 }
657
658 pub fn dl_url(&self) -> Url {
659 Url::parse(&format!("http://{}/dl", self.addr)).unwrap()
660 }
661
662 fn stop(&self) {
663 if let Ok(mut stream) = TcpStream::connect(self.addr) {
664 let _ = stream.write_all(b"stop");
666 let _ = stream.flush();
667 }
668 }
669}
670
671impl Drop for HttpServerHandle {
672 fn drop(&mut self) {
673 self.stop();
674 }
675}
676
677#[derive(Clone)]
679pub struct Request {
680 pub url: Url,
681 pub method: String,
682 pub body: Option<Vec<u8>>,
683 pub authorization: Option<String>,
684 pub if_modified_since: Option<String>,
685 pub if_none_match: Option<String>,
686}
687
688impl fmt::Debug for Request {
689 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
690 f.debug_struct("Request")
692 .field("url", &self.url)
693 .field("method", &self.method)
694 .field("authorization", &self.authorization)
695 .field("if_modified_since", &self.if_modified_since)
696 .field("if_none_match", &self.if_none_match)
697 .finish()
698 }
699}
700
701pub struct Response {
703 pub code: u32,
704 pub headers: Vec<String>,
705 pub body: Vec<u8>,
706}
707
708pub struct HttpServer {
709 listener: TcpListener,
710 registry_path: PathBuf,
711 dl_path: PathBuf,
712 api_path: PathBuf,
713 addr: SocketAddr,
714 token: Token,
715 auth_required: bool,
716 custom_responders: HashMap<String, RequestCallback>,
717 not_found_handler: RequestCallback,
718 delayed_index_update: usize,
719}
720
721struct Mutation<'a> {
724 mutation: &'a str,
725 name: Option<&'a str>,
726 vers: Option<&'a str>,
727 cksum: Option<&'a str>,
728}
729
730impl HttpServer {
731 pub fn new(
732 registry_path: PathBuf,
733 dl_path: PathBuf,
734 api_path: PathBuf,
735 token: Token,
736 auth_required: bool,
737 custom_responders: HashMap<String, RequestCallback>,
738 not_found_handler: RequestCallback,
739 delayed_index_update: usize,
740 ) -> HttpServerHandle {
741 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
742 let addr = listener.local_addr().unwrap();
743 let server = HttpServer {
744 listener,
745 registry_path,
746 dl_path,
747 api_path,
748 addr,
749 token,
750 auth_required,
751 custom_responders,
752 not_found_handler,
753 delayed_index_update,
754 };
755 let handle = Some(thread::spawn(move || server.start()));
756 HttpServerHandle { addr, handle }
757 }
758
759 fn start(&self) {
760 let mut line = String::new();
761 'server: loop {
762 let (socket, _) = self.listener.accept().unwrap();
763 let mut buf = BufReader::new(socket);
764 line.clear();
765 if buf.read_line(&mut line).unwrap() == 0 {
766 continue;
768 }
769 let mut parts = line.split_ascii_whitespace();
771 let method = parts.next().unwrap().to_ascii_lowercase();
772 if method == "stop" {
773 return;
775 }
776 let addr = self.listener.local_addr().unwrap();
777 let url = format!(
778 "http://{}/{}",
779 addr,
780 parts.next().unwrap().trim_start_matches('/')
781 );
782 let url = Url::parse(&url).unwrap();
783
784 let mut if_modified_since = None;
786 let mut if_none_match = None;
787 let mut authorization = None;
788 let mut content_len = None;
789 loop {
790 line.clear();
791 if buf.read_line(&mut line).unwrap() == 0 {
792 continue 'server;
793 }
794 if line == "\r\n" {
795 line.clear();
797 break;
798 }
799 let (name, value) = line.split_once(':').unwrap();
800 let name = name.trim().to_ascii_lowercase();
801 let value = value.trim().to_string();
802 match name.as_str() {
803 "if-modified-since" => if_modified_since = Some(value),
804 "if-none-match" => if_none_match = Some(value),
805 "authorization" => authorization = Some(value),
806 "content-length" => content_len = Some(value),
807 _ => {}
808 }
809 }
810
811 let mut body = None;
812 if let Some(con_len) = content_len {
813 let len = con_len.parse::<u64>().unwrap();
814 let mut content = vec![0u8; len as usize];
815 buf.read_exact(&mut content).unwrap();
816 body = Some(content)
817 }
818
819 let req = Request {
820 authorization,
821 if_modified_since,
822 if_none_match,
823 method,
824 url,
825 body,
826 };
827 let response = self.route(&req);
828 let buf = buf.get_mut();
829 write!(buf, "HTTP/1.1 {}\r\n", response.code).unwrap();
830 write!(buf, "Content-Length: {}\r\n", response.body.len()).unwrap();
831 write!(buf, "Connection: close\r\n").unwrap();
832 for header in response.headers {
833 write!(buf, "{}\r\n", header).unwrap();
834 }
835 write!(buf, "\r\n").unwrap();
836 buf.write_all(&response.body).unwrap();
837 buf.flush().unwrap();
838 }
839 }
840
841 fn check_authorized(&self, req: &Request, mutation: Option<Mutation<'_>>) -> bool {
842 let (private_key, private_key_subject) = if mutation.is_some() || self.auth_required {
843 match &self.token {
844 Token::Plaintext(token) => return Some(token) == req.authorization.as_ref(),
845 Token::Keys(private_key, private_key_subject) => {
846 (private_key.as_str(), private_key_subject)
847 }
848 }
849 } else {
850 assert!(req.authorization.is_none(), "unexpected token");
851 return true;
852 };
853
854 macro_rules! t {
855 ($e:expr) => {
856 match $e {
857 Some(e) => e,
858 None => return false,
859 }
860 };
861 }
862
863 let secret: AsymmetricSecretKey<pasetors::version3::V3> = private_key.try_into().unwrap();
864 let public: AsymmetricPublicKey<pasetors::version3::V3> = (&secret).try_into().unwrap();
865 let pub_key_id: pasetors::paserk::Id = (&public).into();
866 let mut paserk_pub_key_id = String::new();
867 FormatAsPaserk::fmt(&pub_key_id, &mut paserk_pub_key_id).unwrap();
868 let authorization = t!(&req.authorization);
872 let untrusted_token = t!(
873 UntrustedToken::<pasetors::Public, pasetors::version3::V3>::try_from(authorization)
874 .ok()
875 );
876
877 #[derive(serde::Deserialize, Debug)]
879 struct Footer<'a> {
880 url: &'a str,
881 kip: &'a str,
882 }
883 let footer: Footer<'_> =
884 t!(serde_json::from_slice(untrusted_token.untrusted_footer()).ok());
885 if footer.kip != paserk_pub_key_id {
886 return false;
887 }
888 let trusted_token =
889 t!(
890 pasetors::version3::PublicToken::verify(&public, &untrusted_token, None, None,)
891 .ok()
892 );
893
894 if footer.url != "https://github.com/rust-lang/crates.io-index"
896 && footer.url != &format!("sparse+http://{}/index/", self.addr)
897 {
898 return false;
899 }
900
901 #[derive(serde::Deserialize)]
903 struct Message<'a> {
904 iat: &'a str,
905 sub: Option<&'a str>,
906 mutation: Option<&'a str>,
907 name: Option<&'a str>,
908 vers: Option<&'a str>,
909 cksum: Option<&'a str>,
910 _challenge: Option<&'a str>, v: Option<u8>,
912 }
913 let message: Message<'_> = t!(serde_json::from_str(trusted_token.payload()).ok());
914 let token_time = t!(OffsetDateTime::parse(message.iat, &Rfc3339).ok());
915 let now = OffsetDateTime::now_utc();
916 if (now - token_time) > Duration::MINUTE {
917 return false;
918 }
919 if private_key_subject.as_deref() != message.sub {
920 return false;
921 }
922 if let Some(v) = message.v {
924 if v != 1 {
925 return false;
926 }
927 }
928 if let Some(mutation) = mutation {
932 if message.mutation != Some(mutation.mutation) {
934 return false;
935 }
936 if message.name != mutation.name {
938 return false;
939 }
940 if message.vers != mutation.vers {
941 return false;
942 }
943 if mutation.mutation == "publish" {
945 if message.cksum != mutation.cksum {
946 return false;
947 }
948 }
949 } else {
950 if message.mutation.is_some()
952 || message.name.is_some()
953 || message.vers.is_some()
954 || message.cksum.is_some()
955 {
956 return false;
957 }
958 }
959 true
960 }
961
962 fn route(&self, req: &Request) -> Response {
964 if let Some(responder) = self.custom_responders.get(req.url.path()) {
966 return responder(&req, self);
967 }
968 let path: Vec<_> = req.url.path()[1..].split('/').collect();
969 match (req.method.as_str(), path.as_slice()) {
970 ("get", ["index", ..]) => {
971 if !self.check_authorized(req, None) {
972 self.unauthorized(req)
973 } else {
974 self.index(&req)
975 }
976 }
977 ("get", ["dl", ..]) => {
978 if !self.check_authorized(req, None) {
979 self.unauthorized(req)
980 } else {
981 self.dl(&req)
982 }
983 }
984 ("put", ["api", "v1", "crates", "new"]) => self.check_authorized_publish(req),
986 ("delete" | "put", ["api", "v1", "crates", crate_name, version, mutation]) => {
993 if !self.check_authorized(
994 req,
995 Some(Mutation {
996 mutation,
997 name: Some(crate_name),
998 vers: Some(version),
999 cksum: None,
1000 }),
1001 ) {
1002 self.unauthorized(req)
1003 } else {
1004 self.ok(&req)
1005 }
1006 }
1007 ("get" | "put" | "delete", ["api", "v1", "crates", crate_name, "owners"]) => {
1009 if !self.check_authorized(
1010 req,
1011 Some(Mutation {
1012 mutation: "owners",
1013 name: Some(crate_name),
1014 vers: None,
1015 cksum: None,
1016 }),
1017 ) {
1018 self.unauthorized(req)
1019 } else {
1020 self.ok(&req)
1021 }
1022 }
1023 _ => self.not_found(&req),
1024 }
1025 }
1026
1027 pub fn unauthorized(&self, _req: &Request) -> Response {
1029 Response {
1030 code: 401,
1031 headers: vec![
1032 r#"www-authenticate: Cargo login_url="https://test-registry-login/me""#.to_string(),
1033 ],
1034 body: b"Unauthorized message from server.".to_vec(),
1035 }
1036 }
1037
1038 pub fn not_found(&self, req: &Request) -> Response {
1040 (self.not_found_handler)(req, self)
1041 }
1042
1043 pub fn ok(&self, _req: &Request) -> Response {
1045 Response {
1046 code: 200,
1047 headers: vec![],
1048 body: br#"{"ok": true, "msg": "completed!"}"#.to_vec(),
1049 }
1050 }
1051
1052 pub fn internal_server_error(&self, _req: &Request) -> Response {
1054 Response {
1055 code: 500,
1056 headers: vec![],
1057 body: br#"internal server error"#.to_vec(),
1058 }
1059 }
1060
1061 pub fn too_many_requests(&self, _req: &Request, delay: std::time::Duration) -> Response {
1063 Response {
1064 code: 429,
1065 headers: vec![format!("Retry-After: {}", delay.as_secs())],
1066 body: format!(
1067 "too many requests, try again in {} seconds",
1068 delay.as_secs()
1069 )
1070 .into_bytes(),
1071 }
1072 }
1073
1074 pub fn dl(&self, req: &Request) -> Response {
1076 let file = self
1077 .dl_path
1078 .join(req.url.path().strip_prefix("/dl/").unwrap());
1079 println!("{}", file.display());
1080 if !file.exists() {
1081 return self.not_found(req);
1082 }
1083 return Response {
1084 body: fs::read(&file).unwrap(),
1085 code: 200,
1086 headers: vec![],
1087 };
1088 }
1089
1090 pub fn index(&self, req: &Request) -> Response {
1092 let file = self
1093 .registry_path
1094 .join(req.url.path().strip_prefix("/index/").unwrap());
1095 if !file.exists() {
1096 return self.not_found(req);
1097 } else {
1098 let data = fs::read(&file).unwrap();
1100 let etag = Sha256::new().update(&data).finish_hex();
1101 let last_modified = format!("{:?}", file.metadata().unwrap().modified().unwrap());
1102
1103 let mut any_match = false;
1105 let mut all_match = true;
1106 if let Some(expected) = &req.if_none_match {
1107 if &etag != expected {
1108 all_match = false;
1109 } else {
1110 any_match = true;
1111 }
1112 }
1113 if let Some(expected) = &req.if_modified_since {
1114 if &last_modified != expected {
1116 all_match = false;
1117 } else {
1118 any_match = true;
1119 }
1120 }
1121
1122 if any_match && all_match {
1123 return Response {
1124 body: Vec::new(),
1125 code: 304,
1126 headers: vec![],
1127 };
1128 } else {
1129 return Response {
1130 body: data,
1131 code: 200,
1132 headers: vec![
1133 format!("ETag: \"{}\"", etag),
1134 format!("Last-Modified: {}", last_modified),
1135 ],
1136 };
1137 }
1138 }
1139 }
1140
1141 pub fn check_authorized_publish(&self, req: &Request) -> Response {
1142 if let Some(body) = &req.body {
1143 let path = self.api_path.join("api/v1/crates/new");
1146 t!(fs::create_dir_all(path.parent().unwrap()));
1147 t!(fs::write(&path, body));
1148
1149 let (len, remaining) = body.split_at(4);
1151 let json_len = u32::from_le_bytes(len.try_into().unwrap());
1152 let (json, remaining) = remaining.split_at(json_len as usize);
1153 let new_crate = serde_json::from_slice::<crates_io::NewCrate>(json).unwrap();
1154 let (len, remaining) = remaining.split_at(4);
1156 let file_len = u32::from_le_bytes(len.try_into().unwrap());
1157 let (file, _remaining) = remaining.split_at(file_len as usize);
1158 let file_cksum = cksum(&file);
1159
1160 if !self.check_authorized(
1161 req,
1162 Some(Mutation {
1163 mutation: "publish",
1164 name: Some(&new_crate.name),
1165 vers: Some(&new_crate.vers),
1166 cksum: Some(&file_cksum),
1167 }),
1168 ) {
1169 return self.unauthorized(req);
1170 }
1171
1172 let dst = self
1173 .dl_path
1174 .join(&new_crate.name)
1175 .join(&new_crate.vers)
1176 .join("download");
1177
1178 if self.delayed_index_update == 0 {
1179 save_new_crate(dst, new_crate, file, file_cksum, &self.registry_path);
1180 } else {
1181 let delayed_index_update = self.delayed_index_update;
1182 let registry_path = self.registry_path.clone();
1183 let file = Vec::from(file);
1184 thread::spawn(move || {
1185 thread::sleep(std::time::Duration::new(delayed_index_update as u64, 0));
1186 save_new_crate(dst, new_crate, &file, file_cksum, ®istry_path);
1187 });
1188 }
1189
1190 self.ok(&req)
1191 } else {
1192 Response {
1193 code: 400,
1194 headers: vec![],
1195 body: b"The request was missing a body".to_vec(),
1196 }
1197 }
1198 }
1199}
1200
1201fn save_new_crate(
1202 dst: PathBuf,
1203 new_crate: crates_io::NewCrate,
1204 file: &[u8],
1205 file_cksum: String,
1206 registry_path: &Path,
1207) {
1208 t!(fs::create_dir_all(dst.parent().unwrap()));
1210 t!(fs::write(&dst, file));
1211
1212 let deps = new_crate
1213 .deps
1214 .iter()
1215 .map(|dep| {
1216 let (name, package) = match &dep.explicit_name_in_toml {
1217 Some(explicit) => (explicit.to_string(), Some(dep.name.to_string())),
1218 None => (dep.name.to_string(), None),
1219 };
1220 serde_json::json!({
1221 "name": name,
1222 "req": dep.version_req,
1223 "features": dep.features,
1224 "default_features": dep.default_features,
1225 "target": dep.target,
1226 "optional": dep.optional,
1227 "kind": dep.kind,
1228 "registry": dep.registry,
1229 "package": package,
1230 "artifact": dep.artifact,
1231 "bindep_target": dep.bindep_target,
1232 "lib": dep.lib,
1233 })
1234 })
1235 .collect::<Vec<_>>();
1236
1237 let line = create_index_line(
1238 serde_json::json!(new_crate.name),
1239 &new_crate.vers,
1240 deps,
1241 &file_cksum,
1242 new_crate.features,
1243 false,
1244 new_crate.links,
1245 new_crate.rust_version.as_deref(),
1246 None,
1247 None,
1248 );
1249
1250 write_to_index(registry_path, &new_crate.name, line, false);
1251}
1252
1253impl Package {
1254 pub fn new(name: &str, vers: &str) -> Package {
1257 let config = paths::cargo_home().join("config.toml");
1258 if !config.exists() {
1259 init();
1260 }
1261 Package {
1262 name: name.to_string(),
1263 vers: vers.to_string(),
1264 deps: Vec::new(),
1265 files: Vec::new(),
1266 yanked: false,
1267 features: BTreeMap::new(),
1268 local: false,
1269 alternative: false,
1270 invalid_index_line: false,
1271 index_line: None,
1272 edition: None,
1273 resolver: None,
1274 proc_macro: false,
1275 links: None,
1276 rust_version: None,
1277 cargo_features: Vec::new(),
1278 pubtime: None,
1279 v: None,
1280 }
1281 }
1282
1283 pub fn local(&mut self, local: bool) -> &mut Package {
1289 self.local = local;
1290 self
1291 }
1292
1293 pub fn alternative(&mut self, alternative: bool) -> &mut Package {
1303 self.alternative = alternative;
1304 self
1305 }
1306
1307 pub fn file(&mut self, name: &str, contents: &str) -> &mut Package {
1309 self.file_with_mode(name, DEFAULT_MODE, contents)
1310 }
1311
1312 pub fn file_with_mode(&mut self, path: &str, mode: u32, contents: &str) -> &mut Package {
1314 self.files.push(PackageFile {
1315 path: path.to_string(),
1316 contents: EntryData::Regular(contents.into()),
1317 mode,
1318 extra: false,
1319 });
1320 self
1321 }
1322
1323 pub fn symlink(&mut self, dst: &str, src: &str) -> &mut Package {
1325 self.files.push(PackageFile {
1326 path: dst.to_string(),
1327 contents: EntryData::Symlink(src.into()),
1328 mode: DEFAULT_MODE,
1329 extra: false,
1330 });
1331 self
1332 }
1333
1334 pub fn directory(&mut self, path: &str) -> &mut Package {
1336 self.files.push(PackageFile {
1337 path: path.to_string(),
1338 contents: EntryData::Directory,
1339 mode: DEFAULT_MODE,
1340 extra: false,
1341 });
1342 self
1343 }
1344
1345 pub fn extra_file(&mut self, path: &str, contents: &str) -> &mut Package {
1351 self.files.push(PackageFile {
1352 path: path.to_string(),
1353 contents: EntryData::Regular(contents.to_string()),
1354 mode: DEFAULT_MODE,
1355 extra: true,
1356 });
1357 self
1358 }
1359
1360 pub fn dep(&mut self, name: &str, vers: &str) -> &mut Package {
1366 self.add_dep(&Dependency::new(name, vers))
1367 }
1368
1369 pub fn feature_dep(&mut self, name: &str, vers: &str, features: &[&str]) -> &mut Package {
1375 self.add_dep(Dependency::new(name, vers).enable_features(features))
1376 }
1377
1378 pub fn target_dep(&mut self, name: &str, vers: &str, target: &str) -> &mut Package {
1384 self.add_dep(Dependency::new(name, vers).target(target))
1385 }
1386
1387 pub fn registry_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1389 self.add_dep(Dependency::new(name, vers).registry("alternative"))
1390 }
1391
1392 pub fn dev_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1398 self.add_dep(Dependency::new(name, vers).dev())
1399 }
1400
1401 pub fn build_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1407 self.add_dep(Dependency::new(name, vers).build())
1408 }
1409
1410 pub fn add_dep(&mut self, dep: &Dependency) -> &mut Package {
1411 self.deps.push(dep.clone());
1412 self
1413 }
1414
1415 pub fn yanked(&mut self, yanked: bool) -> &mut Package {
1417 self.yanked = yanked;
1418 self
1419 }
1420
1421 pub fn edition(&mut self, edition: &str) -> &mut Package {
1423 self.edition = Some(edition.to_owned());
1424 self
1425 }
1426
1427 pub fn resolver(&mut self, resolver: &str) -> &mut Package {
1429 self.resolver = Some(resolver.to_owned());
1430 self
1431 }
1432
1433 pub fn proc_macro(&mut self, proc_macro: bool) -> &mut Package {
1435 self.proc_macro = proc_macro;
1436 self
1437 }
1438
1439 pub fn feature(&mut self, name: &str, deps: &[&str]) -> &mut Package {
1441 let deps = deps.iter().map(|s| s.to_string()).collect();
1442 self.features.insert(name.to_string(), deps);
1443 self
1444 }
1445
1446 pub fn rust_version(&mut self, rust_version: &str) -> &mut Package {
1448 self.rust_version = Some(rust_version.into());
1449 self
1450 }
1451
1452 pub fn invalid_index_line(&mut self, invalid: bool) -> &mut Package {
1455 self.invalid_index_line = invalid;
1456 self
1457 }
1458
1459 pub fn index_line(&mut self, line: &str) -> &mut Package {
1463 self.index_line = Some(line.to_owned());
1464 self
1465 }
1466
1467 pub fn links(&mut self, links: &str) -> &mut Package {
1468 self.links = Some(links.to_string());
1469 self
1470 }
1471
1472 pub fn cargo_feature(&mut self, feature: &str) -> &mut Package {
1473 self.cargo_features.push(feature.to_owned());
1474 self
1475 }
1476
1477 pub fn pubtime(&mut self, time: &str) -> &mut Package {
1479 self.pubtime = Some(time.to_owned());
1480 self
1481 }
1482
1483 pub fn schema_version(&mut self, version: u32) -> &mut Package {
1487 self.v = Some(version);
1488 self
1489 }
1490
1491 pub fn publish(&self) -> String {
1498 self.make_archive();
1499
1500 let deps = self
1502 .deps
1503 .iter()
1504 .map(|dep| {
1505 let registry_url = match (self.alternative, dep.registry.as_deref()) {
1508 (false, None) => None,
1509 (false, Some("alternative")) => Some(alt_registry_url().to_string()),
1510 (true, None) => {
1511 Some("https://github.com/rust-lang/crates.io-index".to_string())
1512 }
1513 (true, Some("alternative")) => None,
1514 _ => panic!("registry_dep currently only supports `alternative`"),
1515 };
1516 let artifact = if let Some(artifact) = &dep.artifact {
1517 serde_json::json!([artifact])
1518 } else {
1519 serde_json::json!(null)
1520 };
1521 serde_json::json!({
1522 "name": dep.name,
1523 "req": dep.vers,
1524 "features": dep.features,
1525 "default_features": dep.default_features,
1526 "target": dep.target,
1527 "artifact": artifact,
1528 "bindep_target": dep.bindep_target,
1529 "lib": dep.lib,
1530 "optional": dep.optional,
1531 "kind": dep.kind,
1532 "registry": registry_url,
1533 "package": dep.package,
1534 "public": dep.public,
1535 })
1536 })
1537 .collect::<Vec<_>>();
1538 let cksum = {
1539 let c = t!(fs::read(&self.archive_dst()));
1540 cksum(&c)
1541 };
1542 let line = if let Some(line) = self.index_line.clone() {
1543 line
1544 } else {
1545 let name = if self.invalid_index_line {
1546 serde_json::json!(1)
1547 } else {
1548 serde_json::json!(self.name)
1549 };
1550 create_index_line(
1551 name,
1552 &self.vers,
1553 deps,
1554 &cksum,
1555 self.features.clone(),
1556 self.yanked,
1557 self.links.clone(),
1558 self.rust_version.as_deref(),
1559 self.pubtime.as_deref(),
1560 self.v,
1561 )
1562 };
1563
1564 let registry_path = if self.alternative {
1565 alt_registry_path()
1566 } else {
1567 registry_path()
1568 };
1569
1570 write_to_index(®istry_path, &self.name, line, self.local);
1571
1572 cksum
1573 }
1574
1575 fn make_archive(&self) {
1576 let dst = self.archive_dst();
1577 t!(fs::create_dir_all(dst.parent().unwrap()));
1578 let f = t!(File::create(&dst));
1579 let mut a = Builder::new(GzEncoder::new(f, Compression::none()));
1580 a.sparse(false);
1581
1582 if !self
1583 .files
1584 .iter()
1585 .any(|PackageFile { path, .. }| path == "Cargo.toml")
1586 {
1587 self.append_manifest(&mut a);
1588 }
1589 if self.files.is_empty() {
1590 self.append(
1591 &mut a,
1592 "src/lib.rs",
1593 DEFAULT_MODE,
1594 &EntryData::Regular("".into()),
1595 );
1596 } else {
1597 for PackageFile {
1598 path,
1599 contents,
1600 mode,
1601 extra,
1602 } in &self.files
1603 {
1604 if *extra {
1605 self.append_raw(&mut a, path, *mode, contents);
1606 } else {
1607 self.append(&mut a, path, *mode, contents);
1608 }
1609 }
1610 }
1611 }
1612
1613 fn append_manifest<W: Write>(&self, ar: &mut Builder<W>) {
1614 let mut manifest = String::new();
1615
1616 if !self.cargo_features.is_empty() {
1617 let mut features = String::new();
1618 serde::Serialize::serialize(
1619 &self.cargo_features,
1620 toml::ser::ValueSerializer::new(&mut features),
1621 )
1622 .unwrap();
1623 manifest.push_str(&format!("cargo-features = {}\n\n", features));
1624 }
1625
1626 manifest.push_str(&format!(
1627 r#"
1628 [package]
1629 name = "{}"
1630 version = "{}"
1631 authors = []
1632 "#,
1633 self.name, self.vers
1634 ));
1635
1636 if let Some(version) = &self.rust_version {
1637 manifest.push_str(&format!("rust-version = \"{}\"\n", version));
1638 }
1639
1640 if let Some(edition) = &self.edition {
1641 manifest.push_str(&format!("edition = \"{}\"\n", edition));
1642 }
1643
1644 if let Some(resolver) = &self.resolver {
1645 manifest.push_str(&format!("resolver = \"{}\"\n", resolver));
1646 }
1647
1648 if !self.features.is_empty() {
1649 let features: Vec<String> = self
1650 .features
1651 .iter()
1652 .map(|(feature, features)| {
1653 if features.is_empty() {
1654 format!("{} = []", feature)
1655 } else {
1656 format!(
1657 "{} = [{}]",
1658 feature,
1659 features
1660 .iter()
1661 .map(|s| format!("\"{}\"", s))
1662 .collect::<Vec<_>>()
1663 .join(", ")
1664 )
1665 }
1666 })
1667 .collect();
1668
1669 manifest.push_str(&format!("\n[features]\n{}", features.join("\n")));
1670 }
1671
1672 for dep in self.deps.iter() {
1673 let target = match dep.target {
1674 None => String::new(),
1675 Some(ref s) => format!("target.'{}'.", s),
1676 };
1677 let kind = match &dep.kind[..] {
1678 "build" => "build-",
1679 "dev" => "dev-",
1680 _ => "",
1681 };
1682 manifest.push_str(&format!(
1683 r#"
1684 [{}{}dependencies.{}]
1685 version = "{}"
1686 "#,
1687 target, kind, dep.name, dep.vers
1688 ));
1689 if dep.optional {
1690 manifest.push_str("optional = true\n");
1691 }
1692 if let Some(artifact) = &dep.artifact {
1693 manifest.push_str(&format!("artifact = \"{}\"\n", artifact));
1694 }
1695 if let Some(target) = &dep.bindep_target {
1696 manifest.push_str(&format!("target = \"{}\"\n", target));
1697 }
1698 if dep.lib {
1699 manifest.push_str("lib = true\n");
1700 }
1701 if let Some(registry) = &dep.registry {
1702 assert_eq!(registry, "alternative");
1703 manifest.push_str(&format!("registry-index = \"{}\"", alt_registry_url()));
1704 }
1705 if !dep.default_features {
1706 manifest.push_str("default-features = false\n");
1707 }
1708 if !dep.features.is_empty() {
1709 let mut features = String::new();
1710 serde::Serialize::serialize(
1711 &dep.features,
1712 toml::ser::ValueSerializer::new(&mut features),
1713 )
1714 .unwrap();
1715 manifest.push_str(&format!("features = {}\n", features));
1716 }
1717 if let Some(package) = &dep.package {
1718 manifest.push_str(&format!("package = \"{}\"\n", package));
1719 }
1720 }
1721 if self.proc_macro {
1722 manifest.push_str("[lib]\nproc-macro = true\n");
1723 }
1724
1725 self.append(
1726 ar,
1727 "Cargo.toml",
1728 DEFAULT_MODE,
1729 &EntryData::Regular(manifest.into()),
1730 );
1731 }
1732
1733 fn append<W: Write>(&self, ar: &mut Builder<W>, file: &str, mode: u32, contents: &EntryData) {
1734 self.append_raw(
1735 ar,
1736 &format!("{}-{}/{}", self.name, self.vers, file),
1737 mode,
1738 contents,
1739 );
1740 }
1741
1742 fn append_raw<W: Write>(
1743 &self,
1744 ar: &mut Builder<W>,
1745 path: &str,
1746 mode: u32,
1747 contents: &EntryData,
1748 ) {
1749 let mut header = Header::new_ustar();
1754 let contents = match contents {
1755 EntryData::Regular(contents) => contents.as_str(),
1756 EntryData::Symlink(src) => {
1757 header.set_entry_type(tar::EntryType::Symlink);
1758 t!(header.set_link_name(src));
1759 "" }
1761 EntryData::Directory => {
1762 header.set_entry_type(tar::EntryType::Directory);
1763 ""
1764 }
1765 };
1766 header.set_size(contents.len() as u64);
1767 t!(header.set_path(path));
1768 header.set_mode(mode);
1769 header.set_cksum();
1770 t!(ar.append(&header, contents.as_bytes()));
1771 }
1772
1773 pub fn archive_dst(&self) -> PathBuf {
1775 if self.local {
1776 let path = if self.alternative {
1777 alt_registry_path()
1778 } else {
1779 registry_path()
1780 };
1781 path.join(format!("{}-{}.crate", self.name, self.vers))
1782 } else if self.alternative {
1783 alt_dl_path()
1784 .join(&self.name)
1785 .join(&self.vers)
1786 .join("download")
1787 } else {
1788 dl_path().join(&self.name).join(&self.vers).join("download")
1789 }
1790 }
1791}
1792
1793pub fn cksum(s: &[u8]) -> String {
1795 Sha256::new().update(s).finish_hex()
1796}
1797
1798impl Dependency {
1799 pub fn new(name: &str, vers: &str) -> Dependency {
1800 Dependency {
1801 name: name.to_string(),
1802 vers: vers.to_string(),
1803 kind: "normal".to_string(),
1804 artifact: None,
1805 bindep_target: None,
1806 lib: false,
1807 target: None,
1808 features: Vec::new(),
1809 package: None,
1810 optional: false,
1811 registry: None,
1812 default_features: true,
1813 public: false,
1814 }
1815 }
1816
1817 pub fn build(&mut self) -> &mut Self {
1819 self.kind = "build".to_string();
1820 self
1821 }
1822
1823 pub fn dev(&mut self) -> &mut Self {
1825 self.kind = "dev".to_string();
1826 self
1827 }
1828
1829 pub fn target(&mut self, target: &str) -> &mut Self {
1831 self.target = Some(target.to_string());
1832 self
1833 }
1834
1835 pub fn artifact(&mut self, kind: &str, target: Option<String>) -> &mut Self {
1838 self.artifact = Some(kind.to_string());
1839 self.bindep_target = target;
1840 self
1841 }
1842
1843 pub fn registry(&mut self, registry: &str) -> &mut Self {
1845 self.registry = Some(registry.to_string());
1846 self
1847 }
1848
1849 pub fn enable_features(&mut self, features: &[&str]) -> &mut Self {
1851 self.features.extend(features.iter().map(|s| s.to_string()));
1852 self
1853 }
1854
1855 pub fn package(&mut self, pkg: &str) -> &mut Self {
1857 self.package = Some(pkg.to_string());
1858 self
1859 }
1860
1861 pub fn optional(&mut self, optional: bool) -> &mut Self {
1863 self.optional = optional;
1864 self
1865 }
1866
1867 pub fn public(&mut self, public: bool) -> &mut Self {
1869 self.public = public;
1870 self
1871 }
1872
1873 pub fn default_features(&mut self, default_features: bool) -> &mut Self {
1875 self.default_features = default_features;
1876 self
1877 }
1878}