2019-10-12 21:37:16 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2019-12-01 21:26:11 +00:00
|
|
|
|
using System.IO;
|
2019-10-12 21:37:16 +00:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Net.Http;
|
2019-12-17 23:17:44 +00:00
|
|
|
|
using System.Net.Http.Headers;
|
2020-12-31 06:44:42 +00:00
|
|
|
|
using System.Threading;
|
2019-12-06 05:29:17 +00:00
|
|
|
|
using System.Threading.Tasks;
|
2020-04-06 20:48:54 +00:00
|
|
|
|
using Newtonsoft.Json;
|
2019-10-12 21:37:16 +00:00
|
|
|
|
using Wabbajack.Common;
|
2020-05-21 22:12:51 +00:00
|
|
|
|
using Wabbajack.Common.Exceptions;
|
2020-04-06 20:48:54 +00:00
|
|
|
|
using Wabbajack.Common.Serialization.Json;
|
2019-10-16 03:10:34 +00:00
|
|
|
|
using Wabbajack.Lib.Validation;
|
2019-10-12 21:37:16 +00:00
|
|
|
|
|
2020-05-21 23:24:26 +00:00
|
|
|
|
|
2019-10-16 03:10:34 +00:00
|
|
|
|
namespace Wabbajack.Lib.Downloaders
|
2019-10-12 21:37:16 +00:00
|
|
|
|
{
|
2019-10-16 11:44:45 +00:00
|
|
|
|
public class HTTPDownloader : IDownloader, IUrlDownloader
|
2019-10-12 21:37:16 +00:00
|
|
|
|
{
|
2020-04-09 20:20:34 +00:00
|
|
|
|
public async Task<AbstractDownloadState?> GetDownloaderState(dynamic archiveINI, bool quickMode)
|
2019-10-12 21:37:16 +00:00
|
|
|
|
{
|
2019-11-21 15:51:57 +00:00
|
|
|
|
var url = archiveINI?.General?.directURL;
|
|
|
|
|
return GetDownloaderState(url, archiveINI);
|
2019-10-16 11:44:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-04-09 20:20:34 +00:00
|
|
|
|
public AbstractDownloadState? GetDownloaderState(string uri)
|
2019-10-16 11:44:45 +00:00
|
|
|
|
{
|
|
|
|
|
return GetDownloaderState(uri, null);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-09 20:20:34 +00:00
|
|
|
|
public AbstractDownloadState? GetDownloaderState(string url, dynamic? archiveINI)
|
2019-10-16 11:44:45 +00:00
|
|
|
|
{
|
2019-10-12 21:37:16 +00:00
|
|
|
|
if (url != null)
|
|
|
|
|
{
|
2020-04-09 20:20:34 +00:00
|
|
|
|
var tmp = new State(url);
|
2019-11-21 15:51:57 +00:00
|
|
|
|
if (archiveINI?.General?.directURLHeaders != null)
|
2019-10-12 21:37:16 +00:00
|
|
|
|
{
|
2019-11-21 15:51:57 +00:00
|
|
|
|
tmp.Headers.AddRange(archiveINI?.General.directURLHeaders.Split('|'));
|
2019-10-12 21:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
return tmp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-07 02:45:13 +00:00
|
|
|
|
public async Task Prepare()
|
2019-10-12 22:15:20 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-06 20:48:54 +00:00
|
|
|
|
[JsonName("HttpDownloader")]
|
2020-05-20 03:55:12 +00:00
|
|
|
|
public class State : AbstractDownloadState, IUpgradingState
|
2019-10-12 21:37:16 +00:00
|
|
|
|
{
|
2020-04-09 20:20:34 +00:00
|
|
|
|
public string Url { get; }
|
2019-10-12 21:37:16 +00:00
|
|
|
|
|
2020-04-09 20:20:34 +00:00
|
|
|
|
public List<string> Headers { get; } = new List<string>();
|
2019-10-12 21:37:16 +00:00
|
|
|
|
|
2020-04-06 20:48:54 +00:00
|
|
|
|
[JsonIgnore]
|
2020-06-26 17:08:30 +00:00
|
|
|
|
public Wabbajack.Lib.Http.Client? Client { get; set; }
|
2019-10-21 19:05:03 +00:00
|
|
|
|
|
2020-04-06 20:48:54 +00:00
|
|
|
|
[JsonIgnore]
|
2020-04-09 20:20:34 +00:00
|
|
|
|
public override object[] PrimaryKey => new object[] { Url };
|
|
|
|
|
|
|
|
|
|
public State(string url)
|
|
|
|
|
{
|
|
|
|
|
Url = url;
|
|
|
|
|
}
|
2020-01-01 16:19:06 +00:00
|
|
|
|
|
2019-10-12 21:37:16 +00:00
|
|
|
|
public override bool IsWhitelisted(ServerWhitelist whitelist)
|
|
|
|
|
{
|
2020-02-02 12:15:29 +00:00
|
|
|
|
return whitelist.AllowedPrefixes.Any(p => Url.StartsWith(p));
|
2019-10-12 21:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-03-25 22:30:43 +00:00
|
|
|
|
public override Task<bool> Download(Archive a, AbsolutePath destination)
|
2019-10-12 21:37:16 +00:00
|
|
|
|
{
|
2019-12-06 05:29:17 +00:00
|
|
|
|
return DoDownload(a, destination, true);
|
2019-10-12 21:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-31 06:44:42 +00:00
|
|
|
|
public async Task<bool> DoDownload(Archive a, AbsolutePath destination, bool download, CancellationToken? token = null)
|
2019-10-12 21:37:16 +00:00
|
|
|
|
{
|
2019-12-23 04:24:40 +00:00
|
|
|
|
if (download)
|
|
|
|
|
{
|
2020-03-25 22:30:43 +00:00
|
|
|
|
destination.Parent.CreateDirectory();
|
2019-12-23 04:24:40 +00:00
|
|
|
|
}
|
2019-12-18 10:45:48 +00:00
|
|
|
|
|
2020-05-25 17:34:25 +00:00
|
|
|
|
using (var fs = download ? await destination.Create() : null)
|
2019-12-17 23:17:44 +00:00
|
|
|
|
{
|
2020-06-16 22:21:01 +00:00
|
|
|
|
var client = Client ?? await ClientAPI.GetClient();
|
2020-02-14 22:23:27 +00:00
|
|
|
|
client.Headers.Add(("User-Agent", Consts.UserAgent));
|
2019-12-17 23:17:44 +00:00
|
|
|
|
|
2020-04-09 20:20:34 +00:00
|
|
|
|
foreach (var header in Headers)
|
|
|
|
|
{
|
|
|
|
|
var idx = header.IndexOf(':');
|
|
|
|
|
var k = header.Substring(0, idx);
|
|
|
|
|
var v = header.Substring(idx + 1);
|
|
|
|
|
client.Headers.Add((k, v));
|
|
|
|
|
}
|
2019-12-17 23:17:44 +00:00
|
|
|
|
|
|
|
|
|
long totalRead = 0;
|
2020-12-29 23:15:47 +00:00
|
|
|
|
var bufferSize = 1024 * 32 * 8;
|
2019-12-17 23:17:44 +00:00
|
|
|
|
|
2020-04-09 20:20:34 +00:00
|
|
|
|
Utils.Status($"Starting Download {a.Name ?? Url}", Percent.Zero);
|
2020-12-31 06:44:42 +00:00
|
|
|
|
var response = await client.GetAsync(Url, errorsAsExceptions:false, retry:false, token:token);
|
2020-01-18 20:52:09 +00:00
|
|
|
|
TOP:
|
2019-12-17 23:17:44 +00:00
|
|
|
|
|
2020-08-13 04:14:35 +00:00
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
|
|
|
{
|
2020-05-22 04:21:32 +00:00
|
|
|
|
return false;
|
2020-08-13 04:14:35 +00:00
|
|
|
|
}
|
2020-01-18 20:52:09 +00:00
|
|
|
|
|
2019-12-17 23:17:44 +00:00
|
|
|
|
Stream stream;
|
|
|
|
|
try
|
2019-10-12 21:37:16 +00:00
|
|
|
|
{
|
2019-12-17 23:17:44 +00:00
|
|
|
|
stream = await response.Content.ReadAsStreamAsync();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2020-02-02 12:15:29 +00:00
|
|
|
|
Utils.Error(ex, $"While downloading {Url}");
|
2019-12-17 23:17:44 +00:00
|
|
|
|
return false;
|
2019-10-12 21:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2019-12-17 23:17:44 +00:00
|
|
|
|
var headerVar = a.Size == 0 ? "1" : a.Size.ToString();
|
2020-01-13 22:55:55 +00:00
|
|
|
|
long header_content_size = 0;
|
2019-12-17 23:17:44 +00:00
|
|
|
|
if (response.Content.Headers.Contains("Content-Length"))
|
2020-01-13 22:55:55 +00:00
|
|
|
|
{
|
2019-12-17 23:17:44 +00:00
|
|
|
|
headerVar = response.Content.Headers.GetValues("Content-Length").FirstOrDefault();
|
2020-01-13 22:55:55 +00:00
|
|
|
|
if (headerVar != null)
|
|
|
|
|
long.TryParse(headerVar, out header_content_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!download)
|
|
|
|
|
{
|
|
|
|
|
if (a.Size != 0 && header_content_size != 0)
|
|
|
|
|
return a.Size == header_content_size;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 23:17:44 +00:00
|
|
|
|
var supportsResume = response.Headers.AcceptRanges.FirstOrDefault(f => f == "bytes") != null;
|
2019-10-12 21:37:16 +00:00
|
|
|
|
|
2019-12-17 23:17:44 +00:00
|
|
|
|
var contentSize = headerVar != null ? long.Parse(headerVar) : 1;
|
2019-10-12 21:37:16 +00:00
|
|
|
|
|
2019-12-17 23:17:44 +00:00
|
|
|
|
using (var webs = stream)
|
|
|
|
|
{
|
|
|
|
|
var buffer = new byte[bufferSize];
|
2020-02-29 00:00:23 +00:00
|
|
|
|
int readThisCycle = 0;
|
2019-12-17 23:17:44 +00:00
|
|
|
|
|
2020-12-31 06:44:42 +00:00
|
|
|
|
while (!(token ?? CancellationToken.None).IsCancellationRequested)
|
2019-12-17 23:17:44 +00:00
|
|
|
|
{
|
|
|
|
|
int read = 0;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
read = await webs.ReadAsync(buffer, 0, bufferSize);
|
|
|
|
|
}
|
2021-01-29 04:02:26 +00:00
|
|
|
|
catch (Exception)
|
2019-12-17 23:17:44 +00:00
|
|
|
|
{
|
2020-02-29 00:00:23 +00:00
|
|
|
|
if (readThisCycle == 0)
|
2021-01-09 21:46:46 +00:00
|
|
|
|
throw;
|
2019-12-17 23:17:44 +00:00
|
|
|
|
|
|
|
|
|
if (totalRead < contentSize)
|
|
|
|
|
{
|
|
|
|
|
if (supportsResume)
|
|
|
|
|
{
|
|
|
|
|
Utils.Log(
|
2020-02-02 12:15:29 +00:00
|
|
|
|
$"Abort during download, trying to resume {Url} from {totalRead.ToFileSizeString()}");
|
2019-12-17 23:17:44 +00:00
|
|
|
|
|
2020-02-02 12:15:29 +00:00
|
|
|
|
var msg = new HttpRequestMessage(HttpMethod.Get, Url);
|
2019-12-17 23:17:44 +00:00
|
|
|
|
msg.Headers.Range = new RangeHeaderValue(totalRead, null);
|
2020-02-26 05:05:33 +00:00
|
|
|
|
response.Dispose();
|
|
|
|
|
response = await client.SendAsync(msg);
|
2019-12-17 23:17:44 +00:00
|
|
|
|
goto TOP;
|
|
|
|
|
}
|
2021-01-09 21:46:46 +00:00
|
|
|
|
throw;
|
2019-12-17 23:17:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-17 23:43:12 +00:00
|
|
|
|
break;
|
2019-12-17 23:17:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-29 00:00:23 +00:00
|
|
|
|
readThisCycle += read;
|
2019-12-17 23:17:44 +00:00
|
|
|
|
|
|
|
|
|
if (read == 0) break;
|
2020-02-08 04:35:08 +00:00
|
|
|
|
Utils.Status($"Downloading {a.Name}", Percent.FactoryPutInRange(totalRead, contentSize));
|
2019-12-17 23:17:44 +00:00
|
|
|
|
|
2020-04-09 20:20:34 +00:00
|
|
|
|
fs!.Write(buffer, 0, read);
|
2019-12-17 23:17:44 +00:00
|
|
|
|
totalRead += read;
|
|
|
|
|
}
|
2019-10-12 21:37:16 +00:00
|
|
|
|
}
|
2020-02-26 05:05:33 +00:00
|
|
|
|
response.Dispose();
|
2019-12-17 23:17:44 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2019-10-12 21:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-31 06:44:42 +00:00
|
|
|
|
public override async Task<bool> Verify(Archive a, CancellationToken? token)
|
2019-10-12 21:37:16 +00:00
|
|
|
|
{
|
2020-12-31 06:44:42 +00:00
|
|
|
|
return await DoDownload(a, ((RelativePath)"").RelativeToEntryPoint(), false, token: token);
|
2019-10-12 21:37:16 +00:00
|
|
|
|
}
|
2019-10-12 22:15:20 +00:00
|
|
|
|
|
|
|
|
|
public override IDownloader GetDownloader()
|
|
|
|
|
{
|
|
|
|
|
return DownloadDispatcher.GetInstance<HTTPDownloader>();
|
|
|
|
|
}
|
2019-10-12 22:54:25 +00:00
|
|
|
|
|
2020-02-02 12:23:07 +00:00
|
|
|
|
public override string GetManifestURL(Archive a)
|
2020-02-02 12:15:29 +00:00
|
|
|
|
{
|
2020-02-02 12:23:07 +00:00
|
|
|
|
return Url;
|
2020-02-02 12:15:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-01-11 04:15:53 +00:00
|
|
|
|
public override string[] GetMetaIni()
|
|
|
|
|
{
|
2020-04-09 20:20:34 +00:00
|
|
|
|
if (Headers.Count > 0)
|
2020-01-11 04:15:53 +00:00
|
|
|
|
return new [] {"[General]",
|
2020-02-02 12:15:29 +00:00
|
|
|
|
$"directURL={Url}",
|
2020-01-11 04:15:53 +00:00
|
|
|
|
$"directURLHeaders={string.Join("|", Headers)}"};
|
|
|
|
|
else
|
2020-02-02 12:15:29 +00:00
|
|
|
|
return new [] {"[General]", $"directURL={Url}"};
|
2020-01-11 04:15:53 +00:00
|
|
|
|
|
|
|
|
|
}
|
2020-05-20 03:55:12 +00:00
|
|
|
|
|
2020-08-13 02:50:20 +00:00
|
|
|
|
public override async Task<(Archive? Archive, TempFile NewFile)> FindUpgrade(Archive a, Func<Archive, Task<AbsolutePath>> downloadResolver)
|
2020-05-20 03:55:12 +00:00
|
|
|
|
{
|
|
|
|
|
var tmpFile = new TempFile();
|
|
|
|
|
|
|
|
|
|
var newArchive = new Archive(this) {Name = a.Name};
|
2020-05-20 12:18:47 +00:00
|
|
|
|
|
2020-08-13 02:50:20 +00:00
|
|
|
|
Utils.Log($"Downloading via HTTP to find Upgrade for {Url}");
|
|
|
|
|
|
2020-05-20 12:18:47 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (!await Download(newArchive, tmpFile.Path))
|
|
|
|
|
return default;
|
|
|
|
|
}
|
2020-08-13 02:50:20 +00:00
|
|
|
|
catch (HttpException ex)
|
2020-05-20 12:18:47 +00:00
|
|
|
|
{
|
2020-08-13 02:50:20 +00:00
|
|
|
|
Utils.Log($"Error finding upgrade via HTTP to find Upgrade for {Url} {ex}");
|
2020-05-20 03:55:12 +00:00
|
|
|
|
return default;
|
2020-05-20 12:18:47 +00:00
|
|
|
|
}
|
2020-05-20 03:55:12 +00:00
|
|
|
|
|
2021-01-09 19:04:11 +00:00
|
|
|
|
var hash = await tmpFile.Path.FileHashAsync();
|
|
|
|
|
if (hash == null) return default;
|
|
|
|
|
newArchive.Hash = hash.Value;
|
2020-05-20 03:55:12 +00:00
|
|
|
|
newArchive.Size = tmpFile.Path.Size;
|
|
|
|
|
|
2020-05-23 21:03:25 +00:00
|
|
|
|
if (newArchive.Hash == a.Hash || a.Size > 2_500_000_000 || newArchive.Size > 2_500_000_000)
|
|
|
|
|
{
|
|
|
|
|
return default;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-20 03:55:12 +00:00
|
|
|
|
return (newArchive, tmpFile);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-13 22:10:05 +00:00
|
|
|
|
public override async Task<bool> ValidateUpgrade(Hash srcHash, AbstractDownloadState newArchiveState)
|
2020-05-20 03:55:12 +00:00
|
|
|
|
{
|
|
|
|
|
var httpState = (State)newArchiveState;
|
2020-05-25 13:04:31 +00:00
|
|
|
|
|
|
|
|
|
if (new Uri(httpState.Url).Host.EndsWith(".mediafire.com"))
|
|
|
|
|
return false;
|
|
|
|
|
|
2020-05-20 03:55:12 +00:00
|
|
|
|
return httpState.Url == Url;
|
|
|
|
|
}
|
2019-10-12 21:37:16 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|