Skip to content

Conversation

@CZEMacLeod
Copy link
Contributor

Add System.Web.VirtualPathUtility implementation for AspNet Core
Add an implementation of System.Web.VirtualPathUtility based on the codebase for net framework

Adds System.Web.VirtualPathUtility based (in part) on
System.Web.Util.UrlPath code at https://github.com/microsoft/referencesource/blob/master/System.Web/Util/UrlPath.cs
System.Web.Util.StringUtil at https://github.com/microsoft/referencesource/blob/master/System.Web/Util/StringUtil.cs

Addresses #83

Copy link
Member

@twsouthwick twsouthwick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really good! It's reassuring seeing those tests that behavior is aligned :)

Copy link
Member

@twsouthwick twsouthwick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: some whitepace issues

@CZEMacLeod CZEMacLeod force-pushed the czemacleod/virtualpathutility branch from 588fe95 to f03fed1 Compare August 29, 2022 19:27
@CZEMacLeod
Copy link
Contributor Author

nit: some whitepace issues

I think all of these are covered in the latest update.

@twsouthwick
Copy link
Member

@CZEMacLeod there may be a delay in merging this per the comment here #83 (comment). @adityamandaleeka can provide an update here

@twsouthwick
Copy link
Member

While we're waiting - do you think that #185 would help with some of the testing of non-default virtual directories?

@CZEMacLeod
Copy link
Contributor Author

CZEMacLeod commented Aug 31, 2022

@twsouthwick I'm not sure it does anything here. All the tests pass a fixed app virtual path rather than using the overload that defaults to the HttpRuntime value.
The difficulty is actually running these tests on full framework, rather than running them here.
Certainly, for MapPath #184, it would be useful to be able to mock AppDomainAppPath with a known value, as this would, by default, vary depending on the project path.
I didn't add any direct tests on that, as it wasn't really possible to mock httpruntime and the request, but I guess with #185 it would be possible to do this without actually running it on a project (as I do in samples/ClassLibrary/RequestInfo.cs)
I might need some help putting that together though as I'm not entirely familiar with the mocking classes/process you have.

@twsouthwick
Copy link
Member

The difficulty is actually running these tests on full framework, rather than running them here.

I'd say that's a relatively low priority. The policy I've followed is to verify certain behavior happens on framework, and then create tests to mimic that as closely as possible using what we need to mock it up locally

I might need some help putting that together though as I'm not entirely familiar with the mocking classes/process you have.

We've been using Moq for mocking and Autofixture for generating test data (if needed).

I didn't add any direct tests on that, as it wasn't really possible to mock httpruntime and the request

Yup, that's the point of #185 to add the ability to do that. The VirtualPathUtility can follow a similar pattern and be an instance class internally that takes in an IHttpRuntime and is set by the IApplicationBuilder similar to the runtime.

@twsouthwick
Copy link
Member

@CZEMacLeod Can you please include the following file in your PR? https://github.com/dotnet/systemweb-adapters/blob/tasou/license/THIRD-PARTY-NOTICES.txt

Once that's in we can move forward with the PR. Thanks for your patience.

@CZEMacLeod
Copy link
Contributor Author

I don't understand why these tests failed this time round - they worked previously, and the only change was adding the text file.

@twsouthwick
Copy link
Member

Try merging locally and see if it changes things. I believe the builds merge with main and build/test that

Copy link
Member

@twsouthwick twsouthwick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CZEMacLeod In comparing with the framework sources, looks like you pulled in the implementation needed. Would it be much work to keep the implementation using UrlPath, VirtualPath, StringUtil and the other helper classes as is? Additional infrastructure we may want to bring in will be using those as well and it would be nice to keep the similar shape so as we bring in more stuff we don't start duplicating stuff.

@CZEMacLeod
Copy link
Contributor Author

Try merging locally and see if it changes things. I believe the builds merge with main and build/test that

Ah - because #185 has been merged, the tests now fail because HttpRuntime is not set. I'll have to try and rebase after #185 and update the tests to initialize HttpRuntime.

@CZEMacLeod
Copy link
Contributor Author

@CZEMacLeod In comparing with the framework sources, looks like you pulled in the implementation needed. Would it be much work to keep the implementation using UrlPath, VirtualPath, StringUtil and the other helper classes as is? Additional infrastructure we may want to bring in will be using those as well and it would be nice to keep the similar shape so as we bring in more stuff we don't start duplicating stuff.

I only brought in a small part of UrlPath and StringUtil, and didn't bring in VirtualPath at all - I didn't see any need for it and it seems to just result in a lot of allocations and mem copying which aren't needed.
There have also been a number of updates/tweaks to the code afterwards.
Those classes also use resource items for error messages etc. which have all been removed.
Bringing in the whole of those classes would probably be an exercise on its own. If you do decide to bring them in, in the future, you could check if any of the functions in VirtualPathUtility match and swap out to use the shared code.
For now, I would prefer to keep this PR limited to the original scope of implementing just that class.

@twsouthwick
Copy link
Member

I only brought in a small part of UrlPath and StringUtil, and didn't bring in VirtualPath at all - I didn't see any need for it and it seems to just result in a lot of allocations and mem copying which aren't needed.

