HttpUtils: Content-Length von $data mit Emojis wird falsch berechnet 😿

Begonnen von Markus M., 19 August 2019, 20:28:29

Vorheriges Thema - Nächstes Thema

Markus M.

Edit: Wenn ein $data String Emojis enthält, passt die berechnete Content-Length nicht, siehe Kommentar unten.
Kennt jemand eine Lösung?

----------

Ich bastle gerade ein wenig an einem Modul, das mit unserer iCloud Einkaufsliste kommunizieren soll.
Abruf der Liste und Anlegen von neuen Items funktioniert soweit schon via HttpUtils mit REPORT/PROPFIND/GET/PUT Requests,
nur beim Editieren von bestehenden Items scheitere ich.

Egal was ich tue, ich bekomme immer Status 400 von iCloud.
Natürlich ohne Body und sinnvolle Fehlermeldung, wäre sonst ja zu einfach  :(

Am Inhalt scheint es nicht zu liegen, denn wenn ich den gleichen Content in Postman abschicke, funktioniert das Ganze.
Es kann eigentlich nur noch an irgendwelchen Headern liegen, die HttpUtils intern anders setzt.
Und das auch nur bei dem Request auf ein bestehendes Element, Anlegen funktioniert ja.

Kann mir jemand sagen, wie ich mir den kompletten PUT Request incl. allen Headern ansehen kann,
so wie er von HttpUtils_NonblockingGet abgeschickt wird?

Viele Grüße, Markus



Im Log taucht auch noch folgendes auf:2019.08.19 20:31:51 1: PERL WARNING: substr outside of string at /opt/fhem/FHEM/HttpUtils.pm line 628.
2019.08.19 20:31:51 1: stacktrace:
2019.08.19 20:31:51 1:     main::__ANON__                      called by /opt/fhem/FHEM/HttpUtils.pm (628)
2019.08.19 20:31:51 1:     main::__ANON__                      called by /opt/fhem/fhem.pl (755)
2019.08.19 20:31:51 1: PERL WARNING: Use of uninitialized value $data in numeric eq (==) at /opt/fhem/FHEM/HttpUtils.pm line 629.
2019.08.19 20:31:51 1: stacktrace:
2019.08.19 20:31:51 1:     main::__ANON__                      called by /opt/fhem/FHEM/HttpUtils.pm (629)
2019.08.19 20:31:51 1:     main::__ANON__                      called by /opt/fhem/fhem.pl (755)

Das wird wahrscheinlich daran liegen dass weder Daten noch Content-Length zurückkommen.
FHEM dev + HomeBridge + Lenovo Flex15 + HM-CFG-USB + RFXtrx433 + Fritz!Box 7590/7580/546E

HM Aktor/Sensor/Winmatic/Keymatic/Thermostat, HUE, Netatmo Weather/Security/Heating, Xiaomi AirPurifier/Vacuum, Withings Aura/BPM/Cardio/Go/Pulse/Thermo, VSX828, Harmony, Siro ERB15LE
https://paypal.me/mm0

rudolfkoenig

ZitatKann mir jemand sagen, wie ich mir den kompletten PUT Request incl. allen Headern ansehen kann,
Mit "attr global verbose 5" (oder attr DEV verbose loglevel+1, falls loglevel im request-hash gesetzt ist)

Zitat2019.08.19 20:31:51 1: PERL WARNING: substr outside of string at /opt/fhem/FHEM/HttpUtils.pm line 628.
$ret=syswrite($hash->{conn},$data) hat einen positiven Wert zurueckgeliefert, womit substr($data,$ret) nichts anfangen kann.
Weiss nicht so recht, wie das passieren kann. Ist $data womoeglich ein unicode String? Erwuenscht ist utf-8.

Markus M.

Zitat von: rudolfkoenig am 19 August 2019, 20:44:53
Mit "attr global verbose 5" (oder attr DEV verbose loglevel+1, falls loglevel im request-hash gesetzt ist)

Komplett kommt das hier dabei raus:
2019.08.19 22:45:48 2: HttpUtils url=https://markus@icloud.com:abcd-fhem-1234-1234@p10-caldav.icloud.com:443/1234567/calendars/485C467F-18F2-4268-1A38-10A421E480C2/590457D6-8593-47CC-A915-27B65FAF4C29.ics
2019.08.19 22:45:48 1: IP: p10-caldav.icloud.com -> 17.248.146.1
2019.08.19 22:45:49 2: HttpUtils request header:
PUT /1234567/calendars/485C467F-18F2-4268-1A38-10A421E480C2/590457D6-8583-47CC-A915-27B65FAF4C29.ics HTTP/1.0
Host: p71-caldav.icloud.com
Accept-Encoding: gzip,deflate
Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: text/calendar; charset=utf-8
User-Agent: FHEM
Content-Length: 325

2019.08.19 22:45:49 1: PERL WARNING: substr outside of string at /opt/fhem/FHEM/HttpUtils.pm line 628.
2019.08.19 22:45:49 1: stacktrace:
2019.08.19 22:45:49 1:     main::__ANON__                      called by /opt/fhem/FHEM/HttpUtils.pm (628)
2019.08.19 22:45:49 1:     main::__ANON__                      called by /opt/fhem/fhem.pl (755)
2019.08.19 22:45:49 1: PERL WARNING: Use of uninitialized value $data in numeric eq (==) at /opt/fhem/FHEM/HttpUtils.pm line 629.
2019.08.19 22:45:49 1: stacktrace:
2019.08.19 22:45:49 1:     main::__ANON__                      called by /opt/fhem/FHEM/HttpUtils.pm (629)
2019.08.19 22:45:49 1:     main::__ANON__                      called by /opt/fhem/fhem.pl (755)
2019.08.19 22:45:49 1: https://markus@icloud.com:abcd-fhem-1234-1234@p71-caldav.icloud.com:443/1234567/calendars/485C467F-18F2-4268-1A38-10A421E480C2/590457D6-8583-47CC-A915-27B65FAF4C29.ics: HTTP response code 400
2019.08.19 22:45:49 2: HttpUtils https://markus@icloud.com:abcd-fhem-1234-1234@p71-caldav.icloud.com:443/1234567/calendars/485C467F-18F2-4268-1A38-10A421E480C2/590457D6-8583-47CC-A915-27B65FAF4C29.ics: Got data, length: 0
2019.08.19 22:45:49 2: HttpUtils response header:
HTTP/1.1 400 Bad Request
Server: AppleHttpServer/f86376b8
Date: Mon, 19 Aug 2019 20:45:49 GMT
Content-Length: 0
Connection: close
X-Apple-Jingle-Correlation-Key: YKE7K5QLFJDB7CPOFFOGMVCMQI
apple-seq: 0
apple-tk: false
Apple-Originating-System: UnknownOriginatingSystem
X-Responding-Instance: caldavj:57101701:ms11p71ic-hygg28255301:8501:1914B187:e171ae08a8cf
X-Apple-API-Version: v1
DAV: 1, access-control, calendar-access, calendar-schedule, calendar-auto-schedule, calendar-audit, caldavserver-supports-telephone, calendar-managed-attachments, calendarserver-sharing, calendarserver-subscribed, calendarserver-home-sync
Via: xrail:ms11p00ic-qugw17011001.me.com:8301:19B99:grp70
Strict-Transport-Security: max-age=31536000; includeSubDomains;
via: icloudedge:fr02p01ic-ztde011111:7401:19RC395:Frankfurt
X-Apple-Request-UUID: c289f576-0b0a-462f-89ee-295c66544c82
access-control-expose-headers: X-Apple-Request-UUID
access-control-expose-headers: Via


Hab das mal mit Postman verglichen, auf den ersten Blick alles ähnlich.
Wenn ich allerdings dort Content-Length falsch mitgebe, bekomme ich auch nen 400.
Ich probier mal noch ein wenig rum.

Zitat$ret=syswrite($hash->{conn},$data) hat einen positiven Wert zurueckgeliefert, womit substr($data,$ret) nichts anfangen kann.
Weiss nicht so recht, wie das passieren kann. Ist $data womoeglich ein unicode String? Erwuenscht ist utf-8.
$data gibt es schlicht nicht, der Server schickt nur einen Header als Status zurück.
FHEM dev + HomeBridge + Lenovo Flex15 + HM-CFG-USB + RFXtrx433 + Fritz!Box 7590/7580/546E

HM Aktor/Sensor/Winmatic/Keymatic/Thermostat, HUE, Netatmo Weather/Security/Heating, Xiaomi AirPurifier/Vacuum, Withings Aura/BPM/Cardio/Go/Pulse/Thermo, VSX828, Harmony, Siro ERB15LE
https://paypal.me/mm0

Markus M.

Hmmm...
Wie berechnet FHEM eigentlich die Content-Length von Strings, die Emoticons enthalten?
Genau das ist nämlich das Problem  :o
Bei der Neuanlage funktioniert es trotzdem (vielleicht interessiert den Server die Content-Length da nicht),
beim Editieren knallt es an der Stelle: FHEM berechnet wohl die Länge von z.B. 😑 nicht richtig

ps: Ich editiere den String so, wie ich ihn aus einem GET bekomme, ohne irgendwas zu de/encoden etc.
FHEM dev + HomeBridge + Lenovo Flex15 + HM-CFG-USB + RFXtrx433 + Fritz!Box 7590/7580/546E

HM Aktor/Sensor/Winmatic/Keymatic/Thermostat, HUE, Netatmo Weather/Security/Heating, Xiaomi AirPurifier/Vacuum, Withings Aura/BPM/Cardio/Go/Pulse/Thermo, VSX828, Harmony, Siro ERB15LE
https://paypal.me/mm0

herrmannj

ein emoji ist kein 'normales' Zeichen. Der emoji ist entweder eine char mit einem Wert > 255 (unicode) oder eine Sequenz von mehreren Zeichen < 256 (utf-8). Im ersten Fall knallt. Du musst $data in utf-8 konvertieren. perl ist da etwas tricky.

Markus M.

Zitat von: herrmannj am 19 August 2019, 23:21:06
ein emoji ist kein 'normales' Zeichen. Der emoji ist entweder eine char mit einem Wert > 255 (unicode) oder eine Sequenz von mehreren Zeichen < 256 (utf-8). Im ersten Fall knallt. Du musst $data in utf-8 konvertieren. perl ist da etwas tricky.
Vermutlich ersteres.
Wie konvertiere ich denn das nun so, dass es funktioniert?
encode_utf8($data) -> nope :(
FHEM dev + HomeBridge + Lenovo Flex15 + HM-CFG-USB + RFXtrx433 + Fritz!Box 7590/7580/546E

HM Aktor/Sensor/Winmatic/Keymatic/Thermostat, HUE, Netatmo Weather/Security/Heating, Xiaomi AirPurifier/Vacuum, Withings Aura/BPM/Cardio/Go/Pulse/Thermo, VSX828, Harmony, Siro ERB15LE
https://paypal.me/mm0

Markus M.

Einde Stunde lang versucht herauszufinden, wie ich das hinbekomme könnte. Leider nicht wirklich weitergekommen...
Wenn jemand einen Weg kennt wäre ich für Hinweise dankbar.
Der Backup-Plan ist mittlerweile, das selbst zu berechnen. Ich kenne den Wert aus dem GET und weiss, wieviele Zeichen ich ändere bzw. lösche.

Wenn ich nun manuell eine Content-Length im Header angebe, packt mir HttpUtils aber leider nochmal eine dazu und mein schöner Plan geht nicht auf.
Kannst das angepasst werden?
Also Content-Length Header nur durch HttpUtils berechnen, wenn noch nicht vordefiniert?

Grüsse, Markus
FHEM dev + HomeBridge + Lenovo Flex15 + HM-CFG-USB + RFXtrx433 + Fritz!Box 7590/7580/546E

HM Aktor/Sensor/Winmatic/Keymatic/Thermostat, HUE, Netatmo Weather/Security/Heating, Xiaomi AirPurifier/Vacuum, Withings Aura/BPM/Cardio/Go/Pulse/Thermo, VSX828, Harmony, Siro ERB15LE
https://paypal.me/mm0

rudolfkoenig

ZitatEinde Stunde lang versucht herauszufinden, wie ich das hinbekomme könnte. Leider nicht wirklich weitergekommen...
Wenn ich nach "perl unicode to utf8" suche, kommt relativ frueh https://perldoc.perl.org/Encode/Unicode.html
Ich wundere mich aber, wie du ueberhaupt an die interne Unicode Darstellung gekommen bist.

Zitat$data gibt es schlicht nicht, der Server schickt nur einen Header als Status zurück.
Es ging um gesendete Daten, und da gibt es mindestens den HTTP Header zum senden.

Markus M.

Zitat von: rudolfkoenig am 20 August 2019, 11:45:33
Wenn ich nach "perl unicode to utf8" suche, kommt relativ frueh https://perldoc.perl.org/Encode/Unicode.html
Ich wundere mich aber, wie du ueberhaupt an die interne Unicode Darstellung gekommen bist.
Die Daten bekomme ich durch das GET auf den Kalenderinhalt.
Auch mit dem Link komme ich leider nirgendwohin, mein Verständnis von Perl und insbesondere dem Encoding reicht dazu nicht aus.
Ich schaffe es zwar, das Encoding so zu verbiegen dass das Emoticon futsch ist, aber nicht es zu erhalten und wieder an den Server zu schicken.
Was kann ich noch ausprobieren?

ZitatEs ging um gesendete Daten, und da gibt es mindestens den HTTP Header zum senden.
Das war etwas durcheinander: Der Fehler entsteht anscheinend beim Return, weil der Server einen Status 400 ohne Body (aber mit Header Content-Length: 0) sendet.
FHEM dev + HomeBridge + Lenovo Flex15 + HM-CFG-USB + RFXtrx433 + Fritz!Box 7590/7580/546E

HM Aktor/Sensor/Winmatic/Keymatic/Thermostat, HUE, Netatmo Weather/Security/Heating, Xiaomi AirPurifier/Vacuum, Withings Aura/BPM/Cardio/Go/Pulse/Thermo, VSX828, Harmony, Siro ERB15LE
https://paypal.me/mm0

Markus M.

Mittlerweile bin ich durch zu viele nicht funktionierende Iterationen selbst etwas verwirrt.

Nochmal ganz konkret:
Ich speichere den Inhalt des Files im Anhang in einer Variable ab.
Die möchte ich editieren und wieder an den Server schicken.
Und zwar so, dass die 🍋 erhalten bleibt.

Ich schaffe es aber nicht, ein passendes Encoding samt Content-Length hinzubekommen, egal wie ich das Encoding verdrehe.

Kann jemand weiterhelfen?
FHEM dev + HomeBridge + Lenovo Flex15 + HM-CFG-USB + RFXtrx433 + Fritz!Box 7590/7580/546E

HM Aktor/Sensor/Winmatic/Keymatic/Thermostat, HUE, Netatmo Weather/Security/Heating, Xiaomi AirPurifier/Vacuum, Withings Aura/BPM/Cardio/Go/Pulse/Thermo, VSX828, Harmony, Siro ERB15LE
https://paypal.me/mm0

Markus M.

Ok, was letztendlich funktioniert hat, ist folgendes:

Ich nehme den String $data den ich aus dem GET bekomme
Ich verwende für die Content-Length des Requests length($data)
Ich verwende für den Request selbst allerdings decode_utf8($data)
Damit passt alles zusammen und die Emojis bleiben erhalten.

Könnte ich bitte folgenden Patch (sinngemäß) in HttpUtils bekommen?
Index: HttpUtils.pm
===================================================================
--- HttpUtils.pm (revision 20002)
+++ HttpUtils.pm (working copy)
@@ -1,4 +1,4 @@
-##############################################
+##############################################
# $Id$
package main;

@@ -571,7 +571,7 @@
                    $hash->{header} =~ /^Authorization:\s*Digest/mi));
   $hdr .= $hash->{header}."\r\n" if($hash->{header});
   if(defined($data) && length($data) > 0) {
-    $hdr .= "Content-Length: ".length($data)."\r\n";
+    $hdr .= "Content-Length: ".length($data)."\r\n" if(!defined($hash->{header}) || $hash->{header} !~ /Content-Length: /);
     $hdr .= "Content-Type: application/x-www-form-urlencoded\r\n"
                 if ($hdr !~ "Content-Type:");
   }

Damit kann man den Content-Length Header optional selbst berechnen, aktuell wird der immer (auch doppelt) gesetzt.

Viele Grüße, Markus
FHEM dev + HomeBridge + Lenovo Flex15 + HM-CFG-USB + RFXtrx433 + Fritz!Box 7590/7580/546E

HM Aktor/Sensor/Winmatic/Keymatic/Thermostat, HUE, Netatmo Weather/Security/Heating, Xiaomi AirPurifier/Vacuum, Withings Aura/BPM/Cardio/Go/Pulse/Thermo, VSX828, Harmony, Siro ERB15LE
https://paypal.me/mm0

rudolfkoenig

ZitatIch nehme den String $data den ich aus dem GET bekomme
Das impliziert, dass GET unicode zurueckliefert.
Ich vermute eher, dass GET utf-8 zurueckliefert, beim Editieren (s.o.) der Texteditor unicode speichert, und deswegen die Daten als unicode in FHEM lenden.

ZitatIch verwende für die Content-Length des Requests length($data)
Ich verwende für den Request selbst allerdings decode_utf8($data)
Laut https://tools.ietf.org/html/rfc2616#section-14.13 ist Content-Length in Octets zu berechnen, was length($data) macht, falls $data aus 1-Byte Elementen besteht (vulgo: kein unicode ist, sondern z.Bsp. utf-8).

Ich gehe von einem Bug auf dem Server aus, allerdings spricht diese Aussage dagegen:
ZitatAm Inhalt scheint es nicht zu liegen, denn wenn ich den gleichen Content in Postman abschicke, funktioniert das Ganze.

Ich habe jetzt HttpUtils.pm angepasst, damit _alle_ Headerzeilen ueberschreibbar sind, wuerde mich aber freuen, wenn mir jemand die Ursache des Problems erklaeren koennte.

Markus M.

Zitat von: rudolfkoenig am 21 August 2019, 07:39:34Ich vermute eher, dass GET utf-8 zurueckliefert, beim Editieren (s.o.) der Texteditor unicode speichert, und deswegen die Daten als unicode in FHEM landen.
Mir fehlt denke ich immer noch das Verständnis der kompletten Encoding Thematik, aber ich hab noch ein paar Informationen:
Was ich aus dem GET bekomme, kann ich erst nach decode_utf8 in Readings anzeigen, ansonsten habe ich statt dem Emoticon nur Zeichensalat.
Der würde wenn ich mich recht erinnere aber immerhin korrekt berechnet.
Da ist kein Texteditor oder andere externe Bearbeitung zwischendrin, nur ein Regex Replacement im Modul.
In was genau wandle ich den UTF-8 String dabei dann um? Eine andere Möglichkeit die Emojis zu erhalten, habe ich nicht gefunden.

ZitatIch gehe von einem Bug auf dem Server aus, allerdings spricht diese Aussage dagegen
Mit dem Inhalt meinte ich nur den VTODO Datensatz der .ics Datei, nicht das Encoding.
So bin ich überhaupt erst drauf gekommen, dass die Header Daten das Problem verursachen müssen.

ZitatIch habe jetzt HttpUtils.pm angepasst, damit _alle_ Headerzeilen ueberschreibbar sind, wuerde mich aber freuen, wenn mir jemand die Ursache des Problems erklaeren koennte.
Danke! Teste ich wenn ich am Wochenende wieder zuhause bin.
FHEM dev + HomeBridge + Lenovo Flex15 + HM-CFG-USB + RFXtrx433 + Fritz!Box 7590/7580/546E

HM Aktor/Sensor/Winmatic/Keymatic/Thermostat, HUE, Netatmo Weather/Security/Heating, Xiaomi AirPurifier/Vacuum, Withings Aura/BPM/Cardio/Go/Pulse/Thermo, VSX828, Harmony, Siro ERB15LE
https://paypal.me/mm0

Markus M.

Die Änderung funktioniert wie gedacht, Danke!

Bekomme leider immer noch einen Fehler bei manchen Serverantworten.
Die bestehen jeweils nur aus einem Header, Content-Length ist nicht gesetzt.
2019.08.25 22:48:10 1: PERL WARNING: substr outside of string at /opt/fhem/FHEM/HttpUtils.pm line 629.
2019.08.25 22:48:10 1: stacktrace:
2019.08.25 22:48:10 1:     main::__ANON__                      called by /opt/fhem/FHEM/HttpUtils.pm (629)
2019.08.25 22:48:10 1:     main::__ANON__                      called by /opt/fhem/fhem.pl (755)
2019.08.25 22:48:10 1: PERL WARNING: Use of uninitialized value $data in numeric eq (==) at /opt/fhem/FHEM/HttpUtils.pm line 630.
2019.08.25 22:48:10 1: stacktrace:
2019.08.25 22:48:10 1:     main::__ANON__                      called by /opt/fhem/FHEM/HttpUtils.pm (630)
2019.08.25 22:48:10 1:     main::__ANON__                      called by /opt/fhem/fhem.pl (755)




Beheben konnte ich es so:
Index: FHEM/HttpUtils.pm
===================================================================
--- FHEM/HttpUtils.pm (revision 20061)
+++ FHEM/HttpUtils.pm (working copy)
@@ -1,4 +1,4 @@
-##############################################
+##############################################
# $Id$
package main;

@@ -626,7 +626,7 @@
         HttpUtils_Close($hash);
         return $hash->{callback}($hash, "write error: $err", undef)
       }
-      $data = substr($data,$ret);
+      $data = ($ret<=length($data))?substr($data,$ret):"";
       if(length($data) == 0) {
         shutdown($hash->{conn}, 1) if($s);
         delete($hash->{directWriteFn});
FHEM dev + HomeBridge + Lenovo Flex15 + HM-CFG-USB + RFXtrx433 + Fritz!Box 7590/7580/546E

HM Aktor/Sensor/Winmatic/Keymatic/Thermostat, HUE, Netatmo Weather/Security/Heating, Xiaomi AirPurifier/Vacuum, Withings Aura/BPM/Cardio/Go/Pulse/Thermo, VSX828, Harmony, Siro ERB15LE
https://paypal.me/mm0

rudolfkoenig

Der Patch prueft, ob $ret aus
Zitatmy $ret = syswrite $hash->{conn}, $data;
nicht groesser ist, als length($data).

Ich sehe zwei Moeglichkeiten:
- einen Bug im perl/libc/kernel (bei sowas Zentrales wie syswrite extrem unwahrscheinlich)
- $data ist unicode (und nicht UTF-8), syswrite unterstuetzt in deiner Perl Version unicode Strings (bisher ist perl/FHEM im syswrite mit sowas abgestuerzt), und im $ret wird die UTF-8 Laenge zurueckgeliefert. Wenn das stimmt, dann deckt der Patch nicht alle Faelle ab: falls $data 2 Emojis als unicode enthaelt, dann ist length($data)=2, und es muessen 6 UTF-8 Bytes geschrieben werden. Falls syswrite nur 1 Emoji (3 Bytes, $ret == 3) ausgeliefert hat, dann werden mit dem Patch die letzten 3 Bytes (das zweite Emoji) nie versendet.

Ich bin der Ansicht, dass die Ursache gefixt werden muss ($data, in diesem Fall das HTTP Header muss UTF-8 sein, und nicht unicode), und nicht die Symptome versteckt werden.