1use crate::git::repo;
46use crate::paths;
47use crate::publish::{create_index_line, write_to_index};
48use cargo_util::paths::append;
49use cargo_util::Sha256;
50use flate2::write::GzEncoder;
51use flate2::Compression;
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 v: Option<u32>,
582}
583
584pub(crate) type FeatureMap = BTreeMap<String, Vec<String>>;
585
586#[derive(Clone)]
588pub struct Dependency {
589 name: String,
590 vers: String,
591 kind: String,
592 artifact: Option<String>,
593 bindep_target: Option<String>,
594 lib: bool,
595 target: Option<String>,
596 features: Vec<String>,
597 registry: Option<String>,
598 package: Option<String>,
599 optional: bool,
600 default_features: bool,
601 public: bool,
602}
603
604#[non_exhaustive]
606enum EntryData {
607 Regular(String),
608 Symlink(PathBuf),
609}
610
611struct PackageFile {
613 path: String,
614 contents: EntryData,
615 mode: u32,
618 extra: bool,
621}
622
623const DEFAULT_MODE: u32 = 0o644;
624
625pub fn init() -> TestRegistry {
631 RegistryBuilder::new().build()
632}
633
634pub fn alt_init() -> TestRegistry {
638 init();
639 RegistryBuilder::new().alternative().build()
640}
641
642pub struct HttpServerHandle {
643 addr: SocketAddr,
644 handle: Option<JoinHandle<()>>,
645}
646
647impl HttpServerHandle {
648 pub fn index_url(&self) -> Url {
649 Url::parse(&format!("sparse+http://{}/index/", self.addr.to_string())).unwrap()
650 }
651
652 pub fn api_url(&self) -> Url {
653 Url::parse(&format!("http://{}/", self.addr.to_string())).unwrap()
654 }
655
656 pub fn dl_url(&self) -> Url {
657 Url::parse(&format!("http://{}/dl", self.addr.to_string())).unwrap()
658 }
659
660 fn stop(&self) {
661 if let Ok(mut stream) = TcpStream::connect(self.addr) {
662 let _ = stream.write_all(b"stop");
664 let _ = stream.flush();
665 }
666 }
667}
668
669impl Drop for HttpServerHandle {
670 fn drop(&mut self) {
671 self.stop();
672 }
673}
674
675#[derive(Clone)]
677pub struct Request {
678 pub url: Url,
679 pub method: String,
680 pub body: Option<Vec<u8>>,
681 pub authorization: Option<String>,
682 pub if_modified_since: Option<String>,
683 pub if_none_match: Option<String>,
684}
685
686impl fmt::Debug for Request {
687 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
688 f.debug_struct("Request")
690 .field("url", &self.url)
691 .field("method", &self.method)
692 .field("authorization", &self.authorization)
693 .field("if_modified_since", &self.if_modified_since)
694 .field("if_none_match", &self.if_none_match)
695 .finish()
696 }
697}
698
699pub struct Response {
701 pub code: u32,
702 pub headers: Vec<String>,
703 pub body: Vec<u8>,
704}
705
706pub struct HttpServer {
707 listener: TcpListener,
708 registry_path: PathBuf,
709 dl_path: PathBuf,
710 api_path: PathBuf,
711 addr: SocketAddr,
712 token: Token,
713 auth_required: bool,
714 custom_responders: HashMap<String, RequestCallback>,
715 not_found_handler: RequestCallback,
716 delayed_index_update: usize,
717}
718
719struct Mutation<'a> {
722 mutation: &'a str,
723 name: Option<&'a str>,
724 vers: Option<&'a str>,
725 cksum: Option<&'a str>,
726}
727
728impl HttpServer {
729 pub fn new(
730 registry_path: PathBuf,
731 dl_path: PathBuf,
732 api_path: PathBuf,
733 token: Token,
734 auth_required: bool,
735 custom_responders: HashMap<String, RequestCallback>,
736 not_found_handler: RequestCallback,
737 delayed_index_update: usize,
738 ) -> HttpServerHandle {
739 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
740 let addr = listener.local_addr().unwrap();
741 let server = HttpServer {
742 listener,
743 registry_path,
744 dl_path,
745 api_path,
746 addr,
747 token,
748 auth_required,
749 custom_responders,
750 not_found_handler,
751 delayed_index_update,
752 };
753 let handle = Some(thread::spawn(move || server.start()));
754 HttpServerHandle { addr, handle }
755 }
756
757 fn start(&self) {
758 let mut line = String::new();
759 'server: loop {
760 let (socket, _) = self.listener.accept().unwrap();
761 let mut buf = BufReader::new(socket);
762 line.clear();
763 if buf.read_line(&mut line).unwrap() == 0 {
764 continue;
766 }
767 let mut parts = line.split_ascii_whitespace();
769 let method = parts.next().unwrap().to_ascii_lowercase();
770 if method == "stop" {
771 return;
773 }
774 let addr = self.listener.local_addr().unwrap();
775 let url = format!(
776 "http://{}/{}",
777 addr,
778 parts.next().unwrap().trim_start_matches('/')
779 );
780 let url = Url::parse(&url).unwrap();
781
782 let mut if_modified_since = None;
784 let mut if_none_match = None;
785 let mut authorization = None;
786 let mut content_len = None;
787 loop {
788 line.clear();
789 if buf.read_line(&mut line).unwrap() == 0 {
790 continue 'server;
791 }
792 if line == "\r\n" {
793 line.clear();
795 break;
796 }
797 let (name, value) = line.split_once(':').unwrap();
798 let name = name.trim().to_ascii_lowercase();
799 let value = value.trim().to_string();
800 match name.as_str() {
801 "if-modified-since" => if_modified_since = Some(value),
802 "if-none-match" => if_none_match = Some(value),
803 "authorization" => authorization = Some(value),
804 "content-length" => content_len = Some(value),
805 _ => {}
806 }
807 }
808
809 let mut body = None;
810 if let Some(con_len) = content_len {
811 let len = con_len.parse::<u64>().unwrap();
812 let mut content = vec![0u8; len as usize];
813 buf.read_exact(&mut content).unwrap();
814 body = Some(content)
815 }
816
817 let req = Request {
818 authorization,
819 if_modified_since,
820 if_none_match,
821 method,
822 url,
823 body,
824 };
825 println!("req: {:#?}", req);
826 let response = self.route(&req);
827 let buf = buf.get_mut();
828 write!(buf, "HTTP/1.1 {}\r\n", response.code).unwrap();
829 write!(buf, "Content-Length: {}\r\n", response.body.len()).unwrap();
830 write!(buf, "Connection: close\r\n").unwrap();
831 for header in response.headers {
832 write!(buf, "{}\r\n", header).unwrap();
833 }
834 write!(buf, "\r\n").unwrap();
835 buf.write_all(&response.body).unwrap();
836 buf.flush().unwrap();
837 }
838 }
839
840 fn check_authorized(&self, req: &Request, mutation: Option<Mutation<'_>>) -> bool {
841 let (private_key, private_key_subject) = if mutation.is_some() || self.auth_required {
842 match &self.token {
843 Token::Plaintext(token) => return Some(token) == req.authorization.as_ref(),
844 Token::Keys(private_key, private_key_subject) => {
845 (private_key.as_str(), private_key_subject)
846 }
847 }
848 } else {
849 assert!(req.authorization.is_none(), "unexpected token");
850 return true;
851 };
852
853 macro_rules! t {
854 ($e:expr) => {
855 match $e {
856 Some(e) => e,
857 None => return false,
858 }
859 };
860 }
861
862 let secret: AsymmetricSecretKey<pasetors::version3::V3> = private_key.try_into().unwrap();
863 let public: AsymmetricPublicKey<pasetors::version3::V3> = (&secret).try_into().unwrap();
864 let pub_key_id: pasetors::paserk::Id = (&public).into();
865 let mut paserk_pub_key_id = String::new();
866 FormatAsPaserk::fmt(&pub_key_id, &mut paserk_pub_key_id).unwrap();
867 let authorization = t!(&req.authorization);
871 let untrusted_token = t!(
872 UntrustedToken::<pasetors::Public, pasetors::version3::V3>::try_from(authorization)
873 .ok()
874 );
875
876 #[derive(serde::Deserialize, Debug)]
878 struct Footer<'a> {
879 url: &'a str,
880 kip: &'a str,
881 }
882 let footer: Footer<'_> =
883 t!(serde_json::from_slice(untrusted_token.untrusted_footer()).ok());
884 if footer.kip != paserk_pub_key_id {
885 return false;
886 }
887 let trusted_token =
888 t!(
889 pasetors::version3::PublicToken::verify(&public, &untrusted_token, None, None,)
890 .ok()
891 );
892
893 if footer.url != "https://github.com/rust-lang/crates.io-index"
895 && footer.url != &format!("sparse+http://{}/index/", self.addr.to_string())
896 {
897 return false;
898 }
899
900 #[derive(serde::Deserialize)]
902 struct Message<'a> {
903 iat: &'a str,
904 sub: Option<&'a str>,
905 mutation: Option<&'a str>,
906 name: Option<&'a str>,
907 vers: Option<&'a str>,
908 cksum: Option<&'a str>,
909 _challenge: Option<&'a str>, v: Option<u8>,
911 }
912 let message: Message<'_> = t!(serde_json::from_str(trusted_token.payload()).ok());
913 let token_time = t!(OffsetDateTime::parse(message.iat, &Rfc3339).ok());
914 let now = OffsetDateTime::now_utc();
915 if (now - token_time) > Duration::MINUTE {
916 return false;
917 }
918 if private_key_subject.as_deref() != message.sub {
919 return false;
920 }
921 if let Some(v) = message.v {
923 if v != 1 {
924 return false;
925 }
926 }
927 if let Some(mutation) = mutation {
931 if message.mutation != Some(mutation.mutation) {
933 return false;
934 }
935 if message.name != mutation.name {
937 return false;
938 }
939 if message.vers != mutation.vers {
940 return false;
941 }
942 if mutation.mutation == "publish" {
944 if message.cksum != mutation.cksum {
945 return false;
946 }
947 }
948 } else {
949 if message.mutation.is_some()
951 || message.name.is_some()
952 || message.vers.is_some()
953 || message.cksum.is_some()
954 {
955 return false;
956 }
957 }
958 true
959 }
960
961 fn route(&self, req: &Request) -> Response {
963 if let Some(responder) = self.custom_responders.get(req.url.path()) {
965 return responder(&req, self);
966 }
967 let path: Vec<_> = req.url.path()[1..].split('/').collect();
968 match (req.method.as_str(), path.as_slice()) {
969 ("get", ["index", ..]) => {
970 if !self.check_authorized(req, None) {
971 self.unauthorized(req)
972 } else {
973 self.index(&req)
974 }
975 }
976 ("get", ["dl", ..]) => {
977 if !self.check_authorized(req, None) {
978 self.unauthorized(req)
979 } else {
980 self.dl(&req)
981 }
982 }
983 ("put", ["api", "v1", "crates", "new"]) => self.check_authorized_publish(req),
985 ("delete" | "put", ["api", "v1", "crates", crate_name, version, mutation]) => {
992 if !self.check_authorized(
993 req,
994 Some(Mutation {
995 mutation,
996 name: Some(crate_name),
997 vers: Some(version),
998 cksum: None,
999 }),
1000 ) {
1001 self.unauthorized(req)
1002 } else {
1003 self.ok(&req)
1004 }
1005 }
1006 ("get" | "put" | "delete", ["api", "v1", "crates", crate_name, "owners"]) => {
1008 if !self.check_authorized(
1009 req,
1010 Some(Mutation {
1011 mutation: "owners",
1012 name: Some(crate_name),
1013 vers: None,
1014 cksum: None,
1015 }),
1016 ) {
1017 self.unauthorized(req)
1018 } else {
1019 self.ok(&req)
1020 }
1021 }
1022 _ => self.not_found(&req),
1023 }
1024 }
1025
1026 pub fn unauthorized(&self, _req: &Request) -> Response {
1028 Response {
1029 code: 401,
1030 headers: vec![
1031 r#"WWW-Authenticate: Cargo login_url="https://test-registry-login/me""#.to_string(),
1032 ],
1033 body: b"Unauthorized message from server.".to_vec(),
1034 }
1035 }
1036
1037 pub fn not_found(&self, req: &Request) -> Response {
1039 (self.not_found_handler)(req, self)
1040 }
1041
1042 pub fn ok(&self, _req: &Request) -> Response {
1044 Response {
1045 code: 200,
1046 headers: vec![],
1047 body: br#"{"ok": true, "msg": "completed!"}"#.to_vec(),
1048 }
1049 }
1050
1051 pub fn internal_server_error(&self, _req: &Request) -> Response {
1053 Response {
1054 code: 500,
1055 headers: vec![],
1056 body: br#"internal server error"#.to_vec(),
1057 }
1058 }
1059
1060 pub fn dl(&self, req: &Request) -> Response {
1062 let file = self
1063 .dl_path
1064 .join(req.url.path().strip_prefix("/dl/").unwrap());
1065 println!("{}", file.display());
1066 if !file.exists() {
1067 return self.not_found(req);
1068 }
1069 return Response {
1070 body: fs::read(&file).unwrap(),
1071 code: 200,
1072 headers: vec![],
1073 };
1074 }
1075
1076 pub fn index(&self, req: &Request) -> Response {
1078 let file = self
1079 .registry_path
1080 .join(req.url.path().strip_prefix("/index/").unwrap());
1081 if !file.exists() {
1082 return self.not_found(req);
1083 } else {
1084 let data = fs::read(&file).unwrap();
1086 let etag = Sha256::new().update(&data).finish_hex();
1087 let last_modified = format!("{:?}", file.metadata().unwrap().modified().unwrap());
1088
1089 let mut any_match = false;
1091 let mut all_match = true;
1092 if let Some(expected) = &req.if_none_match {
1093 if &etag != expected {
1094 all_match = false;
1095 } else {
1096 any_match = true;
1097 }
1098 }
1099 if let Some(expected) = &req.if_modified_since {
1100 if &last_modified != expected {
1102 all_match = false;
1103 } else {
1104 any_match = true;
1105 }
1106 }
1107
1108 if any_match && all_match {
1109 return Response {
1110 body: Vec::new(),
1111 code: 304,
1112 headers: vec![],
1113 };
1114 } else {
1115 return Response {
1116 body: data,
1117 code: 200,
1118 headers: vec![
1119 format!("ETag: \"{}\"", etag),
1120 format!("Last-Modified: {}", last_modified),
1121 ],
1122 };
1123 }
1124 }
1125 }
1126
1127 pub fn check_authorized_publish(&self, req: &Request) -> Response {
1128 if let Some(body) = &req.body {
1129 let path = self.api_path.join("api/v1/crates/new");
1132 t!(fs::create_dir_all(path.parent().unwrap()));
1133 t!(fs::write(&path, body));
1134
1135 let (len, remaining) = body.split_at(4);
1137 let json_len = u32::from_le_bytes(len.try_into().unwrap());
1138 let (json, remaining) = remaining.split_at(json_len as usize);
1139 let new_crate = serde_json::from_slice::<crates_io::NewCrate>(json).unwrap();
1140 let (len, remaining) = remaining.split_at(4);
1142 let file_len = u32::from_le_bytes(len.try_into().unwrap());
1143 let (file, _remaining) = remaining.split_at(file_len as usize);
1144 let file_cksum = cksum(&file);
1145
1146 if !self.check_authorized(
1147 req,
1148 Some(Mutation {
1149 mutation: "publish",
1150 name: Some(&new_crate.name),
1151 vers: Some(&new_crate.vers),
1152 cksum: Some(&file_cksum),
1153 }),
1154 ) {
1155 return self.unauthorized(req);
1156 }
1157
1158 let dst = self
1159 .dl_path
1160 .join(&new_crate.name)
1161 .join(&new_crate.vers)
1162 .join("download");
1163
1164 if self.delayed_index_update == 0 {
1165 save_new_crate(dst, new_crate, file, file_cksum, &self.registry_path);
1166 } else {
1167 let delayed_index_update = self.delayed_index_update;
1168 let registry_path = self.registry_path.clone();
1169 let file = Vec::from(file);
1170 thread::spawn(move || {
1171 thread::sleep(std::time::Duration::new(delayed_index_update as u64, 0));
1172 save_new_crate(dst, new_crate, &file, file_cksum, ®istry_path);
1173 });
1174 }
1175
1176 self.ok(&req)
1177 } else {
1178 Response {
1179 code: 400,
1180 headers: vec![],
1181 body: b"The request was missing a body".to_vec(),
1182 }
1183 }
1184 }
1185}
1186
1187fn save_new_crate(
1188 dst: PathBuf,
1189 new_crate: crates_io::NewCrate,
1190 file: &[u8],
1191 file_cksum: String,
1192 registry_path: &Path,
1193) {
1194 t!(fs::create_dir_all(dst.parent().unwrap()));
1196 t!(fs::write(&dst, file));
1197
1198 let deps = new_crate
1199 .deps
1200 .iter()
1201 .map(|dep| {
1202 let (name, package) = match &dep.explicit_name_in_toml {
1203 Some(explicit) => (explicit.to_string(), Some(dep.name.to_string())),
1204 None => (dep.name.to_string(), None),
1205 };
1206 serde_json::json!({
1207 "name": name,
1208 "req": dep.version_req,
1209 "features": dep.features,
1210 "default_features": dep.default_features,
1211 "target": dep.target,
1212 "optional": dep.optional,
1213 "kind": dep.kind,
1214 "registry": dep.registry,
1215 "package": package,
1216 "artifact": dep.artifact,
1217 "bindep_target": dep.bindep_target,
1218 "lib": dep.lib,
1219 })
1220 })
1221 .collect::<Vec<_>>();
1222
1223 let line = create_index_line(
1224 serde_json::json!(new_crate.name),
1225 &new_crate.vers,
1226 deps,
1227 &file_cksum,
1228 new_crate.features,
1229 false,
1230 new_crate.links,
1231 new_crate.rust_version.as_deref(),
1232 None,
1233 );
1234
1235 write_to_index(registry_path, &new_crate.name, line, false);
1236}
1237
1238impl Package {
1239 pub fn new(name: &str, vers: &str) -> Package {
1242 let config = paths::cargo_home().join("config.toml");
1243 if !config.exists() {
1244 init();
1245 }
1246 Package {
1247 name: name.to_string(),
1248 vers: vers.to_string(),
1249 deps: Vec::new(),
1250 files: Vec::new(),
1251 yanked: false,
1252 features: BTreeMap::new(),
1253 local: false,
1254 alternative: false,
1255 invalid_index_line: false,
1256 index_line: None,
1257 edition: None,
1258 resolver: None,
1259 proc_macro: false,
1260 links: None,
1261 rust_version: None,
1262 cargo_features: Vec::new(),
1263 v: None,
1264 }
1265 }
1266
1267 pub fn local(&mut self, local: bool) -> &mut Package {
1273 self.local = local;
1274 self
1275 }
1276
1277 pub fn alternative(&mut self, alternative: bool) -> &mut Package {
1287 self.alternative = alternative;
1288 self
1289 }
1290
1291 pub fn file(&mut self, name: &str, contents: &str) -> &mut Package {
1293 self.file_with_mode(name, DEFAULT_MODE, contents)
1294 }
1295
1296 pub fn file_with_mode(&mut self, path: &str, mode: u32, contents: &str) -> &mut Package {
1298 self.files.push(PackageFile {
1299 path: path.to_string(),
1300 contents: EntryData::Regular(contents.into()),
1301 mode,
1302 extra: false,
1303 });
1304 self
1305 }
1306
1307 pub fn symlink(&mut self, dst: &str, src: &str) -> &mut Package {
1309 self.files.push(PackageFile {
1310 path: dst.to_string(),
1311 contents: EntryData::Symlink(src.into()),
1312 mode: DEFAULT_MODE,
1313 extra: false,
1314 });
1315 self
1316 }
1317
1318 pub fn extra_file(&mut self, path: &str, contents: &str) -> &mut Package {
1324 self.files.push(PackageFile {
1325 path: path.to_string(),
1326 contents: EntryData::Regular(contents.to_string()),
1327 mode: DEFAULT_MODE,
1328 extra: true,
1329 });
1330 self
1331 }
1332
1333 pub fn dep(&mut self, name: &str, vers: &str) -> &mut Package {
1339 self.add_dep(&Dependency::new(name, vers))
1340 }
1341
1342 pub fn feature_dep(&mut self, name: &str, vers: &str, features: &[&str]) -> &mut Package {
1348 self.add_dep(Dependency::new(name, vers).enable_features(features))
1349 }
1350
1351 pub fn target_dep(&mut self, name: &str, vers: &str, target: &str) -> &mut Package {
1357 self.add_dep(Dependency::new(name, vers).target(target))
1358 }
1359
1360 pub fn registry_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1362 self.add_dep(Dependency::new(name, vers).registry("alternative"))
1363 }
1364
1365 pub fn dev_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1371 self.add_dep(Dependency::new(name, vers).dev())
1372 }
1373
1374 pub fn build_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1380 self.add_dep(Dependency::new(name, vers).build())
1381 }
1382
1383 pub fn add_dep(&mut self, dep: &Dependency) -> &mut Package {
1384 self.deps.push(dep.clone());
1385 self
1386 }
1387
1388 pub fn yanked(&mut self, yanked: bool) -> &mut Package {
1390 self.yanked = yanked;
1391 self
1392 }
1393
1394 pub fn edition(&mut self, edition: &str) -> &mut Package {
1396 self.edition = Some(edition.to_owned());
1397 self
1398 }
1399
1400 pub fn resolver(&mut self, resolver: &str) -> &mut Package {
1402 self.resolver = Some(resolver.to_owned());
1403 self
1404 }
1405
1406 pub fn proc_macro(&mut self, proc_macro: bool) -> &mut Package {
1408 self.proc_macro = proc_macro;
1409 self
1410 }
1411
1412 pub fn feature(&mut self, name: &str, deps: &[&str]) -> &mut Package {
1414 let deps = deps.iter().map(|s| s.to_string()).collect();
1415 self.features.insert(name.to_string(), deps);
1416 self
1417 }
1418
1419 pub fn rust_version(&mut self, rust_version: &str) -> &mut Package {
1421 self.rust_version = Some(rust_version.into());
1422 self
1423 }
1424
1425 pub fn invalid_index_line(&mut self, invalid: bool) -> &mut Package {
1428 self.invalid_index_line = invalid;
1429 self
1430 }
1431
1432 pub fn index_line(&mut self, line: &str) -> &mut Package {
1436 self.index_line = Some(line.to_owned());
1437 self
1438 }
1439
1440 pub fn links(&mut self, links: &str) -> &mut Package {
1441 self.links = Some(links.to_string());
1442 self
1443 }
1444
1445 pub fn cargo_feature(&mut self, feature: &str) -> &mut Package {
1446 self.cargo_features.push(feature.to_owned());
1447 self
1448 }
1449
1450 pub fn schema_version(&mut self, version: u32) -> &mut Package {
1454 self.v = Some(version);
1455 self
1456 }
1457
1458 pub fn publish(&self) -> String {
1465 self.make_archive();
1466
1467 let deps = self
1469 .deps
1470 .iter()
1471 .map(|dep| {
1472 let registry_url = match (self.alternative, dep.registry.as_deref()) {
1475 (false, None) => None,
1476 (false, Some("alternative")) => Some(alt_registry_url().to_string()),
1477 (true, None) => {
1478 Some("https://github.com/rust-lang/crates.io-index".to_string())
1479 }
1480 (true, Some("alternative")) => None,
1481 _ => panic!("registry_dep currently only supports `alternative`"),
1482 };
1483 let artifact = if let Some(artifact) = &dep.artifact {
1484 serde_json::json!([artifact])
1485 } else {
1486 serde_json::json!(null)
1487 };
1488 serde_json::json!({
1489 "name": dep.name,
1490 "req": dep.vers,
1491 "features": dep.features,
1492 "default_features": dep.default_features,
1493 "target": dep.target,
1494 "artifact": artifact,
1495 "bindep_target": dep.bindep_target,
1496 "lib": dep.lib,
1497 "optional": dep.optional,
1498 "kind": dep.kind,
1499 "registry": registry_url,
1500 "package": dep.package,
1501 "public": dep.public,
1502 })
1503 })
1504 .collect::<Vec<_>>();
1505 let cksum = {
1506 let c = t!(fs::read(&self.archive_dst()));
1507 cksum(&c)
1508 };
1509 let line = if let Some(line) = self.index_line.clone() {
1510 line
1511 } else {
1512 let name = if self.invalid_index_line {
1513 serde_json::json!(1)
1514 } else {
1515 serde_json::json!(self.name)
1516 };
1517 create_index_line(
1518 name,
1519 &self.vers,
1520 deps,
1521 &cksum,
1522 self.features.clone(),
1523 self.yanked,
1524 self.links.clone(),
1525 self.rust_version.as_deref(),
1526 self.v,
1527 )
1528 };
1529
1530 let registry_path = if self.alternative {
1531 alt_registry_path()
1532 } else {
1533 registry_path()
1534 };
1535
1536 write_to_index(®istry_path, &self.name, line, self.local);
1537
1538 cksum
1539 }
1540
1541 fn make_archive(&self) {
1542 let dst = self.archive_dst();
1543 t!(fs::create_dir_all(dst.parent().unwrap()));
1544 let f = t!(File::create(&dst));
1545 let mut a = Builder::new(GzEncoder::new(f, Compression::none()));
1546 a.sparse(false);
1547
1548 if !self
1549 .files
1550 .iter()
1551 .any(|PackageFile { path, .. }| path == "Cargo.toml")
1552 {
1553 self.append_manifest(&mut a);
1554 }
1555 if self.files.is_empty() {
1556 self.append(
1557 &mut a,
1558 "src/lib.rs",
1559 DEFAULT_MODE,
1560 &EntryData::Regular("".into()),
1561 );
1562 } else {
1563 for PackageFile {
1564 path,
1565 contents,
1566 mode,
1567 extra,
1568 } in &self.files
1569 {
1570 if *extra {
1571 self.append_raw(&mut a, path, *mode, contents);
1572 } else {
1573 self.append(&mut a, path, *mode, contents);
1574 }
1575 }
1576 }
1577 }
1578
1579 fn append_manifest<W: Write>(&self, ar: &mut Builder<W>) {
1580 let mut manifest = String::new();
1581
1582 if !self.cargo_features.is_empty() {
1583 let mut features = String::new();
1584 serde::Serialize::serialize(
1585 &self.cargo_features,
1586 toml::ser::ValueSerializer::new(&mut features),
1587 )
1588 .unwrap();
1589 manifest.push_str(&format!("cargo-features = {}\n\n", features));
1590 }
1591
1592 manifest.push_str(&format!(
1593 r#"
1594 [package]
1595 name = "{}"
1596 version = "{}"
1597 authors = []
1598 "#,
1599 self.name, self.vers
1600 ));
1601
1602 if let Some(version) = &self.rust_version {
1603 manifest.push_str(&format!("rust-version = \"{}\"\n", version));
1604 }
1605
1606 if let Some(edition) = &self.edition {
1607 manifest.push_str(&format!("edition = \"{}\"\n", edition));
1608 }
1609
1610 if let Some(resolver) = &self.resolver {
1611 manifest.push_str(&format!("resolver = \"{}\"\n", resolver));
1612 }
1613
1614 if !self.features.is_empty() {
1615 let features: Vec<String> = self
1616 .features
1617 .iter()
1618 .map(|(feature, features)| {
1619 if features.is_empty() {
1620 format!("{} = []", feature)
1621 } else {
1622 format!(
1623 "{} = [{}]",
1624 feature,
1625 features
1626 .iter()
1627 .map(|s| format!("\"{}\"", s))
1628 .collect::<Vec<_>>()
1629 .join(", ")
1630 )
1631 }
1632 })
1633 .collect();
1634
1635 manifest.push_str(&format!("\n[features]\n{}", features.join("\n")));
1636 }
1637
1638 for dep in self.deps.iter() {
1639 let target = match dep.target {
1640 None => String::new(),
1641 Some(ref s) => format!("target.'{}'.", s),
1642 };
1643 let kind = match &dep.kind[..] {
1644 "build" => "build-",
1645 "dev" => "dev-",
1646 _ => "",
1647 };
1648 manifest.push_str(&format!(
1649 r#"
1650 [{}{}dependencies.{}]
1651 version = "{}"
1652 "#,
1653 target, kind, dep.name, dep.vers
1654 ));
1655 if dep.optional {
1656 manifest.push_str("optional = true\n");
1657 }
1658 if let Some(artifact) = &dep.artifact {
1659 manifest.push_str(&format!("artifact = \"{}\"\n", artifact));
1660 }
1661 if let Some(target) = &dep.bindep_target {
1662 manifest.push_str(&format!("target = \"{}\"\n", target));
1663 }
1664 if dep.lib {
1665 manifest.push_str("lib = true\n");
1666 }
1667 if let Some(registry) = &dep.registry {
1668 assert_eq!(registry, "alternative");
1669 manifest.push_str(&format!("registry-index = \"{}\"", alt_registry_url()));
1670 }
1671 if !dep.default_features {
1672 manifest.push_str("default-features = false\n");
1673 }
1674 if !dep.features.is_empty() {
1675 let mut features = String::new();
1676 serde::Serialize::serialize(
1677 &dep.features,
1678 toml::ser::ValueSerializer::new(&mut features),
1679 )
1680 .unwrap();
1681 manifest.push_str(&format!("features = {}\n", features));
1682 }
1683 if let Some(package) = &dep.package {
1684 manifest.push_str(&format!("package = \"{}\"\n", package));
1685 }
1686 }
1687 if self.proc_macro {
1688 manifest.push_str("[lib]\nproc-macro = true\n");
1689 }
1690
1691 self.append(
1692 ar,
1693 "Cargo.toml",
1694 DEFAULT_MODE,
1695 &EntryData::Regular(manifest.into()),
1696 );
1697 }
1698
1699 fn append<W: Write>(&self, ar: &mut Builder<W>, file: &str, mode: u32, contents: &EntryData) {
1700 self.append_raw(
1701 ar,
1702 &format!("{}-{}/{}", self.name, self.vers, file),
1703 mode,
1704 contents,
1705 );
1706 }
1707
1708 fn append_raw<W: Write>(
1709 &self,
1710 ar: &mut Builder<W>,
1711 path: &str,
1712 mode: u32,
1713 contents: &EntryData,
1714 ) {
1715 let mut header = Header::new_ustar();
1716 let contents = match contents {
1717 EntryData::Regular(contents) => contents.as_str(),
1718 EntryData::Symlink(src) => {
1719 header.set_entry_type(tar::EntryType::Symlink);
1720 t!(header.set_link_name(src));
1721 "" }
1723 };
1724 header.set_size(contents.len() as u64);
1725 t!(header.set_path(path));
1726 header.set_mode(mode);
1727 header.set_cksum();
1728 t!(ar.append(&header, contents.as_bytes()));
1729 }
1730
1731 pub fn archive_dst(&self) -> PathBuf {
1733 if self.local {
1734 let path = if self.alternative {
1735 alt_registry_path()
1736 } else {
1737 registry_path()
1738 };
1739 path.join(format!("{}-{}.crate", self.name, self.vers))
1740 } else if self.alternative {
1741 alt_dl_path()
1742 .join(&self.name)
1743 .join(&self.vers)
1744 .join("download")
1745 } else {
1746 dl_path().join(&self.name).join(&self.vers).join("download")
1747 }
1748 }
1749}
1750
1751pub fn cksum(s: &[u8]) -> String {
1753 Sha256::new().update(s).finish_hex()
1754}
1755
1756impl Dependency {
1757 pub fn new(name: &str, vers: &str) -> Dependency {
1758 Dependency {
1759 name: name.to_string(),
1760 vers: vers.to_string(),
1761 kind: "normal".to_string(),
1762 artifact: None,
1763 bindep_target: None,
1764 lib: false,
1765 target: None,
1766 features: Vec::new(),
1767 package: None,
1768 optional: false,
1769 registry: None,
1770 default_features: true,
1771 public: false,
1772 }
1773 }
1774
1775 pub fn build(&mut self) -> &mut Self {
1777 self.kind = "build".to_string();
1778 self
1779 }
1780
1781 pub fn dev(&mut self) -> &mut Self {
1783 self.kind = "dev".to_string();
1784 self
1785 }
1786
1787 pub fn target(&mut self, target: &str) -> &mut Self {
1789 self.target = Some(target.to_string());
1790 self
1791 }
1792
1793 pub fn artifact(&mut self, kind: &str, target: Option<String>) -> &mut Self {
1796 self.artifact = Some(kind.to_string());
1797 self.bindep_target = target;
1798 self
1799 }
1800
1801 pub fn registry(&mut self, registry: &str) -> &mut Self {
1803 self.registry = Some(registry.to_string());
1804 self
1805 }
1806
1807 pub fn enable_features(&mut self, features: &[&str]) -> &mut Self {
1809 self.features.extend(features.iter().map(|s| s.to_string()));
1810 self
1811 }
1812
1813 pub fn package(&mut self, pkg: &str) -> &mut Self {
1815 self.package = Some(pkg.to_string());
1816 self
1817 }
1818
1819 pub fn optional(&mut self, optional: bool) -> &mut Self {
1821 self.optional = optional;
1822 self
1823 }
1824
1825 pub fn public(&mut self, public: bool) -> &mut Self {
1827 self.public = public;
1828 self
1829 }
1830
1831 pub fn default_features(&mut self, default_features: bool) -> &mut Self {
1833 self.default_features = default_features;
1834 self
1835 }
1836}