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 std::collections::{BTreeMap, HashMap};
53use std::fmt;
54use std::fs::{self, File};
55use std::io::{BufRead, BufReader, Read, Write};
56use std::net::{SocketAddr, TcpListener, TcpStream};
57use std::path::{Path, PathBuf};
58use std::thread::{self, JoinHandle};
59use tar::{Builder, Header};
60use url::Url;
61
62pub fn registry_path() -> PathBuf {
70 generate_path("registry")
71}
72
73pub fn api_path() -> PathBuf {
80 generate_path("api")
81}
82
83pub fn dl_path() -> PathBuf {
91 generate_path("dl")
92}
93
94pub fn alt_registry_path() -> PathBuf {
98 generate_path("alternative-registry")
99}
100
101fn alt_registry_url() -> Url {
103 generate_url("alternative-registry")
104}
105
106pub fn alt_dl_path() -> PathBuf {
110 generate_path("alternative-dl")
111}
112
113pub fn alt_api_path() -> PathBuf {
117 generate_path("alternative-api")
118}
119
120fn generate_path(name: &str) -> PathBuf {
121 paths::root().join(name)
122}
123fn generate_url(name: &str) -> Url {
124 Url::from_file_path(generate_path(name)).ok().unwrap()
125}
126
127type RequestCallback = Box<dyn Send + Fn(&Request, &HttpServer) -> Response>;
128
129pub struct RegistryBuilder {
133 alternative: Option<String>,
135 token: Option<String>,
137 auth_required: bool,
139 http_index: bool,
141 http_api: bool,
143 api: bool,
145 configure_token: bool,
147 configure_registry: bool,
149 custom_responders: HashMap<String, RequestCallback>,
151 not_found_handler: RequestCallback,
153 delayed_index_update: usize,
155 credential_provider: Option<String>,
157}
158
159pub struct TestRegistry {
163 server: Option<HttpServerHandle>,
164 index_url: Url,
165 path: PathBuf,
166 api_url: Url,
167 dl_url: Url,
168 token: String,
169}
170
171impl TestRegistry {
172 pub fn index_url(&self) -> &Url {
173 &self.index_url
174 }
175
176 pub fn api_url(&self) -> &Url {
177 &self.api_url
178 }
179
180 pub fn token(&self) -> &str {
181 &self.token
182 }
183
184 pub fn join(self) {
188 if let Some(mut server) = self.server {
189 server.stop();
190 let handle = server.handle.take().unwrap();
191 handle.join().unwrap();
192 }
193 }
194}
195
196impl RegistryBuilder {
197 #[must_use]
198 pub fn new() -> RegistryBuilder {
199 let not_found = |_req: &Request, _server: &HttpServer| -> Response {
200 Response {
201 code: 404,
202 headers: vec![],
203 body: b"not found".to_vec(),
204 }
205 };
206 RegistryBuilder {
207 alternative: None,
208 token: None,
209 auth_required: false,
210 http_api: false,
211 http_index: false,
212 api: true,
213 configure_registry: true,
214 configure_token: true,
215 custom_responders: HashMap::new(),
216 not_found_handler: Box::new(not_found),
217 delayed_index_update: 0,
218 credential_provider: None,
219 }
220 }
221
222 #[must_use]
224 pub fn add_responder<R: 'static + Send + Fn(&Request, &HttpServer) -> Response>(
225 mut self,
226 url: impl Into<String>,
227 responder: R,
228 ) -> Self {
229 self.custom_responders
230 .insert(url.into(), Box::new(responder));
231 self
232 }
233
234 #[must_use]
235 pub fn not_found_handler<R: 'static + Send + Fn(&Request, &HttpServer) -> Response>(
236 mut self,
237 responder: R,
238 ) -> Self {
239 self.not_found_handler = Box::new(responder);
240 self
241 }
242
243 #[must_use]
245 pub fn delayed_index_update(mut self, delay: usize) -> Self {
246 self.delayed_index_update = delay;
247 self
248 }
249
250 #[must_use]
252 pub fn alternative_named(mut self, alt: &str) -> Self {
253 self.alternative = Some(alt.to_string());
254 self
255 }
256
257 #[must_use]
259 pub fn alternative(self) -> Self {
260 self.alternative_named("alternative")
261 }
262
263 #[must_use]
265 pub fn no_configure_token(mut self) -> Self {
266 self.configure_token = false;
267 self
268 }
269
270 #[must_use]
272 pub fn no_configure_registry(mut self) -> Self {
273 self.configure_registry = false;
274 self
275 }
276
277 #[must_use]
279 pub fn token(mut self, token: String) -> Self {
280 self.token = Some(token);
281 self
282 }
283
284 #[must_use]
287 pub fn auth_required(mut self) -> Self {
288 self.auth_required = true;
289 self
290 }
291
292 #[must_use]
294 pub fn http_index(mut self) -> Self {
295 self.http_index = true;
296 self
297 }
298
299 #[must_use]
301 pub fn http_api(mut self) -> Self {
302 self.http_api = true;
303 self
304 }
305
306 #[must_use]
308 pub fn no_api(mut self) -> Self {
309 self.api = false;
310 self
311 }
312
313 #[must_use]
315 pub fn credential_provider(mut self, provider: &[&str]) -> Self {
316 self.credential_provider = Some(format!("['{}']", provider.join("','")));
317 self
318 }
319
320 #[must_use]
322 pub fn build(self) -> TestRegistry {
323 let config_path = paths::cargo_home().join("config.toml");
324 t!(fs::create_dir_all(config_path.parent().unwrap()));
325 let prefix = if let Some(alternative) = &self.alternative {
326 format!("{alternative}-")
327 } else {
328 String::new()
329 };
330 let registry_path = generate_path(&format!("{prefix}registry"));
331 let index_url = generate_url(&format!("{prefix}registry"));
332 let api_url = generate_url(&format!("{prefix}api"));
333 let dl_url = generate_url(&format!("{prefix}dl"));
334 let dl_path = generate_path(&format!("{prefix}dl"));
335 let api_path = generate_path(&format!("{prefix}api"));
336 let token = self.token.unwrap_or_else(|| format!("{prefix}sekrit"));
337
338 let (server, index_url, api_url, dl_url) = if !self.http_index && !self.http_api {
339 (None, index_url, api_url, dl_url)
341 } else {
342 let server = HttpServer::new(
343 registry_path.clone(),
344 dl_path,
345 api_path.clone(),
346 token.clone(),
347 self.auth_required,
348 self.custom_responders,
349 self.not_found_handler,
350 self.delayed_index_update,
351 );
352 let index_url = if self.http_index {
353 server.index_url()
354 } else {
355 index_url
356 };
357 let api_url = if self.http_api {
358 server.api_url()
359 } else {
360 api_url
361 };
362 let dl_url = server.dl_url();
363 (Some(server), index_url, api_url, dl_url)
364 };
365
366 let registry = TestRegistry {
367 api_url,
368 index_url,
369 server,
370 dl_url,
371 path: registry_path,
372 token,
373 };
374
375 if self.configure_registry {
376 if let Some(alternative) = &self.alternative {
377 append(
378 &config_path,
379 format!(
380 "
381 [registries.{alternative}]
382 index = '{}'",
383 registry.index_url
384 )
385 .as_bytes(),
386 )
387 .unwrap();
388 if let Some(p) = &self.credential_provider {
389 append(
390 &config_path,
391 &format!(
392 "
393 credential-provider = {p}
394 "
395 )
396 .as_bytes(),
397 )
398 .unwrap()
399 }
400 } else {
401 append(
402 &config_path,
403 format!(
404 "
405 [source.crates-io]
406 replace-with = 'dummy-registry'
407
408 [registries.dummy-registry]
409 index = '{}'",
410 registry.index_url
411 )
412 .as_bytes(),
413 )
414 .unwrap();
415
416 if let Some(p) = &self.credential_provider {
417 append(
418 &config_path,
419 &format!(
420 "
421 [registry]
422 credential-provider = {p}
423 "
424 )
425 .as_bytes(),
426 )
427 .unwrap()
428 }
429 }
430 }
431
432 if self.configure_token {
433 let credentials = paths::cargo_home().join("credentials.toml");
434 let token = ®istry.token;
435 if let Some(alternative) = &self.alternative {
436 append(
437 &credentials,
438 format!(
439 r#"
440 [registries.{alternative}]
441 token = "{token}"
442 "#
443 )
444 .as_bytes(),
445 )
446 .unwrap();
447 } else {
448 append(
449 &credentials,
450 format!(
451 r#"
452 [registry]
453 token = "{token}"
454 "#
455 )
456 .as_bytes(),
457 )
458 .unwrap();
459 }
460 }
461
462 let auth = if self.auth_required {
463 r#","auth-required":true"#
464 } else {
465 ""
466 };
467 let api = if self.api {
468 format!(r#","api":"{}""#, registry.api_url)
469 } else {
470 String::new()
471 };
472 repo(®istry.path)
474 .file(
475 "config.json",
476 &format!(r#"{{"dl":"{}"{api}{auth}}}"#, registry.dl_url),
477 )
478 .build();
479 fs::create_dir_all(api_path.join("api/v1/crates")).unwrap();
480
481 registry
482 }
483}
484
485#[must_use]
511pub struct Package {
512 name: String,
513 vers: String,
514 deps: Vec<Dependency>,
515 files: Vec<PackageFile>,
516 yanked: bool,
517 features: FeatureMap,
518 local: bool,
519 alternative: bool,
520 invalid_index_line: bool,
521 index_line: Option<String>,
522 edition: Option<String>,
523 resolver: Option<String>,
524 proc_macro: bool,
525 links: Option<String>,
526 rust_version: Option<String>,
527 cargo_features: Vec<String>,
528 pubtime: Option<String>,
529 v: Option<u32>,
530}
531
532pub(crate) type FeatureMap = BTreeMap<String, Vec<String>>;
533
534#[derive(Clone)]
536pub struct Dependency {
537 name: String,
538 vers: String,
539 kind: String,
540 artifact: Option<String>,
541 bindep_target: Option<String>,
542 lib: bool,
543 target: Option<String>,
544 features: Vec<String>,
545 registry: Option<String>,
546 package: Option<String>,
547 optional: bool,
548 default_features: bool,
549 public: bool,
550}
551
552#[non_exhaustive]
554enum EntryData {
555 Regular(String),
556 Symlink(PathBuf),
557 Directory,
558}
559
560struct PackageFile {
562 path: String,
563 contents: EntryData,
564 mode: u32,
567 extra: bool,
570}
571
572const DEFAULT_MODE: u32 = 0o644;
573
574pub fn init() -> TestRegistry {
580 RegistryBuilder::new().build()
581}
582
583pub fn alt_init() -> TestRegistry {
587 init();
588 RegistryBuilder::new().alternative().build()
589}
590
591pub struct HttpServerHandle {
592 addr: SocketAddr,
593 handle: Option<JoinHandle<()>>,
594}
595
596impl HttpServerHandle {
597 pub fn index_url(&self) -> Url {
598 Url::parse(&format!("sparse+http://{}/index/", self.addr)).unwrap()
599 }
600
601 pub fn api_url(&self) -> Url {
602 Url::parse(&format!("http://{}/", self.addr)).unwrap()
603 }
604
605 pub fn dl_url(&self) -> Url {
606 Url::parse(&format!("http://{}/dl", self.addr)).unwrap()
607 }
608
609 fn stop(&self) {
610 if let Ok(mut stream) = TcpStream::connect(self.addr) {
611 let _ = stream.write_all(b"stop");
613 let _ = stream.flush();
614 }
615 }
616}
617
618impl Drop for HttpServerHandle {
619 fn drop(&mut self) {
620 self.stop();
621 }
622}
623
624#[derive(Clone)]
626pub struct Request {
627 pub url: Url,
628 pub method: String,
629 pub body: Option<Vec<u8>>,
630 pub authorization: Option<String>,
631 pub if_modified_since: Option<String>,
632 pub if_none_match: Option<String>,
633}
634
635impl fmt::Debug for Request {
636 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637 f.debug_struct("Request")
639 .field("url", &self.url)
640 .field("method", &self.method)
641 .field("authorization", &self.authorization)
642 .field("if_modified_since", &self.if_modified_since)
643 .field("if_none_match", &self.if_none_match)
644 .finish()
645 }
646}
647
648pub struct Response {
650 pub code: u32,
651 pub headers: Vec<String>,
652 pub body: Vec<u8>,
653}
654
655pub struct HttpServer {
656 listener: TcpListener,
657 registry_path: PathBuf,
658 dl_path: PathBuf,
659 api_path: PathBuf,
660 token: String,
661 auth_required: bool,
662 custom_responders: HashMap<String, RequestCallback>,
663 not_found_handler: RequestCallback,
664 delayed_index_update: usize,
665}
666
667struct Mutation {}
670
671impl HttpServer {
672 pub fn new(
673 registry_path: PathBuf,
674 dl_path: PathBuf,
675 api_path: PathBuf,
676 token: String,
677 auth_required: bool,
678 custom_responders: HashMap<String, RequestCallback>,
679 not_found_handler: RequestCallback,
680 delayed_index_update: usize,
681 ) -> HttpServerHandle {
682 let listener = TcpListener::bind("127.0.0.1:0").unwrap();
683 let addr = listener.local_addr().unwrap();
684 let server = HttpServer {
685 listener,
686 registry_path,
687 dl_path,
688 api_path,
689 token,
690 auth_required,
691 custom_responders,
692 not_found_handler,
693 delayed_index_update,
694 };
695 let handle = Some(thread::spawn(move || server.start()));
696 HttpServerHandle { addr, handle }
697 }
698
699 fn start(&self) {
700 let mut line = String::new();
701 'server: loop {
702 let (socket, _) = self.listener.accept().unwrap();
703 let mut buf = BufReader::new(socket);
704 line.clear();
705 if buf.read_line(&mut line).unwrap() == 0 {
706 continue;
708 }
709 let mut parts = line.split_ascii_whitespace();
711 let method = parts.next().unwrap().to_ascii_lowercase();
712 if method == "stop" {
713 return;
715 }
716 let addr = self.listener.local_addr().unwrap();
717 let url = format!(
718 "http://{}/{}",
719 addr,
720 parts.next().unwrap().trim_start_matches('/')
721 );
722 let url = Url::parse(&url).unwrap();
723
724 let mut if_modified_since = None;
726 let mut if_none_match = None;
727 let mut authorization = None;
728 let mut content_len = None;
729 loop {
730 line.clear();
731 if buf.read_line(&mut line).unwrap() == 0 {
732 continue 'server;
733 }
734 if line == "\r\n" {
735 line.clear();
737 break;
738 }
739 let (name, value) = line.split_once(':').unwrap();
740 let name = name.trim().to_ascii_lowercase();
741 let value = value.trim().to_string();
742 match name.as_str() {
743 "if-modified-since" => if_modified_since = Some(value),
744 "if-none-match" => if_none_match = Some(value),
745 "authorization" => authorization = Some(value),
746 "content-length" => content_len = Some(value),
747 _ => {}
748 }
749 }
750
751 let mut body = None;
752 if let Some(con_len) = content_len {
753 let len = con_len.parse::<u64>().unwrap();
754 let mut content = vec![0u8; len as usize];
755 buf.read_exact(&mut content).unwrap();
756 body = Some(content)
757 }
758
759 let req = Request {
760 authorization,
761 if_modified_since,
762 if_none_match,
763 method,
764 url,
765 body,
766 };
767 let response = self.route(&req);
768 let buf = buf.get_mut();
769 write!(buf, "HTTP/1.1 {}\r\n", response.code).unwrap();
770 write!(buf, "Content-Length: {}\r\n", response.body.len()).unwrap();
771 write!(buf, "Connection: close\r\n").unwrap();
772 for header in response.headers {
773 write!(buf, "{}\r\n", header).unwrap();
774 }
775 write!(buf, "\r\n").unwrap();
776 buf.write_all(&response.body).unwrap();
777 buf.flush().unwrap();
778 }
779 }
780
781 fn check_authorized(&self, req: &Request, mutation: Option<Mutation>) -> bool {
782 if mutation.is_some() || self.auth_required {
783 Some(&self.token) == req.authorization.as_ref()
784 } else {
785 assert!(req.authorization.is_none(), "unexpected token");
786 true
787 }
788 }
789
790 fn route(&self, req: &Request) -> Response {
792 if let Some(responder) = self.custom_responders.get(req.url.path()) {
794 return responder(&req, self);
795 }
796 let path: Vec<_> = req.url.path()[1..].split('/').collect();
797 match (req.method.as_str(), path.as_slice()) {
798 ("get", ["index", ..]) => {
799 if !self.check_authorized(req, None) {
800 self.unauthorized(req)
801 } else {
802 self.index(&req)
803 }
804 }
805 ("get", ["dl", ..]) => {
806 if !self.check_authorized(req, None) {
807 self.unauthorized(req)
808 } else {
809 self.dl(&req)
810 }
811 }
812 ("put", ["api", "v1", "crates", "new"]) => self.check_authorized_publish(req),
814 ("delete" | "put", ["api", "v1", "crates", _, _, _]) => {
821 if !self.check_authorized(req, Some(Mutation {})) {
822 self.unauthorized(req)
823 } else {
824 self.ok(&req)
825 }
826 }
827 ("get" | "put" | "delete", ["api", "v1", "crates", _, "owners"]) => {
829 if !self.check_authorized(req, Some(Mutation {})) {
830 self.unauthorized(req)
831 } else {
832 self.ok(&req)
833 }
834 }
835 _ => self.not_found(&req),
836 }
837 }
838
839 pub fn unauthorized(&self, _req: &Request) -> Response {
841 Response {
842 code: 401,
843 headers: vec![
844 r#"www-authenticate: Cargo login_url="https://test-registry-login/me""#.to_string(),
845 ],
846 body: b"Unauthorized message from server.".to_vec(),
847 }
848 }
849
850 pub fn not_found(&self, req: &Request) -> Response {
852 (self.not_found_handler)(req, self)
853 }
854
855 pub fn ok(&self, _req: &Request) -> Response {
857 Response {
858 code: 200,
859 headers: vec![],
860 body: br#"{"ok": true, "msg": "completed!"}"#.to_vec(),
861 }
862 }
863
864 pub fn internal_server_error(&self, _req: &Request) -> Response {
866 Response {
867 code: 500,
868 headers: vec![],
869 body: br#"internal server error"#.to_vec(),
870 }
871 }
872
873 pub fn too_many_requests(&self, _req: &Request, delay: std::time::Duration) -> Response {
875 Response {
876 code: 429,
877 headers: vec![format!("Retry-After: {}", delay.as_secs())],
878 body: format!(
879 "too many requests, try again in {} seconds",
880 delay.as_secs()
881 )
882 .into_bytes(),
883 }
884 }
885
886 pub fn dl(&self, req: &Request) -> Response {
888 let file = self
889 .dl_path
890 .join(req.url.path().strip_prefix("/dl/").unwrap());
891 println!("{}", file.display());
892 if !file.exists() {
893 return self.not_found(req);
894 }
895 return Response {
896 body: fs::read(&file).unwrap(),
897 code: 200,
898 headers: vec![],
899 };
900 }
901
902 pub fn index(&self, req: &Request) -> Response {
904 let file = self
905 .registry_path
906 .join(req.url.path().strip_prefix("/index/").unwrap());
907 if !file.exists() {
908 return self.not_found(req);
909 } else {
910 let data = fs::read(&file).unwrap();
912 let etag = Sha256::new().update(&data).finish_hex();
913 let last_modified = format!("{:?}", file.metadata().unwrap().modified().unwrap());
914
915 let mut any_match = false;
917 let mut all_match = true;
918 if let Some(expected) = &req.if_none_match {
919 if &etag != expected {
920 all_match = false;
921 } else {
922 any_match = true;
923 }
924 }
925 if let Some(expected) = &req.if_modified_since {
926 if &last_modified != expected {
928 all_match = false;
929 } else {
930 any_match = true;
931 }
932 }
933
934 if any_match && all_match {
935 return Response {
936 body: Vec::new(),
937 code: 304,
938 headers: vec![],
939 };
940 } else {
941 return Response {
942 body: data,
943 code: 200,
944 headers: vec![
945 format!("ETag: \"{}\"", etag),
946 format!("Last-Modified: {}", last_modified),
947 ],
948 };
949 }
950 }
951 }
952
953 pub fn check_authorized_publish(&self, req: &Request) -> Response {
954 if let Some(body) = &req.body {
955 let path = self.api_path.join("api/v1/crates/new");
958 t!(fs::create_dir_all(path.parent().unwrap()));
959 t!(fs::write(&path, body));
960
961 let (len, remaining) = body.split_at(4);
963 let json_len = u32::from_le_bytes(len.try_into().unwrap());
964 let (json, remaining) = remaining.split_at(json_len as usize);
965 let new_crate = serde_json::from_slice::<crates_io::NewCrate>(json).unwrap();
966 let (len, remaining) = remaining.split_at(4);
968 let file_len = u32::from_le_bytes(len.try_into().unwrap());
969 let (file, _remaining) = remaining.split_at(file_len as usize);
970 let file_cksum = cksum(&file);
971
972 if !self.check_authorized(req, Some(Mutation {})) {
973 return self.unauthorized(req);
974 }
975
976 let dst = self
977 .dl_path
978 .join(&new_crate.name)
979 .join(&new_crate.vers)
980 .join("download");
981
982 if self.delayed_index_update == 0 {
983 save_new_crate(dst, new_crate, file, file_cksum, &self.registry_path);
984 } else {
985 let delayed_index_update = self.delayed_index_update;
986 let registry_path = self.registry_path.clone();
987 let file = Vec::from(file);
988 thread::spawn(move || {
989 thread::sleep(std::time::Duration::new(delayed_index_update as u64, 0));
990 save_new_crate(dst, new_crate, &file, file_cksum, ®istry_path);
991 });
992 }
993
994 self.ok(&req)
995 } else {
996 Response {
997 code: 400,
998 headers: vec![],
999 body: b"The request was missing a body".to_vec(),
1000 }
1001 }
1002 }
1003}
1004
1005fn save_new_crate(
1006 dst: PathBuf,
1007 new_crate: crates_io::NewCrate,
1008 file: &[u8],
1009 file_cksum: String,
1010 registry_path: &Path,
1011) {
1012 t!(fs::create_dir_all(dst.parent().unwrap()));
1014 t!(fs::write(&dst, file));
1015
1016 let deps = new_crate
1017 .deps
1018 .iter()
1019 .map(|dep| {
1020 let (name, package) = match &dep.explicit_name_in_toml {
1021 Some(explicit) => (explicit.to_string(), Some(dep.name.to_string())),
1022 None => (dep.name.to_string(), None),
1023 };
1024 serde_json::json!({
1025 "name": name,
1026 "req": dep.version_req,
1027 "features": dep.features,
1028 "default_features": dep.default_features,
1029 "target": dep.target,
1030 "optional": dep.optional,
1031 "kind": dep.kind,
1032 "registry": dep.registry,
1033 "package": package,
1034 "artifact": dep.artifact,
1035 "bindep_target": dep.bindep_target,
1036 "lib": dep.lib,
1037 })
1038 })
1039 .collect::<Vec<_>>();
1040
1041 let line = create_index_line(
1042 serde_json::json!(new_crate.name),
1043 &new_crate.vers,
1044 deps,
1045 &file_cksum,
1046 new_crate.features,
1047 false,
1048 new_crate.links,
1049 new_crate.rust_version.as_deref(),
1050 None,
1051 None,
1052 );
1053
1054 write_to_index(registry_path, &new_crate.name, line, false);
1055}
1056
1057impl Package {
1058 pub fn new(name: &str, vers: &str) -> Package {
1061 let config = paths::cargo_home().join("config.toml");
1062 if !config.exists() {
1063 init();
1064 }
1065 Package {
1066 name: name.to_string(),
1067 vers: vers.to_string(),
1068 deps: Vec::new(),
1069 files: Vec::new(),
1070 yanked: false,
1071 features: BTreeMap::new(),
1072 local: false,
1073 alternative: false,
1074 invalid_index_line: false,
1075 index_line: None,
1076 edition: None,
1077 resolver: None,
1078 proc_macro: false,
1079 links: None,
1080 rust_version: None,
1081 cargo_features: Vec::new(),
1082 pubtime: None,
1083 v: None,
1084 }
1085 }
1086
1087 pub fn local(&mut self, local: bool) -> &mut Package {
1093 self.local = local;
1094 self
1095 }
1096
1097 pub fn alternative(&mut self, alternative: bool) -> &mut Package {
1107 self.alternative = alternative;
1108 self
1109 }
1110
1111 pub fn file(&mut self, name: &str, contents: &str) -> &mut Package {
1113 self.file_with_mode(name, DEFAULT_MODE, contents)
1114 }
1115
1116 pub fn file_with_mode(&mut self, path: &str, mode: u32, contents: &str) -> &mut Package {
1118 self.files.push(PackageFile {
1119 path: path.to_string(),
1120 contents: EntryData::Regular(contents.into()),
1121 mode,
1122 extra: false,
1123 });
1124 self
1125 }
1126
1127 pub fn symlink(&mut self, dst: &str, src: &str) -> &mut Package {
1129 self.files.push(PackageFile {
1130 path: dst.to_string(),
1131 contents: EntryData::Symlink(src.into()),
1132 mode: DEFAULT_MODE,
1133 extra: false,
1134 });
1135 self
1136 }
1137
1138 pub fn directory(&mut self, path: &str) -> &mut Package {
1140 self.files.push(PackageFile {
1141 path: path.to_string(),
1142 contents: EntryData::Directory,
1143 mode: DEFAULT_MODE,
1144 extra: false,
1145 });
1146 self
1147 }
1148
1149 pub fn extra_file(&mut self, path: &str, contents: &str) -> &mut Package {
1155 self.files.push(PackageFile {
1156 path: path.to_string(),
1157 contents: EntryData::Regular(contents.to_string()),
1158 mode: DEFAULT_MODE,
1159 extra: true,
1160 });
1161 self
1162 }
1163
1164 pub fn dep(&mut self, name: &str, vers: &str) -> &mut Package {
1170 self.add_dep(&Dependency::new(name, vers))
1171 }
1172
1173 pub fn feature_dep(&mut self, name: &str, vers: &str, features: &[&str]) -> &mut Package {
1179 self.add_dep(Dependency::new(name, vers).enable_features(features))
1180 }
1181
1182 pub fn target_dep(&mut self, name: &str, vers: &str, target: &str) -> &mut Package {
1188 self.add_dep(Dependency::new(name, vers).target(target))
1189 }
1190
1191 pub fn registry_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1193 self.add_dep(Dependency::new(name, vers).registry("alternative"))
1194 }
1195
1196 pub fn dev_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1202 self.add_dep(Dependency::new(name, vers).dev())
1203 }
1204
1205 pub fn build_dep(&mut self, name: &str, vers: &str) -> &mut Package {
1211 self.add_dep(Dependency::new(name, vers).build())
1212 }
1213
1214 pub fn add_dep(&mut self, dep: &Dependency) -> &mut Package {
1215 self.deps.push(dep.clone());
1216 self
1217 }
1218
1219 pub fn yanked(&mut self, yanked: bool) -> &mut Package {
1221 self.yanked = yanked;
1222 self
1223 }
1224
1225 pub fn edition(&mut self, edition: &str) -> &mut Package {
1227 self.edition = Some(edition.to_owned());
1228 self
1229 }
1230
1231 pub fn resolver(&mut self, resolver: &str) -> &mut Package {
1233 self.resolver = Some(resolver.to_owned());
1234 self
1235 }
1236
1237 pub fn proc_macro(&mut self, proc_macro: bool) -> &mut Package {
1239 self.proc_macro = proc_macro;
1240 self
1241 }
1242
1243 pub fn feature(&mut self, name: &str, deps: &[&str]) -> &mut Package {
1245 let deps = deps.iter().map(|s| s.to_string()).collect();
1246 self.features.insert(name.to_string(), deps);
1247 self
1248 }
1249
1250 pub fn rust_version(&mut self, rust_version: &str) -> &mut Package {
1252 self.rust_version = Some(rust_version.into());
1253 self
1254 }
1255
1256 pub fn invalid_index_line(&mut self, invalid: bool) -> &mut Package {
1259 self.invalid_index_line = invalid;
1260 self
1261 }
1262
1263 pub fn index_line(&mut self, line: &str) -> &mut Package {
1267 self.index_line = Some(line.to_owned());
1268 self
1269 }
1270
1271 pub fn links(&mut self, links: &str) -> &mut Package {
1272 self.links = Some(links.to_string());
1273 self
1274 }
1275
1276 pub fn cargo_feature(&mut self, feature: &str) -> &mut Package {
1277 self.cargo_features.push(feature.to_owned());
1278 self
1279 }
1280
1281 pub fn pubtime(&mut self, time: &str) -> &mut Package {
1283 self.pubtime = Some(time.to_owned());
1284 self
1285 }
1286
1287 pub fn schema_version(&mut self, version: u32) -> &mut Package {
1291 self.v = Some(version);
1292 self
1293 }
1294
1295 pub fn publish(&self) -> String {
1302 self.make_archive();
1303
1304 let deps = self
1306 .deps
1307 .iter()
1308 .map(|dep| {
1309 let registry_url = match (self.alternative, dep.registry.as_deref()) {
1312 (false, None) => None,
1313 (false, Some("alternative")) => Some(alt_registry_url().to_string()),
1314 (true, None) => {
1315 Some("https://github.com/rust-lang/crates.io-index".to_string())
1316 }
1317 (true, Some("alternative")) => None,
1318 _ => panic!("registry_dep currently only supports `alternative`"),
1319 };
1320 let artifact = if let Some(artifact) = &dep.artifact {
1321 serde_json::json!([artifact])
1322 } else {
1323 serde_json::json!(null)
1324 };
1325 serde_json::json!({
1326 "name": dep.name,
1327 "req": dep.vers,
1328 "features": dep.features,
1329 "default_features": dep.default_features,
1330 "target": dep.target,
1331 "artifact": artifact,
1332 "bindep_target": dep.bindep_target,
1333 "lib": dep.lib,
1334 "optional": dep.optional,
1335 "kind": dep.kind,
1336 "registry": registry_url,
1337 "package": dep.package,
1338 "public": dep.public,
1339 })
1340 })
1341 .collect::<Vec<_>>();
1342 let cksum = {
1343 let c = t!(fs::read(&self.archive_dst()));
1344 cksum(&c)
1345 };
1346 let line = if let Some(line) = self.index_line.clone() {
1347 line
1348 } else {
1349 let name = if self.invalid_index_line {
1350 serde_json::json!(1)
1351 } else {
1352 serde_json::json!(self.name)
1353 };
1354 create_index_line(
1355 name,
1356 &self.vers,
1357 deps,
1358 &cksum,
1359 self.features.clone(),
1360 self.yanked,
1361 self.links.clone(),
1362 self.rust_version.as_deref(),
1363 self.pubtime.as_deref(),
1364 self.v,
1365 )
1366 };
1367
1368 let registry_path = if self.alternative {
1369 alt_registry_path()
1370 } else {
1371 registry_path()
1372 };
1373
1374 write_to_index(®istry_path, &self.name, line, self.local);
1375
1376 cksum
1377 }
1378
1379 fn make_archive(&self) {
1380 let dst = self.archive_dst();
1381 t!(fs::create_dir_all(dst.parent().unwrap()));
1382 let f = t!(File::create(&dst));
1383 let mut a = Builder::new(GzEncoder::new(f, Compression::none()));
1384 a.sparse(false);
1385
1386 if !self
1387 .files
1388 .iter()
1389 .any(|PackageFile { path, .. }| path == "Cargo.toml")
1390 {
1391 self.append_manifest(&mut a);
1392 }
1393 if self.files.is_empty() {
1394 self.append(
1395 &mut a,
1396 "src/lib.rs",
1397 DEFAULT_MODE,
1398 &EntryData::Regular("".into()),
1399 );
1400 } else {
1401 for PackageFile {
1402 path,
1403 contents,
1404 mode,
1405 extra,
1406 } in &self.files
1407 {
1408 if *extra {
1409 self.append_raw(&mut a, path, *mode, contents);
1410 } else {
1411 self.append(&mut a, path, *mode, contents);
1412 }
1413 }
1414 }
1415 }
1416
1417 fn append_manifest<W: Write>(&self, ar: &mut Builder<W>) {
1418 let mut manifest = String::new();
1419
1420 if !self.cargo_features.is_empty() {
1421 let mut features = String::new();
1422 serde::Serialize::serialize(
1423 &self.cargo_features,
1424 toml::ser::ValueSerializer::new(&mut features),
1425 )
1426 .unwrap();
1427 manifest.push_str(&format!("cargo-features = {}\n\n", features));
1428 }
1429
1430 manifest.push_str(&format!(
1431 r#"
1432 [package]
1433 name = "{}"
1434 version = "{}"
1435 authors = []
1436 "#,
1437 self.name, self.vers
1438 ));
1439
1440 if let Some(version) = &self.rust_version {
1441 manifest.push_str(&format!("rust-version = \"{}\"\n", version));
1442 }
1443
1444 if let Some(edition) = &self.edition {
1445 manifest.push_str(&format!("edition = \"{}\"\n", edition));
1446 }
1447
1448 if let Some(resolver) = &self.resolver {
1449 manifest.push_str(&format!("resolver = \"{}\"\n", resolver));
1450 }
1451
1452 if !self.features.is_empty() {
1453 let features: Vec<String> = self
1454 .features
1455 .iter()
1456 .map(|(feature, features)| {
1457 if features.is_empty() {
1458 format!("{} = []", feature)
1459 } else {
1460 format!(
1461 "{} = [{}]",
1462 feature,
1463 features
1464 .iter()
1465 .map(|s| format!("\"{}\"", s))
1466 .collect::<Vec<_>>()
1467 .join(", ")
1468 )
1469 }
1470 })
1471 .collect();
1472
1473 manifest.push_str(&format!("\n[features]\n{}", features.join("\n")));
1474 }
1475
1476 for dep in self.deps.iter() {
1477 let target = match dep.target {
1478 None => String::new(),
1479 Some(ref s) => format!("target.'{}'.", s),
1480 };
1481 let kind = match &dep.kind[..] {
1482 "build" => "build-",
1483 "dev" => "dev-",
1484 _ => "",
1485 };
1486 manifest.push_str(&format!(
1487 r#"
1488 [{}{}dependencies.{}]
1489 version = "{}"
1490 "#,
1491 target, kind, dep.name, dep.vers
1492 ));
1493 if dep.optional {
1494 manifest.push_str("optional = true\n");
1495 }
1496 if let Some(artifact) = &dep.artifact {
1497 manifest.push_str(&format!("artifact = \"{}\"\n", artifact));
1498 }
1499 if let Some(target) = &dep.bindep_target {
1500 manifest.push_str(&format!("target = \"{}\"\n", target));
1501 }
1502 if dep.lib {
1503 manifest.push_str("lib = true\n");
1504 }
1505 if let Some(registry) = &dep.registry {
1506 assert_eq!(registry, "alternative");
1507 manifest.push_str(&format!("registry-index = \"{}\"", alt_registry_url()));
1508 }
1509 if !dep.default_features {
1510 manifest.push_str("default-features = false\n");
1511 }
1512 if !dep.features.is_empty() {
1513 let mut features = String::new();
1514 serde::Serialize::serialize(
1515 &dep.features,
1516 toml::ser::ValueSerializer::new(&mut features),
1517 )
1518 .unwrap();
1519 manifest.push_str(&format!("features = {}\n", features));
1520 }
1521 if let Some(package) = &dep.package {
1522 manifest.push_str(&format!("package = \"{}\"\n", package));
1523 }
1524 }
1525 if self.proc_macro {
1526 manifest.push_str("[lib]\nproc-macro = true\n");
1527 }
1528
1529 self.append(
1530 ar,
1531 "Cargo.toml",
1532 DEFAULT_MODE,
1533 &EntryData::Regular(manifest.into()),
1534 );
1535 }
1536
1537 fn append<W: Write>(&self, ar: &mut Builder<W>, file: &str, mode: u32, contents: &EntryData) {
1538 self.append_raw(
1539 ar,
1540 &format!("{}-{}/{}", self.name, self.vers, file),
1541 mode,
1542 contents,
1543 );
1544 }
1545
1546 fn append_raw<W: Write>(
1547 &self,
1548 ar: &mut Builder<W>,
1549 path: &str,
1550 mode: u32,
1551 contents: &EntryData,
1552 ) {
1553 let mut header = Header::new_ustar();
1558 let contents = match contents {
1559 EntryData::Regular(contents) => contents.as_str(),
1560 EntryData::Symlink(src) => {
1561 header.set_entry_type(tar::EntryType::Symlink);
1562 t!(header.set_link_name(src));
1563 "" }
1565 EntryData::Directory => {
1566 header.set_entry_type(tar::EntryType::Directory);
1567 ""
1568 }
1569 };
1570 header.set_size(contents.len() as u64);
1571 t!(header.set_path(path));
1572 header.set_mode(mode);
1573 header.set_cksum();
1574 t!(ar.append(&header, contents.as_bytes()));
1575 }
1576
1577 pub fn archive_dst(&self) -> PathBuf {
1579 if self.local {
1580 let path = if self.alternative {
1581 alt_registry_path()
1582 } else {
1583 registry_path()
1584 };
1585 path.join(format!("{}-{}.crate", self.name, self.vers))
1586 } else if self.alternative {
1587 alt_dl_path()
1588 .join(&self.name)
1589 .join(&self.vers)
1590 .join("download")
1591 } else {
1592 dl_path().join(&self.name).join(&self.vers).join("download")
1593 }
1594 }
1595}
1596
1597pub fn cksum(s: &[u8]) -> String {
1599 Sha256::new().update(s).finish_hex()
1600}
1601
1602impl Dependency {
1603 pub fn new(name: &str, vers: &str) -> Dependency {
1604 Dependency {
1605 name: name.to_string(),
1606 vers: vers.to_string(),
1607 kind: "normal".to_string(),
1608 artifact: None,
1609 bindep_target: None,
1610 lib: false,
1611 target: None,
1612 features: Vec::new(),
1613 package: None,
1614 optional: false,
1615 registry: None,
1616 default_features: true,
1617 public: false,
1618 }
1619 }
1620
1621 pub fn build(&mut self) -> &mut Self {
1623 self.kind = "build".to_string();
1624 self
1625 }
1626
1627 pub fn dev(&mut self) -> &mut Self {
1629 self.kind = "dev".to_string();
1630 self
1631 }
1632
1633 pub fn target(&mut self, target: &str) -> &mut Self {
1635 self.target = Some(target.to_string());
1636 self
1637 }
1638
1639 pub fn artifact(&mut self, kind: &str, target: Option<String>) -> &mut Self {
1642 self.artifact = Some(kind.to_string());
1643 self.bindep_target = target;
1644 self
1645 }
1646
1647 pub fn registry(&mut self, registry: &str) -> &mut Self {
1649 self.registry = Some(registry.to_string());
1650 self
1651 }
1652
1653 pub fn enable_features(&mut self, features: &[&str]) -> &mut Self {
1655 self.features.extend(features.iter().map(|s| s.to_string()));
1656 self
1657 }
1658
1659 pub fn package(&mut self, pkg: &str) -> &mut Self {
1661 self.package = Some(pkg.to_string());
1662 self
1663 }
1664
1665 pub fn optional(&mut self, optional: bool) -> &mut Self {
1667 self.optional = optional;
1668 self
1669 }
1670
1671 pub fn public(&mut self, public: bool) -> &mut Self {
1673 self.public = public;
1674 self
1675 }
1676
1677 pub fn default_features(&mut self, default_features: bool) -> &mut Self {
1679 self.default_features = default_features;
1680 self
1681 }
1682}