diff --git a/include/lgi/common/Base64.h b/include/lgi/common/Base64.h --- a/include/lgi/common/Base64.h +++ b/include/lgi/common/Base64.h @@ -1,50 +1,50 @@ /// \file /// \author Matthew Allen /// \created 1/6/98 /// \brief Base64 encoding/decoding #ifndef __BASE64_H_ #define __BASE64_H_ #include "LgiDefs.h" #include "StringClass.h" // These buffer length macros round up to the nearest block of // bytes. So take the value returned by the convert routine as // the actual value... but use these to allocate buffers. #define BufferLen_64ToBin(l) ( ((l)*3)/4 ) #define BufferLen_BinTo64(l) ( ((((l)+2)/3)*4) ) // Character Conversion Routines // // Format of Base64 char: // 7 0 // |-|-|-|-|-|-|-|-| // |-U-|--- Data --| // // Data = Bits, 0-63 // U = Unused, must be 0 // LgiFunc uchar Base64ToBin(char c); LgiFunc char BinToBase64(uchar c); // String Conversion Routines // // Arguments: // Binary: Pointer to binary buffer // OutBuf: Size of output buffer (in bytes) // Base64: Pointer to Base64 buffer // InBuf: Size of input buffer (in bytes) // // Returns: // Number of bytes converted. // LgiFunc ssize_t ConvertBase64ToBinary(uchar *Binary, ssize_t OutBuf, const char *Base64, ssize_t InBuf); LgiFunc ssize_t ConvertBinaryToBase64(char *Base64, ssize_t OutBuf, const uchar *Binary, ssize_t InBuf); /// LString wrapper functions... LgiExtern LString LToBase64(LString bin); -LgiExtern LString LToBase64(void *binary, size_t length); +LgiExtern LString LToBase64(const void *binary, size_t length); LgiExtern LString LToBinary(LString base64); -LgiExtern LString LToBinary(void *base64, size_t length); +LgiExtern LString LToBinary(const void *base64, size_t length); #endif diff --git a/include/lgi/common/TextConvert.h b/include/lgi/common/TextConvert.h --- a/include/lgi/common/TextConvert.h +++ b/include/lgi/common/TextConvert.h @@ -1,15 +1,16 @@ #pragma once bool Is8Bit(const char *Text); [[deprecated]] char *DecodeBase64Str(char *Str, ssize_t Len = -1); LString LDecodeBase64Str(LString Str); [[deprecated]] char *DecodeQuotedPrintableStr(char *Str, ssize_t Len = -1); -LString LDecodeQuotedPrintableStr(LString Str); +LString LDecodeQuotedPrintable(LString Str); +LString LEncodeQuotedPrintable(LString Str, int MaxLine = 76, int PreCount = 0); [[deprecated]] char *DecodeRfc2047(char *Str); LString LDecodeRfc2047(LString Str); [[deprecated]] char *EncodeRfc2047(char *Input, const char *InCharset, LString::Array *OutCharsets = NULL, ssize_t LineLength = 0); LString LEncodeRfc2047(LString Input, const char *InCharset, LString::Array *OutCharsets = NULL, ssize_t LineLength = 0); diff --git a/include/lgi/common/vCard-vCal.h b/include/lgi/common/vCard-vCal.h --- a/include/lgi/common/vCard-vCal.h +++ b/include/lgi/common/vCard-vCal.h @@ -1,123 +1,123 @@ #ifndef _VCARD_VCAL_H #define _VCARD_VCAL_H // #include "ScribeDefs.h" #include "Store3.h" #define FIELD_PERMISSIONS 2000 class VIoPriv; class VIo { public: struct Parameter { LString Field; LString Value; void Set(const char *f, const char *v) { Field = f; Value = v; } }; struct TimeZoneSection { int From, To; LDateTime Start; LString Rule; }; struct TimeZoneInfo { LString Name; TimeZoneSection Normal; TimeZoneSection Daylight; }; class ParamArray : public LArray { public: ParamArray(const char *Init = NULL) { if (Init) { LString s = Init; LString::Array a = s.SplitDelimit(","); for (unsigned i=0; i= 'A' && c <= 'Z') { return c - 'A'; } if (c >= 'a' && c <= 'z') { return c - 'a' + 26; } if (c >= '0' && c <= '9') { return c - '0' + 52; } if (c == '+') { return 62; } if (c == '/') { return 63; } return 0; } char BinToBase64(uchar c) { if (c <= 25) { return c + 'A'; } if (c >= 26 && c <= 51) { return c - 26 + 'a'; } if (c >= 52 && c <= 61) { return c - 52 + '0'; } if (c == 62) { return '+'; } if (c == 63) { return '/'; } return 0; } ssize_t ConvertBase64ToBinary(uchar *Binary, ssize_t OutBuf, const char *Base64, ssize_t InBuf) { uchar *Start = Binary; if (!HasBase64ToBinLut) { for (int i=0; i 0 && Base64 && InBuf > 0) { int Temp[4]; while (InBuf > 3 && OutBuf > 0) { Temp[0] = Base64ToBinLut[Base64[0] & 0x7f]; Temp[1] = Base64ToBinLut[Base64[1] & 0x7f]; Temp[2] = Base64ToBinLut[Base64[2] & 0x7f]; Temp[3] = Base64ToBinLut[Base64[3] & 0x7f]; *Binary++ = (Temp[0] << 2) | (Temp[1] >> 4); OutBuf--; if (Base64[2] != '=') { LAssert(OutBuf > 0); *Binary++ = (Temp[1] << 4) | (Temp[2] >> 2); OutBuf--; if (Base64[3] != '=') { LAssert(OutBuf > 0); *Binary++ = (Temp[2] << 6) | (Temp[3]); OutBuf--; } else break; } else break; Base64 += 4; InBuf -= 4; } } return Binary - Start; } LString LToBinary(LString base64) { return LToBinary(base64.Get(), base64.Length()); } -LString LToBinary(void *base64, size_t length) +LString LToBinary(const void *base64, size_t length) { LString bin; if (bin.Length(BufferLen_64ToBin(length))) { auto out = ConvertBase64ToBinary((uchar*)bin.Get(), bin.Length(), (char*)base64, length); bin.Get()[out] = 0; // NULL term for debugging. } return bin; } ssize_t ConvertBinaryToBase64(char *Base64, ssize_t OutBuf, const uchar *Binary, ssize_t InBuf) { char *Start = Base64; if (!HasBinToBase64Lut) { for (int i=0; i 0 && Base64 && InBuf > 0) { ssize_t i = InBuf / 3; ssize_t o = OutBuf / 4; ssize_t Segments = MIN(i, o); for (int n=0; n> 2]; Base64[1] = BinToBase64Lut[((Binary[0] & 3) << 4) | (Binary[1] >> 4)]; Base64[2] = BinToBase64Lut[((Binary[1] & 0xF) << 2) | (Binary[2] >> 6)]; Base64[3] = BinToBase64Lut[Binary[2] & 0x3F]; Base64 += 4; Binary += 3; } InBuf -= Segments * 3; OutBuf -= Segments << 2; if (OutBuf > 3 && InBuf > 0) { *Base64++ = BinToBase64Lut[Binary[0] >> 2]; InBuf--; if (InBuf > 0) { *Base64++ = BinToBase64Lut[((Binary[0] & 3) << 4) | (Binary[1] >> 4)]; InBuf--; if (InBuf > 0) { *Base64++ = BinToBase64Lut[((Binary[1] & 0xF) << 2) | (Binary[2] >> 6)]; *Base64++ = BinToBase64Lut[Binary[2] & 0x3F]; } else { *Base64++ = BinToBase64Lut[(Binary[1] & 0xF) << 2]; *Base64++ = '='; } } else { *Base64++ = BinToBase64Lut[(Binary[0] << 4) & 0x3F]; *Base64++ = '='; *Base64++ = '='; } } } return (int) (Base64 - Start); } LString LToBase64(LString bin) { return LToBase64(bin.Get(), bin.Length()); } -LString LToBase64(void *binary, size_t length) +LString LToBase64(const void *binary, size_t length) { LString base64; if (binary && base64.Length(BufferLen_BinTo64(length))) { auto out = ConvertBinaryToBase64(base64.Get(), base64.Length(), (uchar*)binary, length); base64.Get()[out] = 0; // NULL term for debugging. } return base64; } diff --git a/src/common/Text/TextConvert.cpp b/src/common/Text/TextConvert.cpp --- a/src/common/Text/TextConvert.cpp +++ b/src/common/Text/TextConvert.cpp @@ -1,552 +1,578 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TextConvert.h" #include "lgi/common/Mime.h" #include "lgi/common/Base64.h" #include "lgi/common/Charset.h" // return true if there are any characters with the 0x80 bit set bool Is8Bit(const char *Text) { if (!Text) return false; while (*Text) { if (*Text & 0x80) return true; Text++; } return false; } char ConvHexToBin(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c + 10 - 'a'; if (c >= 'A' && c <= 'F') return c + 10 - 'A'; return 0; } char *DecodeBase64Str(char *Str, ssize_t Len) { if (Str) { ssize_t B64Len = (Len < 0) ? strlen(Str) : Len; ssize_t BinLen = BufferLen_64ToBin(B64Len); char *s = new char[BinLen+1]; if (s) { ssize_t Converted = ConvertBase64ToBinary((uchar*)s, BinLen, Str, B64Len); s[Converted] = 0; DeleteArray(Str); Str = s; } } return Str; } LString LDecodeBase64Str(LString Str) { LString r; ssize_t BinLen = BufferLen_64ToBin(Str.Length()); if (Str && r.Length(BinLen) > 0) { ssize_t Converted = ConvertBase64ToBinary((uchar*)r.Get(), r.Length(), Str.Get(), Str.Length()); if (Converted >= 0) r.Length(Converted); else r.Empty(); } return r; } char *DecodeQuotedPrintableStr(char *Str, ssize_t Len) { if (Str) { if (Len < 0) Len = strlen(Str); uchar *s = new uchar[Len+1]; if (s) { char *Out = (char*) s; char *Text = Str; for (int i=0; i MaxLine) + { + p.Print("=\r\n"); + CurLen = 0; + } + if (quote) + CurLen += p.Print("=%2.2x", *s); + else + CurLen += p.Write(s, 1); + } + } + + return p.NewLStr(); +} + +LString LDecodeQuotedPrintable(LString InStr) { if (!InStr.Get()) return InStr; LString OutStr = InStr.Get(); if (OutStr) { auto Out = OutStr.Get(); auto In = InStr.Get(); auto End = In + InStr.Length(); while (In < End) { if (*In == '=') { In++; if (*In == '\r' || *In == '\n') { if (*In == '\r') In++; if (*In == '\n') In++; } else { char c = ConvHexToBin(*In++) << 4; c |= ConvHexToBin(*In++) & 0xF; *Out++ = c; } } else { *Out++ = *In++; } } // Correct the size and NULL terminate. OutStr.Length(Out - OutStr.Get()); } return OutStr; } static void DecodeRfc2047_Impl(char *Str, std::function Output) { if (!Str) return; LStringPipe p(256); bool prevWasEncoded = false; for (char *s = Str; *s; ) { char *e = s; bool Decode = 0, Descape = 0, nonWhitespace = false; while (*e) { if ( (Decode = (e[0] == '=' && e[1] == '?')) || (Descape = (e[0] == '\\')) ) { // Emit characters between 's' and 'e'? if (e > s && !(!nonWhitespace && prevWasEncoded)) p.Write(s, e - s); /* nonWhitespace=0, prevWasEncoded=0 = emit nonWhitespace=1, prevWasEncoded=0 = emit nonWhitespace=0, prevWasEncoded=1 = skip nonWhitespace=1, prevWasEncoded=1 = emit */ break; } if (!strchr(LWhiteSpace, *e)) nonWhitespace = true; e++; } prevWasEncoded = false; if (Decode) { // is there a word remaining bool Encoded = false; char *Start = e + 2; char *First = strchr(Start, '?'); char *Second = First ? strchr(First + 1, '?') : NULL; char *End = Second ? strstr(Second + 1, "?=") : NULL; if (End) { LString Cp(Start, First - Start); int Type = CONTENT_NONE; bool StripUnderscores = false; if (ToUpper(First[1]) == 'B') { // Base64 encoding Type = CONTENT_BASE64; } else if (ToUpper(First[1]) == 'Q') { // Quoted printable Type = CONTENT_QUOTED_PRINTABLE; StripUnderscores = true; } if (Type != CONTENT_NONE) { Second++; LString Block(Second, End - Second); if (Block) { switch (Type) { case CONTENT_BASE64: Block = LDecodeBase64Str(Block); break; case CONTENT_QUOTED_PRINTABLE: - Block = LDecodeQuotedPrintableStr(Block); + Block = LDecodeQuotedPrintable(Block); break; } if (StripUnderscores) { for (char *i=Block; *i; i++) { if (*i == '_') *i = ' '; } } if (Cp && !_stricmp(Cp, "utf-8")) { p.Write(Block); } else { auto Inst = LCharsetSystem::Inst(); LString Detect = Inst && Inst->DetectCharset ? Inst->DetectCharset(Block) : LString(); LAutoString Utf8((char*)LNewConvertCp("utf-8", Block, Detect ? Detect : Cp, Block.Length())); if (Utf8) { if (LIsUtf8(Utf8)) p.Write((uchar*)Utf8.Get(), strlen(Utf8)); } else { p.Write(Block); } } } s = End + 2; if (*s == '\n') { s++; while (*s && strchr(LWhiteSpace, *s)) s++; } Encoded = true; } } if (Encoded) { prevWasEncoded = true; } else { // Encoding error, just emit the raw string and exit. size_t Len = strlen(s); p.Write((uchar*) s, Len); break; } } else if (Descape) { // Un-escape the string... e++; if (*e) p.Write(e, 1); else break; s = e + 1; } else { // Last segment of string... LAssert(*e == 0); if (e > s) p.Write(s, e - s); break; } } Output(p); } char *DecodeRfc2047(char *Str) { DecodeRfc2047_Impl( Str, [&Str](LStringPipe &p) { DeleteArray(Str); Str = p.NewStr(); }); return Str; } LString LDecodeRfc2047(LString Str) { DecodeRfc2047_Impl( Str, [&Str](LStringPipe &p) { Str = p.NewLStr(); }); return Str; } #define MIME_MAX_LINE 76 static void EncodeRfc2047_Impl( char *Input, size_t Length, const char *InCharset, LString::Array *OutCharsets, ssize_t LineLength, std::function Process) { if (!Input) return; if (!InCharset) InCharset = "utf-8"; LStringPipe p(256); if (Is8Bit(Input)) { // pick an encoding bool Base64 = false; const char *DestCs = "utf-8"; char *Buf = NULL; if (!OutCharsets || (OutCharsets && OutCharsets->Length() == 0) || Stristr(InCharset, "utf") != NULL) { if (!Stricmp(InCharset, "utf-8")) { auto DetectedCs = LUnicodeToCharset(Input, Length, OutCharsets); if (DetectedCs) Buf = (char*)LNewConvertCp(DestCs = DetectedCs, Input, InCharset, Length); } else { // Not utf-8 so convert to that first... LAutoString utf8((char*)LNewConvertCp("utf-8", Input, InCharset)); if (utf8) { auto DetectCs = LUnicodeToCharset(utf8, Strlen(utf8.Get()), OutCharsets); if (DetectCs) Buf = (char*)LNewConvertCp(DestCs = DetectCs, Input, InCharset, Length); } } } else { for (auto Cs: *OutCharsets) { if ((Buf = (char*)LNewConvertCp(DestCs = Cs, Input, InCharset, Length))) break; } if (!Buf || !DestCs) { // Fall back to utf-8 Buf = (char*)LNewConvertCp(DestCs = "utf-8", Input, InCharset, Length); } } if (!Buf) { // Fall back to copy... Buf = NewStr(Input, Length); if (InCharset) DestCs = InCharset; } int Chars = 0; for (unsigned i=0; Buf && Buf[i]; i++) { if (Buf[i] & 0x80) Chars++; } if ( Length > 0 && ((double)Chars/Length) > 0.4 ) { Base64 = true; } if (Buf) { // encode the word char Prefix[64]; int Ch = sprintf_s(Prefix, sizeof(Prefix), "=?%s?%c?", DestCs, Base64 ? 'B' : 'Q'); p.Write(Prefix, Ch); LineLength += Ch; if (Base64) { // Base64 size_t InLen = strlen(Buf); // int EstBytes = BufferLen_BinTo64(InLen); char Temp[512]; ssize_t Bytes = ConvertBinaryToBase64(Temp, sizeof(Temp), (uchar*)Buf, InLen); p.Push(Temp, Bytes); } else { // Quoted printable for (char *w = Buf; *w; w++) { if (*w == ' ') { if (LineLength > MIME_MAX_LINE - 3) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } p.Write((char*)"_", 1); LineLength++; } else if (*w & 0x80 || *w == '_' || *w == '?' || *w == '=') { if (LineLength > MIME_MAX_LINE - 5) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } char Temp[16]; Ch = sprintf_s(Temp, sizeof(Temp), "=%2.2X", (uchar)*w); p.Write(Temp, Ch); LineLength += Ch; } else { if (LineLength > MIME_MAX_LINE - 3) { p.Print("?=\r\n\t%s", Prefix); LineLength = 1 + strlen(Prefix); } p.Write(w, 1); LineLength++; } } } p.Push("?="); DeleteArray(Buf); } Process(Input, p); } else { bool RecodeNewLines = false; for (char *s = Input; *s; s++) { if (*s == '\n' && (s == Input || s[-1] != '\r')) { RecodeNewLines = true; break; } } if (RecodeNewLines) { for (char *s = Input; *s; s++) { if (*s == '\r') ; else if (*s == '\n') p.Write("\r\n", 2); else p.Write(s, 1); } Process(Input, p); } } // It's not an error to not call 'OutputStr', in that case // the input is passed through to the output unchanged. } // Old heap string encode method (will eventually remove this...) char *EncodeRfc2047(char *Str, const char *Charset, LString::Array *CharsetPrefs, ssize_t LineLength) { char *Out = Str; EncodeRfc2047_Impl( Str, Strlen(Str), Charset, CharsetPrefs, LineLength, [&Out](auto s, auto &pipe) { DeleteArray(s); Out = pipe.NewStr(); }); return Out; } // New LString encode method LString LEncodeRfc2047(LString Str, const char *Charset, LString::Array *CharsetPrefs, ssize_t LineLength) { EncodeRfc2047_Impl( Str.Get(), Str.Length(), Charset, CharsetPrefs, LineLength, [&Str](auto s, auto &pipe) { Str = pipe.NewLStr(); }); return Str; } diff --git a/src/common/Text/vCard-vCal.cpp b/src/common/Text/vCard-vCal.cpp --- a/src/common/Text/vCard-vCal.cpp +++ b/src/common/Text/vCard-vCal.cpp @@ -1,1636 +1,1658 @@ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/vCard-vCal.h" +#include "lgi/common/Json.h" +#include "lgi/common/TextConvert.h" #include "ScribeDefs.h" -#include "lgi/common/Json.h" #define DEBUG_LOGGING 0 #define Push(s) Write(s, (int)strlen(s)) #define ClearFields() \ Field.Empty(); \ Params.Empty(); \ Data.Empty() #define IsType(str) (Params.Find(str) != 0) struct TzMap { int Offset; const char *Name; }; #define TZ(h,m) (((h)*100) + (m)) static TzMap Timezones[] = { {TZ(11,0), "Australia/Sydney"}, }; int TimezoneToOffset(const char *Tz) { if (!Tz) return 0; for (int i=0; iName, Tz)) return t->Offset; } return (int)Atoi(Tz); } #if 1 bool IsVar(char *field, const char *s) { if (!s) return false; char *dot = strchr(field, '.'); if (dot) return _stricmp(dot + 1, s) == 0; return _stricmp(field, s) == 0; } #else #define IsVar(field, str) (field != 0 && _stricmp(field, str) == 0) #endif char *DeEscape(char *s, bool QuotedPrintable) { if (!s) return 0; char *i = s; char *o = s; while (*i) { if (*i == '\\') { i++; switch (*i) { case 'n': case 'N': *o++ = '\n'; break; case ',': case ';': case ':': *o++ = *i; break; default: *o++ = '\\'; i--; break; } } else if (QuotedPrintable && *i == '=' && i[1] && i[2]) { i++; char h[3] = { i[0], i[1], 0}; *o++ = htoi(h); i++; } else { *o++ = *i; } i++; } *o = 0; return s; } ///////////////////////////////////////////////////////////// // General IO class class VIoPriv { public: LStringPipe Buf; }; VIo::VIo() : d(new VIoPriv) { } VIo::~VIo() { DeleteObj(d); } bool VIo::ParseDate(LDateTime &Out, char *In) { bool Status = false; if (In) { Out.SetTimeZone(0, false); auto v = LString(In).SplitDelimit("T"); if (v.Length() > 0) { if (v[0].Length() == 8) { auto Year = v[0](0, 4); auto Month = v[0](4, 6); auto Day = v[0](6, 8); Out.Year ((int)Year.Int()); Out.Month((int)Month.Int()); Out.Day ((int)Day.Int()); Status = true; } char *t = v[1]; if (t && strlen(t) >= 6) { char Hour[3] = {t[0], t[1], 0}; char Minute[3] = {t[2], t[3], 0}; char Second[3] = {t[4], t[5], 0}; Out.Hours(atoi(Hour)); Out.Minutes(atoi(Minute)); Out.Seconds(atoi(Second)); Status = true; } } } return Status; } bool VIo::ParseDuration(LDateTime &Out, int &Sign, char *In) { bool Status = false; if (In) { Sign = 1; if (*In == '-') { Sign = -1; In++; } if (toupper(*In++) == 'P' && toupper(*In++) == 'T') { while (*In) { int i = atoi(In); while (IsDigit(*In)) In++; switch (toupper(*In++)) { case 'W': { Out.Day(Out.Day() + (i * 7)); break; } case 'D': { Out.Day(Out.Day() + i); break; } case 'H': { Out.Hours(Out.Hours() + i); break; } case 'M': { Out.Minutes(Out.Minutes() + i); break; } case 'S': { Out.Seconds(Out.Seconds() + i); break; } } } Status = true; } } return Status; } -void VIo::Fold(LStreamI &o, const char *i, int pre_chars) +void VIo::Fold(LStreamI &o, const char *i, int pre_chars, const char *encoding) { + bool base64 = !Stricmp(encoding, "base64"); + bool quotePrintable = !base64; int x = pre_chars; for (const char *s=i; s && *s;) { if (x >= 74) { // wrapping o.Write(i, (int)(s-i)); o.Write((char*)"\r\n\t", 3); x = 0; i = s; } - else if (*s == '=' || ((((uint8_t)*s) & 0x80) != 0)) + else if (quotePrintable && (*s == '=' || ((((uint8_t)*s) & 0x80) != 0))) { // quoted printable o.Write(i, (int)(s-i)); LStreamPrint(&o, "=%02.2x", (uint8_t)*s); x += 3; i = ++s; } else if (*s == '\n') { // new line o.Write(i, (int)(s-i)); o.Write((char*)"\\n", 2); x += 2; i = ++s; } else if (*s == '\r') { o.Write(i, (int)(s-i)); i = ++s; } else { s++; x++; } } o.Write(i, (int)strlen(i)); } char *VIo::Unfold(char *In) { if (In) { LStringPipe p(256); for (char *i=In; i && *i; i++) { if (*i == '\n') { if (i[1] && strchr(" \t", i[1])) { i++; } else { p.Write(i, 1); } } else { p.Write(i, 1); } } return p.NewStr(); } return 0; } char *VIo::UnMultiLine(char *In) { if (In) { LStringPipe p; char *n; for (char *i=In; i && *i; i=n) { n = stristr(i, "\\n"); if (n) { p.Write(i, (int)(n-i)); p.Push((char*)"\n"); n += 2; } else { p.Push(i); } } return p.NewStr(); } return 0; } ///////////////////////////////////////////////////////////// // VCard class bool VCard::Import(LDataPropI *c, LStreamI *s) { bool Status = false; if (!c || !s) return false; LString Field; ParamArray Params; LString Data; ssize_t PrefEmail = -1; LString::Array Emails; while (ReadField(*s, Field, &Params, Data)) { if (_stricmp(Field, "begin") == 0 && _stricmp(Data, "vcard") == 0) { while (ReadField(*s, Field, &Params, Data)) { if (_stricmp(Field, "end") == 0 && _stricmp(Data, "vcard") == 0) goto ExitLoop; if (IsVar(Field, "n")) { auto Name = Data.SplitDelimit(";", -1, false); char *First = Name[1]; char *Last = Name[0]; char *Title = Name[3]; if (First) { c->SetStr(FIELD_FIRST_NAME, First); Status = true; } if (Last) { c->SetStr(FIELD_LAST_NAME, Last); Status = true; } if (Title) { c->SetStr(FIELD_TITLE, Title); Status = true; } } else if (IsVar(Field, "nickname")) { c->SetStr(FIELD_NICK, Data); } else if (IsVar(Field, "tel")) { auto Phone = Data.SplitDelimit(";", -1, false); for (uint32_t p=0; pSetStr(FIELD_WORK_MOBILE, Phone[p]); } else { c->SetStr(FIELD_HOME_MOBILE, Phone[p]); } } else if (IsType("fax")) { if (IsType("Work")) { c->SetStr(FIELD_WORK_FAX, Phone[p]); } else { c->SetStr(FIELD_HOME_FAX, Phone[p]); } } else { if (IsType("Work")) { c->SetStr(FIELD_WORK_PHONE, Phone[p]); } else { c->SetStr(FIELD_HOME_PHONE, Phone[p]); } } } } else if (IsVar(Field, "email")) { if (IsType("pref")) { PrefEmail = Emails.Length(); } Emails.Add(Data); } else if (IsVar(Field, "org")) { auto Org = Data.SplitDelimit(";", -1, false); if (Org[0]) c->SetStr(FIELD_COMPANY, Org[0]); } else if (IsVar(Field, "adr")) { bool IsWork = IsType("work"); // bool IsHome = IsType("home"); auto Addr = Data.SplitDelimit(";", -1, false); if (Addr[2]) { auto A = Addr[2].SplitDelimit("\r\n"); if (A.Length() > 1) { c->SetStr(IsWork ? FIELD_WORK_STREET : FIELD_HOME_STREET, A[0]); if (A[1]) c->SetStr(IsWork ? FIELD_WORK_SUBURB : FIELD_HOME_SUBURB, A[1]); if (A[2]) c->SetStr(IsWork ? FIELD_WORK_COUNTRY : FIELD_HOME_COUNTRY, A[2]); } else { c->SetStr(IsWork ? FIELD_WORK_STREET : FIELD_HOME_STREET, Addr[2]); } } if (Addr[3]) c->SetStr(IsWork ? FIELD_WORK_SUBURB : FIELD_HOME_SUBURB, Addr[3]); if (Addr[4]) c->SetStr(IsWork ? FIELD_WORK_STATE : FIELD_HOME_STATE, Addr[4]); if (Addr[5]) c->SetStr(IsWork ? FIELD_WORK_POSTCODE : FIELD_HOME_POSTCODE, Addr[5]); if (Addr[6]) c->SetStr(IsWork ? FIELD_WORK_COUNTRY : FIELD_HOME_COUNTRY, Addr[6]); } else if (IsVar(Field, "note")) { c->SetStr(FIELD_NOTE, Data); } else if (IsVar(Field, "uid")) { auto n = Data.SplitDelimit(";", -1, false); c->SetStr(FIELD_UID, n[0]); } else if (IsVar(Field, "x-perm")) { int Perms = atoi(Data); c->SetInt(FIELD_PERMISSIONS, Perms); } else if (IsVar(Field, "url")) { bool IsWork = IsType("work"); bool IsHome = IsType("home"); if (IsWork) { c->SetStr(FIELD_HOME_WEBPAGE, Data); } else if (IsHome) { c->SetStr(FIELD_WORK_WEBPAGE, Data); } } else if (IsVar(Field, "nickname")) { c->SetStr(FIELD_NICK, Data); } else if (IsVar(Field, "photo")) { size_t B64Len = strlen(Data); ssize_t BinLen = BufferLen_64ToBin(B64Len); LAutoPtr Bin(new uint8_t[BinLen]); if (Bin) { ssize_t Bytes = ConvertBase64ToBinary(Bin.Get(), BinLen, Data, B64Len); LVariant v; if (v.SetBinary(Bytes, Bin.Release(), true)) { c->SetVar(FIELD_CONTACT_IMAGE, &v); } } } } } } ExitLoop: if (Emails.Length()) { if (PrefEmail < 0) PrefEmail = 0; c->SetStr(FIELD_EMAIL, Emails[PrefEmail]); Emails.DeleteAt(PrefEmail); if (Emails.Length()) { LStringPipe p; for (uint32_t i=0; iSetStr(FIELD_ALT_EMAIL, v); DeleteArray(v); } } } ClearFields(); return Status; } bool VIo::ReadField(LStreamI &s, LString &Name, ParamArray *Params, LString &Data) { bool Status = false; ParamArray LocalParams; Name.Empty(); Data.Empty(); if (Params) Params->Empty(); else Params = &LocalParams; char Temp[1024]; LArray p; bool Done = false; while (!Done) { bool EatNext = false; ReadNextLine: Temp[0] = 0; int64 r = d->Buf.Pop(Temp, sizeof(Temp)); if (r <= 0) { // Try reading more data... r = s.Read(Temp, sizeof(Temp)); if (r > 0) d->Buf.Write(Temp, r); else break; r = d->Buf.Pop(Temp, sizeof(Temp)); } else if (Temp[0] == '\r' || Temp[1] == '\n') { // Skip empty lines... goto ReadNextLine; } if (r <= 0) break; if ((r == 1 && Temp[0] == '\n') || (r == 2 && Temp[0] == '\r' && Temp[1] == '\n')) // Blank line case... continue; // Unfold for (char *c = Temp; *c; c++) { if (*c == '\r') { // do nothing } else if (*c == '\n') { char Next; r = d->Buf.Peek((uchar*) &Next, 1); if (r == 0) { r = s.Read(Temp, sizeof(Temp)); if (r <= 0) break; d->Buf.Write(Temp, (int)r); r = d->Buf.Peek((uchar*) &Next, 1); } if (r == 1) { if (Next == ' ' || Next == '\t') { // Wrapped, do nothing EatNext = true; goto ReadNextLine; } else { Done = true; break; } } else { break; } } else if (EatNext) { EatNext = false; } else { p.Add(*c); } } } p.Add(0); char *f = p.Length() > 1 ? &p[0] : 0; if (f) { char *e = strchr(f, ':'); if (e) { *e++ = 0; auto t = LString(f).SplitDelimit(";"); if (t.Length() > 0) { Name = t[0]; for (uint32_t i=1; iFind("charset"); if (Charset) { LAutoString u((char*)LNewConvertCp("utf-8", e, Charset)); Data = u.Get(); } else { Data = e; } Status = Name.Length() > 0; } return Status; } void VIo::WriteField(LStreamI &s, const char *Name, ParamArray *Params, const char *Data) { - if (Name && Data) + if (!Name || !Data) { - int64 Size = s.GetSize(); + LAssert(!"Invalid params"); + return; + } + + int64 Size = s.GetSize(); - LStreamPrint(&s, "%s", Name); - if (Params) - { - for (uint32_t i=0; iLength(); i++) - { - Parameter &p = (*Params)[i]; - LStreamPrint(&s, "%s%s=%s", i?"":";", p.Field.Get(), p.Value.Get()); - } - } + LStreamPrint(&s, "%s", Name); + const char *encoding = NULL; + const char *charset = NULL; + if (Params) + { + for (auto &p: *Params) + LStreamPrint(&s, ";%s=%s", p.Field.Get(), p.Value.Get()); + encoding = Params->Find("ENCODING"); + charset = Params->Find("CHARSET"); + } - bool Is8Bit = false; - bool HasEq = false; - for (uint8_t *c = (uint8_t*)Data; *c; c++) + size_t Is7Bit = 0; + size_t Is8Bit = 0; + size_t HasEq = 0; + for (uint8_t *c = (uint8_t*)Data; *c; c++) + { + if ((*c & 0x80) != 0) + Is8Bit++; + else if (*c == '=') + HasEq++; + else + Is7Bit++; + } + + size_t DataLen = Is7Bit + Is8Bit + HasEq; + if (Is8Bit || HasEq) + { + if (!encoding) { - if ((*c & 0x80) != 0) - { - Is8Bit = true; - } - else if (*c == '=') - { - HasEq = true; - } + encoding = "quoted-printable"; + LStreamPrint(&s, ";ENCODING=%s", encoding); } - if (Is8Bit || HasEq) - { - if (Is8Bit) s.Write((char*)";charset=utf-8", 14); - s.Write((char*)";encoding=quoted-printable", 26); - } + + if (Is8Bit && !charset) + LStreamPrint(&s, ";charset=utf-8"); + } + s.Write((char*)":", 1); - s.Write((char*)":", 1); - Fold(s, Data, (int) (s.GetSize() - Size)); - s.Write((char*)"\r\n", 2); - } + LString encoded; + if (encoding && !Stricmp(encoding, "base64")) + { + encoded = LToBase64(Data, DataLen); + Data = encoded.Get(); + } + Fold(s, Data, (int) (s.GetSize() - Size), encoding); + s.Write((char*)"\r\n", 2); } bool VCard::Export(LDataPropI *c, LStreamI *o) { if (!c || !o) return false; bool Status = true; char s[512]; const char *Empty = ""; o->Push("begin:vcard\r\n"); o->Push("version:3.0\r\n"); const char *First = 0, *Last = 0, *Title = 0; First = c->GetStr(FIELD_FIRST_NAME); Last = c->GetStr(FIELD_LAST_NAME); Title = c->GetStr(FIELD_TITLE); if (First || Last) { sprintf_s(s, sizeof(s), "%s;%s;%s", Last?Last:Empty, First?First:Empty, Title?Title:Empty); WriteField(*o, "n", 0, s); } #define OutputTypedField(Field, Name, Type) \ { const char *str = 0; \ if ((str = c->GetStr(Name))) \ { \ WriteField(*o, Field, Type, str); \ } } #define OutputField(Field, Name) \ { const char *str = 0; \ if ((str = c->GetStr(Name))) \ { \ WriteField(*o, Field, 0, str); \ } } ParamArray Work("Work"), WorkCell("Work,Cell"), WorkFax("Work,Fax"); ParamArray Home("Home"), HomeCell("Home,Cell"), HomeFax("Home,Fax"); ParamArray InetPref("internet,pref"), Inet("internet"); OutputTypedField("tel", FIELD_WORK_PHONE, &Work); OutputTypedField("tel", FIELD_WORK_MOBILE, &WorkCell); OutputTypedField("tel", FIELD_WORK_FAX, &WorkFax); OutputTypedField("tel", FIELD_HOME_PHONE, &Home); OutputTypedField("tel", FIELD_HOME_MOBILE, &HomeCell); OutputTypedField("tel", FIELD_HOME_FAX, &HomeFax); OutputField("org", FIELD_COMPANY); OutputTypedField("email", FIELD_EMAIL, &InetPref); const char *Alt; if ((Alt = c->GetStr(FIELD_ALT_EMAIL))) { auto t = LString(Alt).SplitDelimit(","); for (unsigned i=0; iGetStr(FIELD_UID))) { LStreamPrint(o, "UID:%s\r\n", Uid); } const char *Street, *Suburb, *PostCode, *State, *Country; Street = Suburb = PostCode = State = Country = 0; Street = c->GetStr(FIELD_HOME_STREET); Suburb = c->GetStr(FIELD_HOME_SUBURB); PostCode = c->GetStr(FIELD_HOME_POSTCODE); State = c->GetStr(FIELD_HOME_STATE); Country = c->GetStr(FIELD_HOME_COUNTRY); if (Street || Suburb || PostCode || State || Country) { sprintf_s(s, sizeof(s), ";;%s;%s;%s;%s;%s", Street?Street:Empty, Suburb?Suburb:Empty, State?State:Empty, PostCode?PostCode:Empty, Country?Country:Empty); WriteField(*o, "adr", &Home, s); } Street = Suburb = PostCode = State = Country = 0; Street = c->GetStr(FIELD_WORK_STREET); Suburb = c->GetStr(FIELD_WORK_SUBURB); PostCode = c->GetStr(FIELD_WORK_POSTCODE); State = c->GetStr(FIELD_WORK_STATE); Country = c->GetStr(FIELD_WORK_COUNTRY); if (Street || Suburb || PostCode || State || Country) { sprintf_s(s, sizeof(s), ";;%s;%s;%s;%s;%s", Street?Street:Empty, Suburb?Suburb:Empty, State?State:Empty, PostCode?PostCode:Empty, Country?Country:Empty); WriteField(*o, "adr", &Work, s); } // OutputField("X-Perm", FIELD_PERMISSIONS); const char *Url; if ((Url = c->GetStr(FIELD_HOME_WEBPAGE))) { WriteField(*o, "url", &Home, Url); } if ((Url = c->GetStr(FIELD_WORK_WEBPAGE))) { WriteField(*o, "url", &Work, Url); } const char *Nick; if ((Nick = c->GetStr(FIELD_NICK))) { WriteField(*o, "nickname", 0, Nick); } const char *Note; if ((Note = c->GetStr(FIELD_NOTE))) { WriteField(*o, "note", 0, Note); } const LVariant *Photo = c->GetVar(FIELD_CONTACT_IMAGE); if (Photo && Photo->Type == GV_BINARY) { ssize_t B64Len = BufferLen_BinTo64(Photo->Value.Binary.Length); LAutoPtr B64Buf(new char[B64Len]); if (B64Buf) { ssize_t Bytes = ConvertBinaryToBase64(B64Buf, B64Len, (uchar*)Photo->Value.Binary.Data, Photo->Value.Binary.Length); if (Bytes > 0) { LStreamPrint(o, "photo;type=jpeg;encoding=base64:\r\n"); int LineChar = 76; for (ssize_t i=0; i LineChar ? LineChar : Remain; o->Write(" ", 1); o->Write(B64Buf + i, Wr); o->Write("\r\n", 2); i += Wr; } } } } o->Push("end:vcard\r\n"); return Status; } ///////////////////////////////////////////////////////////// // VCal class int StringToWeekDay(const char *s) { const char *days[] = {"SU","MO","TU","WE","TH","FR","SA"}; for (unsigned i=0; i TzInfos; TimeZoneInfo *TzInfo = NULL; bool IsNormalTz = false, IsDaylightTz = false; LJson To; int Attendee = 0; LArray Alarms; int AlarmIdx = -1; while (ReadField(*In, Field, &Params, Data)) { if (!_stricmp(Field, "begin")) { if (_stricmp(Data, "vevent") == 0 || _stricmp(Data, "vtodo") == 0) { IsEvent = true; SectionType = Data; int Type = _stricmp(Data, "vtodo") == 0 ? 1 : 0; c->SetInt(FIELD_CAL_TYPE, Type); } else if (!_stricmp(Data, "vtimezone")) { IsTimeZone = true; TzInfo = &TzInfos.New(); } else if (_stricmp(Data, "vcalendar") == 0) IsCal = true; else if (!_stricmp(Data, "standard")) IsNormalTz = true; else if (!_stricmp(Data, "daylight")) IsDaylightTz = true; else if (!_stricmp(Data, "valarm")) { IsAlarm = true; AlarmIdx++; } } else if (_stricmp(Field, "end") == 0) { if (_stricmp(Data, "vcalendar") == 0) { IsCal = false; } else if (SectionType && _stricmp(Data, SectionType) == 0) { Status = true; IsEvent = false; break; // exit loop } else if (!_stricmp(Data, "vtimezone")) { IsTimeZone = false; TzInfo = NULL; } else if (!_stricmp(Data, "standard")) IsNormalTz = false; else if (!_stricmp(Data, "daylight")) IsDaylightTz = false; else if (!_stricmp(Data, "valarm")) IsAlarm = false; } else if (IsEvent) { if (IsAlarm) { auto &a = Alarms[AlarmIdx]; if (IsVar(Field, "ACTION")) a.Action = Data; else if (IsVar(Field, "DESCRIPTION")) a.Desc = Data; else if (IsVar(Field, "TRIGGER")) a.Trigger = Data; else if (IsVar(Field, "X-PARAM")) a.Param = Data; } else if (IsVar(Field, "dtstart")) { ParseDate(EventStart, Data); StartTz = Params.Find("TZID"); } else if (IsVar(Field, "dtend")) { ParseDate(EventEnd, Data); EndTz = Params.Find("TZID"); } else if (IsVar(Field, "summary")) { c->SetStr(FIELD_CAL_SUBJECT, Data); } else if (IsVar(Field, "description")) { LAutoString Sum(UnMultiLine(Data)); if (Sum) c->SetStr(FIELD_CAL_NOTES, Sum); } else if (IsVar(Field, "location")) { c->SetStr(FIELD_CAL_LOCATION, Data); } else if (IsVar(Field, "uid")) { char *Uid = Data; c->SetStr(FIELD_UID, Uid); } else if (IsVar(Field, "x-showas")) { char *n = Data; if (Stricmp(n, "TENTATIVE") == 0) c->SetInt(FIELD_CAL_SHOW_TIME_AS, CalTentative); else if (Stricmp(n, "BUSY") == 0) c->SetInt(FIELD_CAL_SHOW_TIME_AS, CalBusy); else if (Stricmp(n, "OUT") == 0) c->SetInt(FIELD_CAL_SHOW_TIME_AS, CalOut); else c->SetInt(FIELD_CAL_SHOW_TIME_AS, CalFree); } else if (IsVar(Field, "attendee")) { auto Email = Data.SplitDelimit(":=").Last(); if (LIsValidEmail(Email)) { auto Name = Params.Find("CN"); auto Role = Params.Find("Role"); LString k; k.Printf("[%i].email", Attendee); To.Set(k, Email); if (Name) { k.Printf("[%i].name", Attendee); To.Set(k, Name); } if (Role) { k.Printf("[%i].role", Attendee); To.Set(k, Role); } Attendee++; } } else if (IsVar(Field, "status")) { c->SetStr(FIELD_CAL_STATUS, Data); } else if (IsVar(Field, "attach")) { // None of these error cases are fatal, the backend may not support // attachments... but log something for the user in that case. auto attachments = c->GetList(FIELD_CAL_ATTACHMENTS); if (!attachments) { LgiTrace("%s:%i - vCal import: No attachment list...\n", _FL); } else if (auto data = dynamic_cast(c)) { auto store = data->GetStore(); if (!store) { LgiTrace("%s:%i - vCal import: No store object.\n", _FL); } // Create a new attachment object to receive the file else if (auto file = attachments->Create(store)) { auto FileName = Params.Find("X-FILENAME"); auto MimeType = Params.Find("FMTTYPE"); auto Encoding = Params.Find("ENCODING"); auto Value = Params.Find("VALUE"); + auto Now = LDateTime::Now(); if (FileName) file->SetStr(FIELD_NAME, FileName); if (MimeType) file->SetStr(FIELD_MIME_TYPE, MimeType); + file->SetDate(FIELD_DATE_MODIFIED, &Now); if (!Stricmp(Encoding, "base64")) { auto bin = LToBinary(Data); file->SetStr(FIELD_ATTACHMENTS_DATA, bin); } else if (Data.Find("://") >= 0) { file->SetStr(FIELD_URI, Data); } else { LgiTrace("%s:%i - vCal import: Unknown data type?\n", _FL); LAssert(!"What is 'Data'?"); } if (auto fileData = dynamic_cast(file)) fileData->Save(data); } else { LgiTrace("%s:%i - vCal import: Couldn't create attachment object\n", _FL); } } else { LgiTrace("%s:%i - vCal import: no LDataI object\n", _FL); } } } else if (IsTimeZone && TzInfo) { /* e.g.: TZID:Pacific Standard Time BEGIN:STANDARD DTSTART:16010101T020000 TZOFFSETFROM:-0700 TZOFFSETTO:-0800 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=11 END:STANDARD BEGIN:DAYLIGHT DTSTART:16010101T020000 TZOFFSETFROM:-0800 TZOFFSETTO:-0700 RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=2SU;BYMONTH=3 END:DAYLIGHT */ if (IsVar(Field, "TZID")) { TzInfo->Name = Data; } else if (IsNormalTz || IsDaylightTz) { TimeZoneSection &Sect = IsNormalTz ? TzInfo->Normal : TzInfo->Daylight; if (IsVar(Field, "DTSTART")) ParseDate(Sect.Start, Data); else if (IsVar(Field, "TZOFFSETFROM")) Sect.From = (int)Data.Int(); else if (IsVar(Field, "TZOFFSETTO")) Sect.To = (int)Data.Int(); else if (IsVar(Field, "RRULE")) Sect.Rule = Data; } } } if (Attendee > 0) { auto j = To.GetJson(); c->SetStr(FIELD_ATTENDEE_JSON, j); } if (StartTz || EndTz) { // Did we get a timezone defn? TimeZoneInfo *Match = NULL; for (unsigned i=0; iNormal, EventStart.Year()) && EvalRule(Dst, Match->Daylight, EventStart.Year())) { bool IsDst = false; if (Dst < Norm) { // DST in the middle of the year // |Jan----DST------Norm----Dec| if (EventStart >= Dst && EventStart <= Norm) IsDst = true; } else { // DST over the start and end of the year // |Jan----Norm------DST----Dec| if (EventStart >= Norm && EventStart <= Dst) IsDst = false; else IsDst = true; } #if DEBUG_LOGGING LgiTrace("Eval Start=%s, Norm=%s, Dst=%s, IsDst=%i\n", EventStart.Get().Get(), Norm.Get().Get(), Dst.Get().Get(), IsDst); #endif EffectiveTz = IsDst ? Match->Daylight.To : Match->Normal.To; LString sTz; sTz.Printf("%4.4i,%s", EffectiveTz, StartTz.Get()); c->SetStr(FIELD_CAL_TIMEZONE, sTz); } else goto StoreStringTz; } else { // Store whatever they gave us StoreStringTz: if (StartTz.Equals(EndTz)) c->SetStr(FIELD_CAL_TIMEZONE, StartTz); else if (StartTz.Get() && EndTz.Get()) { LString s; s.Printf("%s,%s", StartTz.Get(), EndTz.Get()); c->SetStr(FIELD_CAL_TIMEZONE, s); } else if (StartTz) c->SetStr(FIELD_CAL_TIMEZONE, StartTz); else if (EndTz) c->SetStr(FIELD_CAL_TIMEZONE, EndTz); if (StartTz) EffectiveTz = TimezoneToOffset(StartTz); } if (EffectiveTz) { // Convert the event to UTC int e = abs(EffectiveTz); int Mins = (((e / 100) * 60) + (e % 100)) * (EffectiveTz < 0 ? -1 : 1); #if DEBUG_LOGGING LgiTrace("%s:%i - EffectiveTz=%i, Mins=%i\n", _FL, EffectiveTz, Mins); #endif if (EventStart.IsValid()) { #if DEBUG_LOGGING LgiTrace("EventStart=%s\n", EventStart.Get().Get()); #endif EventStart.AddMinutes(-Mins); #if DEBUG_LOGGING LgiTrace("EventStart=%s\n", EventStart.Get().Get()); #endif } if (EventEnd.IsValid()) EventEnd.AddMinutes(-Mins); } } if (EventStart.IsValid()) c->SetDate(FIELD_CAL_START_UTC, &EventStart); if (EventEnd.IsValid()) c->SetDate(FIELD_CAL_END_UTC, &EventEnd); if (Alarms.Length()) { LString::Array s; for (LAlarm &a: Alarms) { LString r = a.GetStr(); if (r) s.Add(r); } if (s.Length()) { c->SetStr(FIELD_CAL_REMINDERS, LString("\n").Join(s)); } } ClearFields(); return Status; } LString ToString(LDateTime &dt) { LString s; s.Printf("%04.4i%02.2i%02.2iT%02.2i%02.2i%02.2i%s", dt.Year(), dt.Month(), dt.Day(), dt.Hours(), dt.Minutes(), dt.Seconds(), dt.GetTimeZone() ? "" : "Z"); return s; } bool VCal::Export(LDataPropI *c, LStreamI *o) { if (!c || !o) return false; int64 Type = c->GetInt(FIELD_CAL_TYPE); // auto *TimeZone = c->GetStr(FIELD_CAL_TIMEZONE); char *TypeStr = Type == 1 ? (char*)"VTODO" : (char*)"VEVENT"; o->Push((char*)"BEGIN:VCALENDAR\r\n" "VERSION:2.0\r\n" "CALSCALE:GREGORIAN\r\n" "PRODID:Memecode vcal filter\r\n"); if (c) { LStreamPrint(o, "BEGIN:%s\r\n", TypeStr); #define OutputStr(Field, Name) \ { \ const char *_s = 0; \ if ((_s = c->GetStr(Field))) \ { \ WriteField(*o, Name, 0, _s); \ } \ } LDateTime Now; Now.SetNow(); Now.ToUtc(); LStreamPrint(o, "DTSTAMP:%s\r\n", ToString(Now).Get()); OutputStr(FIELD_CAL_SUBJECT, "SUMMARY"); OutputStr(FIELD_CAL_LOCATION, "LOCATION"); OutputStr(FIELD_CAL_NOTES, "DESCRIPTION"); OutputStr(FIELD_CAL_STATUS, "STATUS"); int64 ShowAs; if ((ShowAs = c->GetInt(FIELD_CAL_SHOW_TIME_AS))) { switch (ShowAs) { default: case CalFree: o->Push((char*)"X-SHOWAS:FREE\r\n"); break; case CalTentative: o->Push((char*)"X-SHOWAS:TENTATIVE\r\n"); break; case CalBusy: o->Push((char*)"X-SHOWAS:BUSY\r\n"); break; case CalOut: o->Push((char*)"X-SHOWAS:OUT\r\n"); break; } } const char *Uid; if ((Uid = c->GetStr(FIELD_UID))) { LStreamPrint(o, "UID:%s\r\n", Uid); } const LDateTime *Dt; if ((Dt = c->GetDate(FIELD_CAL_START_UTC))) { LDateTime dt = *Dt; dt.ToUtc(); LStreamPrint(o, "DTSTART:%s\r\n", ToString(dt).Get()); } if ((Dt = c->GetDate(FIELD_CAL_END_UTC))) { LDateTime dt = *Dt; dt.ToUtc(); LStreamPrint(o, "DTEND:%s\r\n", ToString(dt).Get()); } if (auto attachments = c->GetList(FIELD_CAL_ATTACHMENTS)) { // Save the attachments to 'ATTACH' fields for (auto file = attachments->First(); file; file = attachments->Next()) { auto fileData = dynamic_cast(file); auto uri = file->GetStr(FIELD_URI); auto fileName = file->GetStr(FIELD_NAME); auto mimeType = file->GetStr(FIELD_MIME_TYPE); ParamArray param; if (fileName) param.Set("X-FILENAME", fileName); if (mimeType) param.Set("FMTTYPE", mimeType); if (uri) { // Save as a URI WriteField(*o, "ATTACH", ¶m, uri); } else if (!fileData) { LAssert(!"Should be a LDataI object."); } else { auto stream = fileData->GetStream(_FL); if (!stream) LAssert(!"Should have either URI or Stream"); else { // Base64 encode the stream LString bin; if (!bin.Length(stream->GetSize())) LAssert(!"Failed to set string size"); else { stream->Read(bin.Get(), bin.Length()); auto b64 = LToBase64(bin); if (!b64) LAssert(!"Failed to convert to base64"); else { param.Set("ENCODING", "BASE64"); param.Set("VALUE", "BINARY"); WriteField(*o, "ATTACH", ¶m, b64); } } } } } } const char *Reminders; if ((Reminders = c->GetStr(FIELD_CAL_REMINDERS))) { auto Lines = LString(Reminders).SplitDelimit("\n"); for (auto Ln: Lines) { // Fields are: Number, CalendarReminderUnits, CalendarReminderType, Param auto p = Ln.SplitDelimit(","); if (p.Length() >= 3) { const char *Duration = NULL; int64 Count = p[0].Int(); switch (p[1].Int()) { case CalMinutes: Duration = "M"; break; case CalHours: Duration = "H"; break; case CalDays: Duration = "D"; break; case CalWeeks: Duration = "D"; Count *= 7; break; } const char *Action = NULL; switch (p[2].Int()) { case CalEmail: Action = "EMAIL"; break; case CalPopup: Action = "DISPLAY"; break; case CalScriptCallback: Action = "X-SCRIPT"; break; } if (Action && Duration) { LString s; s.Printf( "BEGIN:VALARM\r\n" "ACTION:%s\r\n" "DESCRIPTION:REMINDER\r\n" "TRIGGER:-PT" LPrintfInt64 "%s\r\n", Action, Count, Duration ); if (p.Length() > 3) s += LString("X-PARAM: ") + p[3]; s += "END:VALARM\r\n"; o->Write(s.Get(), s.Length()); } else LAssert(0); } } } LJson j(c->GetStr(FIELD_ATTENDEE_JSON)); for (auto g: j.GetArray(LString())) { // e.g.: ATTENDEE;CN="Matthew Allen";ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:matthew@company.com.au auto Email = g.Get("email"); if (Email) { auto Name = g.Get("name"); auto Role = g.Get("role"); char s[400]; int ch = sprintf_s(s, sizeof(s), "ATTENDEE"); if (Name) ch += sprintf_s(s+ch, sizeof(s)-ch, ";CN=\"%s\"", Name.Get()); if (Role) ch += sprintf_s(s+ch, sizeof(s)-ch, ";ROLE=\"%s\"", Role.Get()); ch += sprintf_s(s+ch, sizeof(s)-ch, ":MAILTO=%s\r\n", Email.Get()); o->Write(s, ch); } } LStreamPrint(o, "END:%s\r\n", TypeStr); } o->Push((char*)"END:VCALENDAR\r\n"); return true; }