/// <summary> /// Creates a standardized web request /// </summary> /// <param name="uri">Request URI</param> /// <param name="token">Acces token</param> /// <param name="responseType">Expected response MIME type</param> /// <returns>WebRequest object</returns> public static WebRequest CreateRequest(Uri uri, AccessToken token = null, string responseType = "*/*") { var request = WebRequest.Create(uri); request.CachePolicy = CachePolicy; request.Proxy = null; if (token != null) { token.AddToRequest(request); } if (request is HttpWebRequest httpRequest) { httpRequest.Accept = responseType; httpRequest.UserAgent = UserAgent; // Do HTTP redirection manually to refuse non-https schemes as per APIv3 requirement. // Note: By turning AllowAutoRedirect to false, request.GetResponse() will no longer throw on 3xx status. httpRequest.AllowAutoRedirect = false; } return(request); }
/// <summary> /// Gets UTF-8 text from the given URI. /// </summary> /// <param name="res">URI and public key for signature verification</param> /// <param name="param">Parameters to be sent as <c>application/x-www-form-urlencoded</c> name-value pairs</param> /// <param name="token">OAuth access token</param> /// <param name="responseType">Expected response MIME type</param> /// <param name="previous">Previous content, when refresh is required</param> /// <param name="ct">The token to monitor for cancellation requests</param> /// <returns>Content</returns> public static Response Get(ResourceRef res, NameValueCollection param = null, AccessToken token = null, string responseType = "application/json", Response previous = null, CancellationToken ct = default) { // Create request. var request = WebRequest.Create(res.Uri); request.CachePolicy = CachePolicy; request.Proxy = null; if (token != null) { token.AddToRequest(request); } if (request is HttpWebRequest httpRequest) { httpRequest.UserAgent = UserAgent; httpRequest.Accept = responseType; if (previous != null && param != null) { httpRequest.IfModifiedSince = previous.Timestamp; if (previous.ETag != null) { httpRequest.Headers.Add("If-None-Match", previous.ETag); } } } if (param != null) { // Send data. UTF8Encoding utf8 = new UTF8Encoding(); var binBody = Encoding.ASCII.GetBytes(string.Join("&", param.Cast <string>().Select(e => string.Format("{0}={1}", HttpUtility.UrlEncode(e, utf8), HttpUtility.UrlEncode(param[e], utf8))))); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.ContentLength = binBody.Length; try { using (var requestStream = request.GetRequestStream()) requestStream.Write(binBody, 0, binBody.Length, ct); } catch (WebException ex) { throw new AggregateException(Resources.Strings.ErrorUploading, ex.Response is HttpWebResponse ? new WebExceptionEx(ex, ct) : ex); } } ct.ThrowIfCancellationRequested(); // Wait for data to start comming in. WebResponse response; try { response = request.GetResponse(); } catch (WebException ex) { // When the content was not modified, return the previous one. if (ex.Response is HttpWebResponse httpResponse) { if (httpResponse.StatusCode == HttpStatusCode.NotModified) { previous.IsFresh = false; return(previous); } throw new WebExceptionEx(ex, ct); } throw new AggregateException(Resources.Strings.ErrorDownloading, ex); } ct.ThrowIfCancellationRequested(); using (response) { // Read the data. var data = new byte[0]; using (var stream = response.GetResponseStream()) { var buffer = new byte[1048576]; for (; ;) { // Read data chunk. var count = stream.Read(buffer, 0, buffer.Length, ct); if (count == 0) { break; } // Append it to the data. var newData = new byte[data.LongLength + count]; Array.Copy(data, newData, data.LongLength); Array.Copy(buffer, 0, newData, data.LongLength, count); data = newData; } } if (res.PublicKeys != null) { // Generate signature URI. var uriBuilderSig = new UriBuilder(res.Uri); uriBuilderSig.Path += ".minisig"; // Create signature request. request = WebRequest.Create(uriBuilderSig.Uri); request.CachePolicy = CachePolicy; request.Proxy = null; if (token != null) { token.AddToRequest(request); } if (request is HttpWebRequest httpRequestSig) { httpRequestSig.UserAgent = UserAgent; httpRequestSig.Accept = "text/plain"; } // Read the Minisign signature. byte[] signature = null; try { using (var responseSig = request.GetResponse()) using (var streamSig = responseSig.GetResponseStream()) { ct.ThrowIfCancellationRequested(); using (var readerSig = new StreamReader(streamSig)) { foreach (var l in readerSig.ReadToEnd(ct).Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries)) { if (l.Trim().StartsWith($"untrusted comment:")) { continue; } signature = Convert.FromBase64String(l); break; } if (signature == null) { throw new SecurityException(string.Format(Resources.Strings.ErrorInvalidSignature, res.Uri)); } } } } catch (WebException ex) { throw new AggregateException(Resources.Strings.ErrorDownloadingSignature, ex.Response is HttpWebResponse ? new WebExceptionEx(ex, ct) : ex); } ct.ThrowIfCancellationRequested(); // Verify Minisign signature. using (var s = new MemoryStream(signature, false)) using (var r = new BinaryReader(s)) { if (r.ReadChar() != 'E') { throw new ArgumentException(Resources.Strings.ErrorUnsupportedMinisignSignature); } byte[] payload; switch (r.ReadChar()) { case 'd': // PureEdDSA payload = data; break; case 'D': // HashedEdDSA payload = new eduEd25519.BLAKE2b(512).ComputeHash(data); break; default: throw new ArgumentException(Resources.Strings.ErrorUnsupportedMinisignSignature); } ulong keyId = r.ReadUInt64(); if (!res.PublicKeys.ContainsKey(keyId)) { throw new SecurityException(Resources.Strings.ErrorUntrustedMinisignPublicKey); } var sig = new byte[64]; if (r.Read(sig, 0, 64) != 64) { throw new ArgumentException(Resources.Strings.ErrorInvalidMinisignSignature); } using (eduEd25519.ED25519 key = new eduEd25519.ED25519(res.PublicKeys[keyId])) if (!key.VerifyDetached(payload, sig)) { throw new SecurityException(string.Format(Resources.Strings.ErrorInvalidSignature, res.Uri)); } } } return (response is HttpWebResponse webResponse ? new Response() { Value = Encoding.UTF8.GetString(data), Timestamp = DateTime.TryParse(webResponse.GetResponseHeader("Last-Modified"), out var timestamp) ? timestamp : default,
public static Response Get(Uri uri, NameValueCollection param = null, AccessToken token = null, string response_type = "application/json", byte[] pub_key = null, CancellationToken ct = default(CancellationToken), Response previous = null) { // Create request. var request = WebRequest.Create(uri); request.CachePolicy = CachePolicy; request.Proxy = null; if (token != null) { token.AddToRequest(request); } if (request is HttpWebRequest request_http) { request_http.UserAgent = UserAgent; request_http.Accept = response_type; if (previous != null && param != null) { request_http.IfModifiedSince = previous.Timestamp; if (previous.ETag != null) { request_http.Headers.Add("If-None-Match", previous.ETag); } } } if (param != null) { // Send data. UTF8Encoding utf8 = new UTF8Encoding(); var body_binary = Encoding.ASCII.GetBytes(String.Join("&", param.Cast <string>().Select(e => String.Format("{0}={1}", HttpUtility.UrlEncode(e, utf8), HttpUtility.UrlEncode(param[e], utf8))))); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.ContentLength = body_binary.Length; try { using (var stream_req = request.GetRequestStream()) { var task = stream_req.WriteAsync(body_binary, 0, body_binary.Length, ct); try { task.Wait(ct); } catch (AggregateException ex) { throw ex.InnerException; } } } catch (WebException ex) { throw new AggregateException(Resources.Strings.ErrorUploading, ex); } } ct.ThrowIfCancellationRequested(); // Wait for data to start comming in. WebResponse response; try { response = request.GetResponse(); } catch (WebException ex) { // When the content was not modified, return the previous one. if (ex.Response is HttpWebResponse response_http) { if (response_http.StatusCode == HttpStatusCode.NotModified) { previous.IsFresh = false; return(previous); } // Create our own version of the exception, which will contain the body of response as text. throw new WebExceptionEx(ex, ct); } else { throw new AggregateException(Resources.Strings.ErrorDownloading, ex); } } ct.ThrowIfCancellationRequested(); using (response) { // Read the data. var data = new byte[0]; using (var stream = response.GetResponseStream()) { var buffer = new byte[1048576]; for (; ;) { // Read data chunk. var task = stream.ReadAsync(buffer, 0, buffer.Length, ct); try { task.Wait(ct); } catch (AggregateException ex) { throw ex.InnerException; } if (task.Result == 0) { break; } // Append it to the data. var data_new = new byte[data.LongLength + task.Result]; Array.Copy(data, data_new, data.LongLength); Array.Copy(buffer, 0, data_new, data.LongLength, task.Result); data = data_new; } } if (pub_key != null) { // Generate signature URI. var builder_sig = new UriBuilder(uri); builder_sig.Path += ".sig"; // Create signature request. request = WebRequest.Create(builder_sig.Uri); request.CachePolicy = CachePolicy; request.Proxy = null; if (token != null) { token.AddToRequest(request); } if (request is HttpWebRequest request_http_sig) { request_http_sig.UserAgent = UserAgent; request_http_sig.Accept = "application/pgp-signature"; } // Read the signature. byte[] signature = null; using (var response_sig = request.GetResponse()) using (var stream_sig = response_sig.GetResponseStream()) { ct.ThrowIfCancellationRequested(); using (var reader_sig = new StreamReader(stream_sig)) { var task = reader_sig.ReadToEndAsync(); try { task.Wait(ct); } catch (AggregateException ex) { throw ex.InnerException; } signature = Convert.FromBase64String(task.Result); } } ct.ThrowIfCancellationRequested(); // Verify signature. using (eduEd25519.ED25519 key = new eduEd25519.ED25519(pub_key)) if (!key.VerifyDetached(data, signature)) { throw new System.Security.SecurityException(String.Format(Resources.Strings.ErrorInvalidSignature, uri)); } } return (response is HttpWebResponse response_web ? new Response() { Value = Encoding.UTF8.GetString(data), Timestamp = DateTime.TryParse(response_web.GetResponseHeader("Last-Modified"), out var _timestamp) ? _timestamp : default(DateTime), ETag = response_web.GetResponseHeader("ETag"), IsFresh = true } :