[C#] Bestand uploaden via HTTP(s)

Pagina: 1
Acties:

Onderwerpen

Vraag


Acties:
  • 0 Henk 'm!

  • Disksoft
  • Registratie: September 2003
  • Laatst online: 18-05 13:57
Ik ben bezig om een script te schrijven om de configuratie van een specifiek apparaat aan te passen.
Nu was dit gelukt in de quick and dirty manier, alleen bleek dit later niet lekker te werken op HTTPS.
Nu ben ik opnieuw begonnen, en het meteen in een class gegoten, maar ik krijg het nu helemaal niet meer voor elkaar, terwijl mijn Wireshark identiek is. Ik heb geen verklaring waarom het nu niet werkt.

C#:
1
2
3
4
5
6
7
8
9
10
11
    public async Task<string> UploadFile(byte[] fileData, string filename)
    {
        using var content = new MultipartFormDataContent();
        var fileContent = new ByteArrayContent(fileData);
        fileContent.Headers.Add("Content-Disposition", $"form-data; name=\"uploadfile\"; filename=\"{filename}\"");
        fileContent.Headers.Add("Content-Type", "application/octet-stream");
        content.Add(fileContent, "uploadfile", filename);

        using var response = await _httpClient.PostAsync("/cgi-bin/admin/system.cgi", content);
        return await response.Content.ReadAsStringAsync();
    }


In Wireshark ziet dit er zo uit:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
OST /cgi-bin/admin/system.cgi HTTP/1.1
Host: 10.10.10.131
Cookie: id=bc923e7b-dccf-468f-9a18-ac43f6332ef3
Content-Type: multipart/form-data; boundary="f1423c9c-1cde-4569-b7ef-f946d4e98960"
Content-Length: 10832

--f1423c9c-1cde-4569-b7ef-f946d4e98960
Content-Disposition: form-data; name="uploadfile"; filename="default.cfg"
Content-Type: application/octet-stream

.. Inhoud bestand ...

--f1423c9c-1cde-4569-b7ef-f946d4e98960--


Dit werkt niet, maar onderstaande Wireshark is een werkende upload via een andere tool (die helaas niet kan wat ik wil).

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST /cgi-bin/admin/system.cgi HTTP/1.1
Content-Type: multipart/form-data; boundary=--8dc317af4ecff6f
Host: 10.10.10.131
Cookie: id=8cab3145-9843-44d3-a530-d54d623f1290
Content-Length: 11204
Expect: 100-continue

----8dc317af4ecff6f
Content-Disposition: form-data; name="uploadfile"; filename="default.cfg"
Content-Type: application/octet-stream

.. Inhoud bestand ...

----8dc317af4ecff6f--


In mijn ogen geen verschil op `Expect: 100-continue` na, maar als ik deze header toevoeg heeft het geen effect.


De code die wel werkte op HTTP maar niet op HTTPS was:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// TODO: Werkt met HTTP maar niet met HTTPS
static void UploadBestand(string apiUrl, string bestandspad, string cookie, string backupContent)
{
   try
    {
        // Maak een nieuwe HttpWebRequest
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(apiUrl);
        
        // Negeer alle SSL-fouten
        ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;
    
        request.AllowAutoRedirect = false;
        request.Method = "POST";
    
        request.Headers.Add("Cookie", "id=" + cookie);
    
        // Boundary creëren voor het scheiden van de bestandsgegevens
        string boundary = Guid.NewGuid().ToString("N");
        request.ContentType = "multipart/form-data; boundary=" + boundary;
    
        // Begin de aanvraag schrijven
        using (Stream requestStream = request.GetRequestStream())
        {
            // Boundary en Content-Disposition voor bestandsinformatie
            string header = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"uploadfile\"; filename=\"default.cfg\"\r\nContent-Type: application/octet-stream\r\n\r\n";
            byte[] headerBytes = System.Text.Encoding.UTF8.GetBytes(header);
            requestStream.Write(headerBytes, 0, headerBytes.Length);
    
            // Multi-line string (backupContent) schrijven naar de aanvraag
            byte[] backupContentBytes = System.Text.Encoding.UTF8.GetBytes(backupContent);
            requestStream.Write(backupContentBytes, 0, backupContentBytes.Length);
    
            // Boundary toevoegen na de multi-line string
            byte[] boundaryBytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
            requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
        }
    
        // Antwoord ontvangen
        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        {
            Console.WriteLine("Status Code: " + response.StatusCode);
            Console.WriteLine(response.Dump());
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("Fout bij het uploaden van het bestand: " + ex.Message);
    }

}



De huidige volledige class is:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
using Newtonsoft.Json;

public class HttpDataFetcher
{
    private readonly HttpClientHandler _handler;
    private readonly HttpClient _httpClient;

    public HttpDataFetcher(string hostname)
    {
        _handler = new HttpClientHandler() { AllowAutoRedirect = false, ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator };
        _httpClient = new HttpClient(_handler);
        _httpClient.BaseAddress = new Uri(hostname);
    }

    public async Task<string> Login(string username, string password, string action, string page)
    {
        var loginData = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("username", username),
            new KeyValuePair<string, string>("password", password),
            new KeyValuePair<string, string>("action", action),
            new KeyValuePair<string, string>("page", page)
        });

        var response = await _httpClient.PostAsync("/ajax", loginData);
        return await response.Content.ReadAsStringAsync();
    }

    public async Task<DeviceInfo> GetDeviceInfo()
    {
        var response = await _httpClient.GetAsync("/api/device/info/");
        if (response.IsSuccessStatusCode)
        {
            var jsonString = await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<DeviceInfo>(jsonString);
        }
        else
        {
            // TODO: Handel de fout af
            throw new HttpRequestException($"Error: {response.StatusCode}");
        }
    }

    public async Task<string> MakeBackup()
    {
        var response = await _httpClient.GetAsync("/cgi-bin/admin/ajax?page=backup&action=export");
        return await response.Content.ReadAsStringAsync();
    }

    public async Task<string> UploadFile(byte[] fileData)
    {
        var multipartFormData = new MultipartFormDataContent();
        var fileContent = new ByteArrayContent(fileData);
        multipartFormData.Add(fileContent, "uploadfile", "default.cfg");

        var response = await _httpClient.PostAsync("/cgi-bin/admin/system.cgi", multipartFormData);
        return await response.Content.ReadAsStringAsync();
    }

    public async Task<string> UploadFile(byte[] fileData, string filename)
    {
        using var content = new MultipartFormDataContent();
        var fileContent = new ByteArrayContent(fileData);
        fileContent.Headers.Add("Content-Disposition", $"form-data; name=\"uploadfile\"; filename=\"{filename}\"");
        fileContent.Headers.Add("Content-Type", "application/octet-stream");
        content.Add(fileContent, "uploadfile", filename);

        using var response = await _httpClient.PostAsync("/cgi-bin/admin/system.cgi", content);
        return await response.Content.ReadAsStringAsync();
    }

}


