when i do a GET with WebRequest.Create("http://abc/test.") i get 404 because according to fiddler the trailing dot gets stripped away by .NET and the web server needs the dot. how can i prevent that or work around it. any workaround is appreciated!
Workaround in workaround tab at the official bug report:
https://connect.microsoft.com/VisualStudio/feedback/details/386695/system-uri-incorrectly-strips-trailing-dots?wa=wsignin1.0#tabs
.. seems to be valid. Basically, run this code to reset a static flag in .NET before working with System.Uri:
MethodInfo getSyntax = typeof(UriParser).GetMethod("GetSyntax", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
FieldInfo flagsField = typeof(UriParser).GetField("m_Flags", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
if (getSyntax != null && flagsField != null)
{
foreach (string scheme in new[] { "http", "https" })
{
UriParser parser = (UriParser)getSyntax.Invoke(null, new object[] { scheme });
if (parser != null)
{
int flagsValue = (int)flagsField.GetValue(parser);
// Clear the CanonicalizeAsFilePath attribute
if ((flagsValue & 0x1000000) != 0)
flagsField.SetValue(parser, flagsValue & ~0x1000000);
}
}
}
Demonstrated:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
var surl = "http://x/y./z";
var url = new Uri(surl);
Console.WriteLine("Broken: " + url.ToString());
MethodInfo getSyntax = typeof(UriParser).GetMethod("GetSyntax", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
FieldInfo flagsField = typeof(UriParser).GetField("m_Flags", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
if (getSyntax != null && flagsField != null)
{
foreach (string scheme in new[] { "http", "https" })
{
UriParser parser = (UriParser)getSyntax.Invoke(null, new object[] { scheme });
if (parser != null)
{
int flagsValue = (int)flagsField.GetValue(parser);
// Clear the CanonicalizeAsFilePath attribute
if ((flagsValue & 0x1000000) != 0)
flagsField.SetValue(parser, flagsValue & ~0x1000000);
}
}
}
url = new Uri(surl);
Console.WriteLine("Fixed: " + url.ToString());
Console.WriteLine("Press ENTER to exit ...");
Console.ReadLine();
}
}
}
Re-wrote some of it to a function that dont require you to add any namespaces
private Uri MyUri(string url)
{
Uri uri = new Uri(url);
System.Reflection.MethodInfo getSyntax = typeof(UriParser).GetMethod("GetSyntax", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
System.Reflection.FieldInfo flagsField = typeof(UriParser).GetField("m_Flags", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
if (getSyntax != null && flagsField != null)
{
foreach (string scheme in new[] { "http", "https" })
{
UriParser parser = (UriParser)getSyntax.Invoke(null, new object[] { scheme });
if (parser != null)
{
int flagsValue = (int)flagsField.GetValue(parser);
// Clear the CanonicalizeAsFilePath attribute
if ((flagsValue & 0x1000000) != 0)
flagsField.SetValue(parser, flagsValue & ~0x1000000);
}
}
}
uri = new Uri(url);
return uri;
}
This is a known problem that has come up on the Microsoft forums a few times.
The Uri class incorrectly thinks that all URIs act like Windows disk files where a trailing dot (for no file extension) is not relevant.
http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/5206beca-071f-485d-a2bd-657d635239c9/
you change the dot into String to Hex
string.format("{0:x2}",yoururl);
i think it's useful for u, because i used it in twitter API Oauth formatting
Related
I want to export a CookieContainer to JSON using Newtonsoft.Json but unfortunately CookieContainer hasn't an enumerator or stuff, so I can't cycle through it ...
Edit: With my posted solution it would be something like this:
private static void Main(string[] args)
{
CookieContainer cookieContainer = new CookieContainer();
cookieContainer.Add(new Cookie("name1", "value1", "/", ".testdomain1.com"));
cookieContainer.Add(new Cookie("name2", "value1", "/path1/", ".testdomain1.com"));
cookieContainer.Add(new Cookie("name2", "value1", "/path1/path2/", ".testdomain1.com"));
cookieContainer.Add(new Cookie("name1", "value1", "/", ".testdomain2.com"));
cookieContainer.Add(new Cookie("name2", "value1", "/path1/", ".testdomain2.com"));
cookieContainer.Add(new Cookie("name2", "value1", "/path1/path2/", ".testdomain2.com"));
CookieCollection cookies = GetAllCookies(cookieContainer);
Console.WriteLine(JsonConvert.SerializeObject(cookies, Formatting.Indented));
Console.Read();
}
A solution using reflection:
public static CookieCollection GetAllCookies(CookieContainer cookieJar)
{
CookieCollection cookieCollection = new CookieCollection();
Hashtable table = (Hashtable) cookieJar.GetType().InvokeMember("m_domainTable",
BindingFlags.NonPublic |
BindingFlags.GetField |
BindingFlags.Instance,
null,
cookieJar,
new object[] {});
foreach (var tableKey in table.Keys)
{
String str_tableKey = (string) tableKey;
if (str_tableKey[0] == '.')
{
str_tableKey = str_tableKey.Substring(1);
}
SortedList list = (SortedList) table[tableKey].GetType().InvokeMember("m_list",
BindingFlags.NonPublic |
BindingFlags.GetField |
BindingFlags.Instance,
null,
table[tableKey],
new object[] { });
foreach (var listKey in list.Keys)
{
String url = "https://" + str_tableKey + (string) listKey;
cookieCollection.Add(cookieJar.GetCookies(new Uri(url)));
}
}
return cookieCollection;
}
.NET 6 Update
Finally, .NET 6 was released and introduced the CookieContainer.GetAllCookies() method which extracts the CookieCollection - Documentation link.
public System.Net.CookieCollection GetAllCookies();
This method will ensure to get all cookies, no matter what the protocol is:
public static IEnumerable<Cookie> GetAllCookies(this CookieContainer c)
{
Hashtable k = (Hashtable)c.GetType().GetField("m_domainTable", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(c);
foreach (DictionaryEntry element in k)
{
SortedList l = (SortedList)element.Value.GetType().GetField("m_list", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(element.Value);
foreach (var e in l)
{
var cl = (CookieCollection)((DictionaryEntry)e).Value;
foreach (Cookie fc in cl)
{
yield return fc;
}
}
}
}
The first answer did not work for a portable project. So here's option 2, also uses reflection
using System.Linq;
using System.Collections;
using System.Reflection;
using System.Net;
public static CookieCollection GetAllCookies(this CookieContainer container)
{
var allCookies = new CookieCollection();
var domainTableField = container.GetType().GetRuntimeFields().FirstOrDefault(x => x.Name == "m_domainTable");
var domains = (IDictionary)domainTableField.GetValue(container);
foreach (var val in domains.Values)
{
var type = val.GetType().GetRuntimeFields().First(x => x.Name == "m_list");
var values = (IDictionary)type.GetValue(val);
foreach (CookieCollection cookies in values.Values)
{
allCookies.Add(cookies);
}
}
return allCookies;
}
1) I also tried
var domainTableField = container.GetType().GetRuntimeField("m_domainTable");
but it returned null.
2) You can iterate through domains.Keys and use container.GetCookies() for all keys. But I've had problems with that, because GetCookies expects Uri and not all my keys matched Uri pattern.
Use CookieContainer.GetCookies Method
CookieCollection cookies = cookieContainer.GetCookies(new Uri(url));
where url is URL of your site.
In my case, I was not able to use reflection, as suggested in other answers. However, I did know URL of my site to query. I think it is even logical that container does not return all cookies blindly but returns them per URL because cookies always belong to a particular URL and cannot be used outside of context of the domain associated with them.
I am trying to retrieve the TLS Version information. The code I have below makes a successful HTTP GET call using HttpClient. What am I missing? Where do I get the TLS Version information from HttpClient?
I am kind of doing the same thing as was suggested in Which TLS version was negotiated? but that is specific to WebRequest which is not the same as HttpClient.
static async Task MainAsync()
{
Uri baseURI = new Uri("https://jsonplaceholder.typicode.com/posts/1");
string apiPath = "";
using (var client = new HttpClient())
{
client.BaseAddress = baseURI;
HttpResponseMessage response = await client.GetAsync(apiPath);
Console.WriteLine("HTTP status code: " + response.StatusCode.ToString());
GetSSLConnectionInfo(response, client.BaseAddress.ToString(), apiPath);
}
Console.ReadKey();
}
static async Task GetSSLConnectionInfo(HttpResponseMessage response, string baseURI, string apiPath)
{
using (Stream stream = await response.RequestMessage.Content.ReadAsStreamAsync())
{
BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
Stream CompressedStream = null;
if (stream.GetType().BaseType == typeof(GZipStream))
{
CompressedStream = (GZipStream)stream;
}
else if (stream.GetType().BaseType == typeof(DeflateStream))
{
CompressedStream = (DeflateStream)stream;
}
var objbaseStream = CompressedStream?.GetType().GetProperty("BaseStream").GetValue(stream);
if (objbaseStream == null)
{
objbaseStream = stream;
}
var objConnection = objbaseStream.GetType().GetField("m_Connection", bindingFlags).GetValue(objbaseStream);
var objTlsStream = objConnection.GetType().GetProperty("NetworkStream", bindingFlags).GetValue(objConnection);
var objSslState = objTlsStream.GetType().GetField("m_Worker", bindingFlags).GetValue(objTlsStream);
SslProtocols b = (SslProtocols)objSslState.GetType().GetProperty("SslProtocol", bindingFlags).GetValue(objSslState);
Console.WriteLine("SSL Protocol Used for " + baseURI + apiPath + System.Environment.NewLine + "The TLS version used is " + b);
}
}
I am expecting TLS connection Info but I get an exception.
Under the hood HttpClient uses internal TlsStream class (as in your example for WebRequest). We just need to find it in another location. Here is an example:
static void Main(string[] args)
{
using (var client = new HttpClient())
{
using (var response = client.GetAsync("https://example.com/").Result)
{
if (response.Content is StreamContent)
{
var webExceptionWrapperStream = GetPrivateField(response.Content, "content");
var connectStream = GetBasePrivateField(webExceptionWrapperStream, "innerStream");
var connection = GetPrivateProperty(connectStream, "Connection");
var tlsStream = GetPrivateProperty(connection, "NetworkStream");
var state = GetPrivateField(tlsStream, "m_Worker");
var protocol = (SslProtocols)GetPrivateProperty(state, "SslProtocol");
Console.WriteLine(protocol);
}
else
{
// not sure if this is possible
}
}
}
}
private static object GetPrivateProperty(object obj, string property)
{
return obj.GetType().GetProperty(property, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
}
private static object GetPrivateField(object obj, string field)
{
return obj.GetType().GetField(field, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
}
private static object GetBasePrivateField(object obj, string field)
{
return obj.GetType().BaseType.GetField(field, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
}
Just adjusting the old code to make it work on current dotnet 6.0 version.
using System.Net;
using System.Reflection;
using System.Security.Authentication;
public static class Program
{
static void Main(string[] args)
{
using (var client = new WebClient())
{
var stm = client.OpenRead("https://www.google.com");
var connection = GetField(stm, "_connection");
var transportContext = GetProperty(connection, "TransportContext");
var sslStream = GetField(transportContext, "_sslStream");
var protocol = (SslProtocols)GetProperty(sslStream, "SslProtocol");
Console.WriteLine(protocol);
stm.Close();
}
}
private static object GetProperty(object obj, string property)
{
return obj.GetType().GetProperty(property, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).GetValue(obj);
}
private static object GetField(object obj, string field)
{
return obj.GetType().GetField(field, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).GetValue(obj);
}
}
I am using the code snippet provided in below URL for instantiating ResourceResponse for unit test mocking purpose
https://github.com/Azure/azure-cosmosdb-dotnet/issues/342#issuecomment-367827999
But I am getting below error at given line:
var documentServiceResponse = Activator.CreateInstance(documentServiceResponseType, flags, null, arguments, null);
System.MissingMethodException: 'Constructor on type
'Microsoft.Azure.Documents.DocumentServiceResponse' not found.'
Ultimately I want to mock Response properties like RequestCharge.
Please suggest how t achieve that.
Thanks in advance
You can do that by adding Cosmonaut's TestingExtensions
Here is an extension method that convert any object to a ResourceReponse.
public static ResourceResponse<T> ToResourceResponse<T>(this T resource, HttpStatusCode statusCode, IDictionary<string, string> responseHeaders = null) where T : Resource, new()
{
var resourceResponse = new ResourceResponse<T>(resource);
var documentServiceResponseType = Type.GetType("Microsoft.Azure.Documents.DocumentServiceResponse, Microsoft.Azure.DocumentDB.Core, Version=1.9.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var headers = new NameValueCollection { { "x-ms-request-charge", "0" } };
if (responseHeaders != null)
{
foreach (var responseHeader in responseHeaders)
{
headers[responseHeader.Key] = responseHeader.Value;
}
}
var arguments = new object[] { Stream.Null, headers, statusCode, null };
var documentServiceResponse =
documentServiceResponseType.GetTypeInfo().GetConstructors(flags)[0].Invoke(arguments);
var responseField = typeof(ResourceResponse<T>).GetTypeInfo().GetField("response", BindingFlags.NonPublic | BindingFlags.Instance);
responseField?.SetValue(resourceResponse, documentServiceResponse);
return resourceResponse;
}
This will only work for pre-2.0.0 SDK versions.
For post 2.0.0 use this one instead.
public static ResourceResponse<T> ToResourceResponse<T>(this T resource, HttpStatusCode statusCode, IDictionary<string, string> responseHeaders = null) where T : Resource, new()
{
var resourceResponse = new ResourceResponse<T>(resource);
var documentServiceResponseType = Type.GetType("Microsoft.Azure.Documents.DocumentServiceResponse, Microsoft.Azure.DocumentDB.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var headers = new NameValueCollection { { "x-ms-request-charge", "0" } };
if (responseHeaders != null)
{
foreach (var responseHeader in responseHeaders)
{
headers[responseHeader.Key] = responseHeader.Value;
}
}
var headersDictionaryType = Type.GetType("Microsoft.Azure.Documents.Collections.DictionaryNameValueCollection, Microsoft.Azure.DocumentDB.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var headersDictionaryInstance = Activator.CreateInstance(headersDictionaryType, headers);
var arguments = new [] { Stream.Null, headersDictionaryInstance, statusCode, null };
var documentServiceResponse = documentServiceResponseType.GetTypeInfo().GetConstructors(flags)[0].Invoke(arguments);
var responseField = typeof(ResourceResponse<T>).GetTypeInfo().GetField("response", flags);
responseField?.SetValue(resourceResponse, documentServiceResponse);
return resourceResponse;
}
You can read more about CosmosDB C# code unit testing here
I am trying to replicate https://ffmpeg.org/doxygen/3.0/doc_2examples_2metadata_8c_source.html in C# using the wrapper from https://github.com/Ruslan-B/FFmpeg.AutoGen
I can open and read some properties of the file just fine, however tag is always null, even after the call to av_dict_get
My code is as follows
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using FFmpeg.AutoGen;
namespace ffmpeg_test
{
class Program
{
static void Main(string[] args)
{
var currentPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var libPath = Path.Combine(currentPath, "lib");
SetDllDirectory(libPath);
ffmpeg.av_register_all();
ffmpeg.avcodec_register_all();
DoRiskyThings();
}
private static unsafe void DoRiskyThings()
{
var pFormatContext = ffmpeg.avformat_alloc_context();
if (ffmpeg.avformat_open_input(&pFormatContext, "01 - 999,999.opus", null, null) != 0)
throw new ApplicationException(#"Could not open file.");
ffmpeg.avformat_find_stream_info(pFormatContext, null);
AVStream* pStream = null;
pStream = pFormatContext->streams[0];
var codecContext = *pStream->codec;
Console.WriteLine($"codec name: {ffmpeg.avcodec_get_name(codecContext.codec_id)}");
Console.WriteLine($"number of streams: {pFormatContext->nb_streams}");
//attempting to replicate https://ffmpeg.org/doxygen/3.0/doc_2examples_2metadata_8c_source.html
AVDictionaryEntry* tag = null;
tag = ffmpeg.av_dict_get(pFormatContext->metadata, "", null, 2);
while (tag != null)
{
tag = ffmpeg.av_dict_get(pFormatContext->metadata, "", tag, 2);
Console.WriteLine(Encoding.UTF8.GetString(tag->key,100));
//tag->key and //tag->value are byte pointers
}
}
[DllImport("kernel32", SetLastError = true)]
private static extern bool SetDllDirectory(string lpPathName);
}
}
You need to marshal strings. This is works like a charm:
var url = #"http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4";
var pFormatContext = ffmpeg.avformat_alloc_context();
if (ffmpeg.avformat_open_input(&pFormatContext, url, null, null) != 0)
throw new ApplicationException(#"Could not open file.");
if (ffmpeg.avformat_find_stream_info(pFormatContext, null) != 0)
throw new ApplicationException(#"Could not find stream info");
AVDictionaryEntry* tag = null;
while ((tag = ffmpeg.av_dict_get(pFormatContext->metadata, "", tag, ffmpeg.AV_DICT_IGNORE_SUFFIX)) != null)
{
var key = Marshal.PtrToStringAnsi((IntPtr) tag->key);
var value = Marshal.PtrToStringAnsi((IntPtr) tag->value);
Console.WriteLine($"{key} = {value}");
}
does anyone know, how I can implement the TLS-ALPN in .NET?
I've implemented a basic HTTP/2 server, but without TLS encryption.
I searched in google, but I only found resources for C, Java or other languages, but nothing for .NET (C#)
According to HttpTwo project on Github it is not possible currently because of a bug.
Update: It's not supported in .NET. You can vote for it here: https://visualstudio.uservoice.com/forums/121579-visual-studio-2015/suggestions/6264363-add-support-for-alpn-to-system-net-security-sslstr
quote:
The HTTP/2 RFC states that secure connections must use ALPN to
negotiate the protocol. Unfortunately, .NET's SslStream has no ability
to specify application protocols as part of the TLS authentication, so
it can't support ALPN. There's an issue tracking this on
dotnetfix however it seems like this isn't going to happen very
soon (especially on mono and .NET 4.x).
It actually is possible. With some reflection you can inject any extension in client or server hello.
Here's some code to give you an idea:
// Refer IANA on ApplicationProtocols: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
public static void FixALPN(params string[] protocols) {
if (Interlocked.Increment(ref cntFixALPN) > 1)
{
throw new Exception("FixALPN should be called only ONCE, put it in your Main or use a static constructor.");
return;
}
// get the needed (internal) System types
string tpname = typeof(System.Net.HttpListener).AssemblyQualifiedName;
Type tpiface = Type.GetType(tpname.Replace("HttpListener", "SSPIInterface"));
Type tpgsspi = Type.GetType(tpname.Replace("HttpListener", "GlobalSSPI"));
Type tpsdc = Type.GetType(tpname.Replace("HttpListener", "SafeDeleteContext"));
Type tpsecbuf = Type.GetType(tpname.Replace("HttpListener", "SecurityBuffer"));
// create ALPN buffer
ConstructorInfo ci = (from x in tpsecbuf.GetConstructors() where x.GetParameters().Length == 4 select x).First();
var secbufempty = ci.Invoke(new object[] { new byte[0], 0, 0, 0 });
byte[] btsalpn = GetALPNBuffer(protocols);
var secbufalpn = ci.Invoke(new object[] { btsalpn, 0, btsalpn.Length, 18 });
// grab the object to replace...
FieldInfo fi = tpgsspi.GetField("SSPISecureChannel", BindingFlags.NonPublic | BindingFlags.Static);
var secchan = fi.GetValue(null);
// ...and the method(s) we'll use in our intercepted call(s)
MethodInfo miSDC_ISC = tpsdc.GetMethod("InitializeSecurityContext", BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo miSDC_ASC = tpsdc.GetMethod("AcceptSecurityContext", BindingFlags.NonPublic | BindingFlags.Static);
// fake the internal interface
var result = new InterfaceImplementer(tpiface, (mcm) => {
MethodInfo mi = (MethodInfo)mcm.MethodBase;
object[] args = mcm.Args;
object ret = null;
if (mi.Name == "InitializeSecurityContext") // For Client Mode
{
if (args[5] == null) // empty input, new connection
{
dynamic[] secbufs = (dynamic[])Activator.CreateInstance(miSDC_ASC.GetParameters()[6].ParameterType, new object[] { 1 });
secbufs[0] = secbufalpn;
object[] sdcargs = new object[] { 0, args[0], args[1], args[2], args[3], args[4],
null,
secbufs,
args[6], args[7]
};
ret = miSDC_ISC.Invoke(null, sdcargs);
args[0] = sdcargs[1];
args[1] = sdcargs[2];
args[7] = sdcargs[9];
}
else
{
ret = mi.Invoke(secchan, args);
}
}
else if (mi.Name == "AcceptSecurityContext") // For Server Mode
{
dynamic[] secbufs = (dynamic[])Activator.CreateInstance(miSDC_ASC.GetParameters()[6].ParameterType, new object[] { 3 });
secbufs[0] = args[2];
secbufs[1] = secbufempty;
secbufs[2] = secbufalpn;
object[] sdcargs = new object[] { 0, args[0], args[1], args[3], args[4],
null,
secbufs,
args[5], args[6]
};
ret = miSDC_ASC.Invoke(null, sdcargs);
args[0] = sdcargs[1];
args[1] = sdcargs[2];
args[6] = sdcargs[8];
}
else
ret = mi.Invoke(secchan, args);
return new ReturnMessage(ret, args, args.Length, mcm.LogicalCallContext, mcm);
}).GetTransparentProxy();
// and set it, done
fi.SetValue(null, result);
}
.NET Core 2.1.2 includes the necessary changes to SslStream required to support ALPN. It isn't documented yet, but the pull request that adds it is here