Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async NancyFX with HttpClient - GetAsync for fbcdn returns 403 forbidden?

NOTE: I've changed this question quite a bit in an attempt to make it point more to the problem. The comments below no longer reflect this question.

I'm trying to get this image from fbcdn:

https://scontent.xx.fbcdn.net/v/t1.0-1/c15.0.50.50/p50x50/10354686_10150004552801856_220367501106153455_n.jpg?oh=6c801f82cd5a32fd6e5a4258ce00a314&oe=589AAD2F

Browser gets it just fine. Here's my code:

public class ReverseProxyController : NancyModule
{
    public ReverseProxyController()
    {
        Get["/", true] = async (parameters, ct) =>
        {
            var result = await GetResult(parameters, ct);
            return result;
        };
    }

    private async Task<Response> GetResult(dynamic parameters, CancellationToken ct)
    {
        var client = new HttpClient();
        string url = Request.Query["url"].Value.ToString();
        if (url == null) return null;

        client.DefaultRequestHeaders.Add("Access-Control-Allow-Origin", "*");
        client.DefaultRequestHeaders.Add("User-Agent",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36");
        client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
        client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
        client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, sdch, br");
        client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.8,ru;q=0.6");
        var response = await client.GetAsync(url, ct);

        ct.ThrowIfCancellationRequested();


        switch (response.StatusCode)
        {
            case HttpStatusCode.OK:
                var stream = await response.Content.ReadAsStreamAsync();

                return Response.FromStream(stream, response.Content.Headers.ContentType != null
                    ? response.Content.Headers.ContentType.ToString()
                    : "application/octet-stream");

            default:
                return Response.AsText("\nError " + response.StatusCode);
        }
    }
}

I get a 403 Forbidden response every time. I thought adding the headers would make it work but no go.

This code works for other images on other hosts like this one:

https://s-media-cache-ak0.pinimg.com/564x/ff/f0/c9/fff0c988a4516659d4009f60e0694cb6.jpg

like image 592
richard Avatar asked Oct 29 '16 00:10

richard


2 Answers

The problem is in URL Retrieval from Nancy request, not getting Data with HttpClient.

I Suppose you are sending request to nancy like:

http://localhost/?url=...

hence for Facebook, it would be:

http://localhost/?url=https://scontent.xx.fbcdn.net/v/t1.0-1/c15.0.50.50/p50x50/10354686_10150004552801856_220367501106153455_n.jpg?oh=6c801f82cd5a32fd6e5a4258ce00a314&oe=589AAD2F

but for this url string url = Request.Query["url"].Value.ToString(); is incomplete and missing the last part (&oe=589AAD2F), hence the server responses with Forbidden.

enter image description here

Here is the simple change to demonstrate the problem:

private async Task<Response> GetResult(dynamic parameters, CancellationToken ct)
{
    var client = new HttpClient();
    var req = Request.Url.ToString();
    var queryStart = req.IndexOf("url=");
    if (queryStart == -1)
        return Nancy.HttpStatusCode.BadRequest;

    var url = req.Substring(queryStart + 4);
    if (string.IsNullOrEmpty(url))
        return Nancy.HttpStatusCode.BadRequest;

    client.DefaultRequestHeaders.Add("Access-Control-Allow-Origin", "*");
    client.DefaultRequestHeaders.Add("User-Agent",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36");
    client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
    client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
    client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, sdch, br");
    client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.8,ru;q=0.6");
    var response = await client.GetAsync(url, ct);

    ct.ThrowIfCancellationRequested();


    switch (response.StatusCode)
    {
        case System.Net.HttpStatusCode.OK:
            var stream = await response.Content.ReadAsStreamAsync();

            return Response.FromStream(stream, response.Content.Headers.ContentType != null
                ? response.Content.Headers.ContentType.ToString()
                : "application/octet-stream");

        default:
            return Response.AsText("\nError " + response.StatusCode);
    }
}

Solution

We can actually Encode the URL prior sending it, and Nancy will Automatically decode the URL for us, hence no need to change anything server side.

Here is a link example generated using HttpUtility.UrlEncode applied on

https://scontent.xx.fbcdn.net/v/t1.0-1/c15.0.50.50/p50x50/10354686_10150004552801856_220367501106153455_n.jpg?oh=6c801f82cd5a32fd6e5a4258ce00a314&oe=589AAD2F

Result:

https%3a%2f%2fscontent.xx.fbcdn.net%2fv%2ft1.0-1%2fc15.0.50.50%2fp50x50%2f10354686_10150004552801856_220367501106153455_n.jpg%3foh%3d6c801f82cd5a32fd6e5a4258ce00a314%26oe%3d589AAD2F

and the actual request for this specific link would be:

http://localhost:9876/?url=https%3a%2f%2fscontent.xx.fbcdn.net%2fv%2ft1.0-1%2fc15.0.50.50%2fp50x50%2f10354686_10150004552801856_220367501106153455_n.jpg%3foh%3d6c801f82cd5a32fd6e5a4258ce00a314%26oe%3d589AAD2F

Alternate Solution

I personally prefer POST over GET in this situation, so here it is:

public class ReverseProxyController : NancyModule
{
    class ProxyRequest
    {
        public string Url { get; set; }
    }

    public ReverseProxyController()
    {
        Post["/", true] = async (parameters, ct) =>
        {
            var result = await GetResult(parameters, ct);
            return result;
        };
    }

    private async Task<Response> GetResult(dynamic parameters, CancellationToken ct)
    {
        var pReq = this.Bind<ProxyRequest>();
        var url = pReq.Url;
        if (string.IsNullOrEmpty(url))
            return null;

        var client = new HttpClient();
        client.DefaultRequestHeaders.Add("Access-Control-Allow-Origin", "*");
        client.DefaultRequestHeaders.Add("User-Agent",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36");
        client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
        client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
        client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, sdch, br");
        client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.8,ru;q=0.6");
        var response = await client.GetAsync(url, ct);

        ct.ThrowIfCancellationRequested();


        switch (response.StatusCode)
        {
            case System.Net.HttpStatusCode.OK:
                var stream = await response.Content.ReadAsStreamAsync();

                return Response.FromStream(stream, response.Content.Headers.ContentType != null
                    ? response.Content.Headers.ContentType.ToString()
                    : "application/octet-stream");

            default:
                return Response.AsText("\nError " + response.StatusCode);
        }
    }
}
like image 181
user3473830 Avatar answered Nov 19 '22 03:11

user3473830


as console application you code works fine

   static void Main(string[] args)
    {
        GetStuff();
        Console.ReadLine();
    }

    private static async void GetStuff()
    {
        CancellationToken ct = new CancellationToken();
        var client = new HttpClient();
        //this is only a Simple Change to demonstrate the problem, and should not be considered as a proper solution

        string url = "https://scontent.xx.fbcdn.net/v/t1.0-1/c15.0.50.50/p50x50/10354686_10150004552801856_220367501106153455_n.jpg?oh=6c801f82cd5a32fd6e5a4258ce00a314&oe=589AAD2F";
        if (url == null) return;

        client.DefaultRequestHeaders.Add("Access-Control-Allow-Origin", "*");
        client.DefaultRequestHeaders.Add("User-Agent",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36");
        client.DefaultRequestHeaders.Add("Upgrade-Insecure-Requests", "1");
        client.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
        client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, sdch, br");
        client.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.8,ru;q=0.6");
        var response = await client.GetAsync(url, ct);

        ct.ThrowIfCancellationRequested();


        switch (response.StatusCode)
        {
            case System.Net.HttpStatusCode.OK:
                var stream = await response.Content.ReadAsStreamAsync();


                break;
            default:
                break;
        }
    }

i get a OK 200 return message. Is it other images that you use that might need a login information ?

like image 1
Henrik Bøgelund Lavstsen Avatar answered Nov 19 '22 03:11

Henrik Bøgelund Lavstsen