diff --git a/src/common/Lgi/GuiUtils.cpp b/src/common/Lgi/GuiUtils.cpp --- a/src/common/Lgi/GuiUtils.cpp +++ b/src/common/Lgi/GuiUtils.cpp @@ -1,283 +1,289 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #if defined(LINUX) && !defined(LGI_SDL) #include "LgiWinManGlue.h" #endif ////////////////////////////////////////////////////////////////////////////////// #if !defined(LK_CONTEXTKEY) // LK_CONTEXTKEY is the key that brings up the context menu #if defined(WINDOWS) #define LK_CONTEXTKEY 0x5d #elif defined(MAC) #define LK_CONTEXTKEY VK_APPS #elif defined(__GTK_H__) #define LK_CONTEXTKEY GDK_KEY_Menu #else #define LK_CONTEXTKEY 0x5d #warning "Check local platform def for app menu key." #endif #endif +LKey::LKey(int key, uint32_t flags) +{ + vkey = key; + LAssert(flags == 0); // Or impl if you pass something in. +} + bool LKey::IsContextMenu() const { return !IsChar && vkey == LK_CONTEXTKEY; } ////////////////////////////////////////////////////////////////////////////////// LString LMouse::ToString() const { LString s; s.Printf("LMouse(pos=%i,%i view=%p/%s btns=%i/%i/%i/%i/%i dwn=%i dbl=%i " "ctrl=%i alt=%i sh=%i sys=%i)", x, y, // pos Target, Target?Target->GetClass():NULL, // view Left(), Middle(), Right(), Button1(), Button2(), // btns Down(), Double(), // dwn Ctrl(), Alt(), Shift(), System()); // mod keys return s; } bool LMouse::IsContextMenu() const { if (Right()) return true; #if defined(MAC) if (Left() && Ctrl()) return true; #endif return false; } bool LMouse::ToScreen() { if (ViewCoords) { if (!Target) { printf("%s:%i - ToScreen Error: Target=%p ViewCoords=%i\n", _FL, Target, ViewCoords); return false; } LPoint p(x, y); Target->PointToScreen(p); x = p.x; y = p.y; ViewCoords = false; } return true; } bool LMouse::ToView() { if (!ViewCoords) { if (!Target) { printf("%s:%i - ToView Error: Target=%p ViewCoords=%i\n", _FL, Target, ViewCoords); return false; } LPoint p(x, y); Target->PointToView(p); x = p.x; y = p.y; ViewCoords = true; } return true; } #if WINNATIVE #if !defined(DM_POSITION) #define DM_POSITION 0x00000020L #endif typedef WINUSERAPI BOOL (WINAPI *pEnumDisplayDevicesA)(PVOID, DWORD, PDISPLAY_DEVICEA, DWORD); typedef WINUSERAPI BOOL (WINAPI *pEnumDisplayDevicesW)(PVOID, DWORD, PDISPLAY_DEVICEW, DWORD); typedef WINUSERAPI BOOL (WINAPI *pEnumDisplaySettingsA)(LPCSTR lpszDeviceName, DWORD iModeNum, LPDEVMODEA lpDevMode); typedef WINUSERAPI BOOL (WINAPI *pEnumDisplaySettingsW)(LPCWSTR lpszDeviceName, DWORD iModeNum, LPDEVMODEW lpDevMode); #endif LPointF GDisplayInfo::Scale() { LPointF p((double)Dpi.x / 96.0, (double)Dpi.y / 96.0); return p; } bool LGetDisplays(::LArray &Displays, LRect *AllDisplays) { #if WINNATIVE if (AllDisplays) AllDisplays->ZOff(-1, -1); LLibrary User32("User32"); DISPLAY_DEVICEW disp; ZeroObj(disp); disp.cb = sizeof(disp); pEnumDisplayDevicesW EnumDisplayDevicesW = (pEnumDisplayDevicesW) User32.GetAddress("EnumDisplayDevicesW"); pEnumDisplaySettingsW EnumDisplaySettingsW = (pEnumDisplaySettingsW) User32.GetAddress("EnumDisplaySettingsW"); for (int i=0; EnumDisplayDevicesW(0, i, &disp, 0); i++) { DEVMODEW mode; ZeroObj(mode); mode.dmSize = sizeof(mode); mode.dmDriverExtra = sizeof(mode); if (EnumDisplaySettingsW(disp.DeviceName, ENUM_CURRENT_SETTINGS, &mode)) { GDisplayInfo *Dsp = new GDisplayInfo; if (Dsp) { Dsp->r.ZOff(mode.dmPelsWidth-1, mode.dmPelsHeight-1); if (mode.dmFields & DM_POSITION) { Dsp->r.Offset(mode.dmPosition.x, mode.dmPosition.y); } if (AllDisplays) { if (AllDisplays->Valid()) AllDisplays->Union(&Dsp->r); else *AllDisplays = Dsp->r; } disp.cb = sizeof(disp); Dsp->BitDepth = mode.dmBitsPerPel; Dsp->Refresh = mode.dmDisplayFrequency; Dsp->Name = disp.DeviceString; Dsp->Device = disp.DeviceName; Dsp->Dpi.x = Dsp->Dpi.y = mode.dmLogPixels; DISPLAY_DEVICEW temp = disp; if (EnumDisplayDevicesW(temp.DeviceName, 0, &disp, 0)) Dsp->Monitor = disp.DeviceString; Displays.Add(Dsp); } } disp.cb = sizeof(disp); } #elif defined __GTK_H__ Gtk::GdkDisplay *Dsp = Gtk::gdk_display_get_default(); #if GtkVer(3, 22) int monitors = Gtk::gdk_display_get_n_monitors(Dsp); for (int i=0; ir = geometry; di->Device = Gtk::gdk_monitor_get_manufacturer(m); di->Name = Gtk::gdk_monitor_get_model(m); di->Refresh = Gtk::gdk_monitor_get_refresh_rate(m); Displays.Add(di); } #endif #elif LGI_COCOA for (NSScreen *s in [NSScreen screens]) { GDisplayInfo *di = new GDisplayInfo; di->r = s.frame; Displays.Add(di); } #endif return Displays.Length() > 0; } void GetChildrenList(LViewI *w, List &l) { if (!w) return; for (auto v: w->IterateViews()) { #if WINNATIVE int Style = GetWindowLong(v->Handle(), GWL_STYLE); if (TestFlag(Style, WS_VISIBLE) && !TestFlag(Style, WS_DISABLED)) { if (TestFlag(Style, WS_TABSTOP)) { l.Insert(v); } GetChildrenList(v, l); } #else if (v->Visible() && v->Enabled()) { if (v->GetTabStop()) { l.Insert(v); } GetChildrenList(v, l); } #endif } } LViewI *GetNextTabStop(LViewI *v, bool Back) { if (v) { LWindow *Wnd = v->GetWindow(); if (Wnd) { List All; GetChildrenList(Wnd, All); ssize_t MyIndex = All.IndexOf(v); if (MyIndex >= 0) { int Inc = Back ? -1 : 1; size_t NewIndex = (MyIndex + All.Length() + Inc) % All.Length(); return All.ItemAt(NewIndex); } else { return All[0]; } } } return 0; } diff --git a/src/common/Net/MailHttp.cpp b/src/common/Net/MailHttp.cpp --- a/src/common/Net/MailHttp.cpp +++ b/src/common/Net/MailHttp.cpp @@ -1,644 +1,644 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Mail.h" #include "lgi/common/DocView.h" ////////////////////////////////////////////////////////////////////////// #include "lgi/common/Http.h" static char PopOverHttpSep[] = "\n-----------------[EndPopOverHttp]-----------------"; class Msg { public: int Index; int Size; char *Uid; char *Headers; bool Delete; Msg() { Index = 0; Size = 0; Uid = 0; Headers = 0; Delete = false; } ~Msg() { DeleteArray(Uid); DeleteArray(Headers); } }; class MailPhpPrivate { public: int Messages; char Uri[256]; bool HasDeletes; bool HeadersRetreived; List Msgs; char *ProxyServer; int ProxyPort; char *UserName; char *UserPass; MailPhpPrivate() { Uri[0] = 0; Messages = 0; HasDeletes = 0; ProxyServer = 0; ProxyPort = 0; UserName = 0; UserPass = 0; HeadersRetreived = false; } ~MailPhpPrivate() { DeleteArray(ProxyServer); Msgs.DeleteObjects(); DeleteArray(UserName); DeleteArray(UserPass); } }; MailPhp::MailPhp() { d = new MailPhpPrivate; } MailPhp::~MailPhp() { DeleteObj(d); } class MailSocket : public LSocket { LSocketI *S; MailProtocolProgress *T; MailProtocol *Log; LStringPipe ReadBuf, WriteBuf; bool InData; LFile *Temp; ssize_t SepLen; bool OwnSocket; public: MailSocket(LSocketI *s, MailProtocolProgress *t, MailProtocol *l) { if (s) { S = s; OwnSocket = false; } else { S = new LSocket; OwnSocket = true; } T = t; InData = false; Log = l; Temp = 0; SepLen = strlen(PopOverHttpSep) - 1; } ~MailSocket() { DeleteObj(Temp); if (OwnSocket) { DeleteObj(S); } } // Stream int Open(const char *Str, int Int) { return S->Open(Str, Int); } bool IsOpen() { return S->IsOpen(); } int Close() { return S->Close(); } int64 GetSize() { return S->GetSize(); } int64 SetSize(int64 Size) { return S->SetSize(Size); } int64 GetPos() { return S->GetPos(); } int64 SetPos(int64 Pos) { return S->SetPos(Pos); } LStreamI *Clone() { return S->Clone(); } // Socket OsSocket Handle(OsSocket Set) { return S->Handle(Set); } bool GetLocalIp(char *IpAddr) { return S->GetLocalIp(IpAddr); } int GetLocalPort() { return S->GetLocalPort(); } bool GetRemoteIp(char *IpAddr) { return S->GetRemoteIp(IpAddr); } int GetRemotePort() { return S->GetRemotePort(); } bool IsReadable(int TimeoutMs = 0) { return S->IsReadable(); } bool Listen(int Port = 0) { return S->Listen(Port = 0); } bool Accept(LSocketI *c) { return S->Accept(c); } int Error(void *param) { return S->Error(param); } void OnDisconnect() { S->OnDisconnect(); } void OnError(int ErrorCode, const char *ErrorDescription) { S->OnError(ErrorCode, ErrorDescription); } void OnInformation(const char *Str) { S->OnInformation(Str); } void OnRead(char *Data, ssize_t Len) { S->OnRead(Data, Len); if (Temp) { Temp->Write(Data, Len); } else if (!Data) { ReadBuf.Push(Data, Len); char Buf[512]; while (ReadBuf.Pop(Buf, sizeof(Buf))) { if (strchr("\r\n", Buf[0])) { InData = true; ReadBuf.Empty(); break; } else { Log->Log(Buf, LSocketI::SocketMsgReceive); } } } else if (Log->Items) { ReadBuf.Push(Data, Len); char Buf[512]; while (ReadBuf.Pop(Buf, sizeof(Buf))) { if (strncmp(Buf, PopOverHttpSep+1, SepLen) == 0) { Log->Items->Value++; } } } } void OnWrite(const char *Data, ssize_t Len) { S->OnWrite(Data, Len); WriteBuf.Push(Data, Len); char Buf[256]; while (WriteBuf.Pop(Buf, sizeof(Buf))) { if (!strchr("\r\n", Buf[0])) { Log->Log(Buf, LSocketI::SocketMsgSend); } } } bool SetValue(const char *Which, LVariant &What) { int r = S->SetValue(Which, What); if (T && _stricmp(Which, LSocket_TransferSize) == 0) { T->Range = What.CastInt32(); } return r != 0; } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) { // Remove null characters char *o = (char*) Buffer; char *i = o; while (i < (char*)Buffer+Size) { if (*i) { *o++ = *i; } i++; } return S->Write(Buffer, o - (char*)Buffer, Flags); } ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) { ssize_t s = S->Read(Buffer, Size, Flags); if (T && s > 0) { T->Value += s; } return s; } }; void MailPhp::SetProxy(char *Server, int Port) { d->ProxyServer = NewStr(Server); d->ProxyPort = Port; } bool MailPhp::Get(LSocketI *S, char *Uri, LStream &Out, bool MailTransfer) { bool Status = false; if (S && Uri) { char *Start = Uri; if (_strnicmp(Start, "http://", 7) == 0) Start += 7; char *s = strchr(Start, '/'); if (s) { char *Base = NewStr(Start, s-Start); LHttp Http; if (d->UserName && d->UserPass) { Http.SetAuth(d->UserName, d->UserPass); } if (d->ProxyServer) { Http.SetProxy(d->ProxyServer, d->ProxyPort); } LAutoPtr s(new MailSocket(S, MailTransfer ? Transfer : 0, this)); if (Http.Open(s, Base)) { LStringPipe Buf; int Code = 0; LHttp::ContentEncoding Enc; if (Http.Get(Uri, 0, &Code, &Buf, &Enc)) { char Start[256]; if (Buf.Peek((uchar*)Start, sizeof(Start))) { Start[sizeof(Start)-1] = 0; if (_stricmp(Start, "Error:") == 0) { return false; } } LCopyStreamer s; Status = s.Copy(&Buf, &Out) > 0; } } DeleteArray(Base); } } return Status; } ssize_t MailPhp::GetMessages() { return d->Messages; } bool MailPhp::Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags) { if (S && RemoteHost) { d->HeadersRetreived = false; DeleteArray(d->UserName); d->UserName = NewStr(User); DeleteArray(d->UserPass); d->UserPass = NewStr(Password); uint64 Token = LCurrentTime(); strcpy_s(d->Uri, sizeof(d->Uri), RemoteHost); char *e = d->Uri + strlen(d->Uri); sprintf_s(e, sizeof(d->Uri)-(e-d->Uri), "?token=" LPrintfInt64, Token); char *m = 0; LStringPipe Text; Socket.Reset(S); if (Get(Socket, d->Uri, Text, false)) { m = Text.NewStr(); } *e = 0; if (m) { bool PopOverHttp = false; bool GotToken = false; auto Lines = LString(m).SplitDelimit("\r\n"); if (_strnicmp(m, "error:", 6) == 0) { for (unsigned Line=0; Line 1) { for (unsigned Line=0; Line 1) { Msg *m = new Msg; if (m) { m->Index = atoi(Var); m->Size = atoi(p[0]); m->Uid = NewStr(p[1]); d->Msgs.Insert(m); } } } } else if (_stricmp(Var, "protocol") == 0) { PopOverHttp = stristr(Val, "popoverhttp") != 0; } else if (_stricmp(Var, "messages") == 0) { d->Messages = atoi(Val); } else if (_stricmp(Var, "token") == 0) { int Tok = atoi(Val); GotToken = Tok == Token; } } } } else Log("Empty index page returned.", LSocketI::SocketMsgError); DeleteArray(m); if (!PopOverHttp) { Log("Page is not a PopOverHttp index.", LSocketI::SocketMsgError); } if (!GotToken) { Log("Missing or invalid token. Is your PopOverHttp page broken?", LSocketI::SocketMsgError); } return PopOverHttp && GotToken && d->Messages == d->Msgs.Length(); } else Log("No PopOverHttp index page.", LSocketI::SocketMsgError); } else Log("No remote host.", LSocketI::SocketMsgError); return false; } bool MailPhp::Close() { if (d->HasDeletes && d->Messages) { LStringPipe p; p.Print("%s?time=%i&delete=", d->Uri, LCurrentTime()); bool First = true; for (auto m: d->Msgs) { if (m->Delete) { if (!First) p.Push(","); else First = false; p.Print("%i", m->Index+1); } } bool Status = false; LSocketI *S = new MailSocket(Socket, 0, this); if (S) { LStringPipe Out; char *u = p.NewStr(); if (u) { if (Get(S, u, Out, false)) { char *m = Out.NewStr(); if (m) { Status = stristr("Error:", m) == 0; DeleteArray(m); } } DeleteArray(u); } } } d->Msgs.DeleteObjects(); return true; } bool MailPhp::Receive(LArray &Trans, MailCallbacks *Callbacks) { LStringPipe Cmd; Cmd.Push(d->Uri); Cmd.Print("?time=%i&msg=", LCurrentTime()); for (unsigned i=0; iIndex + 1); } bool Status = false; LSocketI *S = new MailSocket(Socket, 0, this); if (S) { // Download all the messages LStringPipe Out; char *c = Cmd.NewStr(); Status = Get(S, c, Out, true); DeleteArray(c); // Split them into individual transactions char *All = Out.NewStr(); if (All) { char *In = All; char *Out = All; while (*In) { if (*In != '\r') { *Out++ = *In; } In++; } *Out = 0; char *s = All; unsigned i = 0; for (char *e = strstr(s, PopOverHttpSep); s && e; i++) { MailTransaction *t = Trans[i]; if (t && t->Stream) { ssize_t Len = e-s; if (!Strnstr(s, "Error: ", MIN(Len, 256))) { t->Stream->Write(s, Len - 2); t->Status = true; Status = true; } else { printf("%s:%i - Error in Mail.\n", __FILE__, __LINE__); } s = strchr(e + 1, '\n'); if (s) s++; e = s ? strstr(s, PopOverHttpSep) : 0; } else { printf("%s:%i - Error: No Stream.\n", __FILE__, __LINE__); break; } } if (i < Trans.Length()) { printf("%s:%i - Error: Only found %i of %i separators in %i bytes from server.\n", __FILE__, __LINE__, i, (int)Trans.Length(), (int)strlen(All)); } DeleteArray(All); } } return Status; } bool MailPhp::Delete(int Message) { Msg *m = d->Msgs[Message]; if (m) { m->Delete = true; d->HasDeletes = true; return true; } return false; } int MailPhp::Sizeof(int Message) { Msg *m = d->Msgs[Message]; return m ? m->Size : 0; } bool MailPhp::GetSizes(LArray &Sizes) { for (auto m: d->Msgs) Sizes.Add(m->Size); return Sizes.Length() == d->Msgs.Length(); } bool MailPhp::GetUid(int Message, char *Id, int IdLen) { Msg *m = d->Msgs[Message]; if (!m) return false; strcpy_s(Id, IdLen, m->Uid); return true; } bool MailPhp::GetUidList(LString::Array &Id) { for (auto m: d->Msgs) if (m->Uid) Id.New() = m->Uid; return Id.Length() > 0; } LString MailPhp::GetHeaders(int Message) { Msg *TheMsg = d->Msgs[Message]; if (!TheMsg) - return NULL; + return LString(); if (!d->HeadersRetreived) { d->HeadersRetreived = true; char *e = d->Uri + strlen(d->Uri); sprintf_s(e, sizeof(d->Uri)-(e-d->Uri), "?top=1"); LStringPipe Text; if (Get(new LSocket, d->Uri, Text, false)) { int n = 0; char *All = Text.NewStr(); // int AllLen = strlen(All); for (char *s = All; s && *s; ) { Msg *m = d->Msgs[n++]; if (m) { DeleteArray(m->Headers); char *e = stristr(s, "\r\n.\r\n"); if (e) { m->Headers = NewStr(s, e-s); s = e + 5; while (*s == '\r' || *s == '\n') s++; } else { m->Headers = NewStr(s, strlen(s)-3); s = 0; } } else break; } DeleteArray(All); } *e = 0; } return TheMsg->Headers; } diff --git a/src/common/Net/OAuth2.cpp b/src/common/Net/OAuth2.cpp --- a/src/common/Net/OAuth2.cpp +++ b/src/common/Net/OAuth2.cpp @@ -1,506 +1,506 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TextLog.h" #include "lgi/common/OpenSSLSocket.h" #include "lgi/common/Base64.h" #include "lgi/common/OAuth2.h" #include "lgi/common/Json.h" ////////////////////////////////////////////////////////////////// #define LOCALHOST_PORT 54900 #define OPT_AccessToken "AccessToken" #define OPT_RefreshToken "RefreshToken" static LString GetHeaders(LSocketI *s) { char Buf[256]; ssize_t Rd; LString p; while ((Rd = s->Read(Buf, sizeof(Buf))) > 0) { p += LString(Buf, Rd); if (p.Find("\r\n\r\n") >= 0) return p; } s->Close(); - return NULL; + return LString(); } ssize_t ChunkSize(ssize_t &Pos, LString &Buf, LString &Body) { static LString Eol("\r\n"); auto End = Buf.Find(Eol, Pos); if (End > Pos) { auto Sz = Buf(Pos, End).Int(16); if (Sz >= 0) { End += Eol.Length(); auto Bytes = End + Sz + Eol.Length(); if (Buf.Length() >= Bytes) { Body += Buf(End, End + Sz); Pos = End + Sz + Eol.Length(); return Sz; } } } return -1; } static bool GetHttp(LSocketI *s, LString &Hdrs, LString &Body, bool IsResponse) { LString Resp = GetHeaders(s); char Buf[512]; ssize_t Rd; auto BodyPos = Resp.Find("\r\n\r\n"); LAutoString Len(InetGetHeaderField(Resp, "Content-Length", BodyPos)); if (Len) { int Bytes = atoi(Len); size_t Total = BodyPos + 4 + Bytes; while (Resp.Length() < Total) { Rd = s->Read(Buf, sizeof(Buf)); if (Rd > 0) { Resp += LString(Buf, Rd); } } } else if (s->IsOpen() && IsResponse) { LAutoString Te(InetGetHeaderField(Resp, "Transfer-Encoding", BodyPos)); bool Chunked = Te && !_stricmp(Te, "chunked"); if (Chunked) { ssize_t Pos = 0; Hdrs = Resp(0, BodyPos); LString Raw = Resp(BodyPos + 4, -1); Body.Empty(); while (s->IsOpen()) { auto Sz = ChunkSize(Pos, Raw, Body); if (Sz == 0) break; if (Sz < 0) { Rd = s->Read(Buf, sizeof(Buf)); if (Rd > 0) Raw += LString(Buf, Rd); else break; } } return true; } else { while ((Rd = s->Read(Buf, sizeof(Buf))) > 0) Resp += LString(Buf, Rd); } } Hdrs = Resp(0, BodyPos); Body = Resp(BodyPos + 4, -1); return true; } static LString UrlFromHeaders(LString Hdrs) { auto Lines = Hdrs.Split("\r\n", 1); auto p = Lines[0].SplitDelimit(); if (p.Length() < 3) { - return NULL; + return LString(); } return p[1]; } static bool Write(LSocketI *s, LString b) { for (size_t i = 0; i < b.Length(); ) { auto Wr = s->Write(b.Get() + i, b.Length() - i); if (Wr <= 0) return false; i += Wr; } return true; } static LString FormEncode(const char *s, bool InValue = true) { LStringPipe p; for (auto c = s; *c; c++) { if (isalpha(*c) || isdigit(*c) || *c == '_' || *c == '.' || (!InValue && *c == '+') || *c == '-' || *c == '%') { p.Write(c, 1); } else if (*c == ' ') { p.Write((char*)"+", 1); } else { p.Print("%%%02.2X", *c); } } return p.NewLStr(); } struct LOAuth2Priv { LOAuth2::Params Params; LString Id; LStream *Log; LString Token; LString CodeVerifier; LStringPipe LocalLog; LDom *Store; LCancel *Cancel; LString AccessToken, RefreshToken; int64 ExpiresIn; struct Server : public LSocket { LSocket Listen; LOAuth2Priv *d; LSocket s; public: LHashTbl,LString> Params; LString Body; Server(LOAuth2Priv *cd) : d(cd) { auto Start = LCurrentTime(); while ( !d->Cancel->IsCancelled() && !Listen.Listen(LOCALHOST_PORT) && (LCurrentTime() - Start) < 60000) { d->Log->Print("Error: Can't listen on %i... (%s)\n", LOCALHOST_PORT, Listen.GetErrorString()); LSleep(1000); } } bool GetReq() { while (!d->Cancel->IsCancelled()) { if (Listen.IsReadable(100) && Listen.Accept(&s)) { // Read access code out of response LString Hdrs; if (GetHttp(&s, Hdrs, Body, false)) { auto Url = UrlFromHeaders(Hdrs); auto Vars = Url.Split("?", 1); if (Vars.Length() != 2) { return false; } Vars = Vars[1].Split("&"); for (auto v : Vars) { auto p = v.Split("=", 1); if (p.Length() != 2) continue; Params.Add(p[0], p[1]); } return true; } } } return false; } bool Response(const char *Txt) { LString Msg; Msg.Printf("HTTP/1.0 200 OK\r\n" "\r\n" "\n" "%s\n" "", Txt); return ::Write(&s, Msg); } }; LString Base64(LString s) { LString b; b.Length(BufferLen_BinTo64(s.Length())); ConvertBinaryToBase64(b.Get(), b.Length(), (uchar*)s.Get(), s.Length()); b.Get()[b.Length()] = 0; return b; } LString ToText(LString Bin) { LArray t; for (char i='0'; i<='9'; i++) t.Add(i); for (char i='a'; i<='z'; i++) t.Add(i); for (char i='A'; i<='Z'; i++) t.Add(i); t.Add('-'); t.Add('.'); t.Add('_'); t.Add('~'); LString Txt; Txt.Length(Bin.Length()); int Pos = 0; for (int i=0; iPrint("%s:%i - Uri: %s\n", _FL, Uri.Get()); LExecute(Uri); // Open browser for user to auth if (Svr.GetReq()) { Token = Svr.Params.Find("code"); Svr.Response(Token ? "Ok: Got token. You can close this window/tab now." : "Error: No token."); } return Token != NULL; } bool GetAccess() { if (!AccessToken) { LStringPipe p(1024); LUri u(Params.ApiUri); SslSocket sock(NULL, NULL, true); if (!sock.Open(u.sHost, HTTPS_PORT)) { Log->Print("Error: Can't connect to '%s:%i'\n", u.sHost.Get(), HTTPS_PORT); return NULL; } LString Body, Http; Body.Printf("code=%s&" "client_id=%s&" "redirect_uri=http://localhost:%i&" "code_verifier=%s&" "grant_type=authorization_code", FormEncode(Token).Get(), Params.ClientID.Get(), LOCALHOST_PORT, FormEncode(CodeVerifier).Get()); if (Params.ClientSecret) { Body += "&client_secret="; Body += Params.ClientSecret; } LUri Api(Params.ApiUri); Http.Printf("POST %s HTTP/1.1\r\n" "Host: %s\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-length: " LPrintfSizeT "\r\n" "\r\n" "%s", Api.sPath.Get(), Api.sHost.Get(), Body.Length(), Body.Get()); if (!Write(&sock, Http)) { Log->Print("%s:%i - Error writing to socket.\n", _FL); return false; } LString Hdrs; if (!GetHttp(&sock, Hdrs, Body, true)) { return false; } // Log->Print("Body=%s\n", Body.Get()); LJson j(Body); AccessToken = j.Get("access_token"); RefreshToken = j.Get("refresh_token"); ExpiresIn = j.Get("expires_in").Int(); if (!AccessToken) Log->Print("Failed to get AccessToken: %s\n", Body.Get()); } return AccessToken.Get() != NULL; } bool Refresh() { if (!RefreshToken) return false; LStringPipe p(1024); LUri u(Params.Scope); SslSocket sock(NULL, NULL, true); if (!sock.Open(u.sHost, HTTPS_PORT)) { Log->Print("Error: Can't connect to '%s:%i'\n", u.sHost.Get(), HTTPS_PORT); return NULL; } LString Body, Http; Body.Printf("refresh_token=%s&" "client_id=%s&" "client_secret=%s&" "grant_type=refresh_token", FormEncode(RefreshToken).Get(), Params.ClientID.Get(), Params.ClientSecret.Get()); LUri Api(Params.ApiUri); Http.Printf("POST %s HTTP/1.1\r\n" "Host: %s\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-length: " LPrintfSizeT "\r\n" "\r\n" "%s", Api.sPath.Get(), Api.sHost.Get(), Body.Length(), Body.Get()); if (!Write(&sock, Http)) { Log->Print("%s:%i - Error writing to socket.\n", _FL); return false; } LString Hdrs; if (!GetHttp(&sock, Hdrs, Body, true)) { return false; } // Log->Print("Body=%s\n%s\n", Params.ApiUri.Get(), Body.Get()); LJson j(Body); AccessToken = j.Get("access_token"); ExpiresIn = j.Get("expires_in").Int(); return AccessToken.Get() != NULL; } LOAuth2Priv(LOAuth2::Params ¶ms, const char *account, LDom *store, LStream *log, LCancel *cancel) { Params = params; Id = account; Store = store; Cancel = cancel; Log = log ? log : &LocalLog; } bool Serialize(bool Write) { if (!Store) return false; LVariant v; LString Key, kAccTok, kRefreshTok; Key.Printf("%s.%s", Params.Scope.Get(), Id.Get()); auto KeyB64 = Base64(Key); kAccTok.Printf("OAuth2-%s-%s", OPT_AccessToken, KeyB64.Get()); kAccTok = kAccTok.RStrip("="); kRefreshTok.Printf("OAuth2-%s-%s", OPT_RefreshToken, KeyB64.Get()); kRefreshTok= kRefreshTok.RStrip("="); if (Write) { Store->SetValue(kAccTok, v = AccessToken.Get()); Store->SetValue(kRefreshTok, v = RefreshToken.Get()); } else { if (Store->GetValue(kAccTok, v)) AccessToken = v.Str(); else return false; if (Store->GetValue(kRefreshTok, v)) RefreshToken = v.Str(); else return false; } return true; } }; LOAuth2::LOAuth2(LOAuth2::Params ¶ms, const char *account, LDom *store, LCancel *cancel, LStream *log) { d = new LOAuth2Priv(params, account, store, log, cancel); d->Serialize(false); } LOAuth2::~LOAuth2() { d->Serialize(true); delete d; } bool LOAuth2::Refresh() { d->AccessToken.Empty(); d->Serialize(true); return d->Refresh(); } LString LOAuth2::GetAccessToken() { if (d->AccessToken) return d->AccessToken; if (d->GetToken()) { d->Log->Print("Got token.\n"); if (d->GetAccess()) return d->AccessToken; else d->Log->Print("No access.\n"); } else d->Log->Print("No token.\n"); return LString(); } 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,1525 +1,1525 @@ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/vCard-vCal.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) { 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)) { // 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; LArray 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(NewStr(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) { 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()); } } bool Is8Bit = false; bool HasEq = false; for (uint8_t *c = (uint8_t*)Data; *c; c++) { if ((*c & 0x80) != 0) { Is8Bit = true; } else if (*c == '=') { HasEq = true; } } if (Is8Bit || HasEq) { if (Is8Bit) s.Write((char*)";charset=utf-8", 14); s.Write((char*)";encoding=quoted-printable", 26); } s.Write((char*)":", 1); Fold(s, Data, (int) (s.GetSize() - Size)); 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)) { const char *Name = Params.Find("CN"); const char *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 (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()); } 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(NULL)) + 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; }