laupäev, 11. september 2010

Base64 striimi parsimine

Kuna POP3 protokolli implementeerimine node.js platvormil õnnestus ootamatult hästi, proovisin katsetada ka maailmast kirjade vastuvõtmist SMTP protokolli abil. Ka see õnnestus üsnagi valutult (vähemalt see osa, mis protokolli puutub) - lisasin nimeserverisse MX kirje; kirjutasin lihtsa skripti mis kuulab porti 25 liiklust; skript tunnistas lihtsamaid vajaminevaid käske nagu HELO, DATA jne. Panin seejärel skripti tööle ja üritasin sellele Gmaili'ist kirja saata. Suureks üllatuseks töötas kõik väga kenasti.

Seega tuligi mõte teha valmis juba täismahuline e-maili server. Koos e-posti kontodega, kirjade saatmise ja vastuvõtmisega ning POP3 ja IMAP liidesega kliendi jaoks. Eks paista, kas sellest projektist ka midagi reaalselt välja tuleb, aga hetkel tundub et kõik vajalik on säärase lihtsama projekti jaoks olemas (st. et ei arvesta väga standardiväliste quirkide'ga jne).

Üheks esimeseks probleemiks tekkis kirja vastuvõtul manuste töötlemine. Manused on MIME formaadis kirjades lihtsustatult esitatud nii:

-----suvaline-unikaalne-ääre-tähis-ascii-tähdega+\r\n
Content-Type: text/plain; name="manus.zip"
Content-Disposition: attachment; filename="manus.zip"
Content-Transfer-Encoding: base64

pOb3RlczoNCg0KTmV2ZXIgaW1wbGVtZW50IGEgSlVNUCB0YWcgd2l0aG91dCBhIGNvcnJlc3Bv
bmRpbmcgQUQgdGFnLCBhcyB0aGlzIHdpbGwgcmVzdWx0IGluIG5vIGltcHJlc3Npb25zIG9yIGNs
aWNrcyBiZWluZyBjb3VudGVkIGZvciB0aGUgYXNzb2NpYXRlZCB0YW
[....]
-----suvaline-unikaalne-ääre-tähis-ascii-tähdega+\r\n

Kuna reeglina on manuseks mitte tekstifailid, vaid binaarsed failid, on kõikide baitide probleemivabaks transpordiks kasutusel base64 kodeering. Base64 kasutab vaid 64 võimalikku sümbolit, mis kõik on "prinditavad", mis tähendab et ühe "päris" baidi asemel (kaheksa bitti) saab ühe sümboliga edasi anda vaid 6 bitti (64 = 26).

Probleem tekib suurte manustega - juhul kui manus pole näiteks mitte 2 kB või 100 MB siis kogu selle faili korraga mällu lugemine ja seejärel base64->baitideks teisendamine kõlab üsna kahtlase väärtusega ideena. Seega tuleks teisendus teha juba faili sisselugemisel. Node.JS näiteks ei annagi kogu infot korraga edasi, vaid lõikab sissetuleva info umbkaudu 10 kB suurusteks juppideks.
socket.on("data", function(data){
    console.log(data); // data suurus ei ole suurem kui 10kB
});

Sellise umbmäärase pikkusega lõikude korral aga ei ole base64 dekodeerimine alati võimalik. Oluline on õigest sümbolist alustada ja õigest lõpetada, vastasel korral läheb bitijada nihkesse ja seetõttu tekivad ka baidid väga valed.

Kuidas seda olukorda siis lahendada, nii et saaks dekodeerida base64 andmestriimi?

Väga lihtsalt, kui algus on teada, saab sellest andmeid teisendada 4 baidiste lõikudena ning ülejääk jätta järgmise korrani.
var current = "";
socket.on("data", function(data){
    // NB! tegelikult on andmetüüp mitte string vaid Buffer!
    current += data;
    base64_decode(current.substr(0, current.length-current.length % 4));
    current = current.substr(-current + data.length % 4); // jääk
});

Juhul kui tegu oleks tavalise striimiga, siis piisaks ka määrangust, et striimi kodeeringuks on base64. MIME tüüpi kirja puhul aga on tegu ühe pika stringiga, kus kodeeringud vahelduvad vastavalt kontekstile. Seega tuleb ise jälgida mis andmetega parasjagu tegemist on.

Kommentaare ei ole: