-
-
Notifications
You must be signed in to change notification settings - Fork 259
/
mod.rs
690 lines (645 loc) · 23.5 KB
/
mod.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
use std::{
cell::RefCell,
collections::HashSet,
error::Error,
io::{self, BufRead, Read, Write},
ops::Deref,
rc::Rc,
vec::IntoIter,
};
use bstr::ByteSlice;
use gix_packetline::read::ProgressAction;
use gix_transport::{
client::{self, http, SetServiceResponse, Transport, TransportV2Ext, TransportWithoutIO},
Protocol, Service,
};
use crate::fixture_bytes;
mod mock;
fn assert_error_status(
status: usize,
kind: std::io::ErrorKind,
) -> Result<(mock::Server, http::Transport<http::Impl>), crate::Error> {
let (server, mut client) =
mock::serve_and_connect(&format!("http-{status}.response"), "path/not-important", Protocol::V1)?;
let error = client
.handshake(Service::UploadPack, &[])
.err()
.expect("non-200 status causes error");
let error = error
.source()
.unwrap_or_else(|| panic!("no source() in: {error:?} "))
.downcast_ref::<std::io::Error>()
.expect("io error as source");
assert_eq!(error.kind(), kind);
let expected = format!("Received HTTP status {status}");
assert_eq!(error.to_string().get(..expected.len()), Some(expected).as_deref());
drop(server.received());
Ok((server, client))
}
#[test]
fn http_status_500_is_communicated_via_special_io_error() -> crate::Result {
assert_error_status(500, std::io::ErrorKind::ConnectionAborted)?;
Ok(())
}
#[test]
fn http_identity_is_picked_up_from_url() -> crate::Result {
let transport =
gix_transport::client::http::connect("https://user:pass@example.com/repo".try_into()?, Protocol::V2, false);
assert_eq!(transport.to_url().as_ref(), "https://user:pass@example.com/repo");
assert_eq!(
transport.identity(),
Some(&gix_sec::identity::Account {
username: "user".into(),
password: "pass".into()
})
);
Ok(())
}
// based on a test in cargo
#[test]
fn http_will_use_pipelining() {
let server = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = server.local_addr().unwrap();
fn headers(rdr: &mut dyn BufRead) -> HashSet<String> {
let valid = ["GET", "Authorization", "Accept"];
rdr.lines()
.map(Result::unwrap)
.take_while(|s| s.len() > 2)
.map(|s| s.trim().to_string())
.filter(|s| valid.iter().any(|prefix| s.starts_with(*prefix)))
.collect()
}
let thread = std::thread::spawn({
move || {
let mut conn = std::io::BufReader::new(server.accept().unwrap().0);
let req = headers(&mut conn);
conn.get_mut()
.write_all(
b"HTTP/1.1 401 Unauthorized\r\n\
WWW-Authenticate: Basic realm=\"wheee\"\r\n\
Content-Length: 0\r\n\
\r\n",
)
.unwrap();
assert_eq!(
req,
vec![
"GET /reponame/info/refs?service=git-upload-pack HTTP/1.1",
"Accept: */*"
]
.into_iter()
.map(ToString::to_string)
.collect()
);
let req = headers(&mut conn);
conn.get_mut()
.write_all(
b"HTTP/1.1 401 Unauthorized\r\n\
WWW-Authenticate: Basic realm=\"testenv\"\r\n\
\r\n",
)
.unwrap();
assert_eq!(
req,
vec![
"GET /reponame/info/refs?service=git-upload-pack HTTP/1.1",
"Authorization: Basic Zm9vOmJhcg==",
"Accept: */*",
]
.into_iter()
.map(ToString::to_string)
.collect()
);
}
});
let url = format!("http://{}:{}/reponame", &addr.ip().to_string(), &addr.port(),);
let mut client =
gix_transport::client::http::connect(url.try_into().expect("valid url"), gix_transport::Protocol::V2, false);
match client.handshake(gix_transport::Service::UploadPack, &[]) {
Ok(_) => unreachable!("expecting permission denied to be detected"),
Err(gix_transport::client::Error::Io(err)) if err.kind() == std::io::ErrorKind::PermissionDenied => {}
Err(err) => unreachable!("{err:?}"),
};
client
.set_identity(gix_sec::identity::Account {
username: "foo".into(),
password: "bar".into(),
})
.unwrap();
match client.handshake(gix_transport::Service::UploadPack, &[]) {
Ok(_) => unreachable!("expecting permission denied to be detected"),
Err(gix_transport::client::Error::Io(err)) if err.kind() == std::io::ErrorKind::PermissionDenied => {}
Err(err) => unreachable!("{err:?}"),
};
thread.join().unwrap();
}
#[test]
fn http_authentication_error_can_be_differentiated_and_identity_is_transmitted() -> crate::Result {
let (server, mut client) = assert_error_status(401, std::io::ErrorKind::PermissionDenied)?;
server.next_read_and_respond_with(fixture_bytes("v1/http-handshake.response"));
client.set_identity(gix_sec::identity::Account {
username: "user".into(),
password: "password".into(),
})?;
client.handshake(Service::UploadPack, &[])?;
assert_eq!(
server
.received_as_string()
.lines()
.map(str::to_lowercase)
.filter(ignore_reqwest_content_length)
.collect::<HashSet<_>>(),
format!(
"GET /path/not-important/info/refs?service=git-upload-pack HTTP/1.1
Host: 127.0.0.1:{}
Accept: */*
User-Agent: git/oxide-{}
Authorization: Basic dXNlcjpwYXNzd29yZA==
",
server.addr.port(),
env!("CARGO_PKG_VERSION")
)
.lines()
.map(str::to_lowercase)
.collect::<HashSet<_>>()
);
server.next_read_and_respond_with(fixture_bytes("v1/http-handshake.response"));
client.request(client::WriteMode::Binary, client::MessageKind::Flush, false)?;
assert_eq!(
{
let mut m = server
.received_as_string()
.lines()
.map(str::to_lowercase)
.filter(|l| !l.starts_with("expect: "))
.filter(ignore_reqwest_content_length)
.collect::<HashSet<_>>();
// On linux on CI, for some reason, it won't have this chunk id here, but
// it has it whenever and where-ever I run it.
m.remove("0");
m
},
format!(
"POST /path/not-important/git-upload-pack HTTP/1.1
Host: 127.0.0.1:{}
Transfer-Encoding: chunked
User-Agent: git/oxide-{}
Content-Type: application/x-git-upload-pack-request
Accept: application/x-git-upload-pack-result
Authorization: Basic dXNlcjpwYXNzd29yZA==
",
server.addr.port(),
env!("CARGO_PKG_VERSION")
)
.lines()
.map(str::to_lowercase)
.collect::<HashSet<_>>(),
"the authentication information is used in subsequent calls"
);
Ok(())
}
#[test]
fn http_error_results_in_observable_error() -> crate::Result {
assert_error_status(404, std::io::ErrorKind::Other)?;
Ok(())
}
#[test]
fn handshake_v1() -> crate::Result {
let (server, mut c) = mock::serve_and_connect(
"v1/http-handshake.response",
"path/not/important/due/to/mock",
Protocol::V1,
)?;
assert!(
!c.connection_persists_across_multiple_requests(),
"http connections are never stateful"
);
let SetServiceResponse {
actual_protocol,
capabilities,
refs,
} = c.handshake(Service::UploadPack, &[])?;
assert_eq!(actual_protocol, Protocol::V1);
assert_eq!(
capabilities
.iter()
.map(|c| (c.name().to_owned(), c.value().map(ToOwned::to_owned)))
.collect::<Vec<_>>(),
[
("multi_ack", None),
("thin-pack", None),
("side-band", None),
("side-band-64k", None),
("ofs-delta", None),
("shallow", None),
("deepen-since", None),
("deepen-not", None),
("deepen-relative", None),
("no-progress", None),
("include-tag", None),
("multi_ack_detailed", None),
("allow-tip-sha1-in-want", None),
("allow-reachable-sha1-in-want", None),
("no-done", None),
("symref", Some("HEAD:refs/heads/main")),
("filter", None),
("agent", Some("git/github-gdf51a71f0236"))
]
.iter()
.map(|(n, v)| (
n.as_bytes().as_bstr().to_owned(),
v.map(|v| v.as_bytes().as_bstr().to_owned())
))
.collect::<Vec<_>>()
);
let refs = refs
.expect("v1 protocol provides refs")
.lines()
.map_while(Result::ok)
.collect::<Vec<_>>();
assert_eq!(
refs,
vec![
"73a6868963993a3328e7d8fe94e5a6ac5078a944 HEAD",
"73a6868963993a3328e7d8fe94e5a6ac5078a944 refs/heads/main",
"8e472f9ccc7d745927426cbb2d9d077de545aa4e refs/pull/13/head",
"1a33becbfa6aaf7661824ce40016acb8c179f13c refs/pull/14/head",
"add2e3e8d155571154c8816cf57f473a6e4d8d31 refs/pull/2/head",
"dce0ea858eef7ff61ad345cc5cdac62203fb3c10 refs/tags/gix-commitgraph-v0.0.0",
"21c9b7500cb144b3169a6537961ec2b9e865be81 refs/tags/gix-commitgraph-v0.0.0^{}",
"7ba6656568da186d153d66f26990b9b364ea9609 refs/tags/gix-features-v0.1.0",
"5688a3427ff3673e1422d43106f4d685fa837aed refs/tags/gix-features-v0.1.0^{}",
"92945a59059bf044744639673f1a0f5b314762ee refs/tags/gix-features-v0.2.0",
"0bb831480d8657e1bb29ee7009aeac673471403e refs/tags/gix-features-v0.2.0^{}",
"97e1d77270a8f9cbff19baf3803de8b4f5a339bf refs/tags/gix-features-v0.3.0",
"4351e2871c9dcf342b8471fffa74cae338a53269 refs/tags/gix-features-v0.3.0^{}",
"d5f78373a75de13ef3c08eedf03e616b2ec395f2 refs/tags/gix-features-v0.4.0",
"9d6b8790e2edd7fa01b3239adff86a7cd2393f10 refs/tags/gix-features-v0.4.0^{}",
"be64896ed543437b67e939c36ecd70945e100d6c refs/tags/gix-object-v0.1.0",
"5688a3427ff3673e1422d43106f4d685fa837aed refs/tags/gix-object-v0.1.0^{}",
"7b34dc75ac5010741c0675d8c3a9645adb9b2ee1 refs/tags/gix-object-v0.3.0",
"e8df6c1ffb7afa27aff9abbe11c7e4b80d19b61e refs/tags/gix-object-v0.3.0^{}",
"2249ae57005b7c5ff94409bbe0e3213cbfd1745f refs/tags/gix-odb-v0.1.0",
"2b80181ad428a9bf267a9660886f347a850fc76f refs/tags/gix-odb-v0.1.0^{}",
"a9bb4d08a8c159d2444615ce9f9bc68f40fe98b1 refs/tags/gix-odb-v0.3.0",
"e8df6c1ffb7afa27aff9abbe11c7e4b80d19b61e refs/tags/gix-odb-v0.3.0^{}",
"d5d9eabaa9f190e535771c8dcc9fd1bcf69b7947 refs/tags/gix-packetline-v0.1.0",
"9d6b8790e2edd7fa01b3239adff86a7cd2393f10 refs/tags/gix-packetline-v0.1.0^{}",
"defd2a7783ab4618f41c270477921aa2336693db refs/tags/gix-protocol-v0.0.0",
"14615143dc170217ca4acc80191f4e6725dc460a refs/tags/gix-protocol-v0.0.0^{}",
"7e168eef62b8ad6ddd49e4e50d500761b84cfb4f refs/tags/gix-ref-v0.1.0",
"e66c9ed041c7ebede869e899ecd4398fee47028b refs/tags/gix-ref-v0.1.0^{}",
"fde229329d5d4540d21a04dcaf8cfb13a1e8a8c5 refs/tags/gix-ref-v0.2.0",
"d350a13784685ea82b84646b18736986aeb68146 refs/tags/gix-ref-v0.2.0^{}",
"4f75945daf9e0a669b694b0652c5a7e8a6dd2246 refs/tags/gix-ref-v0.3.0",
"e8df6c1ffb7afa27aff9abbe11c7e4b80d19b61e refs/tags/gix-ref-v0.3.0^{}",
"058e7f3f554f37f05cc9aaf0c86b4bbe8bea9242 refs/tags/git-repository-v0.1.0",
"2b80181ad428a9bf267a9660886f347a850fc76f refs/tags/git-repository-v0.1.0^{}",
"74b85f2bc7a9bcdd59218ee54135d5dd3a8dbd72 refs/tags/git-repository-v0.3.0",
"e8df6c1ffb7afa27aff9abbe11c7e4b80d19b61e refs/tags/git-repository-v0.3.0^{}",
"40046d9f4ab51a8895e8de8a3ed4e213d87f042e refs/tags/gix-transport-v0.0.0",
"19e7fec7deb5a6419f36a2732c90006377414181 refs/tags/gix-transport-v0.0.0^{}",
"64bdbb4ef5415d4cfb088fbbdc8f5f6dca37aeca refs/tags/gix-tui-v0.0.0",
"a0b73afdd1df9b1096f0c6fe388f795a6dfe7f33 refs/tags/gix-tui-v0.0.0^{}",
"320c79b59068fc5f0fc11d331de7352bb1952f10 refs/tags/gix-url-v0.0.0",
"fd2e5bab97f09666c983634fa89947a4bed1c92d refs/tags/gix-url-v0.0.0^{}",
"58cbf2153987f6f4e91bd58074a1dd648f30f932 refs/tags/gitoxide-core-v0.1.0",
"19e7fec7deb5a6419f36a2732c90006377414181 refs/tags/gitoxide-core-v0.1.0^{}",
"640ce76991e36035af707ec4f9afc550cc33cb58 refs/tags/gitoxide-core-v0.3.0",
"e8df6c1ffb7afa27aff9abbe11c7e4b80d19b61e refs/tags/gitoxide-core-v0.3.0^{}",
"df1d23e4e6c489a74ab6c6845de49e54fe5a8f4d refs/tags/v0.1.0",
"19e7fec7deb5a6419f36a2732c90006377414181 refs/tags/v0.1.0^{}",
"7443892cb6b7925d98687903ab6d7ee0bdd1e9cf refs/tags/v0.3.0",
"e8df6c1ffb7afa27aff9abbe11c7e4b80d19b61e refs/tags/v0.3.0^{}"
]
);
assert_eq!(
server
.received_as_string()
.lines()
.map(str::to_lowercase)
.filter(ignore_reqwest_content_length)
.collect::<HashSet<_>>(),
format!(
"GET /path/not/important/due/to/mock/info/refs?service=git-upload-pack HTTP/1.1
Host: 127.0.0.1:{}
Accept: */*
User-Agent: git/oxide-{}
",
server.addr.port(),
env!("CARGO_PKG_VERSION")
)
.lines()
.map(str::to_lowercase)
.collect::<HashSet<_>>()
);
Ok(())
}
#[test]
fn clone_v1() -> crate::Result {
let (server, mut c) = mock::serve_and_connect(
"v1/http-handshake.response",
"path/not/important/due/to/mock",
Protocol::V1,
)?;
let SetServiceResponse { refs, .. } =
c.handshake(Service::UploadPack, &[("key", Some("value")), ("value-only", None)])?;
io::copy(&mut refs.expect("refs in protocol V1"), &mut io::sink())?;
assert_eq!(
server
.received_as_string()
.lines()
.map(str::to_lowercase)
.find(|l| l.starts_with("git-protocol"))
.expect("git-protocol header"),
"git-protocol: key=value:value-only",
"it writes extra-parameters without the version"
);
server.next_read_and_respond_with(fixture_bytes("v1/http-clone.response"));
let mut writer = c.request(
client::WriteMode::OneLfTerminatedLinePerWriteCall,
client::MessageKind::Text(b"done"),
false,
)?;
writer.write_all(b"hello")?;
writer.write_all(b"world")?;
let mut reader = writer.into_read()?;
let mut line = String::new();
reader.read_line(&mut line)?;
assert_eq!(line, "NAK\n", "we receive a NAK in text mode before the PACK is sent");
let messages = Rc::new(RefCell::new(Vec::<String>::new()));
reader.set_progress_handler(Some(Box::new({
let sb = messages.clone();
move |is_err, data| {
assert!(!is_err);
sb.deref()
.borrow_mut()
.push(std::str::from_utf8(data).expect("valid utf8").to_owned());
ProgressAction::Continue
}
})));
let mut pack = Vec::new();
reader.read_to_end(&mut pack)?;
assert_eq!(pack.len(), 876, "we receive the whole pack…");
drop(reader);
let sidebands = Rc::try_unwrap(messages).expect("no other handle").into_inner();
assert_eq!(sidebands.len(), 3);
assert_eq!(
server
.received_as_string()
.lines()
.map(str::to_lowercase)
.filter(|l| !l.starts_with("expect: "))
.collect::<HashSet<_>>(),
format!(
"POST /path/not/important/due/to/mock/git-upload-pack HTTP/1.1
Host: 127.0.0.1:{}
User-Agent: git/oxide-{}
Content-Type: application/x-git-upload-pack-request
Content-Length: 29
Accept: application/x-git-upload-pack-result
000ahello
000aworld
0009done",
server.addr.port(),
env!("CARGO_PKG_VERSION")
)
.lines()
.map(str::to_lowercase)
.collect::<HashSet<_>>()
);
Ok(())
}
#[test]
fn handshake_and_lsrefs_and_fetch_v2() -> crate::Result {
handshake_and_lsrefs_and_fetch_v2_impl("v2/http-handshake.response")
}
#[test]
fn handshake_and_lsrefs_and_fetch_v2_googlesource() -> crate::Result {
let (_server, mut c) = mock::serve_and_connect(
"v2/http-no-newlines-handshake.response",
"path/not/important/due/to/mock",
Protocol::V2,
)?;
assert!(
!c.connection_persists_across_multiple_requests(),
"http connections are never stateful"
);
let SetServiceResponse {
actual_protocol,
capabilities,
refs,
} = c.handshake(Service::UploadPack, &[("value-only", None), ("key", Some("value"))])?;
assert_eq!(actual_protocol, Protocol::V2);
assert!(
refs.is_none(),
"refs are only returned in V1, as V2 favors a separate command (with more options)"
);
assert_eq!(
capabilities
.iter()
.map(|v| {
(
v.name().to_owned(),
v.values().map(|v| v.map(ToOwned::to_owned).collect::<Vec<_>>()),
)
})
.collect::<Vec<_>>(),
[
("ls-refs", None),
(
"fetch",
Some(
&[
"filter",
"ref-in-want",
"sideband-all",
"packfile-uris",
"wait-for-done",
"shallow"
][..]
)
),
("server-option", None),
("session-id", None),
]
.iter()
.map(|(k, v)| (
k.as_bytes().into(),
v.map(|v| v.iter().map(|v| v.as_bytes().into()).collect::<Vec<_>>())
))
.collect::<Vec<_>>()
);
Ok(())
}
#[test]
fn handshake_and_lsrefs_and_fetch_v2_service_announced() -> crate::Result {
handshake_and_lsrefs_and_fetch_v2_impl("v2/http-handshake-service-announced.response")
}
fn handshake_and_lsrefs_and_fetch_v2_impl(handshake_fixture: &str) -> crate::Result {
let (server, mut c) = mock::serve_and_connect(handshake_fixture, "path/not/important/due/to/mock", Protocol::V2)?;
assert!(
!c.connection_persists_across_multiple_requests(),
"http connections are never stateful"
);
let SetServiceResponse {
actual_protocol,
capabilities,
refs,
} = c.handshake(Service::UploadPack, &[("value-only", None), ("key", Some("value"))])?;
assert_eq!(actual_protocol, Protocol::V2);
assert!(
refs.is_none(),
"refs are only returned in V1, as V2 favors a separate command (with more options)"
);
assert_eq!(
capabilities
.iter()
.map(|v| {
(
v.name().to_owned(),
v.values().map(|v| v.map(ToOwned::to_owned).collect::<Vec<_>>()),
)
})
.collect::<Vec<_>>(),
[
("agent", Some(&["git/github-gdf51a71f0236"][..])),
("ls-refs", None),
("fetch", Some(&["shallow", "filter"])),
("server-option", None)
]
.iter()
.map(|(k, v)| (
k.as_bytes().into(),
v.map(|v| v.iter().map(|v| v.as_bytes().into()).collect::<Vec<_>>())
))
.collect::<Vec<_>>()
);
assert_eq!(
server
.received_as_string()
.lines()
.map(str::to_lowercase)
.filter(ignore_reqwest_content_length)
.collect::<HashSet<_>>(),
format!(
"GET /path/not/important/due/to/mock/info/refs?service=git-upload-pack HTTP/1.1
Host: 127.0.0.1:{}
Accept: */*
User-Agent: git/oxide-{}
Git-Protocol: version=2:value-only:key=value
",
server.addr.port(),
env!("CARGO_PKG_VERSION")
)
.lines()
.map(str::to_lowercase)
.collect::<HashSet<_>>()
);
server.next_read_and_respond_with(fixture_bytes("v2/http-lsrefs.response"));
drop(refs);
let res = c.invoke(
"ls-refs",
[("without-value", None), ("with-value", Some("value"))].iter().copied(),
Some(vec!["arg1".as_bytes().as_bstr().to_owned()].into_iter()),
false,
)?;
assert_eq!(
res.lines().collect::<Result<Vec<_>, _>>()?,
vec![
"808e50d724f604f69ab93c6da2919c014667bedb HEAD symref-target:refs/heads/master",
"808e50d724f604f69ab93c6da2919c014667bedb refs/heads/master"
]
);
assert_eq!(
server
.received_as_string()
.lines()
.map(str::to_lowercase)
.filter(|l| !l.starts_with("expect: "))
.collect::<HashSet<_>>(),
format!(
"POST /path/not/important/due/to/mock/git-upload-pack HTTP/1.1
Host: 127.0.0.1:{}
User-Agent: git/oxide-{}
Content-Type: application/x-git-upload-pack-request
Content-Length: 76
Accept: application/x-git-upload-pack-result
Git-Protocol: version=2
0014command=ls-refs
0012without-value
0015with-value=value
00010009arg1
0000",
server.addr.port(),
env!("CARGO_PKG_VERSION")
)
.lines()
.map(str::to_lowercase)
.collect::<HashSet<_>>()
);
server.next_read_and_respond_with(fixture_bytes("v2/http-fetch.response"));
let mut res = c.invoke(
"fetch",
Vec::<(_, Option<&str>)>::new().into_iter(),
None::<IntoIter<bstr::BString>>,
false,
)?;
let mut line = String::new();
res.read_line(&mut line)?;
assert_eq!(line, "packfile\n");
let messages = Rc::new(RefCell::new(Vec::<String>::new()));
res.set_progress_handler(Some(Box::new({
let sb = messages.clone();
move |is_err, data| {
assert!(!is_err);
sb.deref()
.borrow_mut()
.push(std::str::from_utf8(data).expect("valid utf8").to_owned());
ProgressAction::Continue
}
})));
let mut pack = Vec::new();
res.read_to_end(&mut pack)?;
assert_eq!(pack.len(), 876);
drop(res);
let messages = Rc::try_unwrap(messages).expect("no other handle").into_inner();
assert_eq!(messages.len(), 5);
let actual = server.received_as_string();
let expected = format!(
"POST /path/not/important/due/to/mock/git-upload-pack HTTP/1.1
Host: 127.0.0.1:{}
User-Agent: git/oxide-{}
Content-Type: application/x-git-upload-pack-request
Accept: application/x-git-upload-pack-result
Git-Protocol: version=2
Content-Length: 22
0012command=fetch
0000",
server.addr.port(),
env!("CARGO_PKG_VERSION")
);
assert_eq!(
actual
.lines()
.filter(|l| !l.starts_with("expect: "))
.map(str::to_lowercase)
.collect::<HashSet<_>>(),
expected.lines().map(str::to_lowercase).collect::<HashSet<_>>()
);
Ok(())
}
#[test]
fn check_content_type_is_case_insensitive() -> crate::Result {
let (_server, mut client) = mock::serve_and_connect(
"v2/http-handshake-lowercase-headers.response",
"path/not/important/due/to/mock",
Protocol::V2,
)?;
let result = client.handshake(Service::UploadPack, &[]);
assert!(result.is_ok());
Ok(())
}
fn ignore_reqwest_content_length(header_line: &String) -> bool {
header_line != "content-length: 0"
}