Реплицируйте команду cURL в C# (Unity)

Я попробовал массу вариантов - то, что я выкладываю ниже, это очищенная версия того, с чего я изначально начал. Это вспомогательная утилита, которую я пишу для Unity-приложения. Это не игра, просто 2-е приложение.

Я пытаюсь повторить это:

curl -f -s -S --user $(ROKU_DEV_CREDENTIALS) --anyauth -F "mysubmit=Install" -F "archive=@out/Archive.zip" http://$(ROKUIP)/plugin_install > /dev/null

Это то, что у меня пока есть (см. Код ниже) - и похоже, что это "своего рода" работает, но позволяет проходить аутентификацию, и требует около 108 КБ данных (wireshark говорит, что ПОЛНЫЙ ZIP, около 2,8 МБ), отправляется, но сервер сообщает, что получено только около 108 КБ); Я подумал, что это может быть проблема с кодировкой (приложение на Windows 10, а Server - встроенный Linux-сервер [Roku Player]).

using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

public class PushBuild : MonoBehaviour {

    public RokuDevice SingleDevice;
    public Dictionary<string, RokuDevice> ManyDevice;
    public bool startUpload;

    // Use this for initialization
    void Start() {
    }

    // Update is called once per frame
    void Update() {
        if (startUpload)
        {
            StartCoroutine(Upload());

            startUpload = false;
        }
    }

    public void QueueUpPush()
    {
        startUpload = true;
    }

    IEnumerator Upload()
    {
        string[] substr = null;
        UnityWebRequest www1 = UnityWebRequest.Head("http://10.0.0.232/plugin_install");
        yield return www1.Send();

        if (www1.isError)
        {
            Debug.Log(www1.error);
        }
        else
        {
            Dictionary<string, string> responseHeaders = www1.GetResponseHeaders();
            string nonce;
            string[] split = { "=", "\"" };
            bool _null = responseHeaders.TryGetValue("WWW-Authenticate", out nonce);

            if (nonce != null)
            {
                substr = nonce.Split(split, StringSplitOptions.RemoveEmptyEntries);
            }
        }

        WWW _file = new WWW("file:///" + "D:\\Workspace\\Roku\\Tempest\\src\\Archive.zip");

        yield return _file;

        WWWForm form = new WWWForm();

        form.AddBinaryData("archive", _file.bytes, "Archive.zip", "application/x-zip-compressed");
        form.AddField("mysubmit", "install");

        using (UnityWebRequest www = UnityWebRequest.Post("http://10.0.0.232/plugin_install", form))
        {
            string ha1 = "rokudev:rokudev:0000";
            string ha2 = "POST:/plugin_install";

            string responseDigest = CalculateResponseDigest(ha1, ha2, substr[5], "00000000", "aef3fafadfaedfadf", "auth");

            string authHeaderVal = string.Format("Digest username=\"rokudev\", realm=\"rokudev\", nonce=\"{0}\", uri=\"/plugin_install\", response=\"{1}\", qop=auth, nc=00000000,  cnonce=\"aef3fafadfaedfadf\"", substr[5], responseDigest);

            www.SetRequestHeader("Authorization", authHeaderVal);
            www.Send();

            while (www.uploadProgress < 1.0)
            {
                Debug.Log("still uploading..." + www.uploadProgress);
                yield return null;
            }

            Debug.Log(www.uploadedBytes);
            if (www.isError)
            {
                Debug.Log(www.error);
            }
            else
            {
                Debug.Log("Form upload complete!");
                Debug.Log(www.downloadHandler.data);
            }
        }
    }