Yeah, the challenge is that a lot of stuff internally ends up relying on these methods/classes. It will simplify future work to bring stuff in in the same shape and then we can deal with any perf impacts en masse (i.e. VirtualPath could be converted to use spans/be a struct/etc). I'd really prefer that this happens as part of this PR, otherwise, I'll probably end up needing to redo it to support that scenario. No need to bring in the whole classes - just the parts you used.

When you say "didn't bring in VirtualPath" at all, you mean you inlined it, right? All the implementation at https://referencesource.microsoft.com/#System.Web/VirtualPathUtility.cs,2b829f9059f552ca shows things being delegated to VirtualPath/StringUtil/UrlPath for the work. Those methods that are required are what I'd like to see brought in with this PR (nothing beyond what is needed - that easily can balloon beyond what the original scope here is).

Merge weirdness

Looks like your merge may have done something weird. It's showing files that are in the project already as being additions. Maybe try rebasing and force pushing?

@CZEMacLeod
Copy link
Contributor Author

@twsouthwick The implementation of VirtualPathUtility is partly my own, based on the utility methods. It doesn't use VirtualPath at all, nor did I inline any of that. I did move some of the code up from the UrlPath and StringUtil classes up to the main methods when nothing else was needed, but skipped all the VirtualPath stuff entirely. It is mainly using some of the utility methods combined with implementing what the docs say.
I could certainly move some of the support methods back into those classes, although - e.g. for the MapPath implementation in #184 it just uses the functions of VirtualPathUtility directly rather than hitting the underlying stuff.

As for the merge stuff - yeah. I had to pull in the changes from #185 due to the changes to (I)HttpRuntime, then it thought there were merge conflicts for a file I didn't touch, so I brought in the other changes from #186 so it would merge correctly. I think it is fine now, but I can try rebasing it again if needed.

@twsouthwick
Copy link
Member

twsouthwick commented Sep 6, 2022

Ok - I think I understand now. For now, then, can you put the methods you pulled from UrlPath and StringUtil back into static class with those names and we can skip the VirtualPath stuff for now?

Merge stuff

I'm still seeing a bunch of erroneous changes. It may be GitHub showing the diff weird, but can you try rebasing?

@CZEMacLeod
Copy link
Contributor Author

I'm looking at breaking out the UrlPath stuff and found that some of the methods rely directly or indirectly on HttpRuntime - we had previously discussed potentially making VirtualPathUtility an instance class like HttpRuntime and injecting IHttpRuntime. This is definitely going to make that process more complicated.
There is only a single method in StringUtil that is used - StringStartsWithIgnoreCase.

@CZEMacLeod CZEMacLeod force-pushed the czemacleod/virtualpathutility branch from 78f4a21 to bd6cf61 Compare September 7, 2022 11:22
@CZEMacLeod
Copy link
Contributor Author

@twsouthwick I've broken out the methods into the util classes, and rebased. Hopefully this will a) merge correctly without whatever weirdness you were seeing and b) build, and test correctly (it does locally).

Copy link
Member

@twsouthwick twsouthwick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks @CZEMacLeod for the contribution and working to get it ready to merge.

There's definitely room for improvement as you pointed out in the StringUtil and UrlPath classes, but it will allow to bring in more functionality more easily as so much code in System.Web ends up relying on these helpers. These methods can receive some targeted fixes in the future. (some of them look even like they're probably built-in functions that could do it now)

@twsouthwick twsouthwick requested a review from Tratcher September 7, 2022 15:37
@twsouthwick
Copy link
Member

Hopefully this will a) merge correctly without whatever weirdness you were seeing and b) build, and test correctly (it does locally).

Yup on both accounts :) I've seen that weirdness with GH when doing merges. Not exactly sure why, but rebasing seems to fix it.


internal static bool HasTrailingSlash(string virtualPath) => virtualPath[^1] == '/';

internal static bool IsRooted(string basepath) => string.IsNullOrEmpty(basepath) || basepath[0] == '/' || basepath[0] == '\\';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty is rooted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is according to the original code - https://github.com/microsoft/referencesource/blob/master/System.Web/Util/UrlPath.cs#L33
Null or Empty tends to get caught further up in the calls in VirtualPathUtility and either returns something explicit or throws.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI These methods are taken as-is (mostly) from System.Web and is how they handled things. Not sure how well we can reason about some of these things :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - UrlPath and StringUtil (other than code optimizations based on newer c# / .net functions and error strings) are pretty much a direct port.

Comment on lines +57 to +58
// e.g c:\foo
if (path[1] == ':' && IsDirectorySeparatorChar(path[2])) return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows \:\. What restrictions should be checked for the first character?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again - this is based on the original code. I'm not sure there is ever a reason that something like \:\ would be allowed as a virtual path - the use of this function is to throw if it is not a valid virtual path in CheckValidVirtualPath.

}

// Same as Reduce, but for a virtual path that is known to be well formed
internal static string ReduceVirtualPath(string path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this path contain escaped dots or slashes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless the original call did, I don't think so. This is mainly used with the result of MakeVirtualPathAppAbsolute or MakeRelative which would normally be called with hardcoded strings from the developer, and possibly the current request path, which I think should also be 'clean'.
As it says in the description - only use on a virtual path that is known to be well formed.
This code is pretty much untouched from the original .Net implementation, other than some changes to substring handling and error messages.
If it needs to be made more robust than the original, then I think that is a separate issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants