1 /**
2  * @file usernotifications.cpp
3  * @brief additional megaclient code for user notifications
4  *
5  * (c) 2013-2018 by Mega Limited, Auckland, New Zealand
6  *
7  * This file is part of the MEGA SDK - Client Access Engine.
8  *
9  * Applications using the MEGA API must present a valid application key
10  * and comply with the the rules set forth in the Terms of Service.
11  *
12  * The MEGA SDK is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15  *
16  * @copyright Simplified (2-clause) BSD License.
17  *
18  * You should have received a copy of the license along with this
19  * program.
20  */
21 
22 #include "mega.h"
23 #include "mega/megaclient.h"
24 #include <utility>
25 
26 namespace mega {
27 
UserAlertRaw()28 UserAlertRaw::UserAlertRaw()
29     : t(0)
30 {
31 }
32 
field(nameid nid) const33 JSON UserAlertRaw::field(nameid nid) const
34 {
35     map<nameid, string>::const_iterator i = fields.find(nid);
36     JSON j;
37     j.pos = i == fields.end() ? NULL : i->second.c_str();
38     return j;
39 }
40 
has(nameid nid) const41 bool UserAlertRaw::has(nameid nid) const
42 {
43     JSON j = field(nid);
44     return j.pos != NULL;
45 }
46 
getint(nameid nid,int default_value) const47 int UserAlertRaw::getint(nameid nid, int default_value) const
48 {
49     JSON j = field(nid);
50     return j.pos && j.isnumeric() ? int(j.getint()) : default_value;
51 }
52 
getint64(nameid nid,int64_t default_value) const53 int64_t UserAlertRaw::getint64(nameid nid, int64_t default_value) const
54 {
55     JSON j = field(nid);
56     return j.pos && j.isnumeric() ? j.getint() : default_value;
57 }
58 
gethandle(nameid nid,int handlesize,handle default_value) const59 handle UserAlertRaw::gethandle(nameid nid, int handlesize, handle default_value) const
60 {
61     JSON j = field(nid);
62     byte buf[9] = { 0 };
63     return (j.pos && handlesize == Base64::atob(j.pos, buf, sizeof(buf))) ? MemAccess::get<handle>((const char*)buf) : default_value;
64 }
65 
getnameid(nameid nid,nameid default_value) const66 nameid UserAlertRaw::getnameid(nameid nid, nameid default_value) const
67 {
68     JSON j = field(nid);
69     nameid id = 0;
70     while (*j.pos)
71     {
72         id = (id << 8) + *((const unsigned char*&)j.pos)++;
73     }
74 
75     return id ? id : default_value;
76 }
77 
getstring(nameid nid,const char * default_value) const78 string UserAlertRaw::getstring(nameid nid, const char* default_value) const
79 {
80     JSON j = field(nid);
81     return j.pos ? j.pos : default_value;
82 }
83 
gethandletypearray(nameid nid,vector<handletype> & v) const84 bool UserAlertRaw::gethandletypearray(nameid nid, vector<handletype>& v) const
85 {
86     JSON j = field(nid);
87     if (j.pos && j.enterarray())
88     {
89         for (;;)
90         {
91             if (j.enterobject())
92             {
93                 handletype ht;
94                 ht.h = UNDEF;
95                 ht.t = -1;
96                 bool fields = true;
97                 while (fields)
98                 {
99                     switch (j.getnameid())
100                     {
101                     case 'h':
102                         ht.h = j.gethandle(MegaClient::NODEHANDLE);
103                         break;
104                     case 't':
105                         ht.t = int(j.getint());
106                         break;
107                     case EOO:
108                         fields = false;
109                         break;
110                     default:
111                         j.storeobject(NULL);
112                     }
113                 }
114                 v.push_back(ht);
115                 j.leaveobject();
116             }
117             else
118             {
119                 break;
120             }
121         }
122         j.leavearray();
123         return true;
124     }
125     return false;
126 }
127 
getstringarray(nameid nid,vector<string> & v) const128 bool UserAlertRaw::getstringarray(nameid nid, vector<string>& v) const
129 {
130     JSON j = field(nid);
131     if (j.pos && j.enterarray())
132     {
133         for (;;)
134         {
135             string s;
136             if (j.storeobject(&s))
137             {
138                 v.push_back(s);
139             }
140             else
141             {
142                 break;
143             }
144         }
145         j.leavearray();
146     }
147     return false;
148 }
149 
UserAlertFlags()150 UserAlertFlags::UserAlertFlags()
151     : cloud_enabled(true)
152     , contacts_enabled(true)
153     , cloud_newfiles(true)
154     , cloud_newshare(true)
155     , cloud_delshare(true)
156     , contacts_fcrin(true)
157     , contacts_fcrdel(true)
158     , contacts_fcracpt(true)
159 {
160 }
161 
UserAlertPendingContact()162 UserAlertPendingContact::UserAlertPendingContact()
163     : u(0)
164 {
165 }
166 
167 
168 
Base(UserAlertRaw & un,unsigned int cid)169 UserAlert::Base::Base(UserAlertRaw& un, unsigned int cid)
170 {
171     id = cid;
172     type = un.t;
173     m_time_t timeDelta = un.getint64(MAKENAMEID2('t', 'd'), 0);
174     timestamp = m_time() - timeDelta;
175     userHandle = un.gethandle('u', MegaClient::USERHANDLE, UNDEF);
176     userEmail = un.getstring('m', "");
177 
178     seen = false; // to be updated on EOO
179     relevant = true;
180     tag = -1;
181 }
182 
Base(nameid t,handle uh,const string & email,m_time_t ts,unsigned int cid)183 UserAlert::Base::Base(nameid t, handle uh, const string& email, m_time_t ts, unsigned int cid)
184 {
185     id = cid;
186     type = t;
187     userHandle = uh;
188     userEmail = email;
189     timestamp = ts;
190     seen = false;
191     relevant = true;
192     tag = -1;
193 }
194 
~Base()195 UserAlert::Base::~Base()
196 {
197 }
198 
updateEmail(MegaClient * mc)199 void UserAlert::Base::updateEmail(MegaClient* mc)
200 {
201     if (User* u = mc->finduser(userHandle))
202     {
203         userEmail = u->email;
204     }
205 }
206 
checkprovisional(handle,MegaClient *)207 bool UserAlert::Base::checkprovisional(handle , MegaClient*)
208 {
209     return true;
210 }
211 
text(string & header,string & title,MegaClient * mc)212 void UserAlert::Base::text(string& header, string& title, MegaClient* mc)
213 {
214     // should be overridden
215     updateEmail(mc);
216     ostringstream s;
217     s << "notification: type " << type << " time " << timestamp << " user " << userHandle << " seen " << seen;
218     title =  s.str();
219     header = userEmail;
220 }
221 
IncomingPendingContact(UserAlertRaw & un,unsigned int id)222 UserAlert::IncomingPendingContact::IncomingPendingContact(UserAlertRaw& un, unsigned int id)
223     : Base(un, id)
224 {
225     requestWasDeleted = un.getint64(MAKENAMEID3('d', 't', 's'), 0) != 0;
226     requestWasReminded = un.getint64(MAKENAMEID3('r', 't', 's'), 0) != 0;
227 }
228 
IncomingPendingContact(m_time_t dts,m_time_t rts,handle uh,const string & email,m_time_t timestamp,unsigned int id)229 UserAlert::IncomingPendingContact::IncomingPendingContact(m_time_t dts, m_time_t rts, handle uh, const string& email, m_time_t timestamp, unsigned int id)
230     : Base(UserAlert::type_ipc, uh, email, timestamp, id)
231 {
232     requestWasDeleted = dts != 0;
233     requestWasReminded = rts != 0;
234 
235     if (requestWasDeleted)
236     {
237         this->timestamp = dts;
238     }
239 
240     if (requestWasReminded)
241     {
242         this->timestamp = rts;
243     }
244 }
245 
text(string & header,string & title,MegaClient * mc)246 void UserAlert::IncomingPendingContact::text(string& header, string& title, MegaClient* mc)
247 {
248     updateEmail(mc);
249     if (requestWasDeleted)
250     {
251         title = "Cancelled their contact request"; // 7151
252     }
253     else if (requestWasReminded)
254     {
255         title = "Reminder: You have a contact request"; // 7150
256     }
257     else
258     {
259         title = "Sent you a contact request"; // 5851
260     }
261     header = userEmail;
262 }
263 
ContactChange(UserAlertRaw & un,unsigned int id)264 UserAlert::ContactChange::ContactChange(UserAlertRaw& un, unsigned int id)
265     : Base(un, id)
266 {
267     action = un.getint('c', -1);
268     relevant = action >= 0 && action < 4;
269     assert(action >= 0 && action < 4);
270     otherUserHandle = un.gethandle(MAKENAMEID2('o', 'u'), MegaClient::USERHANDLE, UNDEF);
271 }
272 
ContactChange(int c,handle uh,const string & email,m_time_t timestamp,unsigned int id)273 UserAlert::ContactChange::ContactChange(int c, handle uh, const string& email, m_time_t timestamp, unsigned int id)
274     : Base(UserAlert::type_c, uh, email, timestamp, id)
275 {
276     action = c;
277     assert(action >= 0 && action < 4);
278 }
279 
checkprovisional(handle ou,MegaClient * mc)280 bool UserAlert::ContactChange::checkprovisional(handle ou, MegaClient* mc)
281 {
282     return action == 1 || ou != mc->me;
283 }
284 
text(string & header,string & title,MegaClient * mc)285 void UserAlert::ContactChange::text(string& header, string& title, MegaClient* mc)
286 {
287     updateEmail(mc);
288 
289     if (action == 0)
290     {
291         title = "Deleted you as a contact"; // 7146
292     }
293     else if (action == 1)
294     {
295         title = "Contact relationship established"; // 7145
296     }
297     else if (action == 2)
298     {
299         title = "Account has been deleted/deactivated"; // 7144
300     }
301     else if (action == 3)
302     {
303         title = "Blocked you as a contact"; //7143
304     }
305     header = userEmail;
306 }
307 
UpdatedPendingContactIncoming(UserAlertRaw & un,unsigned int id)308 UserAlert::UpdatedPendingContactIncoming::UpdatedPendingContactIncoming(UserAlertRaw& un, unsigned int id)
309     : Base(un, id)
310 {
311     action = un.getint('s', -1);
312     relevant = action >= 1 && action < 4;
313 }
314 
UpdatedPendingContactIncoming(int s,handle uh,const string & email,m_time_t timestamp,unsigned int id)315 UserAlert::UpdatedPendingContactIncoming::UpdatedPendingContactIncoming(int s, handle uh, const string& email, m_time_t timestamp, unsigned int id)
316     : Base(type_upci, uh, email, timestamp, id)
317     , action(s)
318 {
319 }
320 
text(string & header,string & title,MegaClient * mc)321 void UserAlert::UpdatedPendingContactIncoming::text(string& header, string& title, MegaClient* mc)
322 {
323     updateEmail(mc);
324     if (action == 1)
325     {
326         title = "You ignored a contact request"; // 7149
327     }
328     else if (action == 2)
329     {
330         title = "You accepted a contact request"; // 7148
331     }
332     else if (action == 3)
333     {
334         title = "You denied a contact request"; // 7147
335     }
336     header = userEmail;
337 }
338 
UpdatedPendingContactOutgoing(UserAlertRaw & un,unsigned int id)339 UserAlert::UpdatedPendingContactOutgoing::UpdatedPendingContactOutgoing(UserAlertRaw& un, unsigned int id)
340     : Base(un, id)
341 {
342     action = un.getint('s', -1);
343     relevant = action == 2 || action == 3;
344 }
345 
UpdatedPendingContactOutgoing(int s,handle uh,const string & email,m_time_t timestamp,unsigned int id)346 UserAlert::UpdatedPendingContactOutgoing::UpdatedPendingContactOutgoing(int s, handle uh, const string& email, m_time_t timestamp, unsigned int id)
347     : Base(type_upco, uh, email, timestamp, id)
348     , action(s)
349 {
350 }
351 
text(string & header,string & title,MegaClient * mc)352 void UserAlert::UpdatedPendingContactOutgoing::text(string& header, string& title, MegaClient* mc)
353 {
354     updateEmail(mc);
355     if (action == 2)
356     {
357         title = "Accepted your contact request"; // 5852
358     }
359     else if (action == 3)
360     {
361         title = "Denied your contact request"; // 5853
362     }
363     header = userEmail;
364 }
365 
NewShare(UserAlertRaw & un,unsigned int id)366 UserAlert::NewShare::NewShare(UserAlertRaw& un, unsigned int id)
367     : Base(un, id)
368 {
369     folderhandle = un.gethandle('n', MegaClient::NODEHANDLE, UNDEF);
370 }
371 
NewShare(handle h,handle uh,const string & email,m_time_t timestamp,unsigned int id)372 UserAlert::NewShare::NewShare(handle h, handle uh, const string& email, m_time_t timestamp, unsigned int id)
373     : Base(type_share, uh, email, timestamp, id)
374 {
375     folderhandle = h;
376 }
377 
text(string & header,string & title,MegaClient * mc)378 void UserAlert::NewShare::text(string& header, string& title, MegaClient* mc)
379 {
380     updateEmail(mc);
381     if (!userEmail.empty())
382     {
383         title =  "New shared folder from " + userEmail; // 824
384     }
385     else
386     {
387         title = "New shared folder"; // 825
388     }
389     header = userEmail;
390 }
391 
DeletedShare(UserAlertRaw & un,unsigned int id)392 UserAlert::DeletedShare::DeletedShare(UserAlertRaw& un, unsigned int id)
393     : Base(un, id)
394 {
395     ownerHandle = un.gethandle('o', MegaClient::USERHANDLE, UNDEF);
396     folderHandle = un.gethandle('n', MegaClient::NODEHANDLE, UNDEF);
397 }
398 
DeletedShare(handle uh,const string & email,handle ownerhandle,handle folderhandle,m_time_t ts,unsigned int id)399 UserAlert::DeletedShare::DeletedShare(handle uh, const string& email, handle ownerhandle, handle folderhandle, m_time_t ts, unsigned int id)
400     : Base(type_dshare, uh, email, ts, id)
401 {
402     ownerHandle = ownerhandle;
403     folderHandle = folderhandle;
404 }
405 
updateEmail(MegaClient * mc)406 void UserAlert::DeletedShare::updateEmail(MegaClient* mc)
407 {
408     Base::updateEmail(mc);
409 
410     if (Node* n = mc->nodebyhandle(folderHandle))
411     {
412         folderPath = n->displaypath();
413         folderName = n->displayname();
414     }
415 }
416 
text(string & header,string & title,MegaClient * mc)417 void UserAlert::DeletedShare::text(string& header, string& title, MegaClient* mc)
418 {
419     updateEmail(mc);
420     ostringstream s;
421 
422     if (userHandle == ownerHandle)
423     {
424         if (!userEmail.empty())
425         {
426             s << "Access to folders shared by " << userEmail << " was removed"; // 7879
427         }
428         else
429         {
430             s << "Access to folders was removed"; // 7880
431         }
432     }
433     else
434     {
435        if (!userEmail.empty())
436        {
437            s << "User " << userEmail << " has left the shared folder " << folderName;  //19153
438        }
439        else
440        {
441            s << "A user has left the shared folder " << folderName;  //19154
442        }
443     }
444     title = s.str();
445     header = userEmail;
446 }
447 
NewSharedNodes(UserAlertRaw & un,unsigned int id)448 UserAlert::NewSharedNodes::NewSharedNodes(UserAlertRaw& un, unsigned int id)
449     : Base(un, id), fileCount(0), folderCount(0)
450 {
451     std::vector<UserAlertRaw::handletype> f;
452     un.gethandletypearray('f', f);
453     parentHandle = un.gethandle('n', MegaClient::NODEHANDLE, UNDEF);
454 
455     // Count the number of new files and folders
456     for (size_t n = f.size(); n--; )
457     {
458         ++(f[n].t > 0 ? folderCount : fileCount);
459     }
460 }
461 
NewSharedNodes(int nfolders,int nfiles,handle uh,handle ph,m_time_t timestamp,unsigned int id)462 UserAlert::NewSharedNodes::NewSharedNodes(int nfolders, int nfiles, handle uh, handle ph, m_time_t timestamp, unsigned int id)
463     : Base(UserAlert::type_put, uh, string(), timestamp, id)
464     , parentHandle(ph)
465 {
466     assert(!ISUNDEF(uh));
467     folderCount = nfolders;
468     fileCount = nfiles;
469 }
470 
text(string & header,string & title,MegaClient * mc)471 void UserAlert::NewSharedNodes::text(string& header, string& title, MegaClient* mc)
472 {
473     updateEmail(mc);
474     ostringstream notificationText;
475 
476     // Get wording for the number of files and folders added
477     if ((folderCount > 1) && (fileCount > 1)) {
478         notificationText << folderCount << " folders and " << fileCount << " files";
479     }
480     else if ((folderCount > 1) && (fileCount == 1)) {
481         notificationText << folderCount << " folders and 1 file";
482     }
483     else if ((folderCount == 1) && (fileCount > 1)) {
484         notificationText << "1 folder and " << fileCount << " files";
485     }
486     else if ((folderCount == 1) && (fileCount == 1)) {
487         notificationText << "1 folder and 1 file";
488     }
489     else if (folderCount > 1) {
490         notificationText << folderCount << " folders";
491     }
492     else if (fileCount > 1) {
493         notificationText << fileCount << " files";
494     }
495     else if (folderCount == 1) {
496         notificationText << "1 folder";
497     }
498     else if (fileCount == 1) {
499         notificationText << "1 file";
500     }
501 
502     // Set wording of the title
503     if (!userEmail.empty())
504     {
505         title = userEmail + " added " + notificationText.str();
506     }
507     else if ((fileCount + folderCount) > 1)
508     {
509         title = notificationText.str() + " have been added";
510     }
511     else {
512         title = notificationText.str() + " has been added";
513     }
514     header = userEmail;
515 }
516 
RemovedSharedNode(UserAlertRaw & un,unsigned int id)517 UserAlert::RemovedSharedNode::RemovedSharedNode(UserAlertRaw& un, unsigned int id)
518     : Base(un, id)
519 {
520     vector<string> handles;
521     un.getstringarray('n', handles);
522     itemsNumber = handles.empty() ? 1 : handles.size();
523 }
524 
RemovedSharedNode(int nitems,handle uh,m_time_t timestamp,unsigned int id)525 UserAlert::RemovedSharedNode::RemovedSharedNode(int nitems, handle uh, m_time_t timestamp, unsigned int id)
526     : Base(UserAlert::type_d, uh, string(), timestamp, id)
527 {
528     itemsNumber = nitems;
529 }
530 
text(string & header,string & title,MegaClient * mc)531 void UserAlert::RemovedSharedNode::text(string& header, string& title, MegaClient* mc)
532 {
533     updateEmail(mc);
534     ostringstream s;
535     if (itemsNumber > 1)
536     {
537         s << "Removed " << itemsNumber << " items from a share"; // 8913
538     }
539     else
540     {
541         s << "Removed item from shared folder"; // 8910
542     }
543     title = s.str();
544     header = userEmail;
545 }
546 
getProPlanName()547 string UserAlert::Payment::getProPlanName()
548 {
549     switch (planNumber) {
550     case 1:
551         return "PRO I"; // 5819
552     case 2:
553         return "PRO II"; // 6125
554     case 3:
555         return "PRO III"; // 6126
556     case 4:
557         return "PRO LITE"; // 8413
558     default:
559         return "FREE"; // 435
560     }
561 }
562 
Payment(UserAlertRaw & un,unsigned int id)563 UserAlert::Payment::Payment(UserAlertRaw& un, unsigned int id)
564     : Base(un, id)
565 {
566     success = 's' == un.getnameid('r', 0);
567     planNumber = un.getint('p', 0);
568 }
569 
Payment(bool s,int plan,m_time_t timestamp,unsigned int id)570 UserAlert::Payment::Payment(bool s, int plan, m_time_t timestamp, unsigned int id)
571     : Base(type_psts, UNDEF, "", timestamp, id)
572 {
573     success = s;
574     planNumber = plan;
575 }
576 
text(string & header,string & title,MegaClient * mc)577 void UserAlert::Payment::text(string& header, string& title, MegaClient* mc)
578 {
579     updateEmail(mc);
580     ostringstream s;
581     if (success)
582     {
583         s << "Your payment for the " << getProPlanName() << " plan was received. "; // 7142
584     }
585     else
586     {
587         s << "Your payment for the " << getProPlanName() << " plan was unsuccessful."; // 7141
588     }
589     title = s.str();
590     header = "Payment info"; // 1230
591 }
592 
PaymentReminder(UserAlertRaw & un,unsigned int id)593 UserAlert::PaymentReminder::PaymentReminder(UserAlertRaw& un, unsigned int id)
594     : Base(un, id)
595 {
596     expiryTime = un.getint64(MAKENAMEID2('t', 's'), timestamp);
597     relevant = true;  // relevant until we see a subsequent payment
598 }
599 
PaymentReminder(m_time_t expiryts,unsigned int id)600 UserAlert::PaymentReminder::PaymentReminder(m_time_t expiryts, unsigned int id)
601     : Base(type_pses, UNDEF, "", m_time(), id)
602 {
603     expiryTime = expiryts;
604     relevant = true; // relevant until we see a subsequent payment
605 }
606 
text(string & header,string & title,MegaClient * mc)607 void UserAlert::PaymentReminder::text(string& header, string& title, MegaClient* mc)
608 {
609     updateEmail(mc);
610     m_time_t now = m_time();
611     int days = int((expiryTime - now) / 86400);
612 
613     ostringstream s;
614     if (expiryTime < now)
615     {
616         s << "Your PRO membership plan expired " << -days << (days == -1 ? " day" : " days") << " ago";
617     }
618     else
619     {
620         s << "Your PRO membership plan will expire in " << days << (days == 1 ? " day." : " days.");   // 8596, 8597
621     }
622     title = s.str();
623     header = "PRO membership plan expiring soon"; // 8598
624 }
625 
Takedown(UserAlertRaw & un,unsigned int id)626 UserAlert::Takedown::Takedown(UserAlertRaw& un, unsigned int id)
627     : Base(un, id)
628 {
629     int n = un.getint(MAKENAMEID4('d', 'o', 'w', 'n'), -1);
630     isTakedown = n == 1;
631     isReinstate = n == 0;
632     nodeHandle = un.gethandle('h', MegaClient::NODEHANDLE, UNDEF);
633     relevant = isTakedown || isReinstate;
634 }
635 
Takedown(bool down,bool reinstate,int,handle nh,m_time_t timestamp,unsigned int id)636 UserAlert::Takedown::Takedown(bool down, bool reinstate, int /*t*/, handle nh, m_time_t timestamp, unsigned int id)
637     : Base(type_ph, UNDEF, "", timestamp, id)
638 {
639     isTakedown = down;
640     isReinstate = reinstate;
641     nodeHandle = nh;
642     relevant = isTakedown || isReinstate;
643 }
644 
text(string & header,string & title,MegaClient * mc)645 void UserAlert::Takedown::text(string& header, string& title, MegaClient* mc)
646 {
647     updateEmail(mc);
648     const char* typestring = "node";
649     string name;
650 
651     Node* node = mc->nodebyhandle(nodeHandle);
652     if (node)
653     {
654         if (node->type == FOLDERNODE)
655         {
656             typestring = "folder";
657         }
658         else if (node->type == FILENODE)
659         {
660             typestring = "file";
661         }
662 
663         name = node->displaypath();
664     }
665 
666     if (name.empty())
667     {
668         char buffer[12];
669         Base64::btoa((byte*)&(nodeHandle), MegaClient::NODEHANDLE, buffer);
670         name = "handle ";
671         name += buffer;
672     }
673 
674     ostringstream s;
675     if (isTakedown)
676     {
677         header = "Takedown notice";  //8521
678         s << "Your publicly shared " << typestring << " (" << name << ") has been taken down."; //8522
679     }
680     else if (isReinstate)
681     {
682         header = "Takedown reinstated";  //8524
683         s << "Your taken down " << typestring << " (" << name << ") has been reinstated."; // 8523
684     }
685     title = s.str();
686 }
687 
UserAlerts(MegaClient & cmc)688 UserAlerts::UserAlerts(MegaClient& cmc)
689     : mc(cmc)
690     , nextid(0)
691     , begincatchup(false)
692     , catchupdone(false)
693     , catchup_last_timestamp(0)
694     , lsn(UNDEF)
695     , fsn(UNDEF)
696     , lastTimeDelta(0)
697     , provisionalmode(false)
698     , notingSharedNodes(false)
699     , ignoreNodesUnderShare(UNDEF)
700 {
701 }
702 
nextId()703 unsigned int UserAlerts::nextId()
704 {
705     return ++nextid;
706 }
707 
isUnwantedAlert(nameid type,int action)708 bool UserAlerts::isUnwantedAlert(nameid type, int action)
709 {
710     using namespace UserAlert;
711 
712     if (type == type_put || type == type_share || type == type_dshare)
713     {
714         if (!flags.cloud_enabled) {
715             return true;
716         }
717     }
718     else if (type == type_c || type == type_ipc || type == type_upci || type == type_upco)
719     {
720         if (!flags.contacts_enabled) {
721             return true;
722         }
723     }
724 
725     if (type == type_put)
726     {
727         return !flags.cloud_newfiles;
728     }
729     else if (type == type_share)
730     {
731         return !flags.cloud_newshare;
732     }
733     else if (type == type_dshare)
734     {
735         return !flags.cloud_delshare;
736     }
737     else if (type == type_ipc)
738     {
739         return !flags.contacts_fcrin;
740     }
741     else if (type == type_c)
742     {
743         return (action == -1 || action == 0) && !flags.contacts_fcrdel;
744     }
745     else if (type == type_upco)
746     {
747         return (action == -1 || action == 2) && !flags.contacts_fcracpt;
748     }
749 
750     return false;
751 }
752 
add(UserAlertRaw & un)753 void UserAlerts::add(UserAlertRaw& un)
754 {
755     using namespace UserAlert;
756     namespace u = UserAlert;
757     Base* unb = NULL;
758 
759     switch (un.t) {
760     case type_ipc:
761         unb = new IncomingPendingContact(un, nextId());
762         break;
763     case type_c:
764         unb = new ContactChange(un, nextId());
765         break;
766     case type_upci:
767         unb = new UpdatedPendingContactIncoming(un, nextId());
768         break;
769     case type_upco:
770         unb = new UpdatedPendingContactOutgoing(un, nextId());
771         break;
772     case type_share:
773         unb = new u::NewShare(un, nextId());
774         break;
775     case type_dshare:
776         unb = new DeletedShare(un, nextId());
777         break;
778     case type_put:
779         unb = new NewSharedNodes(un, nextId());
780         break;
781     case type_d:
782         unb = new RemovedSharedNode(un, nextId());
783         break;
784     case type_psts:
785         unb = new Payment(un, nextId());
786         break;
787     case type_pses:
788         unb = new PaymentReminder(un, nextId());
789         break;
790     case type_ph:
791         unb = new Takedown(un, nextId());
792         break;
793     default:
794         unb = NULL;   // If it's a notification type we do not recognise yet
795     }
796 
797     if (unb)
798     {
799         add(unb);
800     }
801 }
802 
add(UserAlert::Base * unb)803 void UserAlerts::add(UserAlert::Base* unb)
804 {
805     // unb is either directly from notification json, or constructed from actionpacket.
806     // We take ownership.
807 
808     if (provisionalmode)
809     {
810         provisionals.push_back(unb);
811         return;
812     }
813 
814     if (!catchupdone && unb->timestamp > catchup_last_timestamp)
815     {
816         catchup_last_timestamp = unb->timestamp;
817     }
818     else if (catchupdone && unb->timestamp < catchup_last_timestamp)
819     {
820         // this is probably a duplicate from the initial set, generated from normal sc packets
821         LOG_warn << "discarding duplicate user alert of type " << unb->type;
822         delete unb;
823         return;
824     }
825 
826     if (!alerts.empty() && unb->type == UserAlert::type_put && alerts.back()->type == UserAlert::type_put)
827     {
828         // If it's file/folders added, and the prior one is for the same user and within 5 mins then we can combine instead
829         UserAlert::NewSharedNodes* np = dynamic_cast<UserAlert::NewSharedNodes*>(unb);
830         UserAlert::NewSharedNodes* op = dynamic_cast<UserAlert::NewSharedNodes*>(alerts.back());
831         if (np && op)
832         {
833             if (np->userHandle == op->userHandle && np->timestamp - op->timestamp < 300 &&
834                 np->parentHandle == op->parentHandle && !ISUNDEF(np->parentHandle))
835             {
836                 op->fileCount += np->fileCount;
837                 op->folderCount += np->folderCount;
838                 LOG_debug << "Merged user alert, type " << np->type << " ts " << np->timestamp;
839 
840                 if (catchupdone && (useralertnotify.empty() || useralertnotify.back() != alerts.back()))
841                 {
842                     alerts.back()->seen = false;
843                     alerts.back()->tag = 0;
844                     useralertnotify.push_back(alerts.back());
845                     LOG_debug << "Updated user alert added to notify queue";
846                 }
847                 delete unb;
848                 return;
849             }
850         }
851     }
852 
853     if (!alerts.empty() && unb->type == UserAlert::type_d && alerts.back()->type == UserAlert::type_d)
854     {
855         // If it's file/folders removed, and the prior one is for the same user and within 5 mins then we can combine instead
856         UserAlert::RemovedSharedNode* nd = dynamic_cast<UserAlert::RemovedSharedNode*>(unb);
857         UserAlert::RemovedSharedNode* od = dynamic_cast<UserAlert::RemovedSharedNode*>(alerts.back());
858         if (nd && od)
859         {
860             if (nd->userHandle == od->userHandle && nd->timestamp - od->timestamp < 300)
861             {
862                 od->itemsNumber += nd->itemsNumber;
863                 LOG_debug << "Merged user alert, type " << nd->type << " ts " << nd->timestamp;
864 
865                 if (catchupdone && (useralertnotify.empty() || useralertnotify.back() != alerts.back()))
866                 {
867                     alerts.back()->seen = false;
868                     alerts.back()->tag = 0;
869                     useralertnotify.push_back(alerts.back());
870                     LOG_debug << "Updated user alert added to notify queue";
871                 }
872                 delete unb;
873                 return;
874             }
875         }
876     }
877 
878     if (!alerts.empty() && unb->type == UserAlert::type_psts && static_cast<UserAlert::Payment*>(unb)->success)
879     {
880         // if a successful payment is made then hide/remove any reminders received
881         for (Alerts::iterator i = alerts.begin(); i != alerts.end(); ++i)
882         {
883             if ((*i)->type == UserAlert::type_pses && (*i)->relevant)
884             {
885                 (*i)->relevant = false;
886                 if (catchupdone)
887                 {
888                     useralertnotify.push_back(*i);
889                 }
890             }
891         }
892     }
893 
894     unb->updateEmail(&mc);
895     alerts.push_back(unb);
896     LOG_debug << "Added user alert, type " << alerts.back()->type << " ts " << alerts.back()->timestamp;
897 
898     if (catchupdone)
899     {
900         unb->tag = 0;
901         useralertnotify.push_back(unb);
902         LOG_debug << "New user alert added to notify queue";
903     }
904 }
905 
startprovisional()906 void UserAlerts::startprovisional()
907 {
908     provisionalmode = true;
909 }
910 
evalprovisional(handle originatinguser)911 void UserAlerts::evalprovisional(handle originatinguser)
912 {
913     provisionalmode = false;
914     for (unsigned i = 0; i < provisionals.size(); ++i)
915     {
916         if (provisionals[i]->checkprovisional(originatinguser, &mc))
917         {
918             add(provisionals[i]);
919         }
920         else
921         {
922             delete provisionals[i];
923         }
924     }
925     provisionals.clear();
926 }
927 
928 
beginNotingSharedNodes()929 void UserAlerts::beginNotingSharedNodes()
930 {
931     notingSharedNodes = true;
932     notedSharedNodes.clear();
933 }
934 
noteSharedNode(handle user,int type,m_time_t ts,Node * n)935 void UserAlerts::noteSharedNode(handle user, int type, m_time_t ts, Node* n)
936 {
937     if (catchupdone && notingSharedNodes && (type == FILENODE || type == FOLDERNODE))
938     {
939         assert(!ISUNDEF(user));
940 
941         if (!ISUNDEF(ignoreNodesUnderShare))
942         {
943             // don't make alerts on files/folders already in the new share
944             for (Node* p = n; p != NULL; p = p->parent)
945             {
946                 if (p->nodehandle == ignoreNodesUnderShare)
947                     return;
948             }
949         }
950 
951         ff& f = notedSharedNodes[std::make_pair(user, n ? n->parenthandle : UNDEF)];
952         ++(type == FOLDERNODE ? f.folders : f.files);
953         if (!f.timestamp || (ts && ts < f.timestamp))
954         {
955             f.timestamp = ts;
956         }
957     }
958 }
959 
960 // make a notification out of the shared nodes noted
convertNotedSharedNodes(bool added,handle originatingUser)961 void UserAlerts::convertNotedSharedNodes(bool added, handle originatingUser)
962 {
963     if (catchupdone && notingSharedNodes && originatingUser != mc.me)
964     {
965         using namespace UserAlert;
966         for (map<pair<handle, handle>, ff>::iterator i = notedSharedNodes.begin(); i != notedSharedNodes.end(); ++i)
967         {
968             add(added ? (Base*) new NewSharedNodes(i->second.folders, i->second.files, i->first.first, i->first.second, i->second.timestamp, nextId())
969                 : (Base*) new RemovedSharedNode(i->second.folders + i->second.files, i->first.first, m_time(), nextId()));
970         }
971     }
972     notedSharedNodes.clear();
973     notingSharedNodes = false;
974     ignoreNodesUnderShare = UNDEF;
975 }
976 
ignoreNextSharedNodesUnder(handle h)977 void UserAlerts::ignoreNextSharedNodesUnder(handle h)
978 {
979     ignoreNodesUnderShare = h;
980 }
981 
982 
983 // process server-client notifications
procsc_useralert(JSON & jsonsc)984 bool UserAlerts::procsc_useralert(JSON& jsonsc)
985 {
986     for (;;)
987     {
988         switch (jsonsc.getnameid())
989         {
990         case 'u':
991             if (jsonsc.enterarray())
992             {
993                 for (;;)
994                 {
995                     UserAlertPendingContact ul;
996                     if (jsonsc.enterobject())
997                     {
998                         bool inobject = true;
999                         while (inobject)
1000                         {
1001                             switch (jsonsc.getnameid())
1002                             {
1003                             case 'u':
1004                                 ul.u = jsonsc.gethandle(MegaClient::USERHANDLE);
1005                                 break;
1006                             case 'm':
1007                                 jsonsc.storeobject(&ul.m);
1008                                 break;
1009                             case MAKENAMEID2('m', '2'):
1010                                 if (jsonsc.enterarray())
1011                                 {
1012                                     for (;;)
1013                                     {
1014                                         string s;
1015                                         if (jsonsc.storeobject(&s))
1016                                         {
1017                                             ul.m2.push_back(s);
1018                                         }
1019                                         else
1020                                         {
1021                                             break;
1022                                         }
1023                                     }
1024                                     jsonsc.leavearray();
1025                                 }
1026                                 break;
1027                             case 'n':
1028                                 jsonsc.storeobject(&ul.n);
1029                                 break;
1030                             case EOO:
1031                                 inobject = false;
1032                             }
1033                         }
1034                         jsonsc.leaveobject();
1035                         if (ul.u)
1036                         {
1037                             pendingContactUsers[ul.u] = ul;
1038                         }
1039                     }
1040                     else
1041                     {
1042                         break;
1043                     }
1044                 }
1045                 jsonsc.leavearray();
1046             }
1047             break;
1048 
1049         case MAKENAMEID3('l', 's', 'n'):
1050             lsn = jsonsc.gethandle(8);
1051             break;
1052 
1053         case MAKENAMEID3('f', 's', 'n'):
1054             fsn = jsonsc.gethandle(8);
1055             break;
1056 
1057         case MAKENAMEID3('l', 't', 'd'):   // last notifcation seen time delta (or 0)
1058             lastTimeDelta = jsonsc.getint();
1059             break;
1060 
1061         case EOO:
1062             for (Alerts::iterator i = alerts.begin(); i != alerts.end(); ++i)
1063             {
1064                 UserAlert::Base* b = *i;
1065                 b->seen = b->timestamp + lastTimeDelta < m_time();
1066 
1067                 if (b->userEmail.empty() && b->userHandle != UNDEF)
1068                 {
1069                     map<handle, UserAlertPendingContact>::iterator i = pendingContactUsers.find(b->userHandle);
1070                     if (i != pendingContactUsers.end())
1071                     {
1072                         b->userEmail = i->second.m;
1073                         if (b->userEmail.empty() && !i->second.m2.empty())
1074                         {
1075                             b->userEmail = i->second.m2[0];
1076                         }
1077                     }
1078                 }
1079             }
1080             begincatchup = false;
1081             catchupdone = true;
1082             return true;
1083 
1084         case 'c':  // notifications
1085             if (jsonsc.enterarray())
1086             {
1087                 for (;;)
1088                 {
1089                     if (jsonsc.enterobject())
1090                     {
1091                         UserAlertRaw un;
1092                         bool inobject = true;
1093                         while (inobject)
1094                         {
1095                             // 't' designates type - but it appears late in the packet
1096                             nameid nid = jsonsc.getnameid();
1097                             switch (nid)
1098                             {
1099 
1100                             case 't':
1101                                 un.t = jsonsc.getnameid();
1102                                 break;
1103 
1104                             case EOO:
1105                                 inobject = false;
1106                                 break;
1107 
1108                             default:
1109                                 // gather up the fields to interpret later as we don't know what type some are
1110                                 // until we get the 't' field which is late in the packet
1111                                 jsonsc.storeobject(&un.fields[nid]);
1112                             }
1113                         }
1114 
1115                         if (!isUnwantedAlert(un.t, un.getint('c', -1)))
1116                         {
1117                             add(un);
1118                         }
1119                         jsonsc.leaveobject();
1120                     }
1121                     else
1122                     {
1123                         break;
1124                     }
1125                 }
1126                 jsonsc.leavearray();
1127                 break;
1128             }
1129 
1130             // fall through
1131         default:
1132             assert(false);
1133             if (!jsonsc.storeobject())
1134             {
1135                 LOG_err << "Error parsing sc user alerts";
1136                 begincatchup = false;
1137                 catchupdone = true;  // if we fail to get useralerts, continue anyway
1138                 return true;
1139             }
1140         }
1141     }
1142 }
1143 
acknowledgeAll()1144 void UserAlerts::acknowledgeAll()
1145 {
1146     for (Alerts::iterator i = alerts.begin(); i != alerts.end(); ++i)
1147     {
1148         if (!(*i)->seen)
1149         {
1150             (*i)->seen = true;
1151             if ((*i)->tag != 0)
1152             {
1153                 (*i)->tag = mc.reqtag;
1154             }
1155             useralertnotify.push_back(*i);
1156         }
1157     }
1158 
1159     // notify the API.  Eg. on when user closes the useralerts list
1160     mc.reqs.add(new CommandSetLastAcknowledged(&mc));
1161 }
1162 
onAcknowledgeReceived()1163 void UserAlerts::onAcknowledgeReceived()
1164 {
1165     if (catchupdone)
1166     {
1167         for (Alerts::iterator i = alerts.begin(); i != alerts.end(); ++i)
1168         {
1169             if (!(*i)->seen)
1170             {
1171                 (*i)->seen = true;
1172                 (*i)->tag = 0;
1173                 useralertnotify.push_back(*i);
1174             }
1175         }
1176     }
1177 }
1178 
clear()1179 void UserAlerts::clear()
1180 {
1181     for (Alerts::iterator i = alerts.begin(); i != alerts.end(); ++i)
1182     {
1183         delete *i;
1184     }
1185     alerts.clear();
1186     useralertnotify.clear();
1187     begincatchup = false;
1188     catchupdone = false;
1189     catchup_last_timestamp = 0;
1190     lsn = UNDEF;
1191     fsn = UNDEF;
1192     lastTimeDelta = 0;
1193     nextid = 0;
1194 }
1195 
~UserAlerts()1196 UserAlerts::~UserAlerts()
1197 {
1198     clear();
1199 }
1200 
1201 
1202 } // namespace
1203