    string CalculateResponseDigest(string ha1, string ha2, string serverNonce, string requestCnt, string clientNonce, string qop)
    {
        byte[] inputBytesHA1 = Encoding.ASCII.GetBytes(ha1);
        byte[] HA1 = MD5.Create().ComputeHash(inputBytesHA1);

        StringBuilder _returnValHA1 = new StringBuilder(HA1.Length * 2);

        foreach (byte b in HA1)
        {
            _returnValHA1.AppendFormat("{0:x2}", b);
        }

        byte[] inputBytesHA2 = Encoding.ASCII.GetBytes(ha2);
        byte[] HA2 = MD5.Create().ComputeHash(inputBytesHA2);

        StringBuilder _returnValHA2 = new StringBuilder(HA2.Length * 2);

        foreach (byte b in HA2)
        {
            _returnValHA2.AppendFormat("{0:x2}", b);
        }

        byte[] inputBytesHA3 = Encoding.ASCII.GetBytes(string.Format("{0}:{1}:{2}:{3}:{4}:{5}", _returnValHA1, serverNonce, requestCnt, clientNonce, qop, _returnValHA2));
        byte[] HA3 = MD5.Create().ComputeHash(inputBytesHA3);

        StringBuilder _returnVal = new StringBuilder(HA3.Length * 2);

        foreach (byte b in HA3)
        {
            _returnVal.AppendFormat("{0:x2}", b);
        }

        return _returnVal.ToString();
    }
}

Важные заметки:

  1. Я пытался загрузить ZIP как FileStream, Я пытался построить сырой C# HttpWebRequest, Я попыталсяточно подобрать заголовки Roku.
  2. Я использовал Fiddler и WireShark для мониторинга сетевого трафика - насколько я могу судить, трафик практически идентичен (заголовки немного отличаются, что создает разные смещения пакетов).
  3. Я даже дошел до того, что разобрал повторно собранные пакеты (в соответствии с тем, что сообщает Wireshark), и они кажутся почти идентичными (опять же, в основном различия в заголовках).
  4. Я использовал реализации других людей для Digest Auth - в конце концов, я сделал свою собственную. Я использую HEAD для сервера, чтобы получить значение nonce, а затем использую это значение nonce для построения моего следующего POST, который используется в дайджесте. Из того, что я могу сказать, похоже, что это работает... но у меня есть подозрения, что это может быть виновником (когда я создаю заголовок аутентификации дайджеста, устройство Roku дает мне HTTP 100 Продолжить, и, кажется, принимает мой дайджест аутентификации и тогда мой POST запрос).
  5. Я также поиграл с кодировками, следуя советам других рекомендаций SO. Я могу получить различные значения загрузки байтов в Roku (например, как есть, без кодирования, я могу получить около 115700 байт, о которых мне сообщают, если я делаю UTF16, я могу получить около 270000 байт). Из wireshark кажется, что ВЕСЬ ZIP-файл передается в Roku. Мое подозрение #1 состоит в том, что мои кодировки испорчены.

У меня есть данные Wireshark, данные Fiddler, и, как 5 различных реализаций (отличается от приведенного выше примера), которыми я могу поделиться. Я даже могу поделиться JavaScript, который используется встроенным WebApplication от Roku, чтобы показать, что он делает (это POST, использующий дайджест-аутентификацию - прямо скажем). Скажи мне, чего тебе не хватает - я боролся с этим уже 2 недели и наконец сломался, чтобы попросить о помощи!

Есть идеи?

1 ответ

Этот вопрос сводится к тому, "как я могу сделать http POST из C#" и "как мне сделать дайджест-аутентификацию из C#" - если вы гуглите это, сразу появится несколько SO-ответов, следуйте им.

Кодировка не имеет к этому никакого отношения, ZIP представляется в виде необработанных октетов.

Вот идея для устранения неполадок (разделяй и властвуй) - сначала научите ваше приложение загружать небольшие ZIP-файлы, <100k. Напишите тривиальное приложение BrightScript (Hello World) и посмотрите, что ваш код может успешно загрузить. Затем сконцентрируйтесь на больших размерах - это может быть, например, то, что ваш клиент пытается использовать chunked-encodding, а сервер не поддерживает его. Моя точка зрения - начните с простого и идите к полному делу

Другие вопросы по тегам