Weet iemand waar het fout gaat want ik zie het helaas niet meer... |:(

Alle reacties


Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Laatst online: 22-05 08:46

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

Definieer "Werkt niet". Welke foutmelding / response krijg je terug? Verder zie ik geen reden waarom je "oude" code niet met HTTPS zou werken als die wel op HTTP werkt. Trek je niet de verkeerde conclusie(s)?

Die 100-continue komt doordat je chunked verstuurt; daar moet de server wel mee overweg kunnen. Je kunt die header wel aan een ander request toevoegen, maar als die request niet daadwerkelijk "chunked" (dus alles zit in 1 request) doet die header (inderdaad) weinig.

Zie ook https://github.com/dotnet/runtime/issues/30283 en https://2e0pgs.github.io/...et-core-request-chunking/ (o.a.)

[ Voor 66% gewijzigd door RobIII op 19-02-2024 19:23 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • Disksoft
  • Registratie: September 2003
  • Laatst online: 18-05 13:57
Het probleem is dat voor het oog alles lijkt te werken, nergens zijn foutmeldingen zichtbaar, vandaar dat ik het via Wireshark vegelijk.
Ook de status code is 200 (OK), het resultaat is alleen dat het bestand dus niet geupload blijkt te zijn, gezien de configuratie niet aangepast is.

Ik pas nu alleen maar de hostnaam aan, om te zien of die wijzigt.

Waarom die andere code het wel deed op HTTP en niet op HTTPS was mij ook een raadsel, maar het was consequent zo, zodra ik HTTPS inschakelde op het apparaat (met een self-signed certificaat) werkte het niet.
Later nog een keer getest met een geldig SSL certificaat, maar dat maakte geen verschil, zette ik HTTPS uit werkte het meteen weer.

Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Laatst online: 22-05 08:46

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

Disksoft schreef op maandag 19 februari 2024 @ 19:30:
Waarom die andere code het wel deed op HTTP en niet op HTTPS was mij ook een raadsel, maar het was consequent zo, zodra ik HTTPS inschakelde op het apparaat (met een self-signed certificaat) werkte het niet.
Later nog een keer getest met een geldig SSL certificaat, maar dat maakte geen verschil, zette ik HTTPS uit werkte het meteen weer.
En het enige verschil was http://foo.local naar https://foo.local? Geen code wijzigingen? Dat geloof ik niet echt eigenlijk.

[ Voor 7% gewijzigd door RobIII op 19-02-2024 19:41 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • Disksoft
  • Registratie: September 2003
  • Laatst online: 18-05 13:57
Ja alleen de url wijzigen van http naar https meer niet.
Ook daar had ik geen verklaring voor maar omdat ik in de test code zowel de HttpClient als HttpWebRequest door elkaar gebruikte, en HttpWebRequest deprecated is wou ik het sowieso omzetten naar alles via de HttpClient.

Acties:
  • 0 Henk 'm!

  • Falcon
  • Registratie: Februari 2000
  • Laatst online: 20:28

Falcon

DevOps/Q.A. Engineer

Disksoft schreef op maandag 19 februari 2024 @ 19:30:
Het probleem is dat voor het oog alles lijkt te werken, nergens zijn foutmeldingen zichtbaar, vandaar dat ik het via Wireshark vegelijk.
Ook de status code is 200 (OK), het resultaat is alleen dat het bestand dus niet geupload blijkt te zijn, gezien de configuratie niet aangepast is.

Ik pas nu alleen maar de hostnaam aan, om te zien of die wijzigt.

Waarom die andere code het wel deed op HTTP en niet op HTTPS was mij ook een raadsel, maar het was consequent zo, zodra ik HTTPS inschakelde op het apparaat (met een self-signed certificaat) werkte het niet.
Later nog een keer getest met een geldig SSL certificaat, maar dat maakte geen verschil, zette ik HTTPS uit werkte het meteen weer.
Dus eigenlijk moet hij geen 200 OK teruggeven, maar een 500 internal server error omdat het niet gelukt is het bestand te verwerken naar de storage?

Kan het zijn dat je octet-stream base64-encode verwacht als inhoud?

"We never grow up. We just learn how to act in public" - "Dyslexie is a bitch"


Acties:
  • 0 Henk 'm!

  • Disksoft
  • Registratie: September 2003
  • Laatst online: 18-05 13:57
In de test code gebruikte ik ook geen base64-encode

Dit was de volledige code die werkt op HTTP maar niet op HTTPS
Het was een POC dus vandaar van alles door elkaar heen.
Het enige wat ik wijzigde was http_protocol van 'http' naar 'https', en dan werkte het niet meer als het apparaat ook op HTTPS staat.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
using System.Net;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;

var http_protocol = "http";
var hostname = "10.10.10.131";

var handler = new HttpClientHandler() { AllowAutoRedirect = false, ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator };
var client = new HttpClient(handler);
var request = new HttpRequestMessage();

request.RequestUri = new Uri(http_protocol + "://" + hostname + "/ajax");
request.Method = HttpMethod.Post;

var formList = new List<KeyValuePair<string, string>>();
formList.Add(new KeyValuePair<string, string>("action", "login"));
formList.Add(new KeyValuePair<string, string>("page", "general"));
formList.Add(new KeyValuePair<string, string>("username", "admin"));
formList.Add(new KeyValuePair<string, string>("password", "password12345"));
request.Content = new FormUrlEncodedContent(formList);

var response = await client.SendAsync(request);
var cookie = await response.Content.ReadAsStringAsync();
Console.WriteLine("Cookie: " + cookie);




// Get MAC-Address
HttpClient clientx = new HttpClient(handler);
HttpResponseMessage responsex = await clientx.GetAsync(http_protocol + "://" + hostname + "/api/device/info/");
responsex.EnsureSuccessStatusCode();
string responseBody = await responsex.Content.ReadAsStringAsync();

var resultx = JsonObject.Parse(responseBody);
var filename = resultx["mac"].ToString().Replace(":", "");


// Assuming 'cookie' is obtained from the first request
var backupRequest = new HttpRequestMessage();
backupRequest.RequestUri = new Uri(http_protocol + "://" + hostname + "/cgi-bin/admin/ajax?page=backup&action=export");
backupRequest.Method = HttpMethod.Get;

// Add the 'Cookie' header with the value obtained from the first request
backupRequest.Headers.Add("Cookie", cookie);

var backupResponse = await client.SendAsync(backupRequest);
var backupContent = await backupResponse.Content.ReadAsStringAsync();


// Upload file to URL
string apiUrl = http_protocol + "://" + hostname + "/cgi-bin/admin/system.cgi";
string bestandspad = "default.cfg";


backupContent = Regex.Replace(backupContent, @"(?<=hostname=)""[^""]*""", "\"test123\"");
/// UploadBestand(apiUrl, bestandspad, cookie, backupContent);
UploadBestand(apiUrl, bestandspad, cookie, backupContent);


// TODO: Werkt met HTTP maar niet met HTTPS
static void UploadBestand(string apiUrl, string bestandspad, string cookie, string backupContent)
{
    try
    {
        // Maak een nieuwe HttpWebRequest
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(apiUrl);

        // Negeer alle SSL-fouten
        ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;

        request.AllowAutoRedirect = false;
        request.Method = "POST";

        request.Headers.Add("Cookie", "id=" + cookie);

        // Boundary creëren voor het scheiden van de bestandsgegevens
        string boundary = Guid.NewGuid().ToString("N");
        request.ContentType = "multipart/form-data; boundary=" + boundary;

        // Begin de aanvraag schrijven
        using (Stream requestStream = request.GetRequestStream())
        {
            // Boundary en Content-Disposition voor bestandsinformatie
            string header = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"uploadfile\"; filename=\"default.cfg\"\r\nContent-Type: application/octet-stream\r\n\r\n";
            byte[] headerBytes = System.Text.Encoding.UTF8.GetBytes(header);
            requestStream.Write(headerBytes, 0, headerBytes.Length);

            // Multi-line string (backupContent) schrijven naar de aanvraag
            byte[] backupContentBytes = System.Text.Encoding.UTF8.GetBytes(backupContent);
            requestStream.Write(backupContentBytes, 0, backupContentBytes.Length);

            // Boundary toevoegen na de multi-line string
            byte[] boundaryBytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
            requestStream.Write(boundaryBytes, 0, boundaryBytes.Length);
        }

        // Antwoord ontvangen
        request.Dump();
        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        {
            // Hier kun je de response verwerken
            Console.WriteLine("Status Code: " + response.StatusCode);
            Console.WriteLine(response.Dump());
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("Fout bij het uploaden van het bestand: " + ex.Message);
    }

}

Acties:
  • +1 Henk 'm!

  • remyz
  • Registratie: Februari 2010
  • Laatst online: 21:56
Ik ben geen C# programmeur dus misschien schrijf ik nu iets dat onzin is in die context, maar het viel mij op dat je een IP-adres als hostname gebruikt. Dat geeft denk ik problemen met https. Heb je een mogelijkheid om een echte hostname te gebruiken?

Edit: als je geen hostname tot je beschikking hebt zou je misschien de https://nip.io/ service kunnen gebruiken.

[ Voor 20% gewijzigd door remyz op 19-02-2024 22:21 . Reden: Link naar nip.io toegevoegd. ]


Acties:
  • +1 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Laatst online: 22-05 08:46

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

@remyz Oeh, scherp! Dat was me nog niet eens opgevallen. Dat kan inderdaad problemen geven (zie SNI). Ik weet niet waar 't precies om gaat maar als "het apparaat" een webinterface heeft, kun je die dan zonder problemen benaderen over SSL @Disksoft? Want dat lijkt me ook stug - je browser zal een SSL certificaat van foo.local niet leuk vinden op 10.10.10.131. Mogelijk spelen zelfs beide problemen (chunked transfer encoding én een IP gebruiken waardoor SNI niet zal werken).

[ Voor 56% gewijzigd door RobIII op 19-02-2024 22:44 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • CodeCaster
  • Registratie: Juni 2003
  • Niet online

CodeCaster

Can I get uhm...

HTTP-servers van hardware zijn vaak erg ... finicky. Probeer het werkende request eens exact na te bouwen. Wat me opvalt is dat de opbouw van de boundary anders is.

Het zou geen problemen moeten geven, maar goed.

Edit: maar als de guid-string op HTTP ook gewoon werkte, zal er wel iets anders aan de hand zijn.

[ Voor 19% gewijzigd door CodeCaster op 19-02-2024 23:00 ]

https://oneerlijkewoz.nl
Het ergste moet nog komen / Het leven is een straf / Een uitgestrekte kwelling van de wieg tot aan het graf


Acties:
  • 0 Henk 'm!

  • Lethalis
  • Registratie: April 2002
  • Niet online
Als C# developer "haat" ik vaak op Python, maar ik moest laatst ook een script schrijven dat een bestand naar Nextcloud uploadt.

Paar regels code die curl uitvoeren met subprocess en klaar.

Dat ga ik pas in C# doen als mijn "script" complexer wordt en een echt programma wordt. Dan rewrite ik naar C# :) Ik vind het alleen zonde om een heel C# programma te schrijven als het ook met een paar regels Python kan.

Maar als tip kan ik jou meegeven om met curl te testen, totdat het werkt en dan eenzelfde soort request met jouw code te doen. Curl zit tegenwoordig standaard in Windows 11 (en uiteraard op de Mac en Linux).

PS
Om wat voor apparaat gaat het? Heb je gezocht of meer mensen problemen hiermee hebben gehad? Soms is het iets stoms, zoals dat maar 1 cookie / sessie tegelijkertijd werkt 8)7 Heb ik ooit met een telefoniecentrale gehad. Maar 1 sessie toegestaan tegelijkertijd en ik mij maar afvragen wat er mis was met mijn requests (zodra ik op het dashboard keek stopte mijn tool met werken).

Juist als je wisselt tussen een werkende tool en jouw code kan dat een issue zijn :) Vaak hebben bepaalde apparaten beperkte geheugencapaciteit en krijg je daardoor dit soort onverwacht gedrag.

PPS
Kijk voor de zekerheid met Wireshark ook even of de andere tool nog extra requests doet van tevoren, zoals een nieuwe cookie aanvragen.

[ Voor 53% gewijzigd door Lethalis op 20-02-2024 17:39 ]

Ask yourself if you are happy and then you cease to be.


Acties:
  • 0 Henk 'm!

  • MatHack
  • Registratie: Oktober 2001
  • Niet online

MatHack

Dev by day, Gamer by night

Grote kans dat het een brakke webserver implementatie is die de quotes in de boundary parameter niet ondersteund. In de RFC staat hier ook expliciet een waarschuwing over (einde van pagina 18):
WARNING TO IMPLEMENTORS: The grammar for parameters on the Content-type field is such that it is often necessary to enclose the boundary parameter values in quotes on the Content-type line. This is not always necessary, but never hurts. Implementors should be sure to study the grammar carefully in order to avoid producing invalid Content-type fields. Thus, a typical "multipart" Content-Type header field might look like this:

Content-Type: multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p

But the following is not valid:

Content-Type: multipart/mixed; boundary=gc0pJq0M:08jU534c0p

(because of the colon) and must instead be represented as

Content-Type: multipart/mixed; boundary="gc0pJq0M:08jU534c0p"

There's no place like 127.0.0.1

Pagina: 1