0

I'm experiencing a weird issue with both of these commands:

nuget.exe push -Source <url> -ApiKey <key> <package-file>

dotnet nuget push -s <url> -k <key> <package-file>

The source NuGet server is a private BaGetter service. Both commands fail, because the HTTP PUT request that they issue to the server arrives with the following headers and no body:

Content-Length: 0

Content-Type: multipart/form-data; boundary="<random-guid>"

I've confirmed this by temporarily plugging a dummy handler in place of BaGetter that only logs the headers and body.

What's interesting is that a raw Postman HTTP PUT request works fine and the package arrives as expected. However, the headers then look like this:

Content-Length: 1695506

Content-Type: application/octet-stream

What am I missing with the original nuget push commands?

FWIW: The BaGetter server is behind a Cloudflare tunnel and is not exposed directly to the internet.

UPDATE

I tried the following:

  • disabling HSTS requirements on Cloudflare for the specific domain where my Bagetter service is hosted
  • lowering TLS requirement all the way down to 1.0

Both to no avail.

As suggested by @zivkan, I even tried making my own simple C# Console app with HttpClient. It worked without a problem, as expected. I still have no idea why nuget.exe and dotnet nuget push commands keep failing.

Here's my app's code in its entirety:

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace FauxNuGetClient
{
    public class Program
    {
        public static async Task<int> Main(string[] args)
        {
            if (args == null || args.Length == 0 || !String.Equals(args[0], "push", StringComparison.OrdinalIgnoreCase))
            {
                Console.Error.WriteLine("Invalid args. Only 'push' is supported.");
                
                return -1;
            }

            var pairs = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

            string pendingKey = null;
            string package = null;

            for (var i = 1; i < args.Length; i++)
            {
                if (pendingKey != null)
                {
                    pairs[pendingKey] = args[i];
                    pendingKey = null;

                    continue;
                }

                switch (args[i].ToLowerInvariant())
                {
                    case "-source":
                    case "-apikey":
                    case "-verbosity":
                    {
                        pendingKey = args[i].TrimStart('-');
                        continue;
                    }
                }

                if (i == 1 || i == args.Length - 1)
                {
                    package = args[i];
                    continue;
                }

                Console.Error.WriteLine("Invalid args. The 'push' command only recognizes -Source, -ApiKey, and -Verbosity parameters.");

                return -2;
            }

            if (String.IsNullOrEmpty(package) || !pairs.TryGetValue("source", out var url) || !Uri.TryCreate(url, UriKind.Absolute, out var uri))
            {
                Console.Error.WriteLine("Invalid args. The 'push' command requires -Source parameter that contains a valid absolute URL.");

                return -3;
            }

            if (!File.Exists(package))
            {
                Console.Error.WriteLine("Invalid args. The 'push' command requires an existing local file name.");

                return -4;
            }

            try
            {
                var uploadUrl = uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped).TrimEnd('/') + "/api/v2/package/";
                if (!Uri.TryCreate(uploadUrl, UriKind.Absolute, out uri))
                {
                    Console.Error.WriteLine("Invalid args. Unable to reparse the URI.");

                    return -5;
                }

                using (var client = new HttpClient
                {
                    BaseAddress = uri,
                    Timeout = TimeSpan.FromSeconds(45)
                })
                {
                    Console.WriteLine("PUT " + uri.ToString());
                    
                    using (var fileStream = new FileStream(package, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        var requestContent = new MultipartFormDataContent();

                        var packageContent = new StreamContent(fileStream);
                        packageContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
                        requestContent.Add(packageContent, "package", "package.nupkg");

                        if (pairs.TryGetValue("apikey", out var apiKey) && !String.IsNullOrWhiteSpace(apiKey))
                        {
                            requestContent.Headers.Add("X-NuGet-ApiKey", apiKey.Trim());
                        }

                        using (var response = await client.PutAsync("", requestContent))
                        {
                            if (response.IsSuccessStatusCode)
                            {
                                Console.WriteLine("OK.");

                                return 0;
                            }
                            
                            Console.WriteLine("Failed. Status code: " + response.StatusCode.ToString());

                            return -6;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed. Exception: " + e.Message);

                return -7;
            }
        }
    }
}
4
  • nuget uses .net's HttpClient. Try writing your own simple console app that uses HttpClient to post or put to the server, to see if it's something nuget is doing specifically, or if .net's httpclient is doing it. github.com/NuGet/NuGet.Client/blob/… Commented Jul 31 at 1:01
  • @zivkan: This seemed like a great suggestion and I tried it just now. Unfortunately, it worked as expected and the package was successfully uploaded (same as with the Postman PUT). The "nuget.exe push" and "dotnet nuget push" both still fail, though. I have no idea what's wrong. Commented Jul 31 at 11:51
  • If I can reproduce the issue, I can debug NuGet to see if I can find a cause. I've never used cloudflare before (except as a CDN client), and I can't find if tunnel is a paid product. Does this problem only happen when using cloudflare tunnel? What if you run bagetter on localhost? What's the minimim steps to reproduce this error? Commented Jul 31 at 20:51
  • You could use Fiddler to compare the two requests and find the difference. Commented Aug 1 at 6:44

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.