55#include " nix/util/url.hh"
66#include " nix/util/users.hh"
77#include " nix/util/hash.hh"
8+ #include " nix/store/ssh.hh"
89
910#include < git2/attr.h>
1011#include < git2/config.h>
1516
1617namespace nix ::lfs {
1718
18- // if authHeader is "", downloadToSink assumes no auth is expected
1919static void downloadToSink (
2020 const std::string & url,
21- const std::string & authHeader,
21+ const std::optional<std:: string> & authHeader,
2222 // FIXME: passing a StringSink is superfluous, we may as well
2323 // return a string. Or use an abstract Sink for streaming.
2424 StringSink & sink,
@@ -27,8 +27,8 @@ static void downloadToSink(
2727{
2828 FileTransferRequest request (url);
2929 Headers headers;
30- if (! authHeader.empty ())
31- headers.push_back ({" Authorization" , authHeader});
30+ if (authHeader.has_value ())
31+ headers.push_back ({" Authorization" , * authHeader});
3232 request.headers = headers;
3333 getFileTransfer ()->download (std::move (request), sink);
3434
@@ -42,30 +42,53 @@ static void downloadToSink(
4242 " hash mismatch while fetching %s: expected sha256:%s but got sha256:%s" , url, sha256Expected, sha256Actual);
4343}
4444
45- static std::string getLfsApiToken (const ParsedURL & url)
45+ namespace {
46+
47+ struct LfsApiInfo
48+ {
49+ std::string endpoint;
50+ std::optional<std::string> authHeader;
51+ };
52+
53+ } // namespace
54+
55+ static LfsApiInfo getLfsApi (const ParsedURL & url)
4656{
4757 assert (url.authority .has_value ());
58+ if (url.scheme == " ssh" ) {
59+ auto args = getNixSshOpts ();
4860
49- // FIXME: Not entirely correct.
50- auto [status, output] = runProgram (
51- RunOptions{
52- .program = " ssh" ,
53- .args = {url.authority ->to_string (), " git-lfs-authenticate" , url.path , " download" },
54- });
61+ if (url.authority ->port )
62+ args.push_back (fmt (" -p%d" , *url.authority ->port ));
5563
56- if (output.empty ())
57- throw Error (
58- " git-lfs-authenticate: no output (cmd: ssh %s git-lfs-authenticate %s download)" ,
59- url.authority .value_or (ParsedURL::Authority{}).to_string (),
60- url.path );
64+ std::ostringstream hostnameAndUser;
65+ if (url.authority ->user )
66+ hostnameAndUser << *url.authority ->user << " @" ;
67+ hostnameAndUser << url.authority ->host ;
68+ args.push_back (std::move (hostnameAndUser).str ());
69+
70+ args.push_back (" --" );
71+ args.push_back (" git-lfs-authenticate" );
72+ args.push_back (url.path );
73+ args.push_back (" download" );
6174
62- auto queryResp = nlohmann::json::parse (output);
63- if (!queryResp.contains (" header" ))
64- throw Error (" no header in git-lfs-authenticate response" );
65- if (!queryResp[" header" ].contains (" Authorization" ))
66- throw Error (" no Authorization in git-lfs-authenticate response" );
75+ auto [status, output] = runProgram ({.program = " ssh" , .args = args});
6776
68- return queryResp[" header" ][" Authorization" ].get <std::string>();
77+ if (output.empty ())
78+ throw Error (" git-lfs-authenticate: no output (cmd: 'ssh %s')" , concatStringsSep (" " , args));
79+
80+ auto queryResp = nlohmann::json::parse (output);
81+ auto headerIt = queryResp.find (" header" );
82+ if (headerIt == queryResp.end ())
83+ throw Error (" no header in git-lfs-authenticate response" );
84+ auto authIt = headerIt->find (" Authorization" );
85+ if (authIt == headerIt->end ())
86+ throw Error (" no Authorization in git-lfs-authenticate response" );
87+
88+ return {queryResp.at (" href" ).get <std::string>(), authIt->get <std::string>()};
89+ }
90+
91+ return {url.to_string () + " /info/lfs" , std::nullopt };
6992}
7093
7194typedef std::unique_ptr<git_config, Deleter<git_config_free>> GitConfig;
@@ -181,13 +204,14 @@ static nlohmann::json pointerToPayload(const std::vector<Pointer> & items)
181204
182205std::vector<nlohmann::json> Fetch::fetchUrls (const std::vector<Pointer> & pointers) const
183206{
184- ParsedURL httpUrl (url);
185- httpUrl.scheme = url.scheme == " ssh" ? " https" : url.scheme ;
186- FileTransferRequest request (httpUrl.to_string () + " /info/lfs/objects/batch" );
207+ auto api = lfs::getLfsApi (this ->url );
208+ auto url = api.endpoint + " /objects/batch" ;
209+ const auto & authHeader = api.authHeader ;
210+ FileTransferRequest request (url);
187211 request.post = true ;
188212 Headers headers;
189- if (this -> url . scheme == " ssh " )
190- headers.push_back ({" Authorization" , lfs::getLfsApiToken ( this -> url ) });
213+ if (authHeader. has_value () )
214+ headers.push_back ({" Authorization" , *authHeader });
191215 headers.push_back ({" Content-Type" , " application/vnd.git-lfs+json" });
192216 headers.push_back ({" Accept" , " application/vnd.git-lfs+json" });
193217 request.headers = headers;
@@ -260,11 +284,16 @@ void Fetch::fetch(
260284 try {
261285 std::string sha256 = obj.at (" oid" ); // oid is also the sha256
262286 std::string ourl = obj.at (" actions" ).at (" download" ).at (" href" );
263- std::string authHeader = " " ;
264- if (obj.at (" actions" ).at (" download" ).contains (" header" )
265- && obj.at (" actions" ).at (" download" ).at (" header" ).contains (" Authorization" )) {
266- authHeader = obj[" actions" ][" download" ][" header" ][" Authorization" ];
267- }
287+ auto authHeader = [&]() -> std::optional<std::string> {
288+ const auto & download = obj.at (" actions" ).at (" download" );
289+ auto headerIt = download.find (" header" );
290+ if (headerIt == download.end ())
291+ return std::nullopt ;
292+ auto authIt = headerIt->find (" Authorization" );
293+ if (authIt == headerIt->end ())
294+ return std::nullopt ;
295+ return *authIt;
296+ }();
268297 const uint64_t size = obj.at (" size" );
269298 sizeCallback (size);
270299 downloadToSink (ourl, authHeader, sink, sha256, size);
0 commit comments