Agent kodiert bei Sonderzeichen im Subject bekanntlich nicht
gerade optimal. Insbesondere kann es Probleme verursachen,
daß das "Re:" mitkodiert wird. Außerdem trennt Agent bei
geändertem Subject die Klammer mit dem alten Subject nicht ab.
Das folgende Script repariert beides. Zur Erinnerung: Mit Korrnews
(ohne Hamster) kann man beides auch erreichen. Näheres weiß
Ralph Bednarski.
Als Kennzeichen für ein geändertes Subject wird nicht nur das
korrekte "(was:" akzeptiert, sondern auch das falsche "(war:".
Wer das nicht will, kann es in der Funktion "Cut" und in der
Variablen "SubjectChange" leicht abstellen.
Das Script kodiert nicht wirklich minimalinvasiv. Es dekodiert
alles, was links vom ersten Wort mit einem Sonderzeichen steht
(also insbesondere immer das "Re:"), und alles, was rechts vom
letzten Wort mit Sonderzeichen steht. Alles, was zwischen
diesem ersten und dem letzten Wort steht, bleibt kodiert.
Die Regeln für das Folding, wie sie in den RFCs 822 und 2047
festgelegt sind, werden weitestgehend eingehalten. In einem
speziellen Fall, der in der Praxis nie eintritt, kann eine Zeile
mit einem "encoded word" knapp über 80 Zeichen lang werden
statt der erlaubten 76. Probleme würden dadurch nicht entstehen.
Ansonsten sind mir keine Bugs bekannt (was nicht heißt, daß
es keine gibt...).
An Zeichensätzen werden außer ASCII nur 8859-1 und 8859-15
unterstützt. Bei anderen Zeichensätzen macht das Script gar
nichts. Falls jemand einen speziellen 8859-x will, könnte ich
das einbauen, bei UTF-7/8 sieht es aber schlecht aus.
Es wird immer der minimale nötige Zeichensatz benutzt. Wenn
bei einem Posting mit 8859-15 durch das Abschneiden eines
alten Subject -15 nicht mehr nötig ist, gibt es also Fallback
auf -1 oder ASCII. Wenn nach dem Abschneiden überhaupt
keine Sonderzeichen mehr da sind, wird auch nichts kodiert.
Wenn kein altes Subject abgeschnitten wird, bleibt der Zeichen-
satz natürlich gleich.
Das Script arbeitet nur bei Subjects bis zu einer Länge von
256 Zeichen, wobei die Brutto-Länge zählt, also die (von Agent)
kodierte Form einschließlich mehrfacher Zeilenumbrüche
und Zeichensatz-Deklaration am Beginn jeder Zeile.
Bei längeren Subjects macht das Script wiederum überhaupt
nichts.
Grund dafür ist, daß WildcardMatch und WildcardMatchReplace
nur mit Strings bis zu dieser Länge funktionieren. Ist sogar
dokumentiert, also ausnahmsweise kein Bug, sondern "nur"
eine ärgerliche Einschränkung. In der Praxis kommen so lange
Subjects aber sowieso recht selten vor.
Vorsicht, Falle: Das Script sollte mit allem zurechtkommen,
was Agent automatisch erzeugt. Wenn man aber ein Subject
manuell kodiert und dabei einen Fehler macht, dann kann es
schiefgehen. Folgendes einfache Subject beispielweise:
=?ISO-8859-1?Q?=?=
schickt das Script in eine Endlosschleife, aus der man es nur
erlösen kann, indem man MTQ abschießt. Was unter Win98
gar nicht so einfach ist, da er da bekanntlich nicht im Task-
manager erscheint.
Solche falsch formatierte "encoded words" sind von RFC 2047
aber ausdrücklich verboten:
In addition, any 'word' within a 'phrase' that begins with "=?"
and ends with "?=" must be a valid 'encoded-word'.
Wer also auf diese Weise das Script leimt, der hat halt Pech
gehabt. Und noch ein Hinweis: Agent verwandelt beim Antworten
sämtliche TABs in einem vorhandenen Subject in SPACEs. Das
Script macht es genauso (nicht aus Nachahmungstrieb, sondern
aus einem echten Grund). Wer also manuell ein =09 hineinkodiert,
der wird es nicht wiederfinden.
So, da isses nun. Damit durch Zeilenumbrüche bei c&p nichts
schiefgeht, in voller Breite:
Define("Unfold",
While(
WildcardMatch(subject, "*?= " ++ charset ++ "*"),
subject := WildcardMatchReplace(subject, "*?= " ++ charset ++ "*", "$1" ++ "$6")
)
);
Define("Umlaut",
Do(
UmlString := $1,
While(
WildcardMatch(UmlString, "*=09*"),
UmlString := WildcardMatchReplace(UmlString, "*=09*", "$1" ++ "$2")
),
UmlString := WildcardMatchReplace(UmlString, "*=09", "$1"),
While(
WildcardMatch(UmlString, "*=3D*"),
UmlString := WildcardMatchReplace(UmlString, "*=3D*", "$1" ++ "$2")
),
UmlString := WildcardMatchReplace(UmlString, "*=3D", "$1"),
While(
WildcardMatch(UmlString, "*=3F*"),
UmlString := WildcardMatchReplace(UmlString, "*=3F*", "$1" ++ "$2")
),
UmlString := WildcardMatchReplace(UmlString, "*=3F", "$1"),
While(
WildcardMatch(UmlString, "*=5F*"),
UmlString := WildcardMatchReplace(UmlString, "*=5F*", "$1" ++ "$2")
),
UmlString := WildcardMatchReplace(UmlString, "*=5F", "$1"),
WildcardMatch(UmlString, "*=*")
)
);
Define("Decode",
Do(
DecString := $1,
While(
WildcardMatch(DecString, "*=09*"),
DecString := WildcardMatchReplace(DecString, "*=09*", "$1" ++ " " ++ "$2")
),
DecString := WildcardMatchReplace(DecString, "*=09", "$1" ++ " "),
While(
WildcardMatch(DecString, "*_*"),
DecString := WildcardMatchReplace(DecString, "*_*", "$1" ++ " " ++ "$2")
),
DecString := WildcardMatchReplace(DecString, "*_", "$1" ++ " "),
While(
WildcardMatch(DecString, "*=3D*"),
DecString := WildcardMatchReplace(DecString, "*=3D*", "$1" ++ "=" ++ "$2")
),
DecString := WildcardMatchReplace(DecString, "*=3D", "$1" ++ "="),
While(
WildcardMatch(DecString, "*=3F*"),
DecString := WildcardMatchReplace(DecString, "*=3F*", "$1" ++ "?" ++ "$2")
),
DecString := WildcardMatchReplace(DecString, "*=3F", "$1" ++ "?"),
While(
WildcardMatch(DecString, "*=5F*"),
DecString := WildcardMatchReplace(DecString, "*=5F*", "$1" ++ "_" ++ "$2")
),
DecString := WildcardMatchReplace(DecString, "*=5F", "$1" ++ "_"),
DecString
)
);
Define("TrailingSpace",
Do(
TrSpaceString := $1,
n := Length(TrSpaceString),
LastChar := SubStr(TrSpaceString, n, 1),
While(
LastChar = $2,
Do(
TrSpaceString := SubStr(TrSpaceString, 1, n-1),
n := n-1,
LastChar := SubStr(TrSpaceString, n, 1)
)
),
TrSpaceString
)
);
Define("Cut",
Do(
While(
WildcardMatch(subject, "*(war:*"),
subject := WildcardMatchReplace(subject, "*(war:*", "$1")
),
While(
WildcardMatch(subject, "*(was:*"),
subject := WildcardMatchReplace(subject, "*(was:*", "$1")
)
)
);
Define("CutOff",
If(
SubjectChange & (WildcardMatch(subject, "=?ISO-8859-1?Q?Re:_*") |
WildcardMatch(subject, "=?ISO-8859-15?Q?Re:_*")),
Do(
Cut(),
subject := TrailingSpace(subject, "_"),
subj := WildcardMatchReplace(subject, charset ++ "Re:*", "$4"),
If(
Umlaut(subj),
subject := charset ++ "Re:" ++ subj ++ "?=",
subject := "Re:" ++ Decode(subj)
)
)
)
);
Define("MinCode",
Do(
MinCodeString := $1,
MinCodeString := WildcardMatchReplace(MinCodeString, charset ++ "*?=", "$4"),
While(
WildcardMatch(MinCodeString, "*=09*"),
MinCodeString := WildcardMatchReplace(MinCodeString, "*=09*", "$1" ++ "_" ++ "$2")
),
MinCodeString := WildcardMatchReplace(MinCodeString, "*=09", "$1" ++ "_"),
MinCodeString := TrailingSpace(MinCodeString, "_"),
lgth := Length(MinCodeString),
i := 1,
While(
Params(MinCodeString, "_", i) <> "",
Do(
wordlist := wordlist ++ Params(MinCodeString, "_", i) ++ " ",
wordlist := WildcardMatchReplace(wordlist, "* *", "$1" ++ " #" ++ "$2"),
i := i+1
)
),
wordlist := WildcardMatchReplace(wordlist, "*", "#" ++ "$1"),
n := ListCount(wordlist),
j := 1,
k := n,
While(
Not(Umlaut(Params(MinCodeString, "_", j))),
j := j+1
),
While(
Not(Umlaut(Params(MinCodeString, "_", k))),
k := k-1
),
lword := Params(MinCodeString, "_", j),
rword := Params(MinCodeString, "_", k),
If(
j > 1,
Do(
If(
j = n,
left := WildcardMatchReplace(MinCodeString, "*" ++ lword, "$1"),
Do(
left := MinCodeString,
While(
WildcardMatch(left, "*" ++ lword ++ "*"),
left := WildcardMatchReplace(left, "*" ++ lword ++ "*", "$1")
)
)
),
left := Decode(left),
),
left := ""
),
llgth := Length(left) + Length(lword),
If(
k < n,
Do(
If(
k = 1,
right := WildcardMatchReplace(MinCodeString, rword ++ "*", "$1"),
Do(
right := MinCodeString,
While(
WildcardMatch(right, "*" ++ rword ++ "*"),
right := WildcardMatchReplace(right, "*" ++ rword ++ "*", "$2")
)
)
),
right := Decode(right),
),
right := ""
),
rlgth := Length(right) + Length(rword),
l := llgth + 1,
m := (lgth - llgth) - rlgth,
middle := SubStr(MinCodeString, l, m),
If(
j = k,
MinCodeString := left ++ NewCharset ++ rword ++ "?=" ++ right
),
If(
j <> k,
MinCodeString := left ++ NewCharset ++ lword ++ middle ++ rword ++ "?=" ++ right
)
)
);
Define("UncodedFold",
Do(
UncodedFoldString := $1,
UncodedFoldLgth := $2,
SubStr(UncodedFoldString, 1, UncodedFoldLgth) ++ "\n" ++
SubStr(UncodedFoldString, UncodedFoldLgth + 1, Length(UncodedFoldString) - UncodedFoldLgth)
)
);
Define("CodedFold",
Do(
CodedFoldString := $1,
CodedFoldLgth := $2,
SubStr(CodedFoldString, 1, CodedFoldLgth) ++ "?=" ++ "\n " ++ NewCharset ++
SubStr(CodedFoldString, CodedFoldLgth + 1, Length(CodedFoldString) - CodedFoldLgth)
)
);
Define("WordBoundary",
Do(
wbound := $1,
Repeat(
wbound := wbound - 1,
(SubStr(MinCodeString, wbound, 1) <> " ") & ((SubStr(MinCodeString, wbound + 1, 1) = " ") |
(SubStr(MinCodeString, wbound + 1, 1) = ""))
),
wbound
)
);
Define("ByteBoundary",
Do(
bbound := $1,
Repeat(
bbound := bbound - 1,
(SubStr(MinCodeString, bbound, 1) <> "=") & (SubStr(MinCodeString, bbound - 1, 1) <> "=")
),
bbound
)
);
Define("Fold",
Do(
lgth := Length(MinCodeString),
lgthl := Length(left),
lgthr := Length(right),
lgthm := (lgth - lgthl) - lgthr,
LeftTrailingSpace := 0,
LTrSpIdx := lgthl,
While(
(Substr(left, LTrSpIdx, 1) = " ") | (Substr(left, LTrSpIdx, 1) = " "),
Do(
LeftTrailingSpace := LeftTrailingSpace + 1,
LTrSpIdx := LTrSpIdx - 1
)
),
If(
lgth > 67,
Do(
If(
lgthl + lgthm < 68,
Do(
eol := WordBoundary(68),
MinCodeString := UncodedFold(MinCodeString, eol)
)
),
If(
(lgthl > 45) & (lgthl + lgthm > 67),
Do(
RightIsFolded := "FALSE",
eol := WordBoundary(lgthl + lgthm),
MinCodeString := UncodedFold(MinCodeString, eol),
If(
lgthm + LeftTrailingSpace > 76,
Do(
If(
LeftTrailingSpace < 56,
eol := ByteBoundary((lgthl - LeftTrailingSpace) + 77),
eol := ByteBoundary(lgthl + 22)
),
If(
lgthm > 21,
MinCodeString := CodedFold(MinCodeString, eol),
Do(
eol := WordBoundary(eol + 3),
MinCodeString := UncodedFold(MinCodeString, eol),
RightIsFolded := "TRUE"
)
),
)
),
If(
lgthm + LeftTrailingSpace > 132,
Do(
eol := ByteBoundary((lgthl - LeftTrailingSpace) + 153),
MinCodeString := CodedFold(MinCodeString, eol)
)
),
If(
(lgthr > 0) & NOT(RightIsFolded),
Do(
eol := WordBoundary(eol + 79),
If(
SubStr(MinCodeString, eol + 1, 1) = " ",
MinCodeString := UncodedFold(MinCodeString, eol)
)
)
)
)
),
If(
(lgthl < 46) & (lgthl + lgthm > 67),
Do(
eol := ByteBoundary(66),
MinCodeString := CodedFold(MinCodeString, eol),
If(
lgthl + lgthm > 123,
Do(
eol := ByteBoundary(142),
MinCodeString := CodedFold(MinCodeString, eol)
)
),
If(
lgthl + lgthm > 180,
Do(
eol := ByteBoundary(218),
MinCodeString := CodedFold(MinCodeString, eol)
)
),
If(lgthr > 0,
Do(
eol := WordBoundary(eol + 79),
If(
SubStr(MinCodeString, eol + 1, 1) = " ",
MinCodeString := UncodedFold(MinCodeString, eol)
)
)
)
)
)
)
)
)
);
Define("CharsetChange",
If(
charset = "=?ISO-8859-15?Q?",
Do(
NewCharset := "=?ISO-8859-1?Q?",
CharList := "=A4, =A6, =A8, =B4, =B8, =BC, =BD, =BE",
For(
CharList, "char",
If(
WildcardMatch(subject, "*" ++ char ++ "*"),
NewCharset := "=?ISO-8859-15?Q?"
)
)
),
NewCharset := charset
)
);
Define("Coded",
WildcardMatch(subject, "=?ISO-8859-1?Q*=") | WildcardMatch(subject, "=?ISO-8859-15?Q*=")
);
subject := Header("Subject");
SubLgth := Length(subject);
SubjectChange := WildcardMatch(subject, "*(war:*") | WildcardMatch(subject, "*(was:*");
If(
Not(Coded()) & (SubLgth < 257),
If(
SubjectChange & WildcardMatch(subject, "Re: *"),
Do(
Cut(),
subject := TrailingSpace(subject, " "),
SetHeader("Subject", subject)
)
)
);
If(
Coded() & (SubLgth < 257),
Do(
If(
WildcardMatch(subject, "=?ISO-8859-15*"),
charset := "=?ISO-8859-15?Q?",
charset := "=?ISO-8859-1?Q?"
),
Unfold(),
CutOff(),
If(
Coded(),
Do(
CharsetChange(),
MinCode(subject),
Fold(),
SetHeader("Subject", MinCodeString)
)
)
)
)
>Kurz bevor hier das Licht ausgeht
Besten Dank. :-)
--
Sven
>>Agent kodiert bei Sonderzeichen im Subject bekanntlich nicht
>>gerade optimal. Insbesondere kann es Probleme verursachen,
>>daß das "Re:" mitkodiert wird.
>
>Das ist etwas weas ich nie verstanden habe. Was für Probleme und wer
>stirbt davon? Will sagen, bisher habe ich noch nichts nachteiliges
>bemerkt. Trotzdem: Schönes Script. :-)
Es könnte sein, daß ein Newsreader beim Antworten auf einen
von Agent kodierten Artikel ein mitkodiertes "Re:" nicht erkennt
und so das "Re:" verdoppelt. Ich weiß zwar nicht, ob es momentan
einen Newsreader mit diesem Problem gibt, aber Subjects mit
doppeltem oder dreifachem "Re:" sind mir jedenfalls schon genug
untergekommen.
Auf jeden Fall real ist das Problem mit Mailinglisten. Bei vielen
setzt der Server ein Tag ins Subject, und bei Antworten muß
das *hinter* dem "Re:" geschehen, sonst würde bei der nächsten
Antwort wieder ein neues "Re:" davorgesetzt, dann ein neues Tag,
usw.
Wenn das "Re:" mitkodiert wird, sollte ein korrekt konfigurierter
Mailinglistenserver trotzdem damit zurechtkommen. Aber nicht
jeder ist korrekt konfiguriert...
Ein übles Beispiel sind die Listen bei Yahoo aka Egroups aka
Onelist. In der Hamsterliste gab es dieses schöne Beispiel:
Subject: [usehamsternet] Re: [usehamsternet] Re: [usehamsternet]
Re: [usehamsternet] Re: [usehamsternet] Re: [usehamsternet] Re:
[usehamsternet] Hamster stürzt ab
Wie gesagt, dieses Beispiel ist nicht erfunden, sondern echt. Die
Schuld an diesem Monstrum teilen sich die kaputte Kodierung
durch Agent und unfähige Admins bei der Liste.
>[Subjects mit einer Länge von 256 Zeichen]
>>dokumentiert, also ausnahmsweise kein Bug, sondern "nur"
>>eine ärgerliche Einschränkung. In der Praxis kommen so lange
>>Subjects aber sowieso recht selten vor.
>
>Ärgerliche Einschränkung? Das Subject ist imho normalerweise nicht dazu
>gedacht den Text aus dem Body aufzunehmen.
Stimmt, aber die Einschränkung gilt ja nicht nur für Subjects, sondern
immer bei WildcardMatch(Replace). Und dann kann es schon ärgerlich
sein, daß bei 256 Schluß ist.
Manfred
Ich schrieb:
>Ansonsten sind mir keine Bugs bekannt (was nicht heißt, daß
>es keine gibt...).
Und schon hab ich gleich 2 gefunden.
>Define("Decode",
> Do(
> DecString := $1,
> While(
> WildcardMatch(DecString, "*=09*"),
> DecString := WildcardMatchReplace(DecString, "*=09*", "$1" ++ " " ++ "$2")
> ), ^^^
Hier bitte 2 von den 3 Spaces streichen. Der Bug war übrigens
in der Originalversion nicht vorhanden und ist durch einen
Bug meines Editors (Proton) reingerutscht, der ungefragt Tabs
in Spaces verwandelt. Benutzt hier zufällig jemand Proton
und weiß, ob das in einer neueren Version korrigiert wurde?
Ich habe 3.0 beta3.
>Define("MinCode",
> [...]
Den ganzen Abschnitt durch folgenden ersetzen:
lword := Params(MinCodeString, "_", j),
rword := Params(MinCodeString, "_", k),
diffll := 0,
difflr := 0,
If(
j > 1,
Do(
If(
j = n,
left := WildcardMatchReplace(MinCodeString, "*" ++ lword, "$1"),
Do(
left := MinCodeString,
While(
WildcardMatch(left, "*" ++ lword ++ "*"),
left := WildcardMatchReplace(left, "*" ++ lword ++ "*", "$1")
)
)
),
ll1 := length(left),
left := Decode(left),
ll2 := length(left),
diffll := ll1 - ll2
),
left := ""
),
llgth := Length(left) + Length(lword),
If(
k < n,
Do(
If(
k = 1,
right := WildcardMatchReplace(MinCodeString, rword ++ "*", "$1"),
Do(
right := MinCodeString,
While(
WildcardMatch(right, "*" ++ rword ++ "*"),
right := WildcardMatchReplace(right, "*" ++ rword ++ "*", "$2")
)
)
),
lr1 := length(right),
right := Decode(right),
lr2 := length(right),
difflr := lr1 - lr2
),
right := ""
),
rlgth := Length(right) + Length(rword),
l := (llgth + 1) + diffll,
m := ((lgth - llgth) - rlgth) - (diffll + difflr),
middle := SubStr(MinCodeString, l, m),
Wir danken für Ihre Aufmerksamkeit. ;-)
Manfred