How to prevent System.Uri from decoding url's - c#

Take this example:
var client = new HttpClient();
await client.GetAsync("http://www.google.com?q=%2D");
this actually sends a request to 'http://www.google.com?q=-'. I don't want .NET to alter my url.
This behavior is from System.Uri, which seems to unescape those character.
How can I prevent Uri/HttpClient from changing my url?
.NET Framework 4.7.2
Update: this behavior seems by design. I still can't believe there is not a way around this. What if I actually want to send '?q=what does %2D mean' to google.com? Now this gets send as 'http://www.google.com/?q=what%20does%20-%20mean'. Which is NOT what I meant to do.

Possible partial solution based on reflection.
I think the problem is that - is listed as a special character, here: https://referencesource.microsoft.com/#System/net/System/UriHelper.cs,657 . I don't think there's a way to modify the http scheme to change that behavior.
There was a previous bug, which has since been fixed, relating to how file paths are parsed by Uri. At the time, the workaround was to change the private flags of the related UriParser using reflection : https://stackoverflow.com/a/2285321/1462295
Here is a quick demo which you'll have to evaluate if it helps or not. It depends on whether uri.ToString() is called (then this might help), or uri.GetComponents (then you'll have to figure something else out). This code reaches into the Uri object and replaces the parsed string with something else. Here's the code and console output:
static void Main(string[] args)
{
var surl = "http://www.google.com?q=%2D";
var url = new Uri(surl);
Console.WriteLine("Broken: " + url.ToString());
// Declared in Uri class as
// private UriInfo m_Info;
// https://referencesource.microsoft.com/#System/net/System/URI.cs,129
FieldInfo infoField = url.GetType().GetField("m_Info", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
// Immediately after m_Info is declared, the private class definition is given:
// private class UriInfo {
// public string String;
// ...
// };
object info = infoField.GetValue(url);
FieldInfo infoStringField = info.GetType().GetField("String");
// If you check the value of m_Info.String, you'll see it has the
// modified string with '?q=-'.
// The idea with this block of code is to replace the parsed
// string with the one that you want.
// This just replaces the string with the original value.
infoStringField.SetValue(info, surl);
// ToString() # https://referencesource.microsoft.com/#System/net/System/URI.cs,1661
// There are a couple of 'if' branches, but the last line is
// return m_Info.String;
// This is the idea behind the above code.
Console.WriteLine("Fixed: " + url.ToString());
// However, GetComponents uses entirely different logic:
Console.WriteLine($"Still broken: {url.GetComponents(UriComponents.AbsoluteUri, UriFormat.Unescaped)}");
Console.WriteLine($"Still broken: {url.GetComponents(UriComponents.AbsoluteUri, UriFormat.SafeUnescaped)}");
Console.WriteLine($"Still broken: {url.GetComponents(UriComponents.AbsoluteUri, UriFormat.UriEscaped)}");
Console.WriteLine("Press ENTER to exit ...");
Console.ReadLine();
}
Console output:
Broken: http://www.google.com/?q=-
Fixed: http://www.google.com?q=%2D
Still broken: http://www.google.com/?q=-
Still broken: http://www.google.com/?q=-
Still broken: http://www.google.com/?q=-
Press ENTER to exit ...
You might find some other inspiration from the code here which does use reflection, but also defines its own scheme to work with. Note the trust issues mentioned.
You mention .Net Framework 4.7.2, which should work with the above code. dotnet core will not.

Related

Calling a Python script from C# - changing the script's filepath causes the program to not work

The following code works perfectly without flaw:
public partial class MainForm : Form
{
string pyInterp = File.ReadAllText(Directory.GetCurrentDirectory() + #"\config\pathToPythonInterpreter.txt");
string pyWeather = #"C:\getWeather.py";
public MainForm()
{
InitializeComponent();
UpdateWeather();
}
public void UpdateWeather()
{
labelWeather.Text = PySharp.ExecutePy(pyInterp, pyWeather);
}
}
However, when I change the path to getWeather.py to not be in an arbitrary random location, like this:
string pyWeather = Directory.GetCurrentDirectory() + #"\scripts\getWeather.py";
Then my program no longer obtains the script's output. The script still works: I launched it using IDLE and it completed its function properly. When I call it using C#, the console opens, yet no output is obtained.
The Python script is the following:
from requests import get
from bs4 import BeautifulSoup as soup
r = get("http://www.ilmateenistus.ee/ilm/prognoosid/4-oopaeva-prognoos/")
parsed = soup(r.content, "html.parser")
container = parsed.find("div",{"class":"point kuusiku"})
print(str(container["data-title"]))
(It webscrapes my local weather)
PySharp.ExecutePy() can be viewed here
By far the strangest bug I've ever encountered. Any ideas?
EDIT 1: It seems that C# is indeed reading something from the script. It just appears that this something is.. nothing. I gave the label a default sample text, and after running the program, the label's text is simply changed to an empty string. Hope this incredible discovery helps somehow.
EDIT 2: The program fails to call the script correctly when its filepath contains spaces. For example:
C:\foo bar\testing\pyWeather.py
does not work!
Try surrounding the path that contains spaces with 2 double quotes.
For e.g.
string pyWeather = #"""C:\Users\[myname]\Documents\Visual Studio 2017\Projects\testing\testing\scripts\getWeather.py""";
Similarly, you can do string pyWeather = Directory.GetCurrentDirectory() + #"\scripts\getWeather.py"; followed by pyWeather = "\"" + pyWeather + "\"";.
I would want you to return the answer instead of printing. Printer is an I/O based solution to display. So it will work super fine with IDLE however it may not return results as you expected. I strongly believe this will solve your problem.
instead of printing please try return. I can give more support after trying this.
return(str(container["data-title"]))

How to get ExpandEnvironmentVariables to return custom variables?

I have added a custom environment variable and I'm unable to get it to return in the ExpandEnvironmentVariables.
These 2 calls work fine:
string s = Environment.GetEnvironmentVariable("TEST", EnvironmentVariableTarget.Machine);
// s = "D:\Temp2"
string path = Environment.ExpandEnvironmentVariables(#"%windir%\Temp1");
// path = "C:\Windows\Temp1"
However, this call returns the same input string:
var path = Environment.ExpandEnvironmentVariables(#"%TEST%\Temp1");
// path = "%TEST%\\Temp1"
I expect to get D:\Temp2\Temp1
What am I missing to correctly get the custom EnvironmentVariable in this last call?
Hans and Evk were correct in their comments. Since no one wanted to add an answer I'll close this question out.
For whatever reason ExpandEnvironmentVariables will not get any keys which were added after an application started. I also tested this with a running Windows Service. It was only after I restarted the service that new keys were found and populated.
This behavior is not documented in the Microsoft Documentation.

API to resolve %% variables

I am working with the C# Event Log API in Windows (essentially everything in System.Diagnostics.Eventing.Reader).
I have an EventMetadata object and pull its Description property to retrieve the message template for an event.
Generally, these templates look similar to the following:
Network interface reports message %1
These variables are easily replaceable with actual data whenever I receive an event.
(EventLogRecord.Properties match up to the placeholders)
Here is where my problem comes in. The EventLogRecord.Properties sometimes contain different kinds of placeholders. These always begin in %% and I cannot find a way of resolving them.
As an example:
// This method is triggered when a new event comes in
async public static void ListenerEvent(object s, EventRecordWrittenEventArgs args) {
var evt = (EventLogRecord)args.EventRecord;
// This method retrieves the template from a ProviderMetadata object
// And replaces all %n with {n}
// So that we can string.Format on it
var tmp = TemplateCache.TemplateFor(evt);
// Need this since the indices start with 1, not 0
var props = new List<object> {string.Empty};
props.AddRange(evt.Properties.Select(prop => prop.Value));
// Now the message should be human-readable
var msg = string.Format(tmp, props);
}
Using the above example template, the Properties might be ["%%16411"] and now I end up with the following message
Network interface reports message %%16411
I figure my question now is, how do I replace this %%16411?
I have looked into ProviderMetadata and the rest of its properties but none seem to match up.
Any help figuring out how to resolve these placeholders (or even what they are/where they come from) is appreciated.
An event that shows this behaviour is 5152, as found here: http://community.spiceworks.com/windows_event/show/452-microsoft-windows-security-auditing-5152
Thank you.

Two things; console app run without .exe and args (C# console app)

Hi I would like to make my self able to maybe define my app into CMD by that I can type program instead of program.exe kind of like how ping works for example.
I also need help with arguments.
The point of my app is to send a get request to a local server evaling PHP fetching a result from it so I can easy quick debug things and calculate things ect from CMD.
So for example I have to do.
W:\Users\example>e.exe
echo "example";
.....
example
W:\Users\example>
Tow things with the above are very annoying. I need to enter e.exe and THAN I need to enter the code to eval :-/
How could I make it so I could just do
W:\Users\example>e echo "example";
.....
example
W:\Users\example
I really would like to get this working to make use faster + more simple ! this is a programmers way to calculating math :P
EDIT:
Below is the code;
static void Main(string[] args)
{
WebClient client = new WebClient();
Console.WriteLine("...");
string input = Console.ReadLine();
string php = client.DownloadString("http://192.168.1.50/test.php?exec="+input);
Console.WriteLine(".....");
Console.WriteLine("");
Console.WriteLine(php);
Console.WriteLine("");
}
I've tried to do "+arg[0] but does nothing at start :?
Don't type ".exe" as you don't need to type it... You may want to put your e.exe some place listed in PATH environment variable (or add path to the executable to the PATH).
Main(string[] args) are arguments passed to your program - use them whatever way you want. Note that they will be split on spaces, so you may need to String.Join them back if you need all arguments as one.
You can also use Environment.CommandLine if you need access to comple not parsed command line.

Visual Studio Custom Language Service

I am attempting to implement a Language Service in a VSPackage using the MPF, and it's not working quite as I understand it should.
I have several implementations already, such as ParseSource parsing the input file with a ParseRequest. However, when it finds an error, it adds it with AuthoringSink.AddError. The documentation for this implies it adds it to the Error List for me; it doesn't.
I also have a simple MySource class, a subclass of Source. I return this new class with an overridden LanguageService.CreateSource method. The documentation for OnCommand says it's fired 'when a command is entered'. However, it's not.
There's obviously some intermediate step which I haven't done correctly. I've already rambled enough, so I'll be glad to give any additional details by request.
Any clarification is much appreciated.
For the AuthoringSink error list question, I use this behavior in my Language Service. In ParseSource, the ParseRequest class has an AuthoringSink. You can also create a new ErrorListProvider if you want to work outside of the parser's behavior. Here is some example code:
error_list = new ErrorListProvider(this.Site);
error_list.ProviderName = "MyLanguageService Errors";
error_list.ProviderGuid = new Guid(this.errorlistGUIDstring.);
}
ErrorTask task = new ErrorTask();
task.Document = filename;
task.CanDelete = true;
task.Category = TaskCategory.CodeSense;
task.Column = column;
task.Line = line;
task.Text = message;
task.ErrorCategory = TaskErrorCategory.Error;
task.Navigate += NavigateToParseError;
error_list.Tasks.Add(task);
I hope this was helpful.
OnCommand should be firing every time there is a command, in your MySource class you can do something like this (pulled from working code):
public override void OnCommand(IVsTextView textView, VsCommands2K command, char ch)
{
if (textView == null || this.LanguageService == null
|| !this.LanguageService.Preferences.EnableCodeSense)
return;
if (command == Microsoft.VisualStudio.VSConstants.VSStd2KCmdID.TYPECHAR)
{
if (char.IsLetterOrDigit(ch))
{
//do something cool
}
}
base.OnCommand(textView, command, ch);
}
If that doesn't work double check that CodeSense = true in your ProvideLanguageService attribute when you setup your LanguageService package. A whole lot of what is cool to do in the LanguageService requires these attributes to be correctly turned on. Some even give cool behaviors for free!
Another thing to be careful of is that some behaviors like colorizer don't function correctly in the hive in my experience. I don't think these were ones that gave me trouble, but I implemented these a couple of years ago so I'm mostly just looking back at old code.
AuthoringSink.AddError only adds errors to the error list if ParseRequest.Reason is ParseReason.Check. When your ParseSource function attempts to add errors while parsing for any other ParseReason, nothing will happen.
It's possible that your language service is never calling ParseSource with this ParseReason. As far as I know, the only way to get a ParseReason of Check (outside of manually calling BeginParse or ParseSource yourself) is to proffer your service with an idle timer.

Categories

Resources