• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

KSP-CKAN / CKAN / 28607223286

02 Jul 2026 04:54PM UTC coverage: 87.931% (+0.06%) from 87.868%
28607223286

Pull #4686

github

web-flow
Merge a0c8c433d into 876952819
Pull Request #4686: More upgradability optimizations

2022 of 2146 branches covered (94.22%)

Branch coverage included in aggregate %.

35 of 42 new or added lines in 9 files covered. (83.33%)

1 existing line in 1 file now uncovered.

8644 of 9984 relevant lines covered (86.58%)

1.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.37
/Core/Net/RedirectingTimeoutWebClient.cs
1
using System;
2
using System.Net;
3
using System.Collections.Generic;
4
using System.Linq;
5

6
using log4net;
7

8
// This WebClient child class does some complicated stuff, let's keep using it for now
9
#pragma warning disable SYSLIB0014
10

11
namespace CKAN
12
{
13
    /// <summary>
14
    /// A WebClient with some CKAN-sepcific adjustments:
15
    /// - A user agent string (required by GitHub API policy)
16
    /// - Sets the Accept header to a given MIME type (needed to get raw files from GitHub API)
17
    /// - Times out after a specified amount of time in milliseconds, 100 000 milliseconds (=100 seconds) by default (https://stackoverflow.com/a/3052637)
18
    /// - Handles permanent redirects to the same host without clearing the Authorization header (needed to get files from renamed GitHub repositories via API)
19
    /// </summary>
20
    internal sealed class RedirectingTimeoutWebClient : WebClient
21
    {
22
        /// <summary>
23
        /// Initialize our special web client
24
        /// </summary>
25
        /// <param name="userAgent">User agent string to send with the request</param>
26
        /// <param name="timeout">Timeout for the request in milliseconds, defaulting to 100 000 (=100 seconds)</param>
27
        /// <param name="mimeType">A mime type sent with the "Accept" header</param>
28
        public RedirectingTimeoutWebClient(string userAgent,
2✔
29
                                           int    timeout  = 100000,
30
                                           string mimeType = "")
31
        {
32
            this.userAgent = userAgent;
2✔
33
            this.timeout   = timeout;
2✔
34
            this.mimeType  = mimeType;
2✔
35
            Encoding       = System.Text.Encoding.UTF8;
2✔
36
        }
2✔
37

38
        protected override WebRequest GetWebRequest(Uri address)
39
        {
40
            // Set user agent and MIME type for every request. including redirects
41
            Headers.Add("User-Agent", userAgent);
2✔
42
            if (!string.IsNullOrEmpty(mimeType))
2✔
43
            {
NEW
44
                log.DebugFormat("Setting MIME type {0}", mimeType);
×
45
                Headers.Add("Accept", mimeType);
×
46
            }
47
            if (permanentRedirects.TryGetValue(address, out Uri? redirUri))
2✔
48
            {
49
                // Obey a previously received permanent redirect
50
                address = redirUri;
×
51
            }
52
            var request = base.GetWebRequest(address);
2✔
53
            if (request is HttpWebRequest hwr)
2✔
54
            {
55
                // GitHub API tokens cannot be passed via auto-redirect
56
                hwr.AllowAutoRedirect = false;
2✔
57
                hwr.Timeout           = timeout;
2✔
58
            }
59
            return request;
2✔
60
        }
61

62
        protected override WebResponse GetWebResponse(WebRequest request)
63
        {
64
            var response = base.GetWebResponse(request);
2✔
65
            if (response is HttpWebResponse hwr)
2✔
66
            {
67
                int statusCode = (int)hwr.StatusCode;
2✔
68
                var location = hwr.Headers["Location"];
2✔
69
                if (statusCode >= 300 && statusCode <= 399 && location != null)
2✔
70
                {
71
                    log.InfoFormat("Redirecting to {0}", location);
2✔
72
                    hwr.Close();
2✔
73
                    var redirUri = new Uri(request.RequestUri, location);
2✔
74
                    if (Headers.AllKeys.Contains("Authorization")
2✔
75
                        && request.RequestUri.Host != redirUri.Host)
76
                    {
77
                        log.InfoFormat("Host mismatch, purging token for redirect");
×
78
                        Headers.Remove("Authorization");
×
79
                    }
80
                    // Moved or PermanentRedirect
81
                    if (statusCode is 301 or 308)
2✔
82
                    {
83
                        permanentRedirects.Add(request.RequestUri, redirUri);
×
84
                    }
85
                    return GetWebResponse(GetWebRequest(redirUri));
2✔
86
                }
87
            }
88
            return response;
2✔
89
        }
90

91
        private readonly string userAgent;
92
        private readonly int    timeout;
93
        private readonly string mimeType;
94
        private static readonly Dictionary<Uri, Uri> permanentRedirects = new Dictionary<Uri, Uri>();
2✔
95
        private static readonly ILog log = LogManager.GetLogger(typeof(RedirectingTimeoutWebClient));
2✔
96
    }
97
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc