diff --git a/Makefile.win32 b/Makefile.win32 index b063c868c9..600b3078d1 100644 --- a/Makefile.win32 +++ b/Makefile.win32 @@ -38,7 +38,6 @@ # In order to build megacli application, additional libraries are required: # \FreeImage compiled FreeImage library, http://freeimage.sourceforge.net/ # \readline compiled The GNU Readline Library, http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html -# \termcap compiled The Termcap Library, http://ftp.gnu.org/gnu/termcap/ # # To compile Mega DLL and all applications: # make -f Makefile.win32 all @@ -56,7 +55,7 @@ MEGACLI_CXXFLAGS=-isystem ../FreeImage/Dist # common linked libraries for all apps LIB=-lwinhttp -lws2_32 -lcrypt32 -lole32 -lwinmm -lshlwapi -L../zlib -lz -L../cryptopp -lcryptopp -L../sodium/src/libsodium/.libs -lsodium -L../db/build_windows -L../ -L../FreeImage/Dist -lfreeimage # megacli specific linked libraries -MEGACLI_LIB=-L../FreeImage/Dist -lfreeimage -L../readline -lreadline -L../termcap -ltermcap +MEGACLI_LIB=-L../FreeImage/Dist -lfreeimage -L../readline -lreadline LDFLAGS= @@ -98,7 +97,7 @@ COMMON_SRC=\ src/win32/fs.cpp src/win32/console.cpp src/win32/net.cpp src/win32/waiter.cpp src/win32/consolewaiter.cpp \ src/db/sqlite.cpp \ src/mega_utf8proc.cpp \ - src/mega_ccronexpr.cpp + src/mega_ccronexpr.cpp \ src/mega_evt_tls.cpp MEGACLI_SRC=examples/megacli.cpp diff --git a/README.md b/README.md index a255ecf5c3..ad758930d0 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ Debian and RedHat derivatives, respectively): * pthread Optional dependency: +* Libraw (`libraw-dev`, `libraw-devel`) * Sodium (`libsodium-dev`, `libsodium-devel`), configure `--with-sodium` * MediaInfoLib (optional, see third_party/README_MediaInfo.txt) diff --git a/bindings/ios/3rdparty/build-libuv.sh b/bindings/ios/3rdparty/build-libuv.sh index a58910f61a..1934910eb3 100644 --- a/bindings/ios/3rdparty/build-libuv.sh +++ b/bindings/ios/3rdparty/build-libuv.sh @@ -1,6 +1,6 @@ #!/bin/sh -UV_VERSION="1.19.2" +UV_VERSION="1.23.0" SDKVERSION=`xcrun -sdk iphoneos --show-sdk-version` ############################################## @@ -86,7 +86,7 @@ mkdir lib || true lipo -create ${CURRENTPATH}/bin/iPhoneSimulator${SDKVERSION}-i386.sdk/libuv.a ${CURRENTPATH}/bin/iPhoneSimulator${SDKVERSION}-x86_64.sdk/libuv.a ${CURRENTPATH}/bin/iPhoneOS${SDKVERSION}-armv7.sdk/libuv.a ${CURRENTPATH}/bin/iPhoneOS${SDKVERSION}-armv7s.sdk/libuv.a ${CURRENTPATH}/bin/iPhoneOS${SDKVERSION}-arm64.sdk/libuv.a -output ${CURRENTPATH}/lib/libuv.a mkdir -p include || true -cp -f libuv-v${UV_VERSION}/include/*.h include/ +cp -f -R libuv-v${UV_VERSION}/include/ include/ rm -rf bin rm -rf libuv-v${UV_VERSION} diff --git a/bindings/ios/MEGANode.h b/bindings/ios/MEGANode.h index 8a048003f3..2af918051e 100644 --- a/bindings/ios/MEGANode.h +++ b/bindings/ios/MEGANode.h @@ -95,6 +95,26 @@ typedef NS_ENUM(NSUInteger, MEGANodeChangeType) { */ @property (readonly, nonatomic) NSInteger duration; +/** + * @brief Width of the node for video files, in pixels. -1 if not set. + */ +@property (readonly, nonatomic) NSInteger width; + +/** + * @brief Height of the node for video files, in pixels. -1 if not set. + */ +@property (readonly, nonatomic) NSInteger height; + +/** + * @brief ShortCode of the node for video files. -1 if not set. + */ +@property (readonly, nonatomic) NSInteger shortFormat; + +/** + * @brief VideoCodecId of the node for video files. -1 if not set. + */ +@property (readonly, nonatomic) NSInteger videoCodecId; + /** * @brief Attribute of the node representing the latitude coordinate in its decimal * degree notation, or nil if this attribute is not set. diff --git a/bindings/ios/MEGANode.mm b/bindings/ios/MEGANode.mm index 1809cc74a6..14a7da92cf 100644 --- a/bindings/ios/MEGANode.mm +++ b/bindings/ios/MEGANode.mm @@ -71,6 +71,22 @@ - (NSInteger)duration { return self.megaNode ? self.megaNode->getDuration() : -1; } +- (NSInteger)width { + return self.megaNode ? self.megaNode->getWidth() : -1; +} + +- (NSInteger)height { + return self.megaNode ? self.megaNode->getHeight(): -1; +} + +- (NSInteger)shortFormat { + return self.megaNode ? self.megaNode->getShortformat() : -1; +} + +- (NSInteger)videoCodecId { + return self.megaNode ? self.megaNode->getVideocodecid(): -1; +} + - (NSNumber *)latitude { if (!self.megaNode) return nil; double latitude = self.megaNode->getLatitude(); diff --git a/bindings/ios/MEGARequest.h b/bindings/ios/MEGARequest.h index 9ed6dbffd0..ba1403238c 100644 --- a/bindings/ios/MEGARequest.h +++ b/bindings/ios/MEGARequest.h @@ -242,7 +242,6 @@ typedef NS_ENUM (NSInteger, MEGANodeAccessLevel) { * * This value is valid for these requests: * - [MEGASdk createAccountWithEmail:password:name:] - Returns the name of the user - * - [MEGASdk fastCreateAccountWithEmail:password:name:] - Returns the name of the user * - [MEGASdk createFolderWithName:parent:] - Returns the name of the new folder * - [MEGASdk renameNode:newName:] - Returns the new name for the node * @@ -263,7 +262,6 @@ typedef NS_ENUM (NSInteger, MEGANodeAccessLevel) { * - [MEGASdk fastLoginWithEmail:password:] - Returns the email of the account * - [MEGASdk loginToFolderLink:] - Returns the string "FOLDER" * - [MEGASdk createAccountWithEmail:password:name:] - Returns the name of the user - * - [MEGASdk fastCreateAccountWithEmail:password:name] - Returns the name of the user * - [MEGASdk shareNode:withUser:level:] - Returns the handle of the folder to share * - [MEGASdk getAvatarUser:destinationFilePath:] - Returns the email of the user to get the avatar * - [MEGASdk removeContactWithEmail:] - Returns the email of the contact @@ -305,7 +303,6 @@ typedef NS_ENUM (NSInteger, MEGANodeAccessLevel) { * * This value is valid for these requests: * - [MEGASdk fastLoginWithEmail:password:] - Returns the base64pwKey parameter - * - [MEGASdk fastCreateAccountWithEmail:password:name] - Returns the base64pwKey parameter * - [MEGASdk fastConfirmAccountWithLink:base64pwkey:] - Returns the base64pwKey parameter * */ diff --git a/bindings/ios/MEGASDK.xcodeproj/project.pbxproj b/bindings/ios/MEGASDK.xcodeproj/project.pbxproj index 4532e6801b..de1a6888eb 100644 --- a/bindings/ios/MEGASDK.xcodeproj/project.pbxproj +++ b/bindings/ios/MEGASDK.xcodeproj/project.pbxproj @@ -903,7 +903,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "3rdparty/include/**", + 3rdparty/include, ../../third_party/utf8proc, 3rdparty/webrtc/third_party/boringssl/src/include, ); @@ -930,7 +930,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "3rdparty/include/**", + 3rdparty/include, ../../third_party/utf8proc, 3rdparty/webrtc/third_party/boringssl/src/include, ); diff --git a/bindings/ios/MEGASdk.h b/bindings/ios/MEGASdk.h index 775dc7566a..6341b1b9a4 100644 --- a/bindings/ios/MEGASdk.h +++ b/bindings/ios/MEGASdk.h @@ -448,19 +448,6 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { #pragma mark - Utils -/** - * @brief Generates a private key based on the access password. - * - * This is a time consuming operation (specially for low-end mobile devices). Since the resulting key is - * required to log in, this function allows to do this step in a separate function. You should run this function - * in a background thread, to prevent UI hangs. The resulting key can be used in - * [MEGASdk fastLoginWithEmail:stringHash:base64pwKey:]. - * - * @param password Access password. - * @return Base64-encoded private key - */ -- (NSString *)base64pwkeyForPassword:(NSString *)password; - /** * @brief Generates a hash based in the provided private key and email. * @@ -469,11 +456,13 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { * in a background thread, to prevent UI hangs. The resulting key can be used in * [MEGASdk fastLoginWithEmail:stringHash:base64pwKey:]. * - * @param base64pwkey Private key returned by [MEGASdk base64PwKeybase64pwkeyForPassword:] + * @param base64pwkey Private key returned by [MEGARequest privateKey] in the onRequestFinish callback of createAccount * @param email Email to create the hash * @return Base64-encoded hash + * @deprecated This function is only useful for old accounts. Once enabled the new registration logic, + * this function will return an empty string for new accounts and will be removed few time after. */ -- (NSString *)hashForBase64pwkey:(NSString *)base64pwkey email:(NSString *)email; +- (NSString *)hashForBase64pwkey:(NSString *)base64pwkey email:(NSString *)email __attribute__((deprecated("This function will return an empty string for new accounts and will be removed few time after"))); /** * @brief Converts a Base64-encoded node handle to a MegaHandle. @@ -535,6 +524,16 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { #pragma mark - Login Requests +/** + * @brief Check if multi-factor authentication can be enabled for the current account. + * + * It's needed to be logged into an account and with the nodes loaded (login + fetchNodes) before + * using this function. Otherwise it will always return false. + * + * @return YES if multi-factor authentication can be enabled for the current account, otherwise false. + */ +- (BOOL)multiFactorAuthAvailable; + /** * @brief Check if multi-factor authentication is enabled for an account * @@ -1155,45 +1154,6 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { */ - (void)resumeCreateAccountWithSessionId:(NSString *)sessionId; -/** - * @brief Initialize the creation of a new MEGA account with precomputed keys - * - * The associated request type with this request is MEGARequestTypeCreateAccount. - * Valid data in the MEGARequest object received on callbacks: - * - [MEGARequest email] - Returns the email for the account - * - [MEGARequest privateKey] - Returns the private key calculated with [MEGASdk base64pwkeyForPassword:] - * - [MEGARequest name] - Returns the name of the user - * - * If this request succeed, a confirmation email will be sent to the users. - * If an account with the same email already exists, you will get the error code - * MEGAErrorTypeApiEExist in onRequestFinish - * - * @param email Email for the account. - * @param base64pwkey Private key calculated with [MEGASdk base64pwkeyForPassword:]. - * @param name Name of the user. - * @param delegate Delegate to track this request. - */ -- (void)fastCreateAccountWithEmail:(NSString *)email base64pwkey:(NSString *)base64pwkey name:(NSString *)name delegate:(id)delegate; - -/** - * @brief Initialize the creation of a new MEGA account with precomputed keys. - * - * The associated request type with this request is MEGARequestTypeCreateAccount. - * Valid data in the MEGARequest object received on callbacks: - * - [MEGARequest email] - Returns the email for the account - * - [MEGARequest privateKey] - Returns the private key calculated with [MEGASdk base64pwkeyForPassword:] - * - [MEGARequest name] - Returns the name of the user - * - * If this request succeed, a confirmation email will be sent to the users. - * If an account with the same email already exists, you will get the error code - * MEGAErrorTypeApiEExist in onRequestFinish. - * - * @param email Email for the account. - * @param base64pwkey Private key calculated with [MEGASdk base64pwkeyForPassword:]. - * @param name Name of the user. - */ -- (void)fastCreateAccountWithEmail:(NSString *)email base64pwkey:(NSString *)base64pwkey name:(NSString *)name; - /** * @brief Sends the confirmation email for a new account * @@ -1227,7 +1187,7 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { * * @param email Email for the account * @param name Firstname of the user - * @param base64pwkey Private key calculated with [MEGASdk base64pwkeyForPassword:] + * @param base64pwkeyPrivate key returned by [MEGARequest privateKey] in the onRequestFinish callback of createAccount * @param delegate MEGARequestDelegate to track this request */ - (void)fastSendSignupLinkWithEmail:(NSString *)email base64pwkey:(NSString *)base64pwkey name:(NSString *)name delegate:(id)delegate; @@ -1240,7 +1200,7 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { * * @param email Email for the account * @param name Firstname of the user - * @param base64pwkey Private key calculated with [MEGASdk base64pwkeyForPassword:] + * @param base64pwkeyPrivate key returned by [MEGARequest privateKey] in the onRequestFinish callback of createAccount */ - (void)fastSendSignupLinkWithEmail:(NSString *)email base64pwkey:(NSString *)base64pwkey name:(NSString *)name; @@ -1330,8 +1290,10 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { * @param link Confirmation link. * @param base64pwkey Private key precomputed with [MEGASdk base64pwkeyForPassword:]. * @param delegate Delegate to track this request. + * @deprecated This function only works using the old registration method and will be removed soon. + * Please use [MEGASdk confirmAccountWithLink:password:delegate] instead. */ -- (void)fastConfirmAccountWithLink:(NSString *)link base64pwkey:(NSString *)base64pwkey delegate:(id)delegate; +- (void)fastConfirmAccountWithLink:(NSString *)link base64pwkey:(NSString *)base64pwkey delegate:(id)delegate __attribute__((deprecated("This function only works using the old registration method and will be removed soon."))); /** * @brief Confirm a MEGA account using a confirmation link and a precomputed key. @@ -1348,8 +1310,10 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { * * @param link Confirmation link. * @param base64pwkey Private key precomputed with [MEGASdk base64pwkeyForPassword:]. + * @deprecated This function only works using the old registration method and will be removed soon. + * Please use [MEGASdk confirmAccountWithLink:password] instead. */ -- (void)fastConfirmAccountWithLink:(NSString *)link base64pwkey:(NSString *)base64pwkey; +- (void)fastConfirmAccountWithLink:(NSString *)link base64pwkey:(NSString *)base64pwkey __attribute__((deprecated("This function only works using the old registration method and will be removed soon."))); /** * @brief Initialize the reset of the existing password, with and without the Master Key. @@ -1531,6 +1495,8 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { /** * @brief Effectively parks the user's account without creating a new fresh account. * + * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. + * * The contents of the account will then be purged after 60 days. Once the account is * parked, the user needs to contact MEGA support to restore the account. * @@ -1552,6 +1518,8 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { /** * @brief Effectively parks the user's account without creating a new fresh account. * + * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. + * * The contents of the account will then be purged after 60 days. Once the account is * parked, the user needs to contact MEGA support to restore the account. * @@ -1636,6 +1604,8 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { /** * @brief Effectively changes the email address associated to the account. * + * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. + * * The associated request type with this request is MEGARequestTypeConfirmChangeEmailLink. * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the recovery link @@ -1654,6 +1624,8 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { /** * @brief Effectively changes the email address associated to the account. * + * If no user is logged in, you will get the error code MEGAErrorTypeApiEAccess in onRequestFinish. + * * The associated request type with this request is MEGARequestTypeConfirmChangeEmailLink. * Valid data in the MEGARequest object received on all callbacks: * - [MEGARequest link] - Returns the recovery link @@ -3363,6 +3335,41 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { */ - (void)shouldShowPasswordReminderDialogAtLogout:(BOOL)atLogout; +/** + * @brief Check if the master key has been exported + * + * The associated request type with this request is MEGARequestTypeGetAttrUser + * Valid data in the MEGARequest object received on callbacks: + * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder + * + * Valid data in the MEGARequest object received in onRequestFinish when the error code + * is MEGAErrorTypeApiOk: + * - [MEGARequest access] - Returns true if the master key has been exported + * + * If the corresponding user attribute is not set yet, the request will fail with the + * error code MEGAErrorTypeApiENoent. + * + * @param delegate MEGARequestDelegate to track this request + */ +- (void)isMasterKeyExportedWithDelegate:(id)delegate; + +/** + * @brief Check if the master key has been exported + * + * The associated request type with this request is MEGARequestTypeGetAttrUser + * Valid data in the MEGARequest object received on callbacks: + * - [MEGARequest paramType] - Returns the attribute type MEGAUserAttributePwdReminder + * + * Valid data in the MEGARequest object received in onRequestFinish when the error code + * is MEGAErrorTypeApiOk: + * - [MEGARequest access] - Returns true if the master key has been exported + * + * If the corresponding user attribute is not set yet, the request will fail with the + * error code MEGAErrorTypeApiENoent. + * + */ +- (void)isMasterKeyExported; + /** * @brief Enable or disable the generation of rich previews * @@ -4808,6 +4815,9 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { */ - (NSString *)fingerprintForFilePath:(NSString *)filePath; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + /** * @brief Get a Base64-encoded fingerprint from an ALAssetRepresentation and a modification time * @@ -4819,6 +4829,8 @@ typedef NS_ENUM(NSInteger, KeepMeAlive) { */ - (NSString *)fingerprintForAssetRepresentation:(ALAssetRepresentation *)assetRepresentation modificationTime:(NSDate *)modificationTime; +#pragma clang diagnostic pop + /** * @brief Get a Base64-encoded fingerprint from a NSData and a modification time * diff --git a/bindings/ios/MEGASdk.mm b/bindings/ios/MEGASdk.mm index 31ce279a46..2648c75fb4 100644 --- a/bindings/ios/MEGASdk.mm +++ b/bindings/ios/MEGASdk.mm @@ -341,18 +341,6 @@ - (void)removeLoggerDelegate:(id)delegate { #pragma mark - Utils -- (NSString *)base64pwkeyForPassword:(NSString *)password { - if(password == nil) return nil; - - const char *val = self.megaApi->getBase64PwKey([password UTF8String]); - if (!val) return nil; - - NSString *ret = [[NSString alloc] initWithUTF8String:val]; - - delete [] val; - return ret; -} - - (NSString *)hashForBase64pwkey:(NSString *)base64pwkey email:(NSString *)email { if(base64pwkey == nil || email == nil) return nil; @@ -401,6 +389,10 @@ - (void)reconnect { #pragma mark - Login Requests +- (BOOL)multiFactorAuthAvailable { + return self.megaApi->multiFactorAuthAvailable(); +} + - (void)multiFactorAuthCheckWithEmail:(NSString *)email delegate:(id)delegate { self.megaApi->multiFactorAuthCheck((email ? email.UTF8String : NULL), [self createDelegateMEGARequestListener:delegate singleListener:YES]); } @@ -573,14 +565,6 @@ - (void)resumeCreateAccountWithSessionId:(NSString *)sessionId { self.megaApi->resumeCreateAccount((sessionId != nil) ? [sessionId UTF8String] : NULL); } -- (void)fastCreateAccountWithEmail:(NSString *)email base64pwkey:(NSString *)base64pwkey name:(NSString *)name { - self.megaApi->fastCreateAccount((email != nil) ? [email UTF8String] : NULL, (base64pwkey != nil) ? [base64pwkey UTF8String] : NULL, (name != nil) ? [name UTF8String] : NULL); -} - -- (void)fastCreateAccountWithEmail:(NSString *)email base64pwkey:(NSString *)base64pwkey name:(NSString *)name delegate:(id)delegate { - self.megaApi->fastCreateAccount((email != nil) ? [email UTF8String] : NULL, (base64pwkey != nil) ? [base64pwkey UTF8String] : NULL, (name != nil) ? [name UTF8String] : NULL, [self createDelegateMEGARequestListener:delegate singleListener:YES]); -} - - (void)sendSignupLinkWithEmail:(NSString *)email name:(NSString *)name password:(NSString *)password delegate:(id)delegate { self.megaApi->sendSignupLink((email != nil) ? [email UTF8String] : NULL, (name != nil) ? [name UTF8String] : NULL, (password != nil) ? [password UTF8String] : NULL, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } @@ -714,7 +698,7 @@ - (void)contactLinkDeleteWithDelegate:(id)delegate { self.megaApi->contactLinkDelete(INVALID_HANDLE, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } -- (void)contactLinkDeleteWithHandle:(uint64_t)handle { +- (void)contactLinkDelete { self.megaApi->contactLinkDelete(); } @@ -1099,6 +1083,14 @@ - (void)shouldShowPasswordReminderDialogAtLogout:(BOOL)atLogout { self.megaApi->shouldShowPasswordReminderDialog(atLogout); } +- (void)isMasterKeyExportedWithDelegate:(id)delegate { + self.megaApi->isMasterKeyExported([self createDelegateMEGARequestListener:delegate singleListener:YES]); +} + +- (void)isMasterKeyExported { + self.megaApi->isMasterKeyExported(); +} + - (void)enableRichPreviews:(BOOL)enable delegate:(id)delegate { self.megaApi->enableRichPreviews(enable, [self createDelegateMEGARequestListener:delegate singleListener:YES]); } @@ -1534,6 +1526,9 @@ - (NSString *)fingerprintForFilePath:(NSString *)filePath { return ret; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + - (NSString *)fingerprintForAssetRepresentation:(ALAssetRepresentation *)assetRepresentation modificationTime:(NSDate *)modificationTime { if (assetRepresentation == nil) return nil; @@ -1546,6 +1541,8 @@ - (NSString *)fingerprintForAssetRepresentation:(ALAssetRepresentation *)assetRe return ret; } +#pragma clang diagnostic pop + - (NSString *)fingerprintForData:(NSData *)data modificationTime:(NSDate *)modificationTime { if (data == nil) return nil; diff --git a/bindings/ios/Private/MEGAInputStream.h b/bindings/ios/Private/MEGAInputStream.h index f75da9dd98..b811892d8d 100644 --- a/bindings/ios/Private/MEGAInputStream.h +++ b/bindings/ios/Private/MEGAInputStream.h @@ -25,6 +25,10 @@ class MEGAInputStream : public mega::MegaInputStream { public: + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + MEGAInputStream(ALAssetRepresentation *assetRepresentation); MEGAInputStream(NSData *data); int64_t getSize(); @@ -32,6 +36,9 @@ class MEGAInputStream : public mega::MegaInputStream { private: ALAssetRepresentation *assetRepresentation; + +#pragma clang diagnostic pop + NSData *data; long offset; }; diff --git a/bindings/ios/Private/MEGAInputStream.mm b/bindings/ios/Private/MEGAInputStream.mm index 9a57ef7f2b..533a8439de 100644 --- a/bindings/ios/Private/MEGAInputStream.mm +++ b/bindings/ios/Private/MEGAInputStream.mm @@ -21,6 +21,9 @@ #import "MEGAInputStream.h" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + MEGAInputStream::MEGAInputStream(ALAssetRepresentation *assetRepresentation) { this->assetRepresentation = assetRepresentation; this->data = NULL; @@ -55,7 +58,7 @@ if (assetRepresentation) { int numBytesToRead = (int)size; while (numBytesToRead > 0) { - int n = [assetRepresentation getBytes:(uint8_t *)buffer fromOffset:offset length:numBytesToRead error:nil]; + int n = (int) [assetRepresentation getBytes:(uint8_t *)buffer fromOffset:offset length:numBytesToRead error:nil]; if (n == 0) { return false; } @@ -70,3 +73,5 @@ return true; } + +#pragma clang diagnostic pop diff --git a/bindings/java/nz/mega/sdk/MegaApiJava.java b/bindings/java/nz/mega/sdk/MegaApiJava.java index bf6a6594fb..4b319d45b2 100644 --- a/bindings/java/nz/mega/sdk/MegaApiJava.java +++ b/bindings/java/nz/mega/sdk/MegaApiJava.java @@ -370,22 +370,6 @@ public void removeGlobalListener(MegaGlobalListenerInterface listener) { // UTILS /****************************************************************************************************/ - /** - * Generates a private key based on the access password. - *

- * This is a time consuming operation (particularly for low-end mobile devices). As the resulting key is - * required to log in, this function allows to do this step in a separate function. You should run this function - * in a background thread, to prevent UI hangs. The resulting key can be used in MegaApiJava.fastLogin(). - * - * @param password - * Access password. - * @return Base64-encoded private key. - * @deprecated Legacy function soon to be removed. - */ - @Deprecated public String getBase64PwKey(String password) { - return megaApi.getBase64PwKey(password); - } - /** * Generates a hash based in the provided private key and email. *

@@ -500,6 +484,18 @@ public void retryPendingConnections() { megaApi.retryPendingConnections(); } + /** + * Check if multi-factor authentication can be enabled for the current account. + * + * It's needed to be logged into an account and with the nodes loaded (login + fetchNodes) before + * using this function. Otherwise it will always return false. + * + * @return True if multi-factor authentication can be enabled for the current account, otherwise false. + */ + public boolean multiFactorAuthAvailable () { + return megaApi.multiFactorAuthAvailable(); + } + /** * Check if multi-factor authentication is enabled for an account * @@ -1194,52 +1190,6 @@ public void resumeCreateAccount(String sid) { megaApi.resumeCreateAccount(sid); } - /** - * Initialize the creation of a new MEGA account with precomputed keys. - *

- * The associated request type with this request is MegaRequest.TYPE_CREATE_ACCOUNT. - * Valid data in the MegaRequest object received on callbacks:
- * - MegaRequest.getEmail() - Returns the email for the account.
- * - MegaRequest.getPrivateKey() - Returns the private key calculated with MegaApiJava.getBase64PwKey().
- * - MegaRequest.getName() - Returns the name of the user. - *

- * If this request succeed, a confirmation email will be sent to the users. - * If an account with the same email already exists, you will get the error code - * MegaError.API_EEXIST in onRequestFinish(). - * - * @param email - * Email for the account. - * @param base64pwkey - * Private key calculated with MegMegaApiJavaaApi.getBase64PwKey(). - * @param name - * Name of the user. - * @param listener - * MegaRequestListener to track this request. - * - * @deprecated This function is deprecated and will eventually be removed. Instead, - * use the new version with firstname and lastname. - */ - public void fastCreateAccount(String email, String base64pwkey, String name, MegaRequestListenerInterface listener) { - megaApi.fastCreateAccount(email, base64pwkey, name, createDelegateRequestListener(listener)); - } - - /** - * Initialize the creation of a new MEGA account with precomputed keys. - * - * @param email - * Email for the account. - * @param base64pwkey - * Private key calculated with MegaApiJava.getBase64PwKey(). - * @param name - * Name of the user. - * - * @deprecated This function is deprecated and will eventually be removed. Instead, - * use the new version with firstname and lastname. - */ - public void fastCreateAccount(String email, String base64pwkey, String name) { - megaApi.fastCreateAccount(email, base64pwkey, name); - } - /** * Sends the confirmation email for a new account * @@ -3601,6 +3551,27 @@ public void masterKeyExported(MegaRequestListenerInterface listener){ megaApi.masterKeyExported(createDelegateRequestListener(listener)); } + /** + * Check if the master key has been exported + * + * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER + * Valid data in the MegaRequest object received on callbacks: + * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER + * + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MegaError::API_OK: + * - MegaRequest::getAccess - Returns true if the master key has been exported + * + * If the corresponding user attribute is not set yet, the request will fail with the + * error code MegaError::API_ENOENT. + * + * @param listener MegaRequestListener to track this request + */ + + public void isMasterKeyExported (MegaRequestListenerInterface listener) { + megaApi.isMasterKeyExported(createDelegateRequestListener(listener)); + } + /** * Notify the user has successfully checked his password * diff --git a/bindings/qt/sdk.pri b/bindings/qt/sdk.pri index 1f96c09be2..5cc28da32a 100644 --- a/bindings/qt/sdk.pri +++ b/bindings/qt/sdk.pri @@ -119,9 +119,9 @@ CONFIG(USE_MEDIAINFO) { CONFIG(USE_LIBRAW) { DEFINES += HAVE_LIBRAW - INCLUDEPATH += $$MEGASDK_BASE_PATH/bindings/qt/3rdparty/include/libraw win32 { + DEFINES += LIBRAW_NODLL LIBS += -llibraw } @@ -131,24 +131,24 @@ CONFIG(USE_LIBRAW) { unix:!macx { exists($$MEGASDK_BASE_PATH/bindings/qt/3rdparty/libs/libraw.a) { - LIBS += $$MEGASDK_BASE_PATH/bindings/qt/3rdparty/libs/libraw.a + LIBS += $$MEGASDK_BASE_PATH/bindings/qt/3rdparty/libs/libraw.a -fopenmp } else { - LIBS += -lraw + LIBS += -lraw -fopenmp } } } CONFIG(USE_FFMPEG) { - DEFINES += HAVE_FFMPEG unix:!macx { exists($$MEGASDK_BASE_PATH/bindings/qt/3rdparty/include/ffmpeg):exists($$MEGASDK_BASE_PATH/bindings/qt/3rdparty/lib/libavcodec.a) { + DEFINES += HAVE_FFMPEG INCLUDEPATH += $$MEGASDK_BASE_PATH/bindings/qt/3rdparty/include/ffmpeg FFMPEGLIBPATH = $$MEGASDK_BASE_PATH/bindings/qt/3rdparty/lib } else:exists(/usr/include/ffmpeg-mega) { - + DEFINES += HAVE_FFMPEG INCLUDEPATH += /usr/include/ffmpeg-mega exists(/usr/lib64/libavcodec.a) { FFMPEGLIBPATH = /usr/lib64 @@ -161,11 +161,9 @@ CONFIG(USE_FFMPEG) { } } else:packagesExist(ffmpeg)|packagesExist(libavcodec) { + DEFINES += HAVE_FFMPEG LIBS += -lavcodec -lavformat -lavutil -lswscale } - else { - DEFINES -= HAVE_FFMPEG - } FFMPEGSTATICLIBS = libavformat.a libavcodec.a libavutil.a libswscale.a @@ -186,6 +184,7 @@ CONFIG(USE_FFMPEG) { } else { #win/mac + DEFINES += HAVE_FFMPEG INCLUDEPATH += $$MEGASDK_BASE_PATH/bindings/qt/3rdparty/include/ffmpeg LIBS += -lavcodec -lavformat -lavutil -lswscale } diff --git a/bindings/wp8/MegaSDK.cpp b/bindings/wp8/MegaSDK.cpp index bca845bf50..8c6407e9a2 100644 --- a/bindings/wp8/MegaSDK.cpp +++ b/bindings/wp8/MegaSDK.cpp @@ -262,26 +262,6 @@ void MegaSDK::removeGlobalListener(MGlobalListenerInterface^ listener) } } -String^ MegaSDK::getBase64PwKey(String^ password) -{ - if (password == nullptr) return nullptr; - - std::string utf8password; - MegaApi::utf16ToUtf8(password->Data(), password->Length(), &utf8password); - - std::string utf16base64PwKey; - const char *utf8base64PwKey = megaApi->getBase64PwKey(utf8password.c_str()); - if (!utf8base64PwKey) - { - return nullptr; - } - - MegaApi::utf8ToUtf16(utf8base64PwKey, &utf16base64PwKey); - delete[] utf8base64PwKey; - - return ref new String((wchar_t *)utf16base64PwKey.c_str()); -} - String^ MegaSDK::getStringHash(String^ base64pwkey, String^ inBuf) { if (base64pwkey == nullptr || inBuf == nullptr) return nullptr; @@ -398,6 +378,11 @@ void MegaSDK::setStatsID(String^ id) MegaApi::setStatsID((id != nullptr) ? utf8id.c_str() : NULL); } +bool MegaSDK::multiFactorAuthAvailable() +{ + return megaApi->multiFactorAuthAvailable(); +} + void MegaSDK::multiFactorAuthCheck(String^ email, MRequestListenerInterface^ listener) { std::string utf8email; @@ -819,45 +804,6 @@ void MegaSDK::createAccount(String^ email, String^ password, String^ firstname, createDelegateMRequestListener(listener)); } -void MegaSDK::fastCreateAccount(String^ email, String^ base64pwkey, String^ name) -{ - std::string utf8email; - if (email != nullptr) - MegaApi::utf16ToUtf8(email->Data(), email->Length(), &utf8email); - - std::string utf8base64pwkey; - if (base64pwkey != nullptr) - MegaApi::utf16ToUtf8(base64pwkey->Data(), base64pwkey->Length(), &utf8base64pwkey); - - std::string utf8name; - if (name != nullptr) - MegaApi::utf16ToUtf8(name->Data(), name->Length(), &utf8name); - - megaApi->fastCreateAccount((email != nullptr) ? utf8email.c_str() : NULL, - (base64pwkey != nullptr) ? utf8base64pwkey.c_str() : NULL, - (name != nullptr) ? utf8name.c_str() : NULL); -} - -void MegaSDK::fastCreateAccount(String^ email, String^ base64pwkey, String^ name, MRequestListenerInterface^ listener) -{ - std::string utf8email; - if (email != nullptr) - MegaApi::utf16ToUtf8(email->Data(), email->Length(), &utf8email); - - std::string utf8base64pwkey; - if (base64pwkey != nullptr) - MegaApi::utf16ToUtf8(base64pwkey->Data(), base64pwkey->Length(), &utf8base64pwkey); - - std::string utf8name; - if (name != nullptr) - MegaApi::utf16ToUtf8(name->Data(), name->Length(), &utf8name); - - megaApi->fastCreateAccount((email != nullptr) ? utf8email.c_str() : NULL, - (base64pwkey != nullptr) ? utf8base64pwkey.c_str() : NULL, - (name != nullptr) ? utf8name.c_str() : NULL, - createDelegateMRequestListener(listener)); -} - void MegaSDK::resumeCreateAccount(String^ sid, MRequestListenerInterface^ listener) { std::string utf8sid; @@ -2411,6 +2357,16 @@ void MegaSDK::shouldShowPasswordReminderDialog(bool atLogout) megaApi->shouldShowPasswordReminderDialog(atLogout); } +void MegaSDK::isMasterKeyExported(MRequestListenerInterface^ listener) +{ + megaApi->isMasterKeyExported(createDelegateMRequestListener(listener)); +} + +void MegaSDK::isMasterKeyExported() +{ + megaApi->isMasterKeyExported(); +} + void MegaSDK::changePassword(String^ oldPassword, String^ newPassword, MRequestListenerInterface^ listener) { std::string utf8oldPassword; diff --git a/bindings/wp8/MegaSDK.h b/bindings/wp8/MegaSDK.h index 2d1951f432..f366488736 100644 --- a/bindings/wp8/MegaSDK.h +++ b/bindings/wp8/MegaSDK.h @@ -159,9 +159,26 @@ namespace mega void removeTransferListener(MTransferListenerInterface^ listener); void removeGlobalListener(MGlobalListenerInterface^ listener); - //Utils - String^ getBase64PwKey(String^ password); + // UTILS + + /** + * @brief Generates a hash based in the provided private key and email + * + * This is a time consuming operation (specially for low-end mobile devices). Since the resulting key is + * required to log in, this function allows to do this step in a separate function. You should run this function + * in a background thread, to prevent UI hangs. The resulting key can be used in MegaApi::fastLogin + * + * You take the ownership of the returned value. + * + * @param base64pwkey Private key returned by MRequest::getPrivateKey in the onRequestFinish callback of createAccount + * @param email Email to create the hash + * @return Base64-encoded hash + * + * @deprecated This function is only useful for old accounts. Once enabled the new registration logic, + * this function will return an empty string for new accounts and will be removed few time after. + */ String^ getStringHash(String^ base64pwkey, String^ inBuf); + void getSessionTransferURL(String^ path, MRequestListenerInterface^ listener); static MegaHandle base32ToHandle(String^ base32Handle); static uint64 base64ToHandle(String^ base64Handle); @@ -174,6 +191,16 @@ namespace mega void reconnect(); static void setStatsID(String^ id); + /** + * @brief Check if multi-factor authentication can be enabled for the current account. + * + * It's needed to be logged into an account and with the nodes loaded (login + fetchNodes) before + * using this function. Otherwise it will always return false. + * + * @return True if multi-factor authentication can be enabled for the current account, otherwise false. + */ + bool multiFactorAuthAvailable(); + /** * @brief Check if multi-factor authentication is enabled for an account * @@ -450,16 +477,111 @@ namespace mega */ void multiFactorAuthCancelAccount(String^ pin); - //API requests + //API REQUESTS + + /** + * @brief Log in to a MEGA account + * + * The associated request type with this request is MRequest::TYPE_LOGIN. + * Valid data in the MRequest object received on callbacks: + * - MRequest::getEmail - Returns the first parameter + * - MRequest::getPassword - Returns the second parameter + * + * If the email/password aren't valid the error code provided in onRequestFinish is + * MError::API_ENOENT. + * + * @param email Email of the user + * @param password Password + * @param listener MRequestListener to track this request + */ void login(String^ email, String^ password, MRequestListenerInterface^ listener); + + /** + * @brief Log in to a MEGA account + * + * The associated request type with this request is MRequest::TYPE_LOGIN. + * Valid data in the MRequest object received on callbacks: + * - MRequest::getEmail - Returns the first parameter + * - MRequest::getPassword - Returns the second parameter + * + * If the email/password aren't valid the error code provided in onRequestFinish is + * MError::API_ENOENT. + * + * @param email Email of the user + * @param password Password + */ void login(String^ email, String^ password); + String^ getSequenceNumber(); String^ dumpSession(); String^ dumpXMPPSession(); + + /** + * @brief Log in to a MEGA account using precomputed keys + * + * The associated request type with this request is MRequest::TYPE_LOGIN. + * Valid data in the MRequest object received on callbacks: + * - MRequest::getEmail - Returns the first parameter + * - MRequest::getPassword - Returns the second parameter + * - MRequest::getPrivateKey - Returns the third parameter + * + * If the email/stringHash/base64pwKey aren't valid the error code provided in onRequestFinish is + * MError::API_ENOENT. + * + * @param email Email of the user + * @param stringHash Hash of the email returned by MegaSDK::getStringHash + * @param base64pwkey Private key returned by MRequest::getPrivateKey in the onRequestFinish callback of createAccount + * @param listener MRequestListener to track this request + * + * @deprecated The parameter stringHash is no longer for new accounts so this function will be replaced by another + * one soon. Please use MegaSDK::login (with email and password) or MegaSDK::fastLogin (with session) instead when possible. + */ void fastLogin(String^ email, String^ stringHash, String^ base64pwkey, MRequestListenerInterface^ listener); + + /** + * @brief Log in to a MEGA account using precomputed keys + * + * The associated request type with this request is MRequest::TYPE_LOGIN. + * Valid data in the MRequest object received on callbacks: + * - MRequest::getEmail - Returns the first parameter + * - MRequest::getPassword - Returns the second parameter + * - MRequest::getPrivateKey - Returns the third parameter + * + * If the email/stringHash/base64pwKey aren't valid the error code provided in onRequestFinish is + * MError::API_ENOENT. + * + * @param email Email of the user + * @param stringHash Hash of the email returned by MegaSDK::getStringHash + * @param base64pwkey Private key returned by MRequest::getPrivateKey in the onRequestFinish callback of createAccount + * + * @deprecated The parameter stringHash is no longer for new accounts so this function will be replaced by another + * one soon. Please use MegaSDK::login (with email and password) or MegaSDK::fastLogin (with session) instead when possible. + */ void fastLogin(String^ email, String^ stringHash, String^ base64pwkey); + + /** + * @brief Log in to a MEGA account using a session key + * + * The associated request type with this request is MRequest::TYPE_LOGIN. + * Valid data in the MRequest object received on callbacks: + * - MRequest::getSessionKey - Returns the session key + * + * @param session Session key previously dumped with MegaSDK::dumpSession + * @param listener MRequestListener to track this request + */ void fastLogin(String^ session, MRequestListenerInterface^ listener); + + /** + * @brief Log in to a MEGA account using a session key + * + * The associated request type with this request is MRequest::TYPE_LOGIN. + * Valid data in the MRequest object received on callbacks: + * - MRequest::getSessionKey - Returns the session key + * + * @param session Session key previously dumped with MegaSDK::dumpSession + */ void fastLogin(String^ session); + void killSession(MegaHandle sessionHandle, MRequestListenerInterface^ listener); void killSession(MegaHandle sessionHandle); void killAllSessions(MRequestListenerInterface^ listener); @@ -472,42 +594,685 @@ namespace mega void getUserDataById(String^ user); String^ getAccountAuth(); void setAccountAuth(String^ auth); + + /** + * @brief Initialize the creation of a new MEGA account, with firstname and lastname + * + * The associated request type with this request is MRequest::TYPE_CREATE_ACCOUNT. + * Valid data in the MRequest object received on callbacks: + * - MRequest::getEmail - Returns the email for the account + * - MRequest::getPassword - Returns the password for the account + * - MRequest::getName - Returns the firstname of the user + * - MRequest::getText - Returns the lastname of the user + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getSessionKey - Returns the session id to resume the process + * + * If this request succeeds, a new ephemeral session will be created for the new user + * and a confirmation email will be sent to the specified email address. The app may + * resume the create-account process by using MegaSDK::resumeCreateAccount. + * + * If an account with the same email already exists, you will get the error code + * MError::API_EEXIST in onRequestFinish + * + * @param email Email for the account + * @param password Password for the account + * @param firstname Firstname of the user + * @param lastname Lastname of the user + * @param listener MRequestListener to track this request + */ void createAccount(String^ email, String^ password, String^ firstname, String^ lastname, MRequestListenerInterface^ listener); + + /** + * @brief Initialize the creation of a new MEGA account, with firstname and lastname + * + * The associated request type with this request is MRequest::TYPE_CREATE_ACCOUNT. + * Valid data in the MRequest object received on callbacks: + * - MRequest::getEmail - Returns the email for the account + * - MRequest::getPassword - Returns the password for the account + * - MRequest::getName - Returns the firstname of the user + * - MRequest::getText - Returns the lastname of the user + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getSessionKey - Returns the session id to resume the process + * + * If this request succeeds, a new ephemeral session will be created for the new user + * and a confirmation email will be sent to the specified email address. The app may + * resume the create-account process by using MegaSDK::resumeCreateAccount. + * + * If an account with the same email already exists, you will get the error code + * MError::API_EEXIST in onRequestFinish + * + * @param email Email for the account + * @param password Password for the account + * @param firstname Firstname of the user + * @param lastname Lastname of the user + */ void createAccount(String^ email, String^ password, String^ firstname, String^ lastname); - void fastCreateAccount(String^ email, String^ base64pwkey, String^ name, MRequestListenerInterface^ listener); - void fastCreateAccount(String^ email, String^ base64pwkey, String^ name); + + /** + * @brief Resume a registration process + * + * When a user begins the account registration process by calling MegaSDK::createAccount, + * an ephemeral account is created. + * + * Until the user successfully confirms the signup link sent to the provided email address, + * you can resume the ephemeral session in order to change the email address, resend the + * signup link (@see MegaSDK::sendSignupLink) and also to receive notifications in case the + * user confirms the account using another client (MGlobalListener::onAccountUpdate or + * MListener::onAccountUpdate). + * + * The associated request type with this request is MRequest::TYPE_CREATE_ACCOUNT. + * Valid data in the MRequest object received on callbacks: + * - MRequest::getSessionKey - Returns the session id to resume the process + * - MRequest::getParamType - Returns the value 1 + * + * In case the account is already confirmed, the associated request will fail with + * error MError::API_EARGS. + * + * @param sid Session id valid for the ephemeral account (@see MegaSDK::createAccount) + * @param listener MRequestListener to track this request + */ void resumeCreateAccount(String^ sid, MRequestListenerInterface^ listener); + + /** + * @brief Resume a registration process + * + * When a user begins the account registration process by calling MegaSDK::createAccount, + * an ephemeral account is created. + * + * Until the user successfully confirms the signup link sent to the provided email address, + * you can resume the ephemeral session in order to change the email address, resend the + * signup link (@see MegaSDK::sendSignupLink) and also to receive notifications in case the + * user confirms the account using another client (MGlobalListener::onAccountUpdate or + * MListener::onAccountUpdate). + * + * The associated request type with this request is MRequest::TYPE_CREATE_ACCOUNT. + * Valid data in the MRequest object received on callbacks: + * - MRequest::getSessionKey - Returns the session id to resume the process + * - MRequest::getParamType - Returns the value 1 + * + * In case the account is already confirmed, the associated request will fail with + * error MError::API_EARGS. + * + * @param sid Session id valid for the ephemeral account (@see MegaSDK::createAccount) + */ void resumeCreateAccount(String^ sid); + + /** + * @brief Sends the confirmation email for a new account + * + * This function is useful to send the confirmation link again or to send it to a different + * email address, in case the user mistyped the email at the registration form. + * + * @param email Email for the account + * @param name Firstname of the user + * @param password Password for the account + * @param listener MRequestListener to track this request + */ void sendSignupLink(String^ email, String^ name, String^ password, MRequestListenerInterface^ listener); + + /** + * @brief Sends the confirmation email for a new account + * + * This function is useful to send the confirmation link again or to send it to a different + * email address, in case the user mistyped the email at the registration form. + * + * @param email Email for the account + * @param name Firstname of the user + * @param password Password for the account + */ void sendSignupLink(String^ email, String^ name, String^ password); + + /** + * @brief Sends the confirmation email for a new account + * + * This function is useful to send the confirmation link again or to send it to a different + * email address, in case the user mistyped the email at the registration form. + * + * @param email Email for the account + * @param name Firstname of the user + * @param base64pwkey Private key returned by MRequest::getPrivateKey in the onRequestFinish callback of createAccount + * @param listener MRequestListener to track this request + * + * @deprecated This function only works using the old registration method and will be removed soon. + * Please use MegaSDK::sendSignupLink (with email and password) instead. + */ void fastSendSignupLink(String^ email, String^ base64pwkey, String^ name, MRequestListenerInterface^ listener); + + /** + * @brief Sends the confirmation email for a new account + * + * This function is useful to send the confirmation link again or to send it to a different + * email address, in case the user mistyped the email at the registration form. + * + * @param email Email for the account + * @param name Firstname of the user + * @param base64pwkey Private key returned by MRequest::getPrivateKey in the onRequestFinish callback of createAccount + * + * @deprecated This function only works using the old registration method and will be removed soon. + * Please use MegaSDK::sendSignupLink (with email and password) instead. + */ void fastSendSignupLink(String^ email, String^ base64pwkey, String^ name); + + /** + * @brief Get information about a confirmation link or a new signup link + * + * The associated request type with this request is MRequest::TYPE_QUERY_SIGNUP_LINK. + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the confirmation link + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * - MRequest::getName - Returns the name associated with the link (available only for confirmation links) + * - MRequest::getFlag - Returns true if the account was automatically confirmed, otherwise false + * + * If MRequest::getFlag returns true, the account was automatically confirmed and it's not needed + * to call MegaSDK::confirmAccount. If it returns false, it's needed to call MegaSDK::confirmAccount + * as usual. New accounts do not require a confirmation with the password, but old confirmation links + * require it, so it's needed to check that parameter in onRequestFinish to know how to proceed. + * + * @param link Confirmation link (#confirm) or new signup link (#newsignup) + * @param listener MRequestListener to track this request + */ void querySignupLink(String^ link, MRequestListenerInterface^ listener); + + /** + * @brief Get information about a confirmation link or a new signup link + * + * The associated request type with this request is MRequest::TYPE_QUERY_SIGNUP_LINK. + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the confirmation link + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * - MRequest::getName - Returns the name associated with the link (available only for confirmation links) + * - MRequest::getFlag - Returns true if the account was automatically confirmed, otherwise false + * + * If MRequest::getFlag returns true, the account was automatically confirmed and it's not needed + * to call MegaSDK::confirmAccount. If it returns false, it's needed to call MegaSDK::confirmAccount + * as usual. New accounts do not require a confirmation with the password, but old confirmation links + * require it, so it's needed to check that parameter in onRequestFinish to know how to proceed. + * + * @param link Confirmation link (#confirm) or new signup link (#newsignup) + */ void querySignupLink(String^ link); + + /** + * @brief Confirm a MEGA account using a confirmation link and the user password + * + * The associated request type with this request is MRequest::TYPE_CONFIRM_ACCOUNT + * Valid data in the MRequest object received on callbacks: + * - MRequest::getLink - Returns the confirmation link + * - MRequest::getPassword - Returns the password + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Email of the account + * - MRequest::getName - Name of the user + * + * As a result of a successfull confirmation, the app will receive the callback + * MListener::onEvent and MGlobalListener::onEvent with an event of type + * MEvent::EVENT_ACCOUNT_CONFIRMATION. You can check the email used to confirm + * the account by checking MEvent::getText. @see MListener::onEvent. + * + * @param link Confirmation link + * @param password Password of the account + * @param listener MRequestListener to track this request + */ void confirmAccount(String^ link, String^ password, MRequestListenerInterface^ listener); + + /** + * @brief Confirm a MEGA account using a confirmation link and the user password + * + * The associated request type with this request is MRequest::TYPE_CONFIRM_ACCOUNT + * Valid data in the MRequest object received on callbacks: + * - MRequest::getLink - Returns the confirmation link + * - MRequest::getPassword - Returns the password + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Email of the account + * - MRequest::getName - Name of the user + * + * As a result of a successfull confirmation, the app will receive the callback + * MListener::onEvent and MGlobalListener::onEvent with an event of type + * MEvent::EVENT_ACCOUNT_CONFIRMATION. You can check the email used to confirm + * the account by checking MEvent::getText. @see MListener::onEvent. + * + * @param link Confirmation link + * @param password Password of the account + */ void confirmAccount(String^ link, String^ password); + + /** + * @brief Confirm a MEGA account using a confirmation link and a precomputed key + * + * The associated request type with this request is MRequest::TYPE_CONFIRM_ACCOUNT + * Valid data in the MRequest object received on callbacks: + * - MRequest::getLink - Returns the confirmation link + * - MRequest::getPrivateKey - Returns the base64pwkey parameter + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Email of the account + * - MRequest::getName - Name of the user + * + * As a result of a successfull confirmation, the app will receive the callback + * MListener::onEvent and MGlobalListener::onEvent with an event of type + * MEvent::EVENT_ACCOUNT_CONFIRMATION. You can check the email used to confirm + * the account by checking MEvent::getText. @see MListener::onEvent. + * + * @param link Confirmation link + * @param base64pwkey Private key precomputed with MegaSDK::getBase64PwKey + * @param listener MRequestListener to track this request + * + * @deprecated This function only works using the old registration method and will be removed soon. + * Please use MegaSDK::confirmAccount instead. + */ void fastConfirmAccount(String^ link, String^ base64pwkey, MRequestListenerInterface^ listener); + + /** + * @brief Confirm a MEGA account using a confirmation link and a precomputed key + * + * The associated request type with this request is MRequest::TYPE_CONFIRM_ACCOUNT + * Valid data in the MRequest object received on callbacks: + * - MRequest::getLink - Returns the confirmation link + * - MRequest::getPrivateKey - Returns the base64pwkey parameter + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Email of the account + * - MRequest::getName - Name of the user + * + * As a result of a successfull confirmation, the app will receive the callback + * MListener::onEvent and MGlobalListener::onEvent with an event of type + * MEvent::EVENT_ACCOUNT_CONFIRMATION. You can check the email used to confirm + * the account by checking MEvent::getText. @see MListener::onEvent. + * + * @param link Confirmation link + * @param base64pwkey Private key precomputed with MegaSDK::getBase64PwKey + * + * @deprecated This function only works using the old registration method and will be removed soon. + * Please use MegaSDK::confirmAccount instead. + */ void fastConfirmAccount(String^ link, String^ base64pwkey); + + /** + * @brief Initialize the reset of the existing password, with and without the Master Key. + * + * The associated request type with this request is MRequest::TYPE_GET_RECOVERY_LINK. + * Valid data in the MRequest object received on callbacks: + * - MRequest::getEmail - Returns the email for the account + * - MRequest::getFlag - Returns whether the user has a backup of the master key or not. + * + * If this request succeeds, a recovery link will be sent to the user. + * If no account is registered under the provided email, you will get the error code + * MError::API_ENOENT in onRequestFinish + * + * @param email Email used to register the account whose password wants to be reset. + * @param hasMasterKey True if the user has a backup of the master key. Otherwise, false. + * @param listener MRequestListener to track this request + */ void resetPassword(String^ email, bool hasMasterKey, MRequestListenerInterface^ listener); + + /** + * @brief Initialize the reset of the existing password, with and without the Master Key. + * + * The associated request type with this request is MRequest::TYPE_GET_RECOVERY_LINK. + * Valid data in the MRequest object received on callbacks: + * - MRequest::getEmail - Returns the email for the account + * - MRequest::getFlag - Returns whether the user has a backup of the master key or not. + * + * If this request succeeds, a recovery link will be sent to the user. + * If no account is registered under the provided email, you will get the error code + * MError::API_ENOENT in onRequestFinish + * + * @param email Email used to register the account whose password wants to be reset. + * @param hasMasterKey True if the user has a backup of the master key. Otherwise, false. + */ void resetPassword(String^ email, bool hasMasterKey); + + /** + * @brief Get information about a recovery link created by MegaSDK::resetPassword. + * + * The associated request type with this request is MRequest::TYPE_QUERY_RECOVERY_LINK + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the recovery link + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * - MRequest::getFlag - Return whether the link requires masterkey to reset password. + * + * @param link Recovery link (#recover) + * @param listener MRequestListener to track this request + */ void queryResetPasswordLink(String^ link, MRequestListenerInterface^ listener); + + /** + * @brief Get information about a recovery link created by MegaSDK::resetPassword. + * + * The associated request type with this request is MRequest::TYPE_QUERY_RECOVERY_LINK + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the recovery link + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * - MRequest::getFlag - Return whether the link requires masterkey to reset password. + * + * @param link Recovery link (#recover) + */ void queryResetPasswordLink(String^ link); + + /** + * @brief Set a new password for the account pointed by the recovery link. + * + * Recovery links are created by calling MegaSDK::resetPassword and may or may not + * require to provide the Master Key. + * + * @see The flag of the MRequest::TYPE_QUERY_RECOVERY_LINK in MegaSDK::queryResetPasswordLink. + * + * The associated request type with this request is MRequest::TYPE_CONFIRM_RECOVERY_LINK + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the recovery link + * - MRequest::getPassword - Returns the new password + * - MRequest::getPrivateKey - Returns the Master Key + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * - MRequest::getFlag - Return whether the link requires masterkey to reset password. + * + * @param link The recovery link sent to the user's email address. + * @param newPwd The new password to be set. + * @param masterKey Base64-encoded string containing the master key. + * @param listener MRequestListener to track this request + */ void confirmResetPassword(String^ link, String^ newPwd, String^ masterKey, MRequestListenerInterface^ listener); + + /** + * @brief Set a new password for the account pointed by the recovery link. + * + * Recovery links are created by calling MegaSDK::resetPassword and may or may not + * require to provide the Master Key. + * + * @see The flag of the MRequest::TYPE_QUERY_RECOVERY_LINK in MegaSDK::queryResetPasswordLink. + * + * The associated request type with this request is MRequest::TYPE_CONFIRM_RECOVERY_LINK + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the recovery link + * - MRequest::getPassword - Returns the new password + * - MRequest::getPrivateKey - Returns the Master Key + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * - MRequest::getFlag - Return whether the link requires masterkey to reset password. + * + * @param link The recovery link sent to the user's email address. + * @param newPwd The new password to be set. + * @param masterKey Base64-encoded string containing the master key. + */ void confirmResetPassword(String^ link, String^ newPwd, String^ masterKey); + + /** + * @brief Set a new password for the account pointed by the recovery link. + * + * Recovery links are created by calling MegaSDK::resetPassword and may or may not + * require to provide the Master Key. + * + * @see The flag of the MRequest::TYPE_QUERY_RECOVERY_LINK in MegaSDK::queryResetPasswordLink. + * + * The associated request type with this request is MRequest::TYPE_CONFIRM_RECOVERY_LINK + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the recovery link + * - MRequest::getPassword - Returns the new password + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * - MRequest::getFlag - Return whether the link requires masterkey to reset password. + * + * @param link The recovery link sent to the user's email address. + * @param newPwd The new password to be set. + * @param listener MRequestListener to track this request + */ void confirmResetPasswordWithoutMasterKey(String^ link, String^ newPwd, MRequestListenerInterface^ listener); + + /** + * @brief Set a new password for the account pointed by the recovery link. + * + * Recovery links are created by calling MegaSDK::resetPassword and may or may not + * require to provide the Master Key. + * + * @see The flag of the MRequest::TYPE_QUERY_RECOVERY_LINK in MegaSDK::queryResetPasswordLink. + * + * The associated request type with this request is MRequest::TYPE_CONFIRM_RECOVERY_LINK + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the recovery link + * - MRequest::getPassword - Returns the new password + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * - MRequest::getFlag - Return whether the link requires masterkey to reset password. + * + * @param link The recovery link sent to the user's email address. + * @param newPwd The new password to be set. + */ void confirmResetPasswordWithoutMasterKey(String^ link, String^ newPwd); + + /** + * @brief Initialize the cancellation of an account. + * + * The associated request type with this request is MRequest::TYPE_GET_CANCEL_LINK. + * + * If this request succeeds, a cancellation link will be sent to the email address of the user. + * If no user is logged in, you will get the error code MError::API_EACCESS in onRequestFinish(). + * + * @see MegaSDK::confirmCancelAccount + * + * @param listener MRequestListener to track this request + */ void cancelAccount(MRequestListenerInterface^ listener); + + /** + * @brief Initialize the cancellation of an account. + * + * The associated request type with this request is MRequest::TYPE_GET_CANCEL_LINK. + * + * If this request succeeds, a cancellation link will be sent to the email address of the user. + * If no user is logged in, you will get the error code MError::API_EACCESS in onRequestFinish(). + * + * @see MegaSDK::confirmCancelAccount + */ void cancelAccount(); + + /** + * @brief Get information about a cancel link created by MegaSDK::cancelAccount. + * + * The associated request type with this request is MRequest::TYPE_QUERY_RECOVERY_LINK + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the cancel link + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * + * @param link Cancel link (#cancel) + * @param listener MRequestListener to track this request + */ void queryCancelLink(String^ link, MRequestListenerInterface^ listener); + + /** + * @brief Get information about a cancel link created by MegaSDK::cancelAccount. + * + * The associated request type with this request is MRequest::TYPE_QUERY_RECOVERY_LINK + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the cancel link + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * + * @param link Cancel link (#cancel) + */ void queryCancelLink(String^ link); + + /** + * @brief Effectively parks the user's account without creating a new fresh account. + * + * If no user is logged in, you will get the error code MError::API_EACCESS in onRequestFinish(). + * + * The contents of the account will then be purged after 60 days. Once the account is + * parked, the user needs to contact MEGA support to restore the account. + * + * The associated request type with this request is MRequest::TYPE_CONFIRM_CANCEL_LINK. + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the recovery link + * - MRequest::getPassword - Returns the new password + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * + * @param link Cancellation link sent to the user's email address; + * @param pwd Password for the account. + * @param listener MRequestListener to track this request + */ void confirmCancelAccount(String^ link, String^ pwd, MRequestListenerInterface^ listener); + + /** + * @brief Effectively parks the user's account without creating a new fresh account. + * + * If no user is logged in, you will get the error code MError::API_EACCESS in onRequestFinish(). + * + * The contents of the account will then be purged after 60 days. Once the account is + * parked, the user needs to contact MEGA support to restore the account. + * + * The associated request type with this request is MRequest::TYPE_CONFIRM_CANCEL_LINK. + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the recovery link + * - MRequest::getPassword - Returns the new password + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * + * @param link Cancellation link sent to the user's email address; + * @param pwd Password for the account. + */ void confirmCancelAccount(String^ link, String^ pwd); + + /** + * @brief Initialize the change of the email address associated to the account. + * + * The associated request type with this request is MRequest::TYPE_GET_CHANGE_EMAIL_LINK. + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getEmail - Returns the email for the account + * + * If this request succeeds, a change-email link will be sent to the specified email address. + * If no user is logged in, you will get the error code MError::API_EACCESS in onRequestFinish(). + * + * @param email The new email to be associated to the account. + * @param listener MRequestListener to track this request + */ void changeEmail(String^ email, MRequestListenerInterface^ listener); + + /** + * @brief Initialize the change of the email address associated to the account. + * + * The associated request type with this request is MRequest::TYPE_GET_CHANGE_EMAIL_LINK. + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getEmail - Returns the email for the account + * + * If this request succeeds, a change-email link will be sent to the specified email address. + * If no user is logged in, you will get the error code MError::API_EACCESS in onRequestFinish(). + * + * @param email The new email to be associated to the account. + */ void changeEmail(String^ email); + + /** + * @brief Get information about a change-email link created by MegaSDK::changeEmail. + * + * The associated request type with this request is MRequest::TYPE_QUERY_RECOVERY_LINK + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the change-email link + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * + * @param link Change-email link (#verify) + * @param listener MRequestListener to track this request + */ void queryChangeEmailLink(String^ link, MRequestListenerInterface^ listener); + + /** + * @brief Get information about a change-email link created by MegaSDK::changeEmail. + * + * The associated request type with this request is MRequest::TYPE_QUERY_RECOVERY_LINK + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the change-email link + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * + * @param link Change-email link (#verify) + */ void queryChangeEmailLink(String^ link); + + /** + * @brief Effectively changes the email address associated to the account. + * + * If no user is logged in, you will get the error code MError::API_EACCESS in onRequestFinish(). + * + * The associated request type with this request is MRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK. + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the change-email link + * - MRequest::getPassword - Returns the password + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * + * @param link Change-email link sent to the user's email address. + * @param pwd Password for the account. + * @param listener MRequestListener to track this request + */ void confirmChangeEmail(String^ link, String^ pwd, MRequestListenerInterface^ listener); + + /** + * @brief Effectively changes the email address associated to the account. + * + * If no user is logged in, you will get the error code MError::API_EACCESS in onRequestFinish(). + * + * The associated request type with this request is MRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK. + * Valid data in the MRequest object received on all callbacks: + * - MRequest::getLink - Returns the change-email link + * - MRequest::getPassword - Returns the password + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getEmail - Return the email associated with the link + * + * @param link Change-email link sent to the user's email address. + * @param pwd Password for the account. + */ void confirmChangeEmail(String^ link, String^ pwd); + + /** + * @brief Check if the MegaApi object is logged in + * @return 0 if not logged in, Otherwise, a number >= 0 + */ int isLoggedIn(); /** @@ -595,7 +1360,7 @@ namespace mega * @param handle Handle of the contact link to delete * If the parameter is INVALID_HANDLE, the active contact link is deleted * - * @param listener MegaRequestListener to track this request + * @param listener MRequestListener to track this request */ void contactLinkDelete(MegaHandle handle, MRequestListenerInterface^ listener); @@ -620,7 +1385,7 @@ namespace mega * Valid data in the MRequest object received on all callbacks: * - MRequest::getNodeHandle - Returns the handle of the contact link * - * @param listener MegaRequestListener to track this request + * @param listener MRequestListener to track this request */ void contactLinkDeleteActive(MRequestListenerInterface^ listener); @@ -817,7 +1582,7 @@ namespace mega * - MRequest::getParamType - Returns the attribute type MUserAttrType::USER_ATTR_PWD_REMINDER * - MRequest::getText - Returns the new value for the attribute * - * @param listener MegaRequestListener to track this request + * @param listener MRequestListener to track this request */ void passwordReminderDialogSkipped(MRequestListenerInterface^ listener); @@ -853,7 +1618,7 @@ namespace mega * - MRequest::getParamType - Returns the attribute type MUserAttrType::USER_ATTR_PWD_REMINDER * - MRequest::getText - Returns the new value for the attribute * - * @param listener MegaRequestListener to track this request + * @param listener MRequestListener to track this request */ void passwordReminderDialogBlocked(MRequestListenerInterface^ listener); @@ -885,8 +1650,12 @@ namespace mega * is MError::API_OK: * - MRequest::getFlag - Returns true if the password reminder dialog should be shown * + * If the corresponding user attribute is not set yet, the request will fail with the + * error code MError::API_ENOENT but the value of MRequest::getFlag will still + * be valid. + * * @param atLogout True if the check is being done just before a logout - * @param listener MegaRequestListener to track this request + * @param listener MRequestListener to track this request */ void shouldShowPasswordReminderDialog(bool atLogout, MRequestListenerInterface^ listener); @@ -901,15 +1670,53 @@ namespace mega * is MError::API_OK: * - MRequest::getFlag - Returns true if the password reminder dialog should be shown * + * If the corresponding user attribute is not set yet, the request will fail with the + * error code MError::API_ENOENT but the value of MRequest::getFlag will still + * be valid. + * * @param atLogout True if the check is being done just before a logout */ void shouldShowPasswordReminderDialog(bool atLogout); + /** + * @brief Check if the master key has been exported + * + * The associated request type with this request is MRequest::TYPE_GET_ATTR_USER + * Valid data in the MRequest object received on callbacks: + * - MRequest::getParamType - Returns the attribute type MUserAttrType::USER_ATTR_PWD_REMINDER + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getAccess - Returns true if the master key has been exported + * + * If the corresponding user attribute is not set yet, the request will fail with the + * error code MError::API_ENOENT. + * + * @param listener MRequestListener to track this request + */ + void isMasterKeyExported(MRequestListenerInterface^ listener); + + /** + * @brief Check if the master key has been exported + * + * The associated request type with this request is MRequest::TYPE_GET_ATTR_USER + * Valid data in the MRequest object received on callbacks: + * - MRequest::getParamType - Returns the attribute type MUserAttrType::USER_ATTR_PWD_REMINDER + * + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: + * - MRequest::getAccess - Returns true if the master key has been exported + * + * If the corresponding user attribute is not set yet, the request will fail with the + * error code MError::API_ENOENT. + */ + void isMasterKeyExported(); + /** * @brief Change the password of the MEGA account * * The associated request type with this request is MRequest::TYPE_CHANGE_PW - * Valid data in the MegaRequest object received on callbacks: + * Valid data in the MRequest object received on callbacks: * - MRequest::getPassword - Returns the old password * - MRequest::getNewPassword - Returns the new password * @@ -923,7 +1730,7 @@ namespace mega * @brief Change the password of the MEGA account * * The associated request type with this request is MRequest::TYPE_CHANGE_PW - * Valid data in the MegaRequest object received on callbacks: + * Valid data in the MRequest object received on callbacks: * - MRequest::getPassword - Returns the old password * - MRequest::getNewPassword - Returns the new password * @@ -936,7 +1743,7 @@ namespace mega * @brief Change the password of the MEGA account without check the old password * * The associated request type with this request is MRequest::TYPE_CHANGE_PW - * Valid data in the MegaRequest object received on callbacks: + * Valid data in the MRequest object received on callbacks: * - MRequest::getNewPassword - Returns the new password * * @param newPassword New password @@ -948,7 +1755,7 @@ namespace mega * @brief Change the password of the MEGA account without check the old password * * The associated request type with this request is MRequest::TYPE_CHANGE_PW - * Valid data in the MegaRequest object received on callbacks: + * Valid data in the MRequest object received on callbacks: * - MRequest::getNewPassword - Returns the new password * * @param newPassword New password @@ -1323,12 +2130,12 @@ namespace mega * Valid data in the MRequest object received on callbacks: * - MRequest::getParamType - Returns the value MUserAttrType::USER_ATTR_CONTACT_LINK_VERIFICATION * - * Valid data in the MegaRequest object received in onRequestFinish when the error code - * is MegaError::API_OK: + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: * - MRequest::getText - "0" for disable, "1" for enable * - MRequest::getFlag - false if disabled, true if enabled * - * @param listener MegaRequestListener to track this request + * @param listener MRequestListener to track this request */ void getContactLinksOption(MRequestListenerInterface^ listener); @@ -1342,8 +2149,8 @@ namespace mega * Valid data in the MRequest object received on callbacks: * - MRequest::getParamType - Returns the value MUserAttrType::USER_ATTR_CONTACT_LINK_VERIFICATION * - * Valid data in the MegaRequest object received in onRequestFinish when the error code - * is MegaError::API_OK: + * Valid data in the MRequest object received in onRequestFinish when the error code + * is MError::API_OK: * - MRequest::getText - "0" for disable, "1" for enable * - MRequest::getFlag - false if disabled, true if enabled */ @@ -1356,7 +2163,7 @@ namespace mega * logout. Pass false to this function to disable that automatic logout and * keep the SDK retrying the request. * - * Even if the automatic logout is disabled, a request of the type MegaRequestType::TYPE_LOGOUT + * Even if the automatic logout is disabled, a request of the type MRequestType::TYPE_LOGOUT * will be automatically created and callbacks (onRequestStart, onRequestFinish) will * be sent. However, logout won't be really executed and in onRequestFinish the error code * for the request will be MError::API_EINCOMPLETE diff --git a/configure.ac b/configure.ac index e539d4c913..29c788709d 100644 --- a/configure.ac +++ b/configure.ac @@ -587,6 +587,87 @@ if test "x$pcre" = "xtrue" ; then fi AM_CONDITIONAL([USE_PCRE], [test "x$pcre" = "xtrue"]) +# +# LIBRAW +# + +#libraw +SAVE_LIBS="$LIBS" +LIBS="$LIBS -fopenmp" +AC_MSG_CHECKING(for libraw) +libraw=undef +AC_ARG_WITH(libraw, + AS_HELP_STRING(--with-libraw=PATH, base of libraw installation), + [ + case $with_libraw in + no) + libraw=false + ;; + yes) + AC_CHECK_HEADERS([libraw/libraw.h],, [ + AC_MSG_ERROR([libraw/libraw.h header not found or not usable]) + ]) + AC_CHECK_LIB(raw, [libraw_init], [LIBRAW_LIBS="$LIBRAW_LIBS -lraw -fopenmp"],[ + AC_MSG_ERROR([Could not find libraw]) + ]) + libraw=true + ;; + *) + + # determine if library is installed + if test -d "$with_libraw/lib"; then + LDFLAGS="-L$with_libraw/lib $LDFLAGS" + CXXFLAGS="-I$with_libraw/include $CXXFLAGS" + CPPFLAGS="-I$with_libraw/include $CPPFLAGS" + + AC_CHECK_HEADERS(libraw/libraw.h, + LIBRAW_CXXFLAGS="-I$with_libraw/include" + LIBRAW_CPPFLAGS="-I$with_libraw/include" + LIBRAW_LDFLAGS="-L$with_libraw/lib", + AC_MSG_ERROR([libraw/libraw.h header not found or not usable]) + ) + fi + + AC_CHECK_LIB(raw, [libraw_init], [LIBRAW_LIBS="$LIBRAW_LIBS -lraw -fopenmp"],[ + AC_MSG_ERROR([Could not find libraw]) + ]) + + libraw=true + + #restore + LDFLAGS=$SAVE_LDFLAGS + CXXFLAGS=$SAVE_CXXFLAGS + CPPFLAGS=$SAVE_CPPFLAGS + ;; + esac + ], +) +AC_SUBST(LIBRAW_CXXFLAGS) +AC_SUBST(LIBRAW_CPPFLAGS) +AC_SUBST(LIBRAW_LDFLAGS) +AC_SUBST(LIBRAW_LIBS) + +if test "x$libraw" = "xundef" ; then + AC_CHECK_HEADERS([libraw/libraw.h], + [ + AC_CHECK_LIB(raw, [libraw_init], + [ + libraw=true + LIBRAW_LIBS="$LIBRAW_LIBS -lraw -fopenmp" + ],[ + AC_MSG_NOTICE([Could not find libraw]) + ]) + ] + , [ + AC_MSG_NOTICE([libraw/libraw.h header not found or not usable]) + ]) +fi + +if test "x$libraw" = "xtrue" ; then + AC_DEFINE(HAVE_LIBRAW, [1], [Define to use libraw]) +fi +AM_CONDITIONAL([USE_LIBRAW], [test "x$libraw" = "xtrue"]) +LIBS="$SAVE_LIBS" # # LIBUV @@ -647,7 +728,12 @@ AC_ARG_WITH(libuv, fi AC_CHECK_LIB(uv, [uv_run], [LIBUV_LIBS="-luv"],[ + SAVE_LIBS="$LIBS" + LIBS="$LIBS -pthread" + AC_CHECK_LIB(uv, [uv_close], [LIBUV_LIBS="-luv -pthread"],[ #different symbol to avoid cached value AC_MSG_ERROR([Could not find libuv]) + ]) + LIBS="$SAVE_LIBS" ]) libuv=true @@ -668,8 +754,19 @@ AC_ARG_WITH(libuv, LIBUV_LIBS="-luv" libuv=true ],[ - AC_MSG_RESULT([Could not find libuv]) - libuv=false + if test "x$libuv" != "xtrue"; then + SAVE_LIBS="$LIBS" + LIBS="$LIBS -pthread" + AC_CHECK_LIB(uv, [uv_close], [ #different symbol to avoid cached value + LIBUV_LIBS="-luv -pthread" + libuv=true + ], + [ + AC_MSG_RESULT([Could not find libuv]) + libuv=false + ]) + LIBS="$SAVE_LIBS" + fi ] ) ,) @@ -1016,7 +1113,12 @@ AC_ARG_WITH(libzen, AC_MSG_ERROR([ZenLib/Ztring.h header not found or not usable]) ]) AC_CHECK_LIB(zen, [_ZN6ZenLib15int8u2BigEndianEPch], [LIBZEN_LIBS="-lzen"],[ + SAVE_LIBS="$LIBS" + LIBS="$LIBS -pthread" + AC_CHECK_LIB(zen, [_ZN6ZenLib19int48s2LittleEndianEPcx], [LIBZEN_LIBS="-lzen -pthread"],[ #different symbol to avoid cached value AC_MSG_ERROR([Could not find libzen]) + ]) + LIBS="$SAVE_LIBS" ]) libzen=true ;; @@ -1049,7 +1151,12 @@ AC_ARG_WITH(libzen, fi AC_CHECK_LIB(zen, [_ZN6ZenLib15int8u2BigEndianEPch], [LIBZEN_LIBS="-lzen"],[ + SAVE_LIBS="$LIBS" + LIBS="$LIBS -pthread" + AC_CHECK_LIB(zen, [_ZN6ZenLib19int48s2LittleEndianEPcx], [LIBZEN_LIBS="-lzen -pthread"],[ AC_MSG_ERROR([Could not find libzen]) + ]) + LIBS="$SAVE_LIBS" ]) libzen=true @@ -1067,6 +1174,16 @@ AC_ARG_WITH(libzen, LIBZEN_LIBS="-lzen" libzen=true ],) + + if test "x$libzen" != "xtrue"; then + SAVE_LIBS="$LIBS" + LIBS="$LIBS -pthread" + AC_CHECK_LIB(zen, [_ZN6ZenLib19int48s2LittleEndianEPcx], [ + LIBZEN_LIBS="-lzen -pthread" + libzen=true + ],) + LIBS="$SAVE_LIBS" + fi ,) ] @@ -1092,7 +1209,6 @@ if test "x$libzen" = "xtrue" && test "$pthread_available" = "yes"; then ]) LIBS="$LIBS $LIBZEN_LIBS $ZLIB_LIBS -pthread" - AC_MSG_RESULT(["AQUIITO LDFLAGS=$LDFLAGS CXXFLAGS=$CXXFLAGS CPPFLAGS=$CPPFLAGS LIBS=$LIBS" ]) #TODO: delete AC_CHECK_LIB(mediainfo, [MediaInfo_Info_Version], [LIBMEDIAINFO_LIBS="-lmediainfo $LIBZEN_LIBS"],[ AC_MSG_ERROR([Could not find libmediainfo]) @@ -2008,6 +2124,7 @@ AC_MSG_NOTICE([Configured to build Mega SDK: Termcap: $TERMCAP_CXXFLAGS $TERMCAP_LDFLAGS $TERMCAP_LIBS PCRE: $PCRE_CXXFLAGS $PCRE_LDFLAGS $PCRE_LIBS LIBUV: $LIBUV_CXXFLAGS $LIBUV_LDFLAGS $LIBUV_LIBS + LIBRAW: $LIBRAW_CXXFLAGS $LIBRAW_LDFLAGS $LIBRAW_LIBS LIBMEDIAINFO: $LIBMEDIAINFO_CXXFLAGS $LIBMEDIAINFO_LDFLAGS $LIBMEDIAINFO_LIBS FFMPEG: $FFMPEG_CXXFLAGS $FFMPEG_LDFLAGS $FFMPEG_LIBS ]) diff --git a/contrib/build_sdk.sh b/contrib/build_sdk.sh index bf0bef8957..418cfff3d5 100755 --- a/contrib/build_sdk.sh +++ b/contrib/build_sdk.sh @@ -45,6 +45,7 @@ enable_sodium=0 enable_cares=0 enable_curl=0 enable_libuv=0 +enable_libraw=0 android_build=0 readline_build=0 enable_cryptopp=0 @@ -491,6 +492,49 @@ libuv_pkg() { package_install $name $libuv_dir $install_dir } +libraw_pkg() { + local build_dir=$1 + local install_dir=$2 + local name="libraw" + local libraw_ver="0.19.0" + local libraw_url="https://www.libraw.org/data/LibRaw-$libraw_ver.tar.gz" + local libraw_md5="789b03f0ec39eebcba3ae8a0e5b780ac" + local libraw_file="libraw-$libraw_ver.tar.gz" + local libraw_dir="LibRaw-$libraw_ver" + if [ $use_dynamic -eq 1 ]; then + local libraw_params="--enable-shared" + else + local libraw_params="--disable-shared --enable-static" + fi + + if [ $incremental -eq 1 ] && [ -e $status_dir/$name.success ]; then + echo "$name already built" + return + else + rm -f $status_dir/$name.success + fi + + package_download $name $libraw_url $libraw_file $libraw_md5 + if [ $download_only -eq 1 ]; then + return + fi + + package_extract $name $libraw_file $libraw_dir + + local OLD_LIBS="$LIBS" + + # linking with static library requires -lstdc++ + if [ $use_dynamic -eq 0 ]; then + export LIBS="$LIBS -lstdc++" + fi + package_configure $name $libraw_dir $install_dir "$libraw_params" + + export LIBS="$OLD_LIBS" + + package_build $name $libraw_dir + package_install $name $libraw_dir $install_dir +} + zlib_pkg() { local build_dir=$1 local install_dir=$2 @@ -999,6 +1043,7 @@ build_sdk() { local readline_flags="" local freeimage_flags="" local libuv_flags="" + local libraw_flags="" local megaapi_flags="" local openssl_flags="" local sodium_flags="--without-sodium" @@ -1036,6 +1081,11 @@ build_sdk() { libuv_flags="--without-libuv" fi + # enable libraw + if [ $enable_libraw -eq 1 ]; then + libraw_flags="--with-libraw=$install_dir" + fi + # use local or system MediaInfo local mediainfo_flags="" if [ $disable_mediainfo -eq 0 ]; then @@ -1079,6 +1129,7 @@ build_sdk() { --with-curl=$install_dir \ $freeimage_flags \ $libuv_flags \ + $libraw_flags \ $readline_flags \ $disable_posix_threads \ $no_examples \ @@ -1104,6 +1155,7 @@ build_sdk() { --with-winhttp=$cwd \ $freeimage_flags \ $libuv_flags \ + $libraw_flags \ $readline_flags \ $disable_posix_threads \ $no_examples \ @@ -1183,7 +1235,7 @@ main() { local_dir=$work_dir status_dir=$work_dir - while getopts ":habcdefgiIlm:no:p:rRsS:tuvyx:XC:O:wqz0" opt; do + while getopts ":habcdefgiIlm:no:p:rRsS:tuvyx:XC:O:wWqz0" opt; do case $opt in h) display_help $0 @@ -1281,6 +1333,10 @@ main() { download_only=1 echo "* Downloading software archives only." ;; + W) + enable_libraw=1 + echo "* Enabling external libraw." + ;; x) config_opts="$OPTARG" echo "* Using configuration options: $config_opts" @@ -1390,6 +1446,10 @@ main() { libuv_pkg $build_dir $install_dir fi + if [ $enable_libraw -eq 1 ]; then + libraw_pkg $build_dir $install_dir + fi + if [ $disable_freeimage -eq 0 ]; then freeimage_pkg $build_dir $install_dir $cwd fi diff --git a/contrib/cmake/CMakeLists.txt b/contrib/cmake/CMakeLists.txt index c235b30841..432480f8c8 100644 --- a/contrib/cmake/CMakeLists.txt +++ b/contrib/cmake/CMakeLists.txt @@ -34,9 +34,9 @@ set (build_64_bit 1 CACHE TYPE BOOL) #indicate which dependent libraries to use in the build set (USE_CRYPTOPP 1 CACHE TYPE BOOL) -set (USE_OPENSSL 0 CACHE TYPE BOOL) +set (USE_OPENSSL 1 CACHE TYPE BOOL) set (OPENSSL_IS_BORINGSSL 0 CACHE TYPE BOOL) -set (USE_CURL 0 CACHE TYPE BOOL) +set (USE_CURL 1 CACHE TYPE BOOL) set (USE_SQLITE 1 CACHE TYPE BOOL) set (USE_MEDIAINFO 1 CACHE TYPE BOOL) set (USE_FREEIMAGE 1 CACHE TYPE BOOL) @@ -50,6 +50,10 @@ else(WIN32) set(NO_READLINE 0) endif(WIN32) +if (ENABLE_CHAT AND NOT USE_SODUIM) + message(FATAL_ERROR "ENABLE_CHAT requires USE_SODIUM") +endif() + if(build_64_bit) project (MegaSDK64 LANGUAGES CXX C) else(build_64_bit) @@ -67,9 +71,9 @@ ENDIF(WIN32) IF(WIN32) if(build_64_bit) - set(vcpkg_dir "${Mega3rdPartyDir}/vcpkg/installed/x64-windows-static") + set(vcpkg_dir "${Mega3rdPartyDir}/vcpkg/installed/x64-windows-static-uncheckediterators") else(build_64_bit) - set(vcpkg_dir "${Mega3rdPartyDir}/vcpkg/installed/x86-windows-static") + set(vcpkg_dir "${Mega3rdPartyDir}/vcpkg/installed/x86-windows-static-uncheckediterators") endif(build_64_bit) ENDIF(WIN32) @@ -83,6 +87,17 @@ endif(NOT CMAKE_BUILD_TYPE) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG -DDEBUG") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG -DDEBUG") +# node deletion in debug under VC++ is pretty slow without this. However libraries we depend on need to be built with the same setting or linking fails +# (hence the build3rdParty script using the xNN-windows-static-uncheckediterators triplets) +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_ITERATOR_DEBUG_LEVEL=0" ) + + +# accurate __cplusplus macro for vc++, selecting c++17 here for windows builds though the MEGA SDK library must build for older c++ standards also +if(WIN32) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:__cplusplus /std:c++17") +add_definitions( -DNOMINMAX ) # also don't allow min and max macros from windows.h, use the std functions like on linux/g++ +endif() + IF(WIN32) #Link against the static C/C++ libraries on windows foreach(flag_var @@ -127,15 +142,7 @@ IF(WIN32) ENDIF(USE_CRYPTOPP) IF(USE_SODIUM) - if (build_64_bit) - ImportVcpkgLibrary(sodium "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/libsodium.lib" "${vcpkg_dir}/lib/libsodium.lib") - else() - ImportStaticLibrary(sodium "${Mega3rdPartyDir}/libsodium-1.0.15/src/libsodium/include" - "${Mega3rdPartyDir}/libsodium-1.0.15/Build/Debug/Win32/libsodium.lib" - "${Mega3rdPartyDir}/libsodium-1.0.15/Build/Release/Win32/libsodium.lib" - "${Mega3rdPartyDir}/libsodium-1.0.15/Build/Debug/x64/libsodium.lib" - "${Mega3rdPartyDir}/libsodium-1.0.15/Build/Release/x64/libsodium.lib") - endif() + ImportVcpkgLibrary(sodium "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/libsodium.lib" "${vcpkg_dir}/lib/libsodium.lib") ENDIF(USE_SODIUM) IF(USE_CURL) @@ -150,11 +157,7 @@ IF(WIN32) ImportVcpkgLibrary(zlib "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/zlibd.lib" "${vcpkg_dir}/lib/zlib.lib") - ImportStaticLibrary(gtest "${Mega3rdPartyDir}/googletest/googletest/include" - "${Mega3rdPartyDir}/googletest/googletest/msvc/2010/gtest/Win32-Debug/gtestd.lib" - "${Mega3rdPartyDir}/googletest/googletest/msvc/2010/gtest/Win32-Release/gtest.lib" - "${Mega3rdPartyDir}/googletest/googletest/msvc/2010/gtest/x64-Debug/gtestd.lib" - "${Mega3rdPartyDir}/googletest/googletest/msvc/2010/gtest/x64-Release/gtest.lib") + ImportVcpkgLibrary(gtest "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/manual-link/gtestd.lib" "${vcpkg_dir}/lib/manual-link/gtest.lib") IF(USE_MEDIAINFO) ImportStaticLibrary(mediainfo "${Mega3rdPartyDir}/MediaInfoLib-mw/Source" @@ -182,16 +185,12 @@ IF(WIN32) IF(USE_SQLITE) ImportVcpkgLibrary(sqlite3 "${vcpkg_dir}/include" "${vcpkg_dir}/debug/lib/sqlite3.lib" "${vcpkg_dir}/lib/sqlite3.lib") - - # add_library(sqlite3 INTERFACE IMPORTED) - # set_property(TARGET sqlite3 PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${Mega3rdPartyDir}/sqlite-3.20.1") ENDIF(USE_SQLITE) add_definitions(-D_CRT_SECURE_NO_WARNINGS -DCURL_STATICLIB -DCARES_STATICLIB -DWIN32_LEAN_AND_MEAN -DUNICODE -DSODIUM_STATIC -DPCRE_STATICWIN32 -D_CONSOLE) SET(Mega_PlatformSpecificIncludes ${MegaDir}/include/mega/$) SET(Mega_PlatformSpecificLibs ws2_32 winhttp Shlwapi) - #get_target_property(sqlite3dir sqlite3 INTERFACE_INCLUDE_DIRECTORIES) SET(Mega_PlatformSpecificFiles ${MegaDir}/src/win32/console.cpp ${MegaDir}/src/win32/autocomplete.cpp ${MegaDir}/src/win32/consolewaiter.cpp @@ -199,7 +198,6 @@ IF(WIN32) $ ${MegaDir}/src/win32/waiter.cpp ${MegaDir}/src/thread/win32thread.cpp - #${sqlite3dir}/sqlite3.c ) @@ -291,13 +289,16 @@ add_executable(test_misc ${MegaDir}/tests/tests.cpp ${MegaDir}/tests/paycrypt_test.cpp ${MegaDir}/tests/crypto_test.cpp) add_executable(test_purge_account ${MegaDir}/tests/purge_account.cpp) +add_executable(test_sync ${MegaDir}/tests/synctests.cpp) target_compile_definitions(test_sdk PRIVATE _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) target_compile_definitions(test_misc PRIVATE _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) target_compile_definitions(test_purge_account PRIVATE _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) +target_compile_definitions(test_sync PRIVATE _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) target_link_libraries(test_sdk gtest Mega ) target_link_libraries(test_misc gtest Mega ) target_link_libraries(test_purge_account gtest Mega ) +target_link_libraries(test_sync gtest Mega ) #test apps need this file or tests fail configure_file("${MegaDir}/logo.png" logo.png COPYONLY) diff --git a/contrib/cmake/build3rdparty.cmd b/contrib/cmake/build3rdparty.cmd new file mode 100644 index 0000000000..68a638e87c --- /dev/null +++ b/contrib/cmake/build3rdparty.cmd @@ -0,0 +1,61 @@ +REM best to use this script outside of the MEGA repo. Once the libraries are built, the CMakeLists.txt can be adjusted to refer to them. +mkdir 3rdParty +cd 3rdParty + +git clone https://github.com/Microsoft/vcpkg.git +cd vcpkg +CALL .\bootstrap-vcpkg.bat + +REM we use a lot of map iterators, and the checking is linear, causing big delays in node deletion for example +copy .\triplets\x86-windows-static.cmake .\triplets\x86-windows-static-uncheckediterators.cmake +copy .\triplets\x64-windows-static.cmake .\triplets\x64-windows-static-uncheckediterators.cmake +echo #comment >> .\triplets\x86-windows-static-uncheckediterators.cmake +echo #comment >> .\triplets\x64-windows-static-uncheckediterators.cmake +echo set(VCPKG_CXX_FLAGS "${VCPKG_CXX_FLAGS} -D_ITERATOR_DEBUG_LEVEL=0") >> .\triplets\x86-windows-static-uncheckediterators.cmake +echo set(VCPKG_CXX_FLAGS "${VCPKG_CXX_FLAGS} -D_ITERATOR_DEBUG_LEVEL=0") >> .\triplets\x64-windows-static-uncheckediterators.cmake +echo set(VCPKG_C_FLAGS "${VCPKG_C_FLAGS} -D_ITERATOR_DEBUG_LEVEL=0") >> .\triplets\x86-windows-static-uncheckediterators.cmake +echo set(VCPKG_C_FLAGS "${VCPKG_C_FLAGS} -D_ITERATOR_DEBUG_LEVEL=0") >> .\triplets\x64-windows-static-uncheckediterators.cmake + +REM for libsodium for x86, currently some adjustments are needed: +REM add this to its portfile.cmake's vcpkg_build_msbuild call (having moved that definition before it): PLATFORM ${BUILD_ARCH} +REM also edit its vcproj file to set Configuration->AllOptions->TargetMachine to x86 for the Win32 configurations. +REM you can build everything first then make those adjustments and manually call vcpkg for libsodium x86. + +CALL :build_one zlib +CALL :build_one cryptopp +CALL :build_one libsodium +CALL :build_one openssl +CALL :build_one curl +CALL :build_one c-ares +CALL :build_one sqlite3 +CALL :build_one libevent +CALL :build_one gtest + +REM for megachat: +REM CALL :build_one libuv +REM CALL :build_one libwebsockets + +REM currently for libwebsockets a few changes are needed (mainly get 2.4.2, turn off ipv6, turn on libuv features) +REM modify these attributes in its portfile: +REM REF v2.4.2 +REM SHA512 +REM 7bee49f6763ff3ab7861fcda25af8d80f6757c56e197ea42be53e0b2480969eee73de3aee5198f5ff06fd1cb8ab2be4c6495243e83cd0acc235b0da83b2353d1 +REM HEAD_REF v2.4-stable +REM and add libuv dependency (this might not be strictly necessary) +REM Feature: libuv +REM Build-Depends: libuv +REM Description: turns on LWS_WITH_LIBUU +REM then use remove and install again. +REM also find libwebsocket's CMakeLists.txt file and modify the libuv find_library, adding 'libuv' as that's what the libuv vcpkg produces: +REM find_library(LIBUV_LIBRARIES NAMES uv libuv) + + + +exit /b 0 + +:build_one +.\vcpkg.exe install --triplet x64-windows-static-uncheckediterators %1% +echo %errorlevel% %1% x64-windows-static-uncheckediterators >> buildlog +.\vcpkg.exe install --triplet x86-windows-static-uncheckediterators %1% +echo %errorlevel% %1% x86-windows-static-uncheckediterators >> buildlog +exit /b 0 diff --git a/examples/android/ExampleApp/app/src/main/jni/build.sh b/examples/android/ExampleApp/app/src/main/jni/build.sh index c03656649e..b6915a8a51 100755 --- a/examples/android/ExampleApp/app/src/main/jni/build.sh +++ b/examples/android/ExampleApp/app/src/main/jni/build.sh @@ -58,11 +58,11 @@ OPENSSL_PREFIX=${JNI_PATH}/${OPENSSL}/${OPENSSL_SOURCE_FOLDER} OPENSSL_SHA1="577585f5f5d299c44dd3c993d3c0ac7a219e4949" SODIUM=sodium -SODIUM_VERSION=1.0.13 +SODIUM_VERSION=1.0.16 SODIUM_SOURCE_FILE=libsodium-${SODIUM_VERSION}.tar.gz SODIUM_SOURCE_FOLDER=libsodium-${SODIUM_VERSION} SODIUM_DOWNLOAD_URL=https://download.libsodium.org/libsodium/releases/${SODIUM_SOURCE_FILE} -SODIUM_SHA1="ba6062fa723e653c6d85a80c3616128e797482ec" +SODIUM_SHA1="c7ea321d7b8534e51c5e3d86055f6c1aa1e48ee9" LIBUV=libuv LIBUV_VERSION=1.8.0 @@ -250,6 +250,7 @@ if [ ! -f ${SODIUM}/${SODIUM_SOURCE_FILE}.ready ]; then pushd ${SODIUM}/${SODIUM} &>> ${LOG_FILE} export ANDROID_NDK_HOME=${NDK_ROOT} ./autogen.sh &>> ${LOG_FILE} + echo "#include " >> src/libsodium/include/sodium/export.h sed -i 's/enable-minimal/enable-minimal --disable-pie/g' dist-build/android-build.sh echo "* Prebuilding libsodium for ARMv7" dist-build/android-armv7-a.sh &>> ${LOG_FILE} diff --git a/examples/include.am b/examples/include.am index 9b6388e0bd..a5fe0ccfa4 100644 --- a/examples/include.am +++ b/examples/include.am @@ -24,18 +24,18 @@ examples_megasimplesync_SOURCES = examples/megasimplesync.cpp if WIN32 -examples_megacli_CXXFLAGS = -D_WIN32=1 -Iinclude/ -Iinclude/mega/win32 $(FI_CXXFLAGS) $(RL_CXXFLAGS) $(ZLIB_CXXFLAGS) $(LIBUV_CXXFLAGS) $(LIBMEDIAINFO_CXXFLAGS) $(FFMPEG_CXXFLAGS) $(LIBCURL_FLAGS) $(CRYPTO_CXXFLAGS) $(DB_CXXFLAGS) $(WINHTTP_CXXFLAGS) $(SODIUM_CXXFLAGS) $(PCRE_CXXFLAGS) -examples_megacli_LDADD = $(FI_LDFLAGS) $(FI_LIBS) $(RL_LDFLAGS) $(RL_LIBS) $(ZLIB_LDFLAGS) $(ZLIB_LIBS) $(LIBUV_LDFLAGS) $(LIBUV_LIBS) $(LIBMEDIAINFO_LDFLAGS) $(LIBMEDIAINFO_LIBS) $(CRYPTO_LDFLAGS) $(CRYPTO_LIBS) $(DB_LDFLAGS) $(DB_LIBS) $(WINHTTP_LDFLAGS) $(WINHTTP_LIBS) $(TERMCAP_LDFLAGS) $(TERMCAP_LIBS) $(top_builddir)/src/libmega.la $(LIBS_EXTRA) $(FFMPEG_LDFLAGS) $(FFMPEG_LIBS) $(PCRE_LDFLAGS) $(PCRE_LIBS) +examples_megacli_CXXFLAGS = -D_WIN32=1 -Iinclude/ -Iinclude/mega/win32 $(FI_CXXFLAGS) $(RL_CXXFLAGS) $(ZLIB_CXXFLAGS) $(LIBUV_CXXFLAGS) $(LIBRAW_CXXFLAGS) $(LIBMEDIAINFO_CXXFLAGS) $(FFMPEG_CXXFLAGS) $(LIBCURL_FLAGS) $(CRYPTO_CXXFLAGS) $(DB_CXXFLAGS) $(WINHTTP_CXXFLAGS) $(SODIUM_CXXFLAGS) $(PCRE_CXXFLAGS) +examples_megacli_LDADD = $(FI_LDFLAGS) $(FI_LIBS) $(RL_LDFLAGS) $(RL_LIBS) $(ZLIB_LDFLAGS) $(ZLIB_LIBS) $(LIBUV_LDFLAGS) $(LIBUV_LIBS) $(LIBRAW_LDFLAGS) $(LIBRAW_LIBS) $(LIBMEDIAINFO_LDFLAGS) $(LIBMEDIAINFO_LIBS) $(CRYPTO_LDFLAGS) $(CRYPTO_LIBS) $(DB_LDFLAGS) $(DB_LIBS) $(WINHTTP_LDFLAGS) $(WINHTTP_LIBS) $(TERMCAP_LDFLAGS) $(TERMCAP_LIBS) $(top_builddir)/src/libmega.la $(LIBS_EXTRA) $(FFMPEG_LDFLAGS) $(FFMPEG_LIBS) $(PCRE_LDFLAGS) $(PCRE_LIBS) -examples_megasimplesync_CXXFLAGS = -D_WIN32=1 -Iinclude/ -Iinclude/mega/win32 $(FI_CXXFLAGS) $(ZLIB_CXXFLAGS) $(LIBUV_CXXFLAGS) $(LIBMEDIAINFO_CXXFLAGS) $(FFMPEG_CXXFLAGS) $(CRYPTO_CXXFLAGS) $(DB_CXXFLAGS) $(WINHTTP_CXXFLAGS) $(SODIUM_CXXFLAGS) $(PCRE_CXXFLAGS) -examples_megasimplesync_LDADD = $(FI_LDFLAGS) $(FI_LIBS) $(ZLIB_LDFLAGS) $(ZLIB_LIBS) $(LIBUV_LDFLAGS) $(LIBUV_LIBS) $(LIBMEDIAINFO_LDFLAGS) $(LIBMEDIAINFO_LIBS) $(CRYPTO_LDFLAGS) $(CRYPTO_LIBS) $(DB_LDFLAGS) $(DB_LIBS) $(WINHTTP_LDFLAGS) $(WINHTTP_LIBS) $(top_builddir)/src/libmega.la $(LIBS_EXTRA) $(FFMPEG_LDFLAGS) $(FFMPEG_LIBS) $(PCRE_LDFLAGS) $(PCRE_LIBS) +examples_megasimplesync_CXXFLAGS = -D_WIN32=1 -Iinclude/ -Iinclude/mega/win32 $(FI_CXXFLAGS) $(ZLIB_CXXFLAGS) $(LIBUV_CXXFLAGS) $(LIBRAW_CXXFLAGS) $(LIBMEDIAINFO_CXXFLAGS) $(FFMPEG_CXXFLAGS) $(CRYPTO_CXXFLAGS) $(DB_CXXFLAGS) $(WINHTTP_CXXFLAGS) $(SODIUM_CXXFLAGS) $(PCRE_CXXFLAGS) +examples_megasimplesync_LDADD = $(FI_LDFLAGS) $(FI_LIBS) $(ZLIB_LDFLAGS) $(ZLIB_LIBS) $(LIBUV_LDFLAGS) $(LIBUV_LIBS) $(LIBRAW_LDFLAGS) $(LIBRAW_LIBS) $(LIBMEDIAINFO_LDFLAGS) $(LIBMEDIAINFO_LIBS) $(CRYPTO_LDFLAGS) $(CRYPTO_LIBS) $(DB_LDFLAGS) $(DB_LIBS) $(WINHTTP_LDFLAGS) $(WINHTTP_LIBS) $(top_builddir)/src/libmega.la $(LIBS_EXTRA) $(FFMPEG_LDFLAGS) $(FFMPEG_LIBS) $(PCRE_LDFLAGS) $(PCRE_LIBS) else examples_megacli_CXXFLAGS = $(FI_CXXFLAGS) $(RL_CXXFLAGS) $(ZLIB_CXXFLAGS) $(CARES_FLAGS) $(LIBCURL_FLAGS) $(CRYPTO_CXXFLAGS) $(DB_CXXFLAGS) $(SODIUM_CXXFLAGS) $(LIBSSL_FLAGS) $(PCRE_CXXFLAGS) -examples_megacli_LDADD = $(FI_LDFLAGS) $(FI_LIBS) $(RL_LDFLAGS) $(RL_LIBS) $(ZLIB_LDFLAGS) $(ZLIB_LIBS) $(LIBUV_LDFLAGS) $(LIBUV_LIBS) $(LIBMEDIAINFO_LDFLAGS) $(LIBMEDIAINFO_LIBS) $(CRYPTO_LDFLAGS) $(CRYPTO_LIBS) $(CARES_LDFLAGS) $(CARES_LIBS) $(LIBCURL_LIBS) $(DB_LDFLAGS) $(DB_LIBS) $(LIBSSL_LDFLAGS) $(LIBSSL_LIBS) $(TERMCAP_LDFLAGS) $(TERMCAP_LIBS) $(PCRE_LDFLAGS) $(PCRE_LIBS) $(top_builddir)/src/libmega.la $(FFMPEG_LDFLAGS) $(FFMPEG_LIBS) +examples_megacli_LDADD = $(FI_LDFLAGS) $(FI_LIBS) $(RL_LDFLAGS) $(RL_LIBS) $(ZLIB_LDFLAGS) $(ZLIB_LIBS) $(LIBUV_LDFLAGS) $(LIBUV_LIBS) $(LIBRAW_LDFLAGS) $(LIBRAW_LIBS) $(LIBMEDIAINFO_LDFLAGS) $(LIBMEDIAINFO_LIBS) $(CRYPTO_LDFLAGS) $(CRYPTO_LIBS) $(CARES_LDFLAGS) $(CARES_LIBS) $(LIBCURL_LIBS) $(DB_LDFLAGS) $(DB_LIBS) $(LIBSSL_LDFLAGS) $(LIBSSL_LIBS) $(TERMCAP_LDFLAGS) $(TERMCAP_LIBS) $(PCRE_LDFLAGS) $(PCRE_LIBS) $(top_builddir)/src/libmega.la $(FFMPEG_LDFLAGS) $(FFMPEG_LIBS) -examples_megasimplesync_CXXFLAGS = $(FI_CXXFLAGS) $(ZLIB_CXXFLAGS) $(LIBUV_CXXFLAGS) $(LIBMEDIAINFO_CXXFLAGS) $(FFMPEG_CXXFLAGS) $(CARES_FLAGS) $(LIBCURL_FLAGS) $(CRYPTO_CXXFLAGS) $(DB_CXXFLAGS) $(SODIUM_CXXFLAGS) $(LIBSSL_FLAGS) $(PCRE_CXXFLAGS) -examples_megasimplesync_LDADD = $(FI_LDFLAGS) $(FI_LIBS) $(ZLIB_LDFLAGS) $(ZLIB_LIBS) $(LIBUV_LDFLAGS) $(LIBUV_LIBS) $(LIBMEDIAINFO_LDFLAGS) $(LIBMEDIAINFO_LIBS) $(CRYPTO_LDFLAGS) $(CRYPTO_LIBS) $(CARES_LDFLAGS) $(CARES_LIBS) $(LIBCURL_LIBS) $(DB_LDFLAGS) $(DB_LIBS) $(LIBSSL_LDFLAGS) $(LIBSSL_LIBS) $(PCRE_LDFLAGS) $(PCRE_LIBS) $(top_builddir)/src/libmega.la $(FFMPEG_LDFLAGS) $(FFMPEG_LIBS) +examples_megasimplesync_CXXFLAGS = $(FI_CXXFLAGS) $(ZLIB_CXXFLAGS) $(LIBUV_CXXFLAGS) $(LIBRAW_CXXFLAGS) $(LIBMEDIAINFO_CXXFLAGS) $(FFMPEG_CXXFLAGS) $(CARES_FLAGS) $(LIBCURL_FLAGS) $(CRYPTO_CXXFLAGS) $(DB_CXXFLAGS) $(SODIUM_CXXFLAGS) $(LIBSSL_FLAGS) $(PCRE_CXXFLAGS) +examples_megasimplesync_LDADD = $(FI_LDFLAGS) $(FI_LIBS) $(ZLIB_LDFLAGS) $(ZLIB_LIBS) $(LIBUV_LDFLAGS) $(LIBUV_LIBS) $(LIBRAW_LDFLAGS) $(LIBRAW_LIBS) $(LIBMEDIAINFO_LDFLAGS) $(LIBMEDIAINFO_LIBS) $(CRYPTO_LDFLAGS) $(CRYPTO_LIBS) $(CARES_LDFLAGS) $(CARES_LIBS) $(LIBCURL_LIBS) $(DB_LDFLAGS) $(DB_LIBS) $(LIBSSL_LDFLAGS) $(LIBSSL_LIBS) $(PCRE_LDFLAGS) $(PCRE_LIBS) $(top_builddir)/src/libmega.la $(FFMPEG_LDFLAGS) $(FFMPEG_LIBS) if BUILD_FUSE_EXAMPLE examples_linux_megafuse_SOURCES = examples/linux/megafuse.cpp diff --git a/examples/megacli.cpp b/examples/megacli.cpp index 34e8b09003..00fb8adb61 100644 --- a/examples/megacli.cpp +++ b/examples/megacli.cpp @@ -48,6 +48,15 @@ namespace fs = std::experimental::filesystem; #include using namespace mega; +using std::cout; +using std::cerr; +using std::endl; +using std::flush; +using std::ifstream; +using std::ofstream; +using std::setw; +using std::hex; +using std::dec; MegaClient* client; MegaClient* clientFolder; @@ -90,6 +99,9 @@ Console* console; // loading progress of lengthy API responses int responseprogress = -1; +//2FA pin attempts +int attempts = 0; + static const char* getAccessLevelStr(int access) { switch(access) @@ -163,6 +175,8 @@ const char* errorstring(error e) return "Invalid application key"; case API_EGOINGOVERQUOTA: return "Not enough quota"; + case API_EMFAREQUIRED: + return "Required 2FA pin"; default: return "Unknown error"; } @@ -1705,12 +1719,12 @@ static char dynamicprompt[128]; static const char* prompts[] = { - "MEGAcli> ", "Password:", "Old Password:", "New Password:", "Retype New Password:", "Master Key (base64):" + "MEGAcli> ", "Password:", "Old Password:", "New Password:", "Retype New Password:", "Master Key (base64):", "Type 2FA pin:", "Type pin to enable 2FA:" }; enum prompttype { - COMMAND, LOGINPASSWORD, OLDPASSWORD, NEWPASSWORD, PASSWORDCONFIRM, MASTERKEY + COMMAND, LOGINPASSWORD, OLDPASSWORD, NEWPASSWORD, PASSWORDCONFIRM, MASTERKEY, LOGINTFA, SETTFA }; static prompttype prompt = COMMAND; @@ -1899,6 +1913,7 @@ void xferq(direction_t d, int cancel) static byte pwkey[SymmCipher::KEYLENGTH]; static byte pwkeybuf[SymmCipher::KEYLENGTH]; static byte newpwkey[SymmCipher::KEYLENGTH]; +static string newpassword; // readline callback - exit if EOF, add to history unless password static void store_line(char* l) @@ -2013,6 +2028,9 @@ autocomplete::ACN autocompleteSyntax() p->Add(sequence(text("chatra"), param("chatid"), param("nodehandle"), param("uid"))); p->Add(sequence(text("chatst"), param("chatid"), param("title64"))); #endif + p->Add(sequence(text("enabletransferresumption"), opt(either(text("on"), text("off"))))); + p->Add(sequence(text("setmaxdownloadspeed"), opt(wholenumber(10000)))); + p->Add(sequence(text("setmaxuploadspeed"), opt(wholenumber(10000)))); p->Add(sequence(text("handles"), opt(either(text("on"), text("off"))))); p->Add(sequence(text("httpsonly"), opt(either(text("on"), text("off"))))); p->Add(sequence(text("autocomplete"), opt(either(text("unix"), text("dos"))))); @@ -2082,6 +2100,16 @@ static void process_line(char* l) { switch (prompt) { + case LOGINTFA: + client->login(login.c_str(), pwkey, l); + setprompt(COMMAND); + return; + + case SETTFA: + client->multifactorauthsetup(l); + setprompt(COMMAND); + return; + case LOGINPASSWORD: client->pw_key(l, pwkey); @@ -2136,6 +2164,7 @@ static void process_line(char* l) return; case NEWPASSWORD: + newpassword = l; client->pw_key(l, newpwkey); cout << endl; @@ -2163,11 +2192,11 @@ static void process_line(char* l) if (hasMasterKey) { - client->confirmrecoverylink(recoverycode.c_str(), recoveryemail.c_str(), newpwkey, masterkey); + client->confirmrecoverylink(recoverycode.c_str(), recoveryemail.c_str(), newpassword.c_str(), masterkey); } else { - client->confirmrecoverylink(recoverycode.c_str(), recoveryemail.c_str(), newpwkey, NULL); + client->confirmrecoverylink(recoverycode.c_str(), recoveryemail.c_str(), newpassword.c_str(), NULL); } recoverycode.clear(); @@ -2177,7 +2206,7 @@ static void process_line(char* l) } else { - if ((e = client->changepw(newpwkey)) == API_OK) + if ((e = client->changepw(newpassword.c_str())) == API_OK) { memcpy(pwkey, newpwkey, sizeof pwkey); cout << endl << "Changing password..." << endl; @@ -2372,6 +2401,9 @@ static void process_line(char* l) cout << " chata chatid archive" << endl; // archive can be 1 or 0 #endif cout << " httpsonly on | off" << endl; + cout << " mfac" << endl; + cout << " mfae" << endl; + cout << " mfad pin" << endl; cout << " quit" << endl; #endif return; @@ -3294,8 +3326,44 @@ static void process_line(char* l) { return; } - break; + else if (words[0] == "mfad") + { + if (words.size() == 2) + { + client->multifactorauthdisable(words[1].c_str()); + } + else + { + cout << " mfad pin" << endl; + } + return; + } + else if (words[0] == "mfac") + { + if (words.size() == 1) + { + client->multifactorauthcheck(login.c_str()); + } + else + { + cout << " mfac" << endl; + } + return; + } + else if (words[0] == "mfae") + { + if (words.size() == 1) + { + client->multifactorauthsetup(); + } + else + { + cout << " mfae" << endl; + } + return; + } + break; case 5: if (words[0] == "login") { @@ -5090,6 +5158,49 @@ static void process_line(char* l) } #endif break; + + case 17: + if (words[0] == "setmaxuploadspeed") + { + if (words.size() > 1) + { + bool done = client->setmaxuploadspeed(atoi(words[1].c_str())); + cout << (done ? "Success. " : "Failed. "); + } + cout << "Max Upload Speed: " << client->getmaxuploadspeed() << endl; + return; + } + break; + + case 19: + if (words[0] == "setmaxdownloadspeed") + { + if (words.size() > 1) + { + bool done = client->setmaxdownloadspeed(atoi(words[1].c_str())); + cout << (done ? "Success. " : "Failed. "); + } + cout << "Max Download Speed: " << client->getmaxdownloadspeed() << endl; + return; + } + break; + + case 24: + if (words[0] == "enabletransferresumption") + { + if (words.size() > 1 && words[1] == "off") + { + client->disabletransferresumption(NULL); + cout << "transfer resumption disabled" << endl; + } + else + { + client->enabletransferresumption(NULL); + cout << "transfer resumption enabled" << endl; + } + return; + } + break; } cout << "?Invalid command: " << l << endl; @@ -5139,18 +5250,86 @@ void DemoApp::request_response_progress(m_off_t current, m_off_t total) } } -// login result -void DemoApp::login_result(error e) +//2FA disable result +void DemoApp::multifactorauthdisable_result(error e) { - if (e) + if (!e) { - cout << "Login failed: " << errorstring(e) << endl; + cout << "2FA, disabled succesfully..." << endl; + } + else + { + cout << "Error enabling 2FA : " << errorstring(e) << endl; + } + setprompt(COMMAND); +} + +//2FA check result +void DemoApp::multifactorauthcheck_result(int enabled) +{ + if (enabled) + { + cout << "2FA is enabled for this account" << endl; + } + else + { + cout << "2FA is disabled for this account" << endl; + } + setprompt(COMMAND); +} + +//2FA enable result +void DemoApp::multifactorauthsetup_result(string *code, error e) +{ + if (!e) + { + if (!code) + { + cout << "2FA enabled successfully" << endl; + setprompt(COMMAND); + attempts = 0; + } + else + { + cout << "2FA code: " << *code << endl; + setprompt(SETTFA); + } } else + { + cout << "Error enabling 2FA : " << errorstring(e) << endl; + if (e == API_EFAILED) + { + if (++attempts >= 3) + { + attempts = 0; + cout << "Two many attempts"<< endl; + setprompt(COMMAND); + } + else + { + setprompt(SETTFA); + } + } + } +} + +// login result +void DemoApp::login_result(error e) +{ + if (!e) { cout << "Login successful, retrieving account..." << endl; client->fetchnodes(); } + else if (e == API_EMFAREQUIRED) + { + setprompt(LOGINTFA); + } + else + { + cout << "Login failed: " << errorstring(e) << endl; + } } // ephemeral session result diff --git a/examples/megacli.h b/examples/megacli.h index ad75a12ea2..463c07eccd 100644 --- a/examples/megacli.h +++ b/examples/megacli.h @@ -91,6 +91,9 @@ struct DemoApp : public MegaApp void request_response_progress(m_off_t, m_off_t); void login_result(error); + void multifactorauthdisable_result(error); + void multifactorauthsetup_result(string *code, error e); + void multifactorauthcheck_result(int enabled); void ephemeral_result(error); void ephemeral_result(handle, const byte*); diff --git a/examples/megasimplesync.cpp b/examples/megasimplesync.cpp index db69a3ad79..3677a6c756 100644 --- a/examples/megasimplesync.cpp +++ b/examples/megasimplesync.cpp @@ -26,6 +26,9 @@ #endif using namespace mega; +using std::cout; +using std::cerr; +using std::endl; class SyncApp : public MegaApp, public Logger { diff --git a/include/mega.h b/include/mega.h index 6842ac5ac0..a8559c83f0 100644 --- a/include/mega.h +++ b/include/mega.h @@ -85,4 +85,33 @@ #include "mega/gfx/freeimage.h" #include "mega/gfx/GfxProcCG.h" + +#if defined(REQUIRE_HAVE_FFMPEG) && !defined(HAVE_FFMPEG) +#error compilation with HAVE_FFMPEG is required +#endif +#if defined(REQUIRE_HAVE_LIBUV) && !defined(HAVE_LIBUV) +#error compilation with HAVE_LIBUV is required +#endif +#if defined(REQUIRE_HAVE_LIBRAW) && !defined(HAVE_LIBRAW) +#error compilation with HAVE_LIBRAW is required +#endif +#if defined(REQUIRE_ENABLE_CHAT) && !defined(ENABLE_CHAT) +#error compilation with ENABLE_CHAT is required +#endif +#if defined(REQUIRE_ENABLE_BACKUPS) && !defined(ENABLE_BACKUPS) +#error compilation with ENABLE_BACKUPS is required +#endif +#if defined(REQUIRE_ENABLE_WEBRTC) && !defined(ENABLE_WEBRTC) +#error compilation with ENABLE_WEBRTC is required +#endif +#if defined(REQUIRE_ENABLE_EVT_TLS) && !defined(ENABLE_EVT_TLS) +#error compilation with ENABLE_EVT_TLS is required +#endif +#if defined(REQUIRE_USE_MEDIAINFO) && !defined(USE_MEDIAINFO) +#error compilation with USE_MEDIAINFO is required +#endif +#if defined(REQUIRE_USE_PCRE) && !defined(USE_PCRE) +#error compilation with USE_PCRE is required +#endif + #endif diff --git a/include/mega/backofftimer.h b/include/mega/backofftimer.h index 799750dbf8..e6628fdb0f 100644 --- a/include/mega/backofftimer.h +++ b/include/mega/backofftimer.h @@ -70,8 +70,8 @@ class MEGA_API BackoffTimer class MEGA_API TimerWithBackoff: public BackoffTimer { public: - long long tag; - TimerWithBackoff(long long tag); + int tag; + TimerWithBackoff(int tag); }; } // namespace diff --git a/include/mega/command.h b/include/mega/command.h index a24f6c273a..96fdb7faf1 100644 --- a/include/mega/command.h +++ b/include/mega/command.h @@ -64,6 +64,7 @@ class MEGA_API Command void beginarray(const char*); void endarray(); void beginobject(); + void beginobject(const char*); void endobject(); void element(int); void element(handle, int = sizeof(handle)); @@ -110,6 +111,16 @@ class MEGA_API CommandGetFA : public Command CommandGetFA(MegaClient *client, int, handle); }; +class MEGA_API CommandPrelogin : public Command +{ + string email; + +public: + void procresult(); + + CommandPrelogin(MegaClient*, const char*); +}; + class MEGA_API CommandLogin : public Command { bool checksession; @@ -118,17 +129,18 @@ class MEGA_API CommandLogin : public Command public: void procresult(); - CommandLogin(MegaClient*, const char*, uint64_t, const byte* = NULL, int = 0, const char* = NULL); + CommandLogin(MegaClient*, const char*, const byte *, int, const byte* = NULL, int = 0, const char* = NULL); }; class MEGA_API CommandSetMasterKey : public Command { byte newkey[SymmCipher::KEYLENGTH]; + string salt; public: void procresult(); - CommandSetMasterKey(MegaClient*, const byte*, uint64_t, const char* = NULL); + CommandSetMasterKey(MegaClient*, const byte*, const byte *, int, const byte* clientrandomvalue = NULL, const char* = NULL, string* = NULL); }; class MEGA_API CommandCreateEphemeralSession : public Command @@ -168,6 +180,15 @@ class MEGA_API CommandSendSignupLink : public Command CommandSendSignupLink(MegaClient*, const char*, const char*, byte*); }; +class MEGA_API CommandSendSignupLink2 : public Command +{ +public: + void procresult(); + + CommandSendSignupLink2(MegaClient*, const char*, const char*); + CommandSendSignupLink2(MegaClient*, const char*, const char*, byte *, byte*, byte*); +}; + class MEGA_API CommandQuerySignupLink : public Command { string confirmcode; @@ -178,6 +199,14 @@ class MEGA_API CommandQuerySignupLink : public Command CommandQuerySignupLink(MegaClient*, const byte*, unsigned); }; +class MEGA_API CommandConfirmSignupLink2 : public Command +{ +public: + void procresult(); + + CommandConfirmSignupLink2(MegaClient*, const byte*, unsigned); +}; + class MEGA_API CommandConfirmSignupLink : public Command { public: @@ -709,7 +738,7 @@ class MEGA_API CommandConfirmRecoveryLink : public Command public: void procresult(); - CommandConfirmRecoveryLink(MegaClient*, const char*, uint64_t, const byte*, const byte*); + CommandConfirmRecoveryLink(MegaClient*, const char*, const byte*, int, const byte*, const byte*, const byte*); }; class MEGA_API CommandConfirmCancelLink : public Command @@ -743,7 +772,7 @@ class MEGA_API CommandConfirmEmailLink : public Command public: void procresult(); - CommandConfirmEmailLink(MegaClient*, const char*, const char *, uint64_t, bool); + CommandConfirmEmailLink(MegaClient*, const char*, const char *, const byte *, bool); }; class MEGA_API CommandGetVersion : public Command diff --git a/include/mega/crypto/cryptopp.h b/include/mega/crypto/cryptopp.h index 51062f81b0..ef87126c93 100644 --- a/include/mega/crypto/cryptopp.h +++ b/include/mega/crypto/cryptopp.h @@ -39,7 +39,7 @@ #include namespace mega { -using namespace std; +using std::string; /** * @brief A generic pseudo-random number generator. diff --git a/include/mega/crypto/sodium.h b/include/mega/crypto/sodium.h index 52da04c6fa..6020bcd610 100644 --- a/include/mega/crypto/sodium.h +++ b/include/mega/crypto/sodium.h @@ -27,7 +27,6 @@ #include namespace mega { -using namespace std; /** * @brief Asymmetric cryptographic signature using EdDSA with Edwards 25519. diff --git a/include/mega/db.h b/include/mega/db.h index 0ce16f63a6..2f4c12394f 100644 --- a/include/mega/db.h +++ b/include/mega/db.h @@ -73,7 +73,7 @@ class MEGA_API DbTable struct MEGA_API DbAccess { - static const int LEGACY_DB_VERSION = 10; + static const int LEGACY_DB_VERSION = 11; static const int DB_VERSION = LEGACY_DB_VERSION + 1; DbAccess(); diff --git a/include/mega/filesystem.h b/include/mega/filesystem.h index d9a61d5c31..92884445f1 100644 --- a/include/mega/filesystem.h +++ b/include/mega/filesystem.h @@ -87,6 +87,9 @@ struct MEGA_API FileAccess // if the open failed, retry indicates a potentially transient reason bool retry; + //error code related to the last call to fopen() without parameters + int errorcode; + // for files "opened" in nonblocking mode, the current local filename string localname; diff --git a/include/mega/http.h b/include/mega/http.h index a6f8cd8b4e..fb517bda14 100644 --- a/include/mega/http.h +++ b/include/mega/http.h @@ -90,10 +90,10 @@ namespace mega { #define APISSLEXPONENTSIZE "\x03" #define APISSLEXPONENT "\x01\x00\x01" -#define MEGA_DNS_SERVERS "2001:978:2:aa::20:2,154.53.224.130," \ - "2001:978:2:aa::21:2,154.53.224.134," \ - "2403:9800:c020::43,122.56.56.216," \ - "2405:f900:3e6a:1::103,103.244.183.5" +#define MEGA_DNS_SERVERS "2001:678:25c:2215::554,89.44.169.136," \ + "2001:67c:1998:2212::13,31.216.148.13," \ + "2405:f900:3e6a:1::103,103.244.183.5," \ + "2403:9800:c020::43,122.56.56.216" class MEGA_API SpeedController { @@ -166,8 +166,8 @@ struct MEGA_API HttpIO : public EventTrigger // request timeout (ds) static const int REQUESTTIMEOUT; - // request timeout (ds) - static const int WAITREQUESTTIMEOUT; + // sc request timeout (ds) + static const int SCREQUESTTIMEOUT; // connection timeout (ds) static const int CONNECTTIMEOUT; @@ -212,6 +212,7 @@ struct MEGA_API HttpReq string posturl; bool protect; + bool minspeed; bool sslcheckfailed; string sslfakeissuer; diff --git a/include/mega/megaapp.h b/include/mega/megaapp.h index 6bc64fa5d3..1b8e6308af 100644 --- a/include/mega/megaapp.h +++ b/include/mega/megaapp.h @@ -34,6 +34,9 @@ struct MEGA_API MegaApp // request response progress virtual void request_response_progress(m_off_t, m_off_t) { } + // prelogin result + virtual void prelogin_result(int, string*, string*, error) { } + // login result virtual void login_result(error) { } @@ -60,6 +63,7 @@ struct MEGA_API MegaApp const byte*, const byte*, const byte*, size_t) { } virtual void confirmsignuplink_result(error) { } + virtual void confirmsignuplink2_result(handle, const char*, const char*, error) { } virtual void setkeypair_result(error) { } // account credentials, properties and history diff --git a/include/mega/megaclient.h b/include/mega/megaclient.h index 23f2d571a7..455837de13 100644 --- a/include/mega/megaclient.h +++ b/include/mega/megaclient.h @@ -194,15 +194,24 @@ class MEGA_API MegaClient // encrypted master key string k; + // version of the account + int accountversion; + + // salt of the account (for v2 accounts) + string accountsalt; + // timestamp of the creation of the account m_time_t accountsince; - // multi-factor authentication globally enabled + // Global Multi-Factor Authentication enabled bool gmfa_enabled; - // server-side Rubbish-bin autopurging enabled + // Server-Side Rubbish-bin Scheduler enabled (autopurging) bool ssrs_enabled; + // New Secure Registration method enabled + bool nsr_enabled; + #ifdef ENABLE_CHAT // all chats textchat_map chats; @@ -234,13 +243,27 @@ class MEGA_API MegaClient // full account confirmation/creation support void sendsignuplink(const char*, const char*, const byte*); + + string sendsignuplink2(const char*, const char *, const char*); + void resendsignuplink2(const char*, const char *); + void querysignuplink(const byte*, unsigned); void confirmsignuplink(const byte*, unsigned, uint64_t); + void confirmsignuplink2(const byte*, unsigned); void setkeypair(); + // prelogin: e-mail + void prelogin(const char*); + // user login: e-mail, pwkey void login(const char*, const byte*, const char* = NULL); + // user login: e-mail, password, salt + void login2(const char*, const char*, string *, const char* = NULL); + + // user login: e-mail, derivedkey, 2FA pin + void login2(const char*, const byte*, const char* = NULL); + // user login: e-mail, pwkey, emailhash void fastlogin(const char*, const byte*, uint64_t); @@ -292,7 +315,7 @@ class MEGA_API MegaClient error encryptlink(const char* link, const char* pwd, string *encryptedLink); // change login password - error changepw(const byte*, const char *pin = NULL); + error changepw(const char *password, const char *pin = NULL); // load all trees: nodes, shares, contacts void fetchnodes(bool nocache = false); @@ -462,7 +485,7 @@ class MEGA_API MegaClient void gelbrequest(const char*, int, int); // send chat stats - void sendchatstats(const char*); + void sendchatstats(const char*, int port); // send chat logs with user's annonymous id void sendchatlogs(const char*, const char*); @@ -514,6 +537,7 @@ class MEGA_API MegaClient // send event void sendevent(int, const char *); + void sendevent(int, const char *, int tag); // clean rubbish bin void cleanrubbishbin(); @@ -581,6 +605,7 @@ class MEGA_API MegaClient // report an event to the API logger void reportevent(const char*, const char* = NULL); + void reportevent(const char*, const char*, int tag); // set max download speed bool setmaxdownloadspeed(m_off_t bpslimit); @@ -648,6 +673,12 @@ class MEGA_API MegaClient // number of ongoing asynchronous fopen int asyncfopens; + // true if user data is cached + bool cachedug; + + // backoff for the expiration of cached user data + BackoffTimer btugexpiration; + private: BackoffTimer btcs; BackoffTimer btbadhost; @@ -667,7 +698,6 @@ class MEGA_API MegaClient // notify URL for new server-client commands string scnotifyurl; - dstime scnotifyurlts; // unique request ID char reqid[10]; @@ -944,6 +974,9 @@ class MEGA_API MegaClient // default number of seconds to wait after a bandwidth overquota static dstime DEFAULT_BW_OVERQUOTA_BACKOFF_SECS; + // number of seconds to invalidate the cached user data + static dstime USER_DATA_EXPIRATION_BACKOFF_SECS; + // initial state load in progress? bool fetchingnodes; int fetchnodestag; @@ -1103,6 +1136,10 @@ class MEGA_API MegaClient // process localnode subtree void proclocaltree(LocalNode*, LocalTreeProc*); + + // unlink the LocalNode from the corresponding node + // if the associated local file or folder still exists + void unlinkifexists(LocalNode*, FileAccess*, string*); #endif // recursively cancel transfers in a subtree @@ -1237,7 +1274,7 @@ class MEGA_API MegaClient User* finduser(handle, int = 0); User* ownuser(); void mapuser(handle, const char*); - void discarduser(handle); + void discarduser(handle, bool = true); void discarduser(const char*); void mappcr(handle, PendingContactRequest*); bool discardnotifieduser(User *); @@ -1295,7 +1332,7 @@ class MEGA_API MegaClient void getprivatekey(const char *code); // confirm a recovery link to restore the account - void confirmrecoverylink(const char *code, const char *email, const byte *pwkey, const byte *masterkey = NULL); + void confirmrecoverylink(const char *code, const char *email, const char *password, const byte *masterkey = NULL, int accountversion = 1); // request a link to cancel the account void getcancellink(const char *email, const char* = NULL); @@ -1341,9 +1378,20 @@ class MEGA_API MegaClient // true if user has disabled fileversioning bool versions_disabled; + // the SDK is trying to log out + int loggingout; + MegaClient(MegaApp*, Waiter*, HttpIO*, FileSystemAccess*, DbAccess*, GfxProc*, const char*, const char*); ~MegaClient(); }; } // namespace +#if __cplusplus < 201100L +#define char_is_not_digit std::not1(std::ptr_fun(static_cast(std::isdigit))) +#define char_is_not_space std::not1(std::ptr_fun(std::isspace)) +#else +#define char_is_not_digit [](char c) { return !std::isdigit(c); } +#define char_is_not_space [](char c) { return !std::isspace(c); } +#endif + #endif diff --git a/include/mega/node.h b/include/mega/node.h index 0c9e10a809..a478272b71 100644 --- a/include/mega/node.h +++ b/include/mega/node.h @@ -118,6 +118,9 @@ struct MEGA_API Node : public NodeCore, FileFingerprint // display name (UTF-8) const char* displayname() const; + // display path from its root in the cloud (UTF-8) + string displaypath() const; + // node attributes AttrMap attrs; @@ -316,6 +319,7 @@ struct MEGA_API LocalNode : public File void setnameparent(LocalNode*, string*); + LocalNode(); void init(Sync*, nodetype_t, LocalNode*, string*); virtual bool serialize(string*); diff --git a/include/mega/sync.h b/include/mega/sync.h index c0c4f884b6..1dbe129b28 100644 --- a/include/mega/sync.h +++ b/include/mega/sync.h @@ -34,7 +34,11 @@ class MEGA_API Sync MegaClient* client; // sync-wide directory notification provider +#if __cplusplus >= 201100L + std::unique_ptr dirnotify; +#else std::auto_ptr dirnotify; +#endif // root of local filesystem tree, holding the sync's root folder LocalNode localroot; diff --git a/include/mega/thread.h b/include/mega/thread.h index bd188f71f1..85f399ec7c 100644 --- a/include/mega/thread.h +++ b/include/mega/thread.h @@ -41,6 +41,15 @@ class Mutex virtual void unlock() = 0; }; +class MutexGuard +{ + Mutex& m; +public: + MutexGuard(Mutex& cm) : m(cm) { m.lock(); } + ~MutexGuard() { m.unlock(); } +}; + + class Semaphore { public: diff --git a/include/mega/transfer.h b/include/mega/transfer.h index a717bb54eb..31ed4b61aa 100644 --- a/include/mega/transfer.h +++ b/include/mega/transfer.h @@ -116,8 +116,8 @@ struct MEGA_API Transfer : public FileFingerprint // transfer state bool finished; - // cached temp URL for upload/download data - string cachedtempurl; + // temp URL for upload/download data. It can be cached. For uploads, a new url means any previously uploaded data is abandoned. + string tempurl; // context of the async fopen operation AsyncIOContext* asyncopencontext; diff --git a/include/mega/transferslot.h b/include/mega/transferslot.h index e07230f1f3..1158d8c9dc 100644 --- a/include/mega/transferslot.h +++ b/include/mega/transferslot.h @@ -48,10 +48,18 @@ struct MEGA_API TransferSlot // max request size for downloads and uploads static const m_off_t MAX_REQ_SIZE; + + // max allowed difference between the next chunk and the first unfinished chunk + static const m_off_t MAX_UPLOAD_GAP; + + bool delayedchunk; + m_off_t maxRequestSize; m_off_t progressreported; + m_off_t progresscontiguous; + m_time_t lastprogressreport; dstime starttime, lastdata; @@ -71,9 +79,6 @@ struct MEGA_API TransferSlot // file attributes mutable int fileattrsmutable; - // storage server access URL - string tempurl; - // maximum number of parallel connections and connection aray int connections; HttpReqXfer** reqs; @@ -90,6 +95,9 @@ struct MEGA_API TransferSlot // indicate progress void progress(); + // update the contiguous progress + void updatecontiguousprogress(); + // compute the meta MAC based on the chunk MACs int64_t macsmac(chunkmac_map*); diff --git a/include/mega/treeproc.h b/include/mega/treeproc.h index a4b278974b..d48ed445f0 100644 --- a/include/mega/treeproc.h +++ b/include/mega/treeproc.h @@ -123,6 +123,12 @@ class MEGA_API LocalTreeProcUpdateTransfers : public LocalTreeProc void proc(MegaClient*, LocalNode*); }; +class MEGA_API LocalTreeProcUnlinkNodes : public LocalTreeProc +{ +public: + void proc(MegaClient*, LocalNode*); +}; + #endif } // namespace diff --git a/include/mega/types.h b/include/mega/types.h index 5ccdab6696..e08561d4b4 100644 --- a/include/mega/types.h +++ b/include/mega/types.h @@ -45,17 +45,24 @@ typedef char __static_check_01__[sizeof(bool) == sizeof(char) ? 1 : -1]; #include "mega/posix/megasys.h" #endif +#ifdef USE_CRYPTOPP +#include // so we can test CRYPTO_VERSION below +#endif + // signed 64-bit generic offset typedef int64_t m_off_t; // opaque filesystem fingerprint typedef uint64_t fsfp_t; -#if USE_CRYPTOPP && (CRYPTOPP_VERSION >= 600) && (__cplusplus >= 201103L) - using byte = CryptoPP::byte; -#else - typedef unsigned char byte; +namespace mega { +// within ::mega namespace, byte is unsigned char (avoids ambiguity when std::byte from c++17 and perhaps other defined ::byte are available) +#if defined(USE_CRYPTOPP) && (CRYPTOPP_VERSION >= 600) && (__cplusplus >= 201103L) +using byte = CryptoPP::byte; +#elif __RPCNDR_H_VERSION__ != 500 +typedef unsigned char byte; #endif +} #ifdef USE_CRYPTOPP #include "mega/crypto/cryptopp.h" @@ -65,8 +72,24 @@ typedef uint64_t fsfp_t; #include "mega/crypto/sodium.h" +#include + namespace mega { -using namespace std; + +// import these select types into the namespace directly, to avoid adding std::byte from c++17 +using std::string; +using std::map; +using std::set; +using std::list; +using std::vector; +using std::pair; +using std::multimap; +using std::deque; +using std::multiset; +using std::queue; +using std::streambuf; +using std::ostringstream; + // forward declaration struct AttrMap; diff --git a/include/mega/version.h b/include/mega/version.h index 74884dad75..29926c05a2 100644 --- a/include/mega/version.h +++ b/include/mega/version.h @@ -2,8 +2,8 @@ #define MEGA_MAJOR_VERSION 3 #endif #ifndef MEGA_MINOR_VERSION -#define MEGA_MINOR_VERSION 3 +#define MEGA_MINOR_VERSION 4 #endif #ifndef MEGA_MICRO_VERSION -#define MEGA_MICRO_VERSION 8 +#define MEGA_MICRO_VERSION 1 #endif diff --git a/include/mega/win32/autocomplete.h b/include/mega/win32/autocomplete.h index c85597b73e..cdaef9cd8b 100644 --- a/include/mega/win32/autocomplete.h +++ b/include/mega/win32/autocomplete.h @@ -65,6 +65,7 @@ namespace autocomplete { quoted_word(const std::string &, const quoting&); std::string s; quoting q; + string getQuoted(); }; std::vector words; @@ -205,10 +206,11 @@ namespace autocomplete { size_t unixListCount = 0; unsigned calcUnixColumnWidthInGlyphs(int col, int rows); const string& unixColumnEntry(int row, int col, int rows); + void tidyCompletions(); }; // helper function - useful in megacli for now - ACState prepACState(const std::string line, size_t insertPos, ACN syntax, bool unixStyle); + ACState prepACState(const std::string line, size_t insertPos, bool unixStyle); // get a list of possible strings at the current cursor position CompletionState autoComplete(const std::string line, size_t insertPos, ACN syntax, bool unixStyle); @@ -218,7 +220,7 @@ namespace autocomplete { void applyCompletion(CompletionState& s, bool forwards, unsigned consoleWidth, CompletionTextOut& consoleOutput); // execute the function attached to the matching syntax - void autoExec(const std::string line, size_t insertPos, ACN syntax, bool unixStyle, string& consoleOutput); + bool autoExec(const std::string line, size_t insertPos, ACN syntax, bool unixStyle, string& consoleOutput, bool reportNoMatch); // functions to bulid command descriptions ACN either(ACN n1 = nullptr, ACN n2 = nullptr, ACN n3 = nullptr, ACN n4 = nullptr); diff --git a/include/mega/win32/megaconsole.h b/include/mega/win32/megaconsole.h index 6957423520..505b6ca531 100644 --- a/include/mega/win32/megaconsole.h +++ b/include/mega/win32/megaconsole.h @@ -49,6 +49,9 @@ struct MEGA_API ConsoleModel // If using autocomplete, client to specify the syntax of commands here so we know what. Assign to this directly ::mega::autocomplete::ACN autocompleteSyntax; + + // If supplied, autocomplete will try to get additional completions from this function (eg for consulting MEGAcmd's server) + std::function(string)> autocompleteFunction; // a buffer to store characters received from keypresses. After a newline is received, we // don't check for keypresses anymore until that line is consumed. @@ -95,6 +98,7 @@ struct MEGA_API ConsoleModel // client can check this after adding characters or performing actions to see if the user submitted the line for processing bool checkForCompletedInputLine(std::wstring& ws); + std::wstring getInputLineToCursor(); private: autocomplete::CompletionState autocompleteState; @@ -127,9 +131,12 @@ struct MEGA_API WinConsole : public Console bool setShellConsole(UINT codepage = CP_UTF8, UINT failover_codepage = CP_UTF8); void getShellCodepages(UINT& codepage, UINT& failover_codepage); void setAutocompleteSyntax(autocomplete::ACN); + void setAutocompleteFunction(std::function(string)>); void setAutocompleteStyle(bool unix); bool getAutocompleteStyle() const; bool consolePeek(); + bool consolePeekNonBlocking(); + bool consolePeekBlocking(); bool consoleGetch(wchar_t& c); void updateInputPrompt(const std::string& newprompt); char* checkForCompletedInputLine(); @@ -142,9 +149,10 @@ struct MEGA_API WinConsole : public Console static std::string toUtf8String(const std::wstring& ws, UINT codepage = CP_UTF8); static std::wstring toUtf16String(const std::string& s, UINT codepage = CP_UTF8); - + bool blockingConsolePeek; private: + std::deque irs; HANDLE hInput; HANDLE hOutput; bool promptRetracted = false; diff --git a/include/megaapi.h b/include/megaapi.h index 2e99f18b62..8ecd9c37d7 100644 --- a/include/megaapi.h +++ b/include/megaapi.h @@ -516,6 +516,34 @@ class MegaNode * @return The number of seconds, or -1 if this attribute is not set. */ virtual int getDuration(); + + /** + * @brief Get the attribute of the node representing its width. + * + * @return The number of pixels for width, or -1 if this attribute is not set. + */ + virtual int getWidth(); + + /** + * @brief Get the attribute of the node representing its height. + * + * @return The number of pixels for height, or -1 if this attribute is not set. + */ + virtual int getHeight(); + + /** + * @brief Get the attribute of the node representing its shortformat. + * + * @return The shortformat, or -1 if this attribute is not set. + */ + virtual int getShortformat(); + + /** + * @brief Get the attribute of the node representing its videocodecid. + * + * @return The videocodecid, or -1 if this attribute is not set. + */ + virtual int getVideocodecid(); /** * @brief Get the attribute of the node representing the latitude. @@ -2239,7 +2267,6 @@ class MegaRequest * * This value is valid for these requests: * - MegaApi::createAccount - Returns the name or the firstname of the user - * - MegaApi::fastCreateAccount - Returns the name of the user * - MegaApi::createFolder - Returns the name of the new folder * - MegaApi::renameNode - Returns the new name for the node * @@ -2265,7 +2292,6 @@ class MegaRequest * - MegaApi::fastLogin - Returns the email of the account * - MegaApi::loginToFolder - Returns the string "FOLDER" * - MegaApi::createAccount - Returns the email for the account - * - MegaApi::fastCreateAccount - Returns the email for the account * - MegaApi::sendFileToUser - Returns the email of the user that receives the node * - MegaApi::share - Returns the email that receives the shared folder * - MegaApi::getUserAvatar - Returns the email of the user to get the avatar @@ -2330,7 +2356,6 @@ class MegaRequest * * This value is valid for these requests: * - MegaApi::fastLogin - Returns the base64pwKey parameter - * - MegaApi::fastCreateAccount - Returns the base64pwKey parameter * - MegaApi::fastConfirmAccount - Returns the base64pwKey parameter * * This value is valid for these request in onRequestFinish when the @@ -2487,6 +2512,7 @@ class MegaRequest * - MegaApi::abortCurrentBackup - Returns the tag of the aborted backup * - MegaApi::removeBackup - Returns the tag of the deleted backup * - MegaApi::startTimer - Returns the selected period + * - MegaApi::sendChatStats - Returns the connection port * * This value is valid for these request in onRequestFinish when the * error code is MegaError::API_OK: @@ -5512,23 +5538,6 @@ class MegaApi */ MegaUserList *getCurrentUsers(); - /** - * @brief Generates a private key based on the access password - * - * This is a time consuming operation (specially for low-end mobile devices). Since the resulting key is - * required to log in, this function allows to do this step in a separate function. You should run this function - * in a background thread, to prevent UI hangs. The resulting key can be used in MegaApi::fastLogin - * - * You take the ownership of the returned value. - * - * @param password Access password - * @return Base64-encoded private key - * - * @deprecated The registration and login procedure will be changed soon. When that happens, this function will stop being - * compatible and will be deleted, so please stop using it as soon as possible. - */ - char* getBase64PwKey(const char *password); - /** * @brief Generates a hash based in the provided private key and email * @@ -5538,9 +5547,12 @@ class MegaApi * * You take the ownership of the returned value. * - * @param base64pwkey Private key returned by MegaApi::getBase64PwKey + * @param base64pwkey Private key returned by MegaRequest::getPrivateKey in the onRequestFinish callback of createAccount * @param email Email to create the hash * @return Base64-encoded hash + * + * @deprecated This function is only useful for old accounts. Once enabled the new registration logic, + * this function will return an empty string for new accounts and will be removed few time after. */ char* getStringHash(const char* base64pwkey, const char* email); @@ -5876,8 +5888,11 @@ class MegaApi * * @param email Email of the user * @param stringHash Hash of the email returned by MegaApi::getStringHash - * @param base64pwkey Private key calculated using MegaApi::getBase64PwKey + * @param base64pwkey Private key returned by MegaRequest::getPrivateKey in the onRequestFinish callback of createAccount * @param listener MegaRequestListener to track this request + * + * @deprecated The parameter stringHash is no longer for new accounts so this function will be replaced by another + * one soon. Please use MegaApi::login (with email and password) or MegaApi::fastLogin (with session) instead when possible. */ void fastLogin(const char* email, const char *stringHash, const char *base64pwkey, MegaRequestListener *listener = NULL); @@ -6120,45 +6135,18 @@ class MegaApi */ void resumeCreateAccount(const char* sid, MegaRequestListener *listener = NULL); - /** - * @brief Initialize the creation of a new MEGA account with precomputed keys - * - * The associated request type with this request is MegaRequest::TYPE_CREATE_ACCOUNT. - * Valid data in the MegaRequest object received on callbacks: - * - MegaRequest::getEmail - Returns the email for the account - * - MegaRequest::getPrivateKey - Returns the private key calculated with MegaApi::getBase64PwKey - * - MegaRequest::getName - Returns the name of the user - * - * Valid data in the MegaRequest object received in onRequestFinish when the error code - * is MegaError::API_OK: - * - MegaRequest::getSessionKey - Returns the session id to resume the process - * - * If this request succeeds, a new ephemeral session will be created for the new user - * and a confirmation email will be sent to the specified email address. The app may - * resume the create-account process by using MegaApi::resumeCreateAccount. - * - * If an account with the same email already exists, you will get the error code - * MegaError::API_EEXIST in onRequestFinish - * - * @param email Email for the account - * @param base64pwkey Private key calculated with MegaApi::getBase64PwKey - * @param name Name of the user - * @param listener MegaRequestListener to track this request - * - * @deprecated This function is deprecated and will eventually be removed. Instead, - * use the new version of MegaApi::createAccount with firstname and lastname. - */ - void fastCreateAccount(const char* email, const char *base64pwkey, const char* name, MegaRequestListener *listener = NULL); - /** * @brief Sends the confirmation email for a new account * * This function is useful to send the confirmation link again or to send it to a different - * email address, in case the user mistyped the email at the registration form. + * email address, in case the user mistyped the email at the registration form. It can only + * be used after a successful call to MegaApi::createAccount or MegaApi::resumeCreateAccount. + * + * The associated request type with this request is MegaRequest::TYPE_SEND_SIGNUP_LINK. * * @param email Email for the account * @param name Firstname of the user - * @param password Password for the account + * @param password Password for the account * @param listener MegaRequestListener to track this request */ void sendSignupLink(const char* email, const char *name, const char *password, MegaRequestListener *listener = NULL); @@ -6171,8 +6159,11 @@ class MegaApi * * @param email Email for the account * @param name Firstname of the user - * @param base64pwkey Private key calculated with MegaApi::getBase64PwKey + * @param base64pwkey Private key returned by MegaRequest::getPrivateKey in the onRequestFinish callback of createAccount * @param listener MegaRequestListener to track this request + * + * @deprecated This function only works using the old registration method and will be removed soon. + * Please use MegaApi::sendSignupLink (with email and password) instead. */ void fastSendSignupLink(const char* email, const char *base64pwkey, const char *name, MegaRequestListener *listener = NULL); @@ -6187,6 +6178,12 @@ class MegaApi * is MegaError::API_OK: * - MegaRequest::getEmail - Return the email associated with the link * - MegaRequest::getName - Returns the name associated with the link (available only for confirmation links) + * - MegaRequest::getFlag - Returns true if the account was automatically confirmed, otherwise false + * + * If MegaRequest::getFlag returns true, the account was automatically confirmed and it's not needed + * to call MegaApi::confirmAccount. If it returns false, it's needed to call MegaApi::confirmAccount + * as usual. New accounts do not require a confirmation with the password, but old confirmation links + * require it, so it's needed to check that parameter in onRequestFinish to know how to proceed. * * @param link Confirmation link (#confirm) or new signup link (#newsignup) * @param listener MegaRequestListener to track this request @@ -6238,6 +6235,9 @@ class MegaApi * @param link Confirmation link * @param base64pwkey Private key precomputed with MegaApi::getBase64PwKey * @param listener MegaRequestListener to track this request + * + * @deprecated This function only works using the old registration method and will be removed soon. + * Please use MegaApi::confirmAccount instead. */ void fastConfirmAccount(const char* link, const char *base64pwkey, MegaRequestListener *listener = NULL); @@ -6335,6 +6335,8 @@ class MegaApi /** * @brief Effectively parks the user's account without creating a new fresh account. * + * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). + * * The contents of the account will then be purged after 60 days. Once the account is * parked, the user needs to contact MEGA support to restore the account. * @@ -6371,8 +6373,6 @@ class MegaApi /** * @brief Get information about a change-email link created by MegaApi::changeEmail. * - * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). - * * The associated request type with this request is MegaRequest::TYPE_QUERY_RECOVERY_LINK * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the change-email link @@ -6389,6 +6389,8 @@ class MegaApi /** * @brief Effectively changes the email address associated to the account. * + * If no user is logged in, you will get the error code MegaError::API_EACCESS in onRequestFinish(). + * * The associated request type with this request is MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK. * Valid data in the MegaRequest object received on all callbacks: * - MegaRequest::getLink - Returns the change-email link @@ -7887,6 +7889,24 @@ class MegaApi */ void shouldShowPasswordReminderDialog(bool atLogout, MegaRequestListener *listener = NULL); + /** + * @brief Check if the master key has been exported + * + * The associated request type with this request is MegaRequest::TYPE_GET_ATTR_USER + * Valid data in the MegaRequest object received on callbacks: + * - MegaRequest::getParamType - Returns the attribute type MegaApi::USER_ATTR_PWD_REMINDER + * + * Valid data in the MegaRequest object received in onRequestFinish when the error code + * is MegaError::API_OK: + * - MegaRequest::getAccess - Returns true if the master key has been exported + * + * If the corresponding user attribute is not set yet, the request will fail with the + * error code MegaError::API_ENOENT. + * + * @param listener MegaRequestListener to track this request + */ + void isMasterKeyExported(MegaRequestListener *listener = NULL); + /** * @brief Enable or disable the generation of rich previews * @@ -8123,6 +8143,11 @@ class MegaApi * * @deprecated This function is for internal usage of MEGA apps for debug purposes. This info * is sent to MEGA servers. + * + * @note Event types are restricted to the following ranges: + * - MEGAchat: [99000, 99150) + * - MEGA SDK: [99400, 99500) + * - MEGAsync: [99500, 99600) */ void sendEvent(int eventType, const char* message, MegaRequestListener *listener = NULL); @@ -12081,6 +12106,7 @@ class MegaApi * Valid data in the MegaRequest object received on callbacks: * - MegaRequest::getName - Returns the data provided. * - MegaRequest::getParamType - Returns number 1 + * - MegaRequest::getNumber - Returns the connection port * * Valid data in the MegaRequest object received in onRequestFinish when the error code * is MegaError::API_OK: @@ -12089,9 +12115,10 @@ class MegaApi * - MegaRequest::getTotalBytes - Returns the number of bytes in the response * * @param data JSON data to send to the stats server + * @param port Server port to connect * @param listener MegaRequestListener to track this request */ - void sendChatStats(const char *data, MegaRequestListener *listener = NULL); + void sendChatStats(const char *data, int port = 0, MegaRequestListener *listener = NULL); /** * @brief Send logs related to MEGAchat to the logs server diff --git a/include/megaapi_impl.h b/include/megaapi_impl.h index d9563c83f1..5fbbe598ba 100644 --- a/include/megaapi_impl.h +++ b/include/megaapi_impl.h @@ -402,6 +402,10 @@ class MegaNodePrivate : public MegaNode, public Cachable MegaStringList *getCustomAttrNames(); virtual const char *getCustomAttr(const char* attrName); virtual int getDuration(); + virtual int getWidth(); + virtual int getHeight(); + virtual int getShortformat(); + virtual int getVideocodecid(); virtual double getLatitude(); virtual double getLongitude(); virtual char *getBase64Handle(); @@ -488,6 +492,10 @@ class MegaNodePrivate : public MegaNode, public Cachable PublicLink *plink; std::string *sharekey; // for plinks of folders int duration; + int width; + int height; + int shortformat; + int videocodecid; double latitude; double longitude; MegaNodeList *children; @@ -1640,7 +1648,6 @@ class MegaApiImpl : public MegaApp MegaUserList *getCurrentUsers(); //Utils - char *getBase64PwKey(const char *password); long long getSDKtime(); char *getStringHash(const char* base64pwkey, const char* inBuf); void getSessionTransferURL(const char *path, MegaRequestListener *listener); @@ -1686,7 +1693,6 @@ class MegaApiImpl : public MegaApp void queryTransferQuota(long long size, MegaRequestListener *listener = NULL); void createAccount(const char* email, const char* password, const char* name, MegaRequestListener *listener = NULL); void createAccount(const char* email, const char* password, const char* firstname, const char* lastname, MegaRequestListener *listener = NULL); - void fastCreateAccount(const char* email, const char *base64pwkey, const char* name, MegaRequestListener *listener = NULL); void resumeCreateAccount(const char* sid, MegaRequestListener *listener = NULL); void sendSignupLink(const char* email, const char *name, const char *password, MegaRequestListener *listener = NULL); void fastSendSignupLink(const char *email, const char *base64pwkey, const char *name, MegaRequestListener *listener = NULL); @@ -2128,7 +2134,7 @@ class MegaApiImpl : public MegaApp void setChatTitle(MegaHandle chatid, const char *title, MegaRequestListener *listener = NULL); void getChatPresenceURL(MegaRequestListener *listener = NULL); void registerPushNotification(int deviceType, const char *token, MegaRequestListener *listener = NULL); - void sendChatStats(const char *data, MegaRequestListener *listener = NULL); + void sendChatStats(const char *data, int port, MegaRequestListener *listener = NULL); void sendChatLogs(const char *data, const char *aid, MegaRequestListener *listener = NULL); MegaTextChatList *getChatList(); MegaHandleList *getAttachmentAccess(MegaHandle chatid, MegaHandle h); @@ -2285,6 +2291,7 @@ class MegaApiImpl : public MegaApp virtual void request_response_progress(m_off_t, m_off_t); // login result + virtual void prelogin_result(int, string*, string*, error); virtual void login_result(error); virtual void logout_result(error); virtual void userdata_result(string*, string*, string*, handle, error); @@ -2315,6 +2322,7 @@ class MegaApiImpl : public MegaApp virtual void querysignuplink_result(error); virtual void querysignuplink_result(handle, const char*, const char*, const byte*, const byte*, const byte*, size_t); virtual void confirmsignuplink_result(error); + virtual void confirmsignuplink2_result(handle, const char*, const char*, error); virtual void setkeypair_result(error); // account credentials, properties and history @@ -2420,7 +2428,6 @@ class MegaApiImpl : public MegaApp virtual void getprivatekey_result(error, const byte *privk = NULL, const size_t len_privk = 0); virtual void confirmrecoverylink_result(error); virtual void confirmcancellink_result(error); - virtual void validatepassword_result(error); virtual void getemaillink_result(error); virtual void confirmemaillink_result(error); virtual void getversion_result(int, const char*, error); diff --git a/src/backofftimer.cpp b/src/backofftimer.cpp index a62ab8fde7..50a2b699c5 100644 --- a/src/backofftimer.cpp +++ b/src/backofftimer.cpp @@ -129,7 +129,7 @@ void BackoffTimer::update(dstime* waituntil) } } -TimerWithBackoff::TimerWithBackoff(long long tag) +TimerWithBackoff::TimerWithBackoff(int tag) { this->tag = tag; } diff --git a/src/command.cpp b/src/command.cpp index edb4006652..1e5db4d98b 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -149,6 +149,14 @@ void Command::beginobject() json.append("{"); } +void Command::beginobject(const char *name) +{ + addcomma(); + json.append("\""); + json.append(name); + json.append("\":{"); +} + // end JSON object void Command::endobject() { diff --git a/src/commands.cpp b/src/commands.cpp index b7cd381f3d..b283d70bf7 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -128,7 +128,7 @@ void HttpReqCommandPutFA::procresult() Node::copystring(&posturl, p); progressreported = 0; HttpReq::type = REQ_BINARY; - post(client, data->data(), data->size()); + post(client, data->data(), unsigned(data->size())); } return; @@ -359,13 +359,13 @@ void CommandPutFile::procresult() switch (client->json.getnameid()) { case 'p': - client->json.storeobject(canceled ? NULL : &tslot->tempurl); + client->json.storeobject(canceled ? NULL : &tslot->transfer->tempurl); break; case EOO: if (canceled) return; - if (tslot->tempurl.size()) + if (tslot->transfer->tempurl.size()) { tslot->starttime = tslot->lastdata = client->waiter->ds; return tslot->progress(); @@ -567,7 +567,7 @@ void CommandGetFile::procresult() switch (client->json.getnameid()) { case 'g': - client->json.storeobject(tslot ? &tslot->tempurl : NULL); + client->json.storeobject(tslot ? &tslot->transfer->tempurl : NULL); e = API_OK; break; @@ -643,7 +643,7 @@ void CommandGetFile::procresult() key.setkey(filekey, FILENODE); if ((buf = Node::decryptattr(tslot ? tslot->transfer->transfercipher() : &key, - at, eos ? eos - at : strlen(at)))) + at, int(eos ? eos - at : strlen(at))))) { JSON json; @@ -712,7 +712,7 @@ void CommandGetFile::procresult() tslot->starttime = tslot->lastdata = client->waiter->ds; - if (tslot->tempurl.size() && s >= 0) + if (tslot->transfer->tempurl.size() && s >= 0) { return tslot->progress(); } @@ -790,10 +790,10 @@ CommandSetAttr::CommandSetAttr(MegaClient* client, Node* n, SymmCipher* cipher, string at; n->attrs.getjson(&at); - client->makeattr(cipher, &at, at.c_str(), at.size()); + client->makeattr(cipher, &at, at.c_str(), int(at.size())); arg("n", (byte*)&n->nodehandle, MegaClient::NODEHANDLE); - arg("at", (byte*)at.c_str(), at.size()); + arg("at", (byte*)at.c_str(), int(at.size())); h = n->nodehandle; tag = client->reqtag; @@ -917,16 +917,16 @@ CommandPutNodes::CommandPutNodes(MegaClient* client, handle th, } arg("t", nn[i].type); - arg("a", (byte*)nn[i].attrstring->data(), nn[i].attrstring->size()); + arg("a", (byte*)nn[i].attrstring->data(), int(nn[i].attrstring->size())); if (nn[i].nodekey.size() <= sizeof key) { - client->key.ecb_encrypt((byte*)nn[i].nodekey.data(), key, nn[i].nodekey.size()); - arg("k", key, nn[i].nodekey.size()); + client->key.ecb_encrypt((byte*)nn[i].nodekey.data(), key, unsigned(nn[i].nodekey.size())); + arg("k", key, int(nn[i].nodekey.size())); } else { - arg("k", (const byte*)nn[i].nodekey.data(), nn[i].nodekey.size()); + arg("k", (const byte*)nn[i].nodekey.data(), int(nn[i].nodekey.size())); } endobject(); @@ -1421,6 +1421,7 @@ void CommandLogout::procresult() { error e = (error)client->json.getint(); MegaApp *app = client->app; + client->loggingout--; if(!e) { client->removecaches(); @@ -1429,8 +1430,65 @@ void CommandLogout::procresult() app->logout_result(e); } +CommandPrelogin::CommandPrelogin(MegaClient* client, const char* email) +{ + cmd("us0"); + arg("user", email); + + this->email = email; + tag = client->reqtag; +} + +void CommandPrelogin::procresult() +{ + if (client->json.isnumeric()) + { + return client->app->prelogin_result(0, NULL, NULL, (error)client->json.getint()); + } + + int v = 0; + string salt; + for (;;) + { + switch (client->json.getnameid()) + { + case 'v': + v = int(client->json.getint()); + break; + case 's': + client->json.storeobject(&salt); + break; + case EOO: + if (v == 0) + { + LOG_err << "No version returned"; + return client->app->prelogin_result(0, NULL, NULL, API_EINTERNAL); + } + else if (v > 2) + { + LOG_err << "Version of account not supported"; + return client->app->prelogin_result(0, NULL, NULL, API_EINTERNAL); + } + else if (v == 2 && !salt.size()) + { + LOG_err << "No salt returned"; + return client->app->prelogin_result(0, NULL, NULL, API_EINTERNAL); + } + client->accountversion = v; + Base64::atob(salt, client->accountsalt); + client->app->prelogin_result(v, &email, &salt, API_OK); + return; + default: + if (!client->json.storeobject()) + { + return client->app->prelogin_result(0, NULL, NULL, API_EINTERNAL); + } + } + } +} + // login request with user e-mail address and user hash -CommandLogin::CommandLogin(MegaClient* client, const char* email, uint64_t emailhash, const byte *sessionkey, int csessionversion, const char *pin) +CommandLogin::CommandLogin(MegaClient* client, const char* email, const byte *emailhash, int emailhashsize, const byte *sessionkey, int csessionversion, const char *pin) { cmd("us"); @@ -1441,7 +1499,7 @@ CommandLogin::CommandLogin(MegaClient* client, const char* email, uint64_t email if (!checksession) { arg("user", email); - arg("uh", (byte*)&emailhash, sizeof emailhash); + arg("uh", emailhash, emailhashsize); if (pin) { arg("mfa", pin); @@ -1472,7 +1530,7 @@ CommandLogin::CommandLogin(MegaClient* client, const char* email, uint64_t email client->fsaccess->statsid(&id); if (id.size()) { - int len = id.size() + 1; + size_t len = id.size() + 1; char *buff = new char[len]; memcpy(buff, id.c_str(), len); MegaClient::statsid = buff; @@ -1487,9 +1545,9 @@ CommandLogin::CommandLogin(MegaClient* client, const char* email, uint64_t email { string hash; HashSHA256 hasher; - hasher.add((const byte*)id.data(), id.size()); + hasher.add((const byte*)id.data(), unsigned(id.size())); hasher.get(&hash); - arg("si", (const byte*)hash.data(), hash.size()); + arg("si", (const byte*)hash.data(), int(hash.size())); } tag = client->reqtag; @@ -1626,16 +1684,27 @@ void CommandLogin::procresult() // account has RSA keypair: decrypt server-provided session ID if (len_privk < 256) { - return client->app->login_result(API_EINTERNAL); + if (!checksession) + { + return client->app->login_result(API_EINTERNAL); + } + else + { + // logging in with tsid to an account without a RSA keypair + LOG_info << "Generating and adding missing RSA keypair"; + client->setkeypair(); + } } - - // decrypt and set private key - client->key.ecb_decrypt(privkbuf, len_privk); - - if (!client->asymkey.setkey(AsymmCipher::PRIVKEY, privkbuf, len_privk)) + else { - LOG_warn << "Error checking private key"; - return client->app->login_result(API_ENOENT); + // decrypt and set private key + client->key.ecb_decrypt(privkbuf, len_privk); + + if (!client->asymkey.setkey(AsymmCipher::PRIVKEY, privkbuf, len_privk)) + { + LOG_warn << "Error checking private key"; + return client->app->login_result(API_ENOENT); + } } if (!checksession) @@ -1694,7 +1763,7 @@ CommandShareKeyUpdate::CommandShareKeyUpdate(MegaClient* client, handle_vector* cmd("k"); beginarray("sr"); - for (int i = v->size(); i--;) + for (size_t i = v->size(); i--;) { handle h = (*v)[i]; @@ -2368,7 +2437,7 @@ CommandPutMultipleUAVer::CommandPutMultipleUAVer(MegaClient *client, const usera beginarray(User::attr2string(type).c_str()); - element((const byte *) it->second.data(), it->second.size()); + element((const byte *) it->second.data(), int(it->second.size())); const string *attrv = client->ownuser()->getattrversion(type); if (attrv) @@ -2713,7 +2782,7 @@ void CommandGetUA::procresult() // convert from ASCII to binary the received data value.resize(buf.size() / 4 * 3 + 3); - value.resize(Base64::atob(buf.data(), (byte *)value.data(), value.size())); + value.resize(Base64::atob(buf.data(), (byte *)value.data(), int(value.size()))); // Some attributes don't keep historic records, ie. *!authring or *!lstint // (none of those attributes are used by the SDK yet) @@ -2724,7 +2793,7 @@ void CommandGetUA::procresult() if (!u) // retrieval of attributes without contact-relationship { - client->app->getua_result((byte*) value.data(), value.size()); + client->app->getua_result((byte*) value.data(), unsigned(value.size())); return; } @@ -2753,7 +2822,7 @@ void CommandGetUA::procresult() case '+': // public u->setattr(at, &value, &version); - client->app->getua_result((byte*) value.data(), value.size()); + client->app->getua_result((byte*) value.data(), unsigned(value.size())); #ifdef ENABLE_CHAT if (client->fetchingkeys && at == ATTR_SIG_RSA_PUBK && u && u->userhandle == client->me) { @@ -2765,14 +2834,14 @@ void CommandGetUA::procresult() case '#': // protected u->setattr(at, &value, &version); - client->app->getua_result((byte*) value.data(), value.size()); + client->app->getua_result((byte*) value.data(), unsigned(value.size())); break; case '^': // private, non-encrypted // store the value in cache in binary format u->setattr(at, &value, &version); - client->app->getua_result((byte*) value.data(), value.size()); + client->app->getua_result((byte*) value.data(), unsigned(value.size())); if (at == ATTR_DISABLE_VERSIONS) { @@ -2802,7 +2871,7 @@ void CommandGetUA::procresult() } u->setattr(at, &value, &version); - client->app->getua_result((byte*) value.data(), value.size()); + client->app->getua_result((byte*) value.data(), unsigned(value.size())); break; } @@ -2905,7 +2974,7 @@ CommandNodeKeyUpdate::CommandNodeKeyUpdate(MegaClient* client, handle_vector* v) cmd("k"); beginarray("nk"); - for (int i = v->size(); i--;) + for (size_t i = v->size(); i--;) { handle h = (*v)[i]; @@ -2913,10 +2982,10 @@ CommandNodeKeyUpdate::CommandNodeKeyUpdate(MegaClient* client, handle_vector* v) if ((n = client->nodebyhandle(h))) { - client->key.ecb_encrypt((byte*)n->nodekey.data(), nodekey, n->nodekey.size()); + client->key.ecb_encrypt((byte*)n->nodekey.data(), nodekey, unsigned(n->nodekey.size())); element(h, MegaClient::NODEHANDLE); - element(nodekey, n->nodekey.size()); + element(nodekey, int(n->nodekey.size())); } } @@ -3093,9 +3162,16 @@ void CommandGetUserData::procresult() string name; string pubk; string privk; + string k; handle jid = UNDEF; byte privkbuf[AsymmCipher::MAXKEYLENGTH * 2]; int len_privk = 0; + m_time_t since = 0; + int v = 0; + string salt; + bool gmfa = false; + bool ssrs = false; + bool nsre = false; if (client->json.isnumeric()) { @@ -3106,6 +3182,14 @@ void CommandGetUserData::procresult() { switch (client->json.getnameid()) { + case MAKENAMEID3('a', 'a', 'v'): // account authentication version + v = (int)client->json.getint(); + break; + + case MAKENAMEID3('a', 'a', 's'): // account authentication salt + client->json.storeobject(&salt); + break; + case MAKENAMEID4('n', 'a', 'm', 'e'): client->json.storeobject(&name); break; @@ -3115,12 +3199,12 @@ void CommandGetUserData::procresult() break; case 'k': - client->k.resize(SymmCipher::KEYLENGTH); - client->json.storebinary((byte *)client->k.data(), client->k.size()); + k.resize(SymmCipher::KEYLENGTH); + client->json.storebinary((byte *)k.data(), int(k.size())); break; case MAKENAMEID5('s', 'i', 'n', 'c', 'e'): - client->accountsince = client->json.getint(); + since = client->json.getint(); break; case MAKENAMEID4('p', 'u', 'b', 'k'): @@ -3129,9 +3213,6 @@ void CommandGetUserData::procresult() case MAKENAMEID5('p', 'r', 'i', 'v', 'k'): len_privk = client->json.storebinary(privkbuf, sizeof privkbuf); - client->key.ecb_decrypt(privkbuf, len_privk); - privk.resize(AsymmCipher::MAXKEYLENGTH * 2); - privk.resize(Base64::btoa(privkbuf, len_privk, (char *)privk.data())); break; case MAKENAMEID5('f', 'l', 'a', 'g', 's'): @@ -3142,11 +3223,14 @@ void CommandGetUserData::procresult() { switch (client->json.getnameid()) { - case MAKENAMEID4('m', 'f', 'a', 'e'): - client->gmfa_enabled = bool(client->json.getint()); + case MAKENAMEID4('m', 'f', 'a', 'e'): // multi-factor authentication enabled + gmfa = bool(client->json.getint()); break; - case MAKENAMEID4('s', 's', 'r', 's'): - client->ssrs_enabled = bool(client->json.getint()); + case MAKENAMEID4('s', 's', 'r', 's'): // server-side rubish-bin scheduler + ssrs = bool(client->json.getint()); + break; + case MAKENAMEID4('n', 's', 'r', 'e'): // new secure registration enabled + nsre = bool(client->json.getint()); break; case EOO: endobject = true; @@ -3162,7 +3246,32 @@ void CommandGetUserData::procresult() } break; - case EOO: + case EOO: + if (v) + { + client->accountversion = v; + } + + if (salt.size()) + { + Base64::atob(salt, client->accountsalt); + } + + client->accountsince = since; + client->gmfa_enabled = gmfa; + client->ssrs_enabled = ssrs; + client->nsr_enabled = nsre; + client->k = k; + if (len_privk) + { + client->key.ecb_decrypt(privkbuf, len_privk); + privk.resize(AsymmCipher::MAXKEYLENGTH * 2); + privk.resize(Base64::btoa(privkbuf, len_privk, (char *)privk.data())); + } + + client->btugexpiration.backoff(MegaClient::USER_DATA_EXPIRATION_BACKOFF_SECS * 10); + client->cachedug = true; + client->app->userdata_result(&name, &pubk, &privk, jid, API_OK); return; @@ -3412,7 +3521,7 @@ void CommandGetUserQuota::procresult() { if ((amount = client->json.getvalue()) && (cur = client->json.getvalue())) { - int t = details->balances.size(); + size_t t = details->balances.size(); details->balances.resize(t + 1); details->balances[t].amount = atof(amount); memcpy(details->balances[t].currency, cur, 3); @@ -3484,7 +3593,7 @@ void CommandGetUserTransactions::procresult() if (handle && (ts > 0) && delta && cur) { - int t = details->transactions.size(); + size_t t = details->transactions.size(); details->transactions.resize(t + 1); memcpy(details->transactions[t].handle, handle, 11); details->transactions[t].handle[11] = 0; @@ -3524,7 +3633,7 @@ void CommandGetUserPurchases::procresult() if (handle && (ts > 0) && amount && cur && (method >= 0)) { - int t = details->purchases.size(); + size_t t = details->purchases.size(); details->purchases.resize(t + 1); memcpy(details->purchases[t].handle, handle, 11); details->purchases[t].handle[11] = 0; @@ -3556,7 +3665,7 @@ void CommandGetUserSessions::procresult() while (client->json.enterarray()) { - int t = details->sessions.size(); + size_t t = details->sessions.size(); details->sessions.resize(t + 1); details->sessions[t].timestamp = client->json.getint(); @@ -3669,7 +3778,7 @@ void CommandGetPH::procresult() // we want at least the attributes if (s >= 0) { - a.resize(Base64::atob(a.c_str(), (byte*)a.data(), a.size())); + a.resize(Base64::atob(a.c_str(), (byte*)a.data(), int(a.size()))); if (havekey) { client->app->openfilelink_result(ph, key, s, &a, &fa, op); @@ -3694,18 +3803,27 @@ void CommandGetPH::procresult() } } -CommandSetMasterKey::CommandSetMasterKey(MegaClient* client, const byte* newkey, uint64_t hash, const char *pin) +CommandSetMasterKey::CommandSetMasterKey(MegaClient* client, const byte* newkey, const byte *hash, int hashsize, const byte *clientrandomvalue, const char *pin, string *salt) { memcpy(this->newkey, newkey, SymmCipher::KEYLENGTH); cmd("up"); arg("k", newkey, SymmCipher::KEYLENGTH); - arg("uh", (byte*)&hash, sizeof hash); + if (clientrandomvalue) + { + arg("crv", clientrandomvalue, SymmCipher::KEYLENGTH); + } + arg("uh", hash, hashsize); if (pin) { arg("mfa", pin); } + if (salt) + { + this->salt = *salt; + } + tag = client->reqtag; } @@ -3717,8 +3835,9 @@ void CommandSetMasterKey::procresult() } else { - // update encrypted MK for further checkups + // update encrypted MK and salt for further checkups client->k.assign((const char *) newkey, SymmCipher::KEYLENGTH); + client->accountsalt = salt; client->json.storeobject(); client->app->changepw_result(API_OK); @@ -3841,8 +3960,8 @@ CommandSendSignupLink::CommandSendSignupLink(MegaClient* client, const char* ema { cmd("uc"); arg("c", c, 2 * SymmCipher::KEYLENGTH); - arg("n", (byte*)name, strlen(name)); - arg("m", (byte*)email, strlen(email)); + arg("n", (byte*)name, int(strlen(name))); + arg("m", (byte*)email, int(strlen(email))); tag = client->reqtag; } @@ -3859,6 +3978,40 @@ void CommandSendSignupLink::procresult() client->app->sendsignuplink_result(API_EINTERNAL); } +CommandSendSignupLink2::CommandSendSignupLink2(MegaClient* client, const char* email, const char* name) +{ + cmd("uc2"); + arg("n", (byte*)name, int(strlen(name))); + arg("m", (byte*)email, int(strlen(email))); + arg("v", 2); + tag = client->reqtag; +} + +CommandSendSignupLink2::CommandSendSignupLink2(MegaClient* client, const char* email, const char* name, byte *clientrandomvalue, byte *encmasterkey, byte *hashedauthkey) +{ + cmd("uc2"); + arg("n", (byte*)name, int(strlen(name))); + arg("m", (byte*)email, int(strlen(email))); + arg("crv", clientrandomvalue, SymmCipher::KEYLENGTH); + arg("hak", hashedauthkey, SymmCipher::KEYLENGTH); + arg("k", encmasterkey, SymmCipher::KEYLENGTH); + arg("v", 2); + + tag = client->reqtag; +} + +void CommandSendSignupLink2::procresult() +{ + if (client->json.isnumeric()) + { + return client->app->sendsignuplink_result((error)client->json.getint()); + } + + client->json.storeobject(); + + client->app->sendsignuplink_result(API_EINTERNAL); +} + CommandQuerySignupLink::CommandQuerySignupLink(MegaClient* client, const byte* code, unsigned len) { confirmcode.assign((char*)code, len); @@ -3906,6 +4059,45 @@ void CommandQuerySignupLink::procresult() client->app->querysignuplink_result(API_EINTERNAL); } +CommandConfirmSignupLink2::CommandConfirmSignupLink2(MegaClient* client, + const byte* code, + unsigned len) +{ + cmd("ud2"); + arg("c", code, len); + + tag = client->reqtag; +} + +void CommandConfirmSignupLink2::procresult() +{ + string name; + string email; + handle uh; + int version = 0; + + if (client->json.isnumeric()) + { + client->app->confirmsignuplink2_result(UNDEF, NULL, NULL, (error)client->json.getint()); + } + + if (client->json.storebinary(&email) && client->json.storebinary(&name)) + { + uh = client->json.gethandle(MegaClient::USERHANDLE); + version = int(client->json.getint()); + } + while (client->json.storeobject()); + + if (!ISUNDEF(uh) && version == 2) + { + client->app->confirmsignuplink2_result(uh, name.c_str(), email.c_str(), API_OK); + } + else + { + client->app->confirmsignuplink2_result(UNDEF, NULL, NULL, API_EINTERNAL); + } +} + CommandConfirmSignupLink::CommandConfirmSignupLink(MegaClient* client, const byte* code, unsigned len, @@ -4518,7 +4710,7 @@ void CommandGetPrivateKey::procresult() } } -CommandConfirmRecoveryLink::CommandConfirmRecoveryLink(MegaClient *client, const char *code, uint64_t newLoginHash, const byte *encMasterKey, const byte *initialSession) +CommandConfirmRecoveryLink::CommandConfirmRecoveryLink(MegaClient *client, const char *code, const byte *hash, int hashsize, const byte *clientrandomvalue, const byte *encMasterKey, const byte *initialSession) { cmd("erx"); @@ -4530,7 +4722,17 @@ CommandConfirmRecoveryLink::CommandConfirmRecoveryLink(MegaClient *client, const arg("c", code); arg("x", encMasterKey, SymmCipher::KEYLENGTH); - arg("y", (byte*)&newLoginHash, sizeof newLoginHash); + if (!clientrandomvalue) + { + arg("y", hash, hashsize); + } + else + { + beginobject("y"); + arg("crv", clientrandomvalue, SymmCipher::KEYLENGTH); + arg("hak", hash, hashsize); //hashed authentication key + endobject(); + } if (initialSession) { @@ -4639,7 +4841,7 @@ void CommandGetEmailLink::procresult() } } -CommandConfirmEmailLink::CommandConfirmEmailLink(MegaClient *client, const char *code, const char *email, uint64_t newLoginHash, bool replace) +CommandConfirmEmailLink::CommandConfirmEmailLink(MegaClient *client, const char *code, const char *email, const byte *newLoginHash, bool replace) { this->email = email; this->replace = replace; @@ -4648,7 +4850,10 @@ CommandConfirmEmailLink::CommandConfirmEmailLink(MegaClient *client, const char arg("c", code); arg("e", email); - arg("uh", (byte*)&newLoginHash, sizeof newLoginHash); + if (newLoginHash) + { + arg("uh", newLoginHash, sizeof(uint64_t)); + } if (replace) { arg("r", 1); // replace the current email address by this one diff --git a/src/crypto/cryptopp.cpp b/src/crypto/cryptopp.cpp index 8f880941bc..c61ff91d0a 100644 --- a/src/crypto/cryptopp.cpp +++ b/src/crypto/cryptopp.cpp @@ -378,7 +378,7 @@ int AsymmCipher::encrypt(const byte* plain, int plainlen, byte* buf, int buflen) *ptr++ = t.GetByte(i); } - return ptr - buf; + return int(ptr - buf); } static void rsadecrypt(Integer* key, Integer* m) diff --git a/src/crypto/sodium.cpp b/src/crypto/sodium.cpp index 039de90653..c35f88cdfe 100644 --- a/src/crypto/sodium.cpp +++ b/src/crypto/sodium.cpp @@ -72,7 +72,7 @@ int EdDSA::sign(const unsigned char* msg, const unsigned long long msglen, int result = crypto_sign_detached(sig, NULL, msg, msglen, (const unsigned char*)privKey); - return (result == 0) ? (crypto_sign_BYTES + msglen) : 0; + return int( (result == 0) ? (crypto_sign_BYTES + msglen) : 0 ); } @@ -144,7 +144,7 @@ void EdDSA::signKey(const unsigned char *key, const unsigned long long keyLength string keyString = "keyauth"; keyString.append(tsstr); - keyString.append((char*)key, keyLength); + keyString.append((char*)key, size_t(keyLength)); byte sigBuf[crypto_sign_BYTES]; sign((unsigned char *)keyString.data(), keyString.size(), sigBuf); @@ -166,7 +166,7 @@ bool EdDSA::verifyKey(const unsigned char *pubk, const unsigned long long pubkLe string message = "keyauth"; message.append(sig->data(), 8); - message.append((char*)pubk, pubkLen); + message.append((char*)pubk, size_t(pubkLen)); string signature = sig->substr(8); diff --git a/src/db.cpp b/src/db.cpp index f806319b8f..7a0fe6b3ab 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -32,7 +32,7 @@ DbTable::DbTable() // add or update record from string bool DbTable::put(uint32_t index, string* data) { - return put(index, (char*)data->data(), data->size()); + return put(index, (char*)data->data(), unsigned(data->size())); } // add or update record with padding and encryption diff --git a/src/gfx.cpp b/src/gfx.cpp index 4b55ad55d3..a971653ac7 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -118,7 +118,7 @@ void GfxProc::loop() // (this assumes that the width of the largest dimension is max) if (readbitmap(NULL, &job->localfilename, dimensions[sizeof dimensions/sizeof dimensions[0]-1][0])) { - for (int i = 0; i < job->imagetypes.size(); i++) + for (unsigned i = 0; i < job->imagetypes.size(); i++) { // successively downscale the original image string* jpeg = new string(); @@ -143,7 +143,7 @@ void GfxProc::loop() } else { - for (int i = 0; i < job->imagetypes.size(); i++) + for (unsigned i = 0; i < job->imagetypes.size(); i++) { job->images.push_back(NULL); } @@ -162,7 +162,7 @@ void GfxProc::loop() while (job = responses.pop()) { - for (int i = 0; i < job->imagetypes.size(); i++) + for (unsigned i = 0; i < job->imagetypes.size(); i++) { delete job->images[i]; } @@ -182,7 +182,7 @@ int GfxProc::checkevents(Waiter *) SymmCipher key; while (job = responses.pop()) { - for (int i = 0; i < job->images.size(); i++) + for (unsigned i = 0; i < job->images.size(); i++) { if (job->images[i]) { @@ -314,7 +314,7 @@ int GfxProc::gendimensionsputfa(FileAccess* fa, string* localfilename, handle th requests.push(job); waiter.notify(); - return job->imagetypes.size(); + return int(job->imagetypes.size()); } bool GfxProc::savefa(string *localfilepath, int width, int height, string *localdstpath) @@ -358,7 +358,7 @@ bool GfxProc::savefa(string *localfilepath, int width, int height, string *local return false; } - if (!f->fwrite((const byte*)jpeg.data(), jpeg.size(), 0)) + if (!f->fwrite((const byte*)jpeg.data(), unsigned(jpeg.size()), 0)) { delete f; return false; diff --git a/src/gfx/GfxProcCG.mm b/src/gfx/GfxProcCG.mm index 93fb4483b5..a30d7fba25 100644 --- a/src/gfx/GfxProcCG.mm +++ b/src/gfx/GfxProcCG.mm @@ -152,8 +152,8 @@ if (!thumbnail) { return false; } - w = CGImageGetWidth(thumbnail); - h = CGImageGetHeight(thumbnail); + w = (int) CGImageGetWidth(thumbnail); + h = (int) CGImageGetHeight(thumbnail); CGImageRelease(thumbnail); } return w && h; diff --git a/src/gfx/qt.cpp b/src/gfx/qt.cpp index 6471ed1293..8787531931 100644 --- a/src/gfx/qt.cpp +++ b/src/gfx/qt.cpp @@ -41,7 +41,7 @@ extern "C" { #endif #ifdef HAVE_LIBRAW -#include +#include #endif namespace mega { @@ -470,9 +470,10 @@ QImage GfxProcQT::createThumbnail(QString imagePath) QImageReader *GfxProcQT::readbitmapQT(int &w, int &h, int &orientation, int &imageType, QString imagePath) { -#ifdef HAVE_FFMPEG QFileInfo info(imagePath); QString ext = QString::fromUtf8(".%1.").arg(info.suffix()).toLower(); + +#ifdef HAVE_FFMPEG if (strstr(GfxProcQT::supportedformatsFfmpeg(), ext.toUtf8().constData())) { imageType = TYPE_VIDEO; @@ -853,7 +854,7 @@ QImageReader *GfxProcQT::readbitmapFfmpeg(int &w, int &h, int &orientation, QStr } AVPixelFormat sourcePixelFormat = codecContext.pix_fmt; - AVPixelFormat targetPixelFormat = AV_PIX_FMT_RGB24; + AVPixelFormat targetPixelFormat = AV_PIX_FMT_RGB32; SwsContext* swsContext = sws_getContext(width, height, sourcePixelFormat, width, height, targetPixelFormat, SWS_FAST_BILINEAR, NULL, NULL, NULL); @@ -988,8 +989,8 @@ QImageReader *GfxProcQT::readbitmapFfmpeg(int &w, int &h, int &orientation, QStr if (scalingResult > 0) { - QImage image(width, height, QImage::Format_RGB888); - if (avpicture_layout((AVPicture *)targetFrame, AV_PIX_FMT_RGB24, + QImage image(width, height, QImage::Format_ARGB32); + if (avpicture_layout((AVPicture *)targetFrame, targetPixelFormat, width, height, image.bits(), image.byteCount()) <= 0) { LOG_warn << "Error copying frame"; diff --git a/src/http.cpp b/src/http.cpp index 9fda052bf9..99d23ae4ec 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -44,7 +44,7 @@ const int HttpIO::NETWORKTIMEOUT = 6000; const int HttpIO::REQUESTTIMEOUT = 1200; // wait request timeout (ds) -const int HttpIO::WAITREQUESTTIMEOUT = 450; +const int HttpIO::SCREQUESTTIMEOUT = 400; // connect timeout (ds) const int HttpIO::CONNECTTIMEOUT = 120; @@ -420,6 +420,7 @@ HttpReq::HttpReq(bool b) type = REQ_JSON; buflen = 0; protect = false; + minspeed = false; init(); } diff --git a/src/include.am b/src/include.am index b1ddce3c36..0d3b827f6d 100644 --- a/src/include.am +++ b/src/include.am @@ -3,16 +3,16 @@ lib_LTLIBRARIES = src/libmega.la # CXX flags if WIN32 -src_libmega_la_CXXFLAGS = -D_WIN32=1 -Iinclude/ -Iinclude/mega/win32 $(LIBS_EXTRA) $(ZLIB_CXXFLAGS) $(LIBUV_CXXFLAGS) $(LIBMEDIAINFO_CXXFLAGS) $(FFMPEG_CXXFLAGS) $(CRYPTO_CXXFLAGS) $(SODIUM_CXXFLAGS) $(DB_CXXFLAGS) $(CXXFLAGS) $(WINHTTP_CXXFLAGS) $(FI_CXXFLAGS) $(PCRE_CXXFLAGS) +src_libmega_la_CXXFLAGS = -D_WIN32=1 -Iinclude/ -Iinclude/mega/win32 $(LIBS_EXTRA) $(ZLIB_CXXFLAGS) $(LIBUV_CXXFLAGS) $(LIBRAW_CXXFLAGS) $(LIBMEDIAINFO_CXXFLAGS) $(FFMPEG_CXXFLAGS) $(CRYPTO_CXXFLAGS) $(SODIUM_CXXFLAGS) $(DB_CXXFLAGS) $(CXXFLAGS) $(WINHTTP_CXXFLAGS) $(FI_CXXFLAGS) $(PCRE_CXXFLAGS) else -src_libmega_la_CXXFLAGS = $(CARES_FLAGS) $(LIBCURL_FLAGS) $(ZLIB_CXXFLAGS) $(LIBUV_CXXFLAGS) $(LIBMEDIAINFO_CXXFLAGS) $(FFMPEG_CXXFLAGS) $(CRYPTO_CXXFLAGS) $(SODIUM_CXXFLAGS) $(DB_CXXFLAGS) $(FI_CXXFLAGS) $(LIBSSL_FLAGS) $(PCRE_CXXFLAGS) +src_libmega_la_CXXFLAGS = $(CARES_FLAGS) $(LIBCURL_FLAGS) $(ZLIB_CXXFLAGS) $(LIBUV_CXXFLAGS) $(LIBRAW_CXXFLAGS) $(LIBMEDIAINFO_CXXFLAGS) $(FFMPEG_CXXFLAGS) $(CRYPTO_CXXFLAGS) $(SODIUM_CXXFLAGS) $(DB_CXXFLAGS) $(FI_CXXFLAGS) $(LIBSSL_FLAGS) $(PCRE_CXXFLAGS) endif # Libs if WIN32 -src_libmega_la_LIBADD = $(LIBS_EXTRA) $(FFMPEG_LDFLAGS) $(FFMPEG_LIBS) $(ZLIB_LDFLAGS) $(ZLIB_LIBS) $(LIBUV_LDFLAGS) $(LIBUV_LIBS) $(LIBMEDIAINFO_LDFLAGS) $(LIBMEDIAINFO_LIBS) $(CRYPTO_LDFLAGS) $(CRYPTO_LIBS) $(SODIUM_LDFLAGS) $(SODIUM_LIBS) $(DB_LDFLAGS) $(DB_LIBS) $(WINHTTP_LDFLAGS) $(WINHTTP_LIBS) $(FI_LDFLAGS) $(FI_LIBS) $(PCRE_LDFLAGS) $(PCRE_LIBS) +src_libmega_la_LIBADD = $(LIBS_EXTRA) $(FFMPEG_LDFLAGS) $(FFMPEG_LIBS) $(ZLIB_LDFLAGS) $(ZLIB_LIBS) $(LIBUV_LDFLAGS) $(LIBUV_LIBS) $(LIBRAW_LDFLAGS) $(LIBRAW_LIBS) $(LIBMEDIAINFO_LDFLAGS) $(LIBMEDIAINFO_LIBS) $(CRYPTO_LDFLAGS) $(CRYPTO_LIBS) $(SODIUM_LDFLAGS) $(SODIUM_LIBS) $(DB_LDFLAGS) $(DB_LIBS) $(WINHTTP_LDFLAGS) $(WINHTTP_LIBS) $(FI_LDFLAGS) $(FI_LIBS) $(PCRE_LDFLAGS) $(PCRE_LIBS) else -src_libmega_la_LIBADD = $(CARES_LDFLAGS) $(CARES_LIBS) $(LIBCURL_LIBS) $(FFMPEG_LDFLAGS) $(FFMPEG_LIBS) $(ZLIB_LDFLAGS) $(ZLIB_LIBS) $(LIBUV_LDFLAGS) $(LIBUV_LIBS) $(LIBMEDIAINFO_LDFLAGS) $(LIBMEDIAINFO_LIBS) $(CRYPTO_LDFLAGS) $(CRYPTO_LIBS) $(SODIUM_LDFLAGS) $(SODIUM_LIBS) $(DB_LDFLAGS) $(DB_LIBS) $(FI_LDFLAGS) $(FI_LIBS) $(LIBSSL_LDFLAGS) $(LIBSSL_LIBS) $(PCRE_LDFLAGS) $(PCRE_LIBS) +src_libmega_la_LIBADD = $(CARES_LDFLAGS) $(CARES_LIBS) $(LIBCURL_LIBS) $(FFMPEG_LDFLAGS) $(FFMPEG_LIBS) $(ZLIB_LDFLAGS) $(ZLIB_LIBS) $(LIBUV_LDFLAGS) $(LIBUV_LIBS) $(LIBRAW_LDFLAGS) $(LIBRAW_LIBS) $(LIBMEDIAINFO_LDFLAGS) $(LIBMEDIAINFO_LIBS) $(CRYPTO_LDFLAGS) $(CRYPTO_LIBS) $(SODIUM_LDFLAGS) $(SODIUM_LIBS) $(DB_LDFLAGS) $(DB_LIBS) $(FI_LDFLAGS) $(FI_LIBS) $(LIBSSL_LDFLAGS) $(LIBSSL_LIBS) $(PCRE_LDFLAGS) $(PCRE_LIBS) endif # add library version diff --git a/src/mediafileattribute.cpp b/src/mediafileattribute.cpp index a6a71a1dbb..9b0742b25d 100644 --- a/src/mediafileattribute.cpp +++ b/src/mediafileattribute.cpp @@ -693,17 +693,27 @@ void MediaProperties::extractMediaPropertyFileAttributes(const std::string& loca ZenLib::Ztring vw = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Width"), MediaInfoLib::Info_Text); ZenLib::Ztring vh = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Height"), MediaInfoLib::Info_Text); ZenLib::Ztring vd = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Duration"), MediaInfoLib::Info_Text); - ZenLib::Ztring vr = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate"), MediaInfoLib::Info_Text); + ZenLib::Ztring vfr = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate"), MediaInfoLib::Info_Text); ZenLib::Ztring vrm = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("FrameRate_Mode"), MediaInfoLib::Info_Text); ZenLib::Ztring vci = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("CodecID"), MediaInfoLib::Info_Text); ZenLib::Ztring vcf = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Format"), MediaInfoLib::Info_Text); + ZenLib::Ztring vr = minfo.Get(MediaInfoLib::Stream_Video, 0, __T("Rotation"), MediaInfoLib::Info_Text); ZenLib::Ztring aci = minfo.Get(MediaInfoLib::Stream_Audio, 0, __T("CodecID"), MediaInfoLib::Info_Text); ZenLib::Ztring acf = minfo.Get(MediaInfoLib::Stream_Audio, 0, __T("Format"), MediaInfoLib::Info_Text); ZenLib::Ztring ad = minfo.Get(MediaInfoLib::Stream_Audio, 0, __T("Duration"), MediaInfoLib::Info_Text); - width = vw.To_int32u(); - height = vh.To_int32u(); - fps = vr.To_int32u(); + if (vr.To_int32u() == 90 || vr.To_int32u() == 270) + { + width = vh.To_int32u(); + height = vw.To_int32u(); + } + else + { + width = vw.To_int32u(); + height = vh.To_int32u(); + } + + fps = vfr.To_int32u(); playtime = (coalesce(gd.To_int32u(), coalesce(vd.To_int32u(), ad.To_int32u()))) / 1000; videocodecNames = vci.To_Local(); videocodecFormat = vcf.To_Local(); diff --git a/src/megaapi.cpp b/src/megaapi.cpp index fb7ac089a6..b39b0cf679 100644 --- a/src/megaapi.cpp +++ b/src/megaapi.cpp @@ -23,7 +23,7 @@ #include "megaapi.h" #include "megaapi_impl.h" -using namespace mega; +namespace mega { MegaProxy::MegaProxy() { @@ -239,6 +239,26 @@ int MegaNode::getDuration() return -1; } +int MegaNode::getWidth() +{ + return -1; +} + +int MegaNode::getHeight() +{ + return -1; +} + +int MegaNode::getShortformat() +{ + return -1; +} + +int MegaNode::getVideocodecid() +{ + return -1; +} + double MegaNode::getLatitude() { return INVALID_COORDINATE; @@ -1442,9 +1462,9 @@ void MegaApi::log(int logLevel, const char *message, const char *filename, int l MegaApiImpl::log(logLevel, message, filename, line); } -char *MegaApi::getBase64PwKey(const char *password) +long long MegaApi::getSDKtime() { - return pImpl->getBase64PwKey(password); + return pImpl->getSDKtime(); } char *MegaApi::getStringHash(const char* base64pwkey, const char* inBuf) @@ -1452,11 +1472,6 @@ char *MegaApi::getStringHash(const char* base64pwkey, const char* inBuf) return pImpl->getStringHash(base64pwkey, inBuf); } -long long MegaApi::getSDKtime() -{ - return pImpl->getSDKtime(); -} - void MegaApi::getSessionTransferURL(const char *path, MegaRequestListener *listener) { pImpl->getSessionTransferURL(path, listener); @@ -1629,11 +1644,6 @@ void MegaApi::resumeCreateAccount(const char* sid, MegaRequestListener *listener pImpl->resumeCreateAccount(sid, listener); } -void MegaApi::fastCreateAccount(const char* email, const char *base64pwkey, const char* name, MegaRequestListener *listener) -{ - pImpl->fastCreateAccount(email, base64pwkey, name, listener); -} - void MegaApi::sendSignupLink(const char *email, const char *name, const char *password, MegaRequestListener *listener) { pImpl->sendSignupLink(email, name, password, listener); @@ -2034,6 +2044,11 @@ void MegaApi::shouldShowPasswordReminderDialog(bool atLogout, MegaRequestListene pImpl->getUserAttr((const char*)NULL, MegaApi::USER_ATTR_PWD_REMINDER, NULL, atLogout, listener); } +void MegaApi::isMasterKeyExported(MegaRequestListener *listener) +{ + pImpl->getUserAttr((const char*)NULL, MegaApi::USER_ATTR_PWD_REMINDER, NULL, 0, listener); +} + void MegaApi::enableRichPreviews(bool enable, MegaRequestListener *listener) { pImpl->enableRichPreviews(enable, listener); @@ -4144,9 +4159,9 @@ void MegaApi::registerPushNotifications(int deviceType, const char *token, MegaR pImpl->registerPushNotification(deviceType, token, listener); } -void MegaApi::sendChatStats(const char *data, MegaRequestListener *listener) +void MegaApi::sendChatStats(const char *data, int port, MegaRequestListener *listener) { - pImpl->sendChatStats(data, listener); + pImpl->sendChatStats(data, port, listener); } void MegaApi::sendChatLogs(const char *data, const char *aid, MegaRequestListener *listener) @@ -5339,3 +5354,5 @@ long long MegaFolderInfo::getVersionsSize() const { return 0; } + +} diff --git a/src/megaapi_impl.cpp b/src/megaapi_impl.cpp index 08d3d967da..b4c6d8eb93 100644 --- a/src/megaapi_impl.cpp +++ b/src/megaapi_impl.cpp @@ -67,7 +67,7 @@ #include "mega/mega_zxcvbn.h" -using namespace mega; +namespace mega { MegaNodePrivate::MegaNodePrivate(const char *name, int type, int64_t size, int64_t ctime, int64_t mtime, uint64_t nodehandle, string *nodekey, string *attrstring, string *fileattrstring, const char *fingerprint, MegaHandle parentHandle, @@ -78,6 +78,10 @@ MegaNodePrivate::MegaNodePrivate(const char *name, int type, int64_t size, int64 this->fingerprint = MegaApi::strdup(fingerprint); this->customAttrs = NULL; this->duration = -1; + this->width = -1; + this->height = -1; + this->shortformat = -1; + this->videocodecid = -1; this->latitude = INVALID_COORDINATE; this->longitude = INVALID_COORDINATE; this->type = type; @@ -123,7 +127,25 @@ MegaNodePrivate::MegaNodePrivate(MegaNode *node) this->name = MegaApi::strdup(node->getName()); this->fingerprint = MegaApi::strdup(node->getFingerprint()); this->customAttrs = NULL; - this->duration = node->getDuration(); + + MegaNodePrivate *np = dynamic_cast(node); + if (np) + { + this->duration = np->duration; + this->width = np->width; + this->height = np->height; + this->shortformat = np->shortformat; + this->videocodecid = np->videocodecid; + } + else + { + this->duration = node->getDuration(); + this->width = node->getWidth(); + this->height = node->getHeight(); + this->shortformat = node->getShortformat(); + this->videocodecid = node->getVideocodecid(); + } + this->latitude = node->getLatitude(); this->longitude = node->getLongitude(); this->restorehandle = node->getRestoreHandle(); @@ -220,6 +242,10 @@ MegaNodePrivate::MegaNodePrivate(Node *node) } this->duration = -1; + this->width = -1; + this->height = -1; + this->shortformat = -1; + this->videocodecid = -1; this->latitude = INVALID_COORDINATE; this->longitude = INVALID_COORDINATE; this->customAttrs = NULL; @@ -685,6 +711,86 @@ int MegaNodePrivate::getDuration() return duration; } +int MegaNodePrivate::getWidth() +{ + if (width == -1) // not initialized yet, or not available + { + if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size()) + { + uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2); + MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey); + if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file + && mediaProperties.shortformat != 254 // 254 = No information available + && mediaProperties.width > 0) + { + width = mediaProperties.width; + } + } + } + + return width; +} + +int MegaNodePrivate::getHeight() +{ + if (height == -1) // not initialized yet, or not available + { + if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size()) + { + uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2); + MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey); + if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file + && mediaProperties.shortformat != 254 // 254 = No information available + && mediaProperties.height > 0) + { + height = mediaProperties.height; + } + } + } + + return height; +} + +int MegaNodePrivate::getShortformat() +{ + if (shortformat == -1) // not initialized yet, or not available + { + if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size()) + { + uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2); + MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey); + if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file + && mediaProperties.shortformat != 254 // 254 = No information available + && mediaProperties.shortformat > 0) + { + shortformat = mediaProperties.shortformat; + } + } + } + + return shortformat; +} + +int MegaNodePrivate::getVideocodecid() +{ + if (videocodecid == -1) // not initialized yet, or not available + { + if (type == MegaNode::TYPE_FILE && nodekey.size() == FILENODEKEYLENGTH && fileattrstring.size()) + { + uint32_t* attrKey = (uint32_t*)(nodekey.data() + FILENODEKEYLENGTH / 2); + MediaProperties mediaProperties = MediaProperties::decodeMediaPropertiesAttributes(fileattrstring, attrKey); + if (mediaProperties.shortformat != 255 // 255 = MediaInfo failed processing the file + && mediaProperties.shortformat != 254 // 254 = No information available + && mediaProperties.videocodecid > 0) + { + videocodecid = mediaProperties.videocodecid; + } + } + } + + return videocodecid; +} + double MegaNodePrivate::getLatitude() { return latitude; @@ -4177,20 +4283,49 @@ bool MegaApiImpl::isAchievementsEnabled() bool MegaApiImpl::checkPassword(const char *password) { - byte pwkey[SymmCipher::KEYLENGTH]; - sdkMutex.lock(); - if (!password || !password[0] - || client->k.size() < SymmCipher::KEYLENGTH - || client->pw_key(password, pwkey)) + if (!password || !password[0] || client->k.size() != SymmCipher::KEYLENGTH) { sdkMutex.unlock(); return false; } string k = client->k; - SymmCipher cipher(pwkey); - cipher.ecb_decrypt((byte *)k.data()); + if (client->accountversion == 1) + { + byte pwkey[SymmCipher::KEYLENGTH]; + if (client->pw_key(password, pwkey)) + { + sdkMutex.unlock(); + return false; + } + + SymmCipher cipher(pwkey); + cipher.ecb_decrypt((byte *)k.data()); + } + else if (client->accountversion == 2) + { + if (client->accountsalt.size() != 32) // SHA256 + { + sdkMutex.unlock(); + return false; + } + + byte derivedKey[2 * SymmCipher::KEYLENGTH]; + CryptoPP::PKCS5_PBKDF2_HMAC pbkdf2; + pbkdf2.DeriveKey(derivedKey, sizeof(derivedKey), 0, (byte *)password, strlen(password), + (const byte *)client->accountsalt.data(), client->accountsalt.size(), 100000); + + SymmCipher cipher(derivedKey); + cipher.ecb_decrypt((byte *)k.data()); + } + else + { + LOG_warn << "Version of account not supported"; + sdkMutex.unlock(); + return false; + } + bool result = !memcmp(k.data(), client->key.key, SymmCipher::KEYLENGTH); sdkMutex.unlock(); return result; @@ -4242,54 +4377,46 @@ void MegaApiImpl::log(int logLevel, const char *message, const char *filename, i externalLogger.postLog(logLevel, message, filename, line); } -char* MegaApiImpl::getBase64PwKey(const char *password) +long long MegaApiImpl::getSDKtime() { - if(!password) return NULL; - - byte pwkey[SymmCipher::KEYLENGTH]; - error e = client->pw_key(password,pwkey); - if(e) - return NULL; - - char* buf = new char[SymmCipher::KEYLENGTH*4/3+4]; - Base64::btoa((byte *)pwkey, SymmCipher::KEYLENGTH, buf); - return buf; + return Waiter::ds; } -long long MegaApiImpl::getSDKtime() +void MegaApiImpl::getSessionTransferURL(const char *path, MegaRequestListener *listener) { - return Waiter::ds; + MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_SESSION_TRANSFER_URL); + request->setText(path); + request->setListener(listener); + requestQueue.push(request); + waiter->notify(); } char* MegaApiImpl::getStringHash(const char* base64pwkey, const char* inBuf) { - if(!base64pwkey || !inBuf) return NULL; + if (!base64pwkey || !inBuf) + { + return NULL; + } - char pwkey[SymmCipher::KEYLENGTH]; - Base64::atob(base64pwkey, (byte *)pwkey, sizeof pwkey); + char pwkey[2 * SymmCipher::KEYLENGTH]; + if (Base64::atob(base64pwkey, (byte *)pwkey, sizeof pwkey) != SymmCipher::KEYLENGTH) + { + return MegaApi::strdup(""); + } - SymmCipher key; - key.setkey((byte*)pwkey); + SymmCipher key; + key.setkey((byte*)pwkey); uint64_t strhash; - string neBuf = inBuf; + string neBuf = inBuf; strhash = client->stringhash64(&neBuf, &key); - char* buf = new char[8*4/3+4]; + char* buf = new char[8*4/3+4]; Base64::btoa((byte*)&strhash, 8, buf); return buf; } -void MegaApiImpl::getSessionTransferURL(const char *path, MegaRequestListener *listener) -{ - MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_GET_SESSION_TRANSFER_URL); - request->setText(path); - request->setListener(listener); - requestQueue.push(request); - waiter->notify(); -} - MegaHandle MegaApiImpl::base32ToHandle(const char *base32Handle) { if(!base32Handle) return INVALID_HANDLE; @@ -4576,10 +4703,10 @@ void MegaApiImpl::multiFactorAuthCancelAccount(const char *pin, MegaRequestListe void MegaApiImpl::fastLogin(const char* email, const char *stringHash, const char *base64pwkey, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_LOGIN, listener); - request->setEmail(email); - request->setPassword(stringHash); - request->setPrivateKey(base64pwkey); - requestQueue.push(request); + request->setEmail(email); + request->setPassword(stringHash); + request->setPrivateKey(base64pwkey); + requestQueue.push(request); waiter->notify(); } @@ -4736,16 +4863,6 @@ void MegaApiImpl::createAccount(const char* email, const char* password, const c waiter->notify(); } -void MegaApiImpl::fastCreateAccount(const char* email, const char *base64pwkey, const char* name, MegaRequestListener *listener) -{ - MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_ACCOUNT, listener); - request->setEmail(email); - request->setPrivateKey(base64pwkey); - request->setName(name); - requestQueue.push(request); - waiter->notify(); -} - void MegaApiImpl::resumeCreateAccount(const char *sid, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CREATE_ACCOUNT, listener); @@ -5536,8 +5653,7 @@ void MegaApiImpl::creditCardStore(const char* address1, const char* address2, co if (creditcard) { screditcard = creditcard; - screditcard.erase(remove_if(screditcard.begin(), screditcard.end(), - not1(ptr_fun(static_cast(isdigit)))), screditcard.end()); + screditcard.erase(std::remove_if(screditcard.begin(), screditcard.end(), char_is_not_digit), screditcard.end()); } if (expire_month) @@ -5690,7 +5806,7 @@ void MegaApiImpl::invalidateCache() int MegaApiImpl::getPasswordStrength(const char *password) { - if (!password || strlen(password) < 4) + if (!password || strlen(password) < 8) { return MegaApi::PASSWORD_STRENGTH_VERYWEAK; } @@ -8196,10 +8312,11 @@ void MegaApiImpl::registerPushNotification(int deviceType, const char *token, Me waiter->notify(); } -void MegaApiImpl::sendChatStats(const char *data, MegaRequestListener *listener) +void MegaApiImpl::sendChatStats(const char *data, int port, MegaRequestListener *listener) { MegaRequestPrivate *request = new MegaRequestPrivate(MegaRequest::TYPE_CHAT_STATS, listener); request->setName(data); + request->setNumber(port); request->setParamType(1); requestQueue.push(request); waiter->notify(); @@ -9206,6 +9323,7 @@ void MegaApiImpl::setPublicKeyPinning(bool enable) void MegaApiImpl::pauseActionPackets() { sdkMutex.lock(); + LOG_debug << "Pausing action packets"; client->scpaused = true; sdkMutex.unlock(); } @@ -9213,6 +9331,7 @@ void MegaApiImpl::pauseActionPackets() void MegaApiImpl::resumeActionPackets() { sdkMutex.lock(); + LOG_debug << "Resuming action packets"; client->scpaused = false; sdkMutex.unlock(); } @@ -10000,12 +10119,6 @@ void MegaApiImpl::queryrecoverylink_result(int type, const char *email, const ch request->setText(ip); // not specified in MegaApi documentation request->setNodeHandle(uh); // not specified in MegaApi documentation - const char *link = request->getLink(); - const char* code; - - byte pwkey[SymmCipher::KEYLENGTH]; - const char *mk64; - if (reqType == MegaRequest::TYPE_QUERY_RECOVERY_LINK) { fireOnRequestFinish(request, MegaError()); @@ -10013,50 +10126,11 @@ void MegaApiImpl::queryrecoverylink_result(int type, const char *email, const ch } else if (reqType == MegaRequest::TYPE_CONFIRM_RECOVERY_LINK) { - if ((code = strstr(link, "#recover"))) - { - code += strlen("#recover"); - } - else - { - fireOnRequestFinish(request, MegaError(API_EARGS)); - return; - } - - switch (type) - { - case RECOVER_WITH_MASTERKEY: - { - mk64 = request->getPrivateKey(); - if (!mk64) - { - fireOnRequestFinish(request, MegaError(API_EARGS)); - return; - } - - int creqtag = client->reqtag; - client->reqtag = client->restag; - client->getprivatekey(code); - client->reqtag = creqtag; - break; - } - - case RECOVER_WITHOUT_MASTERKEY: - { - client->pw_key(request->getPassword(), pwkey); - int creqtag = client->reqtag; - client->reqtag = client->restag; - client->confirmrecoverylink(code, email, pwkey); - client->reqtag = creqtag; - break; - } - - default: - LOG_debug << "Unknown type of recovery link"; - - fireOnRequestFinish(request, MegaError(API_EARGS)); - return; - } + int creqtag = client->reqtag; + client->reqtag = client->restag; + client->prelogin(email); + client->reqtag = creqtag; + return; } else if (reqType == MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK) { @@ -10067,23 +10141,41 @@ void MegaApiImpl::queryrecoverylink_result(int type, const char *email, const ch fireOnRequestFinish(request, MegaError(API_EARGS)); return; } - - if ((code = strstr(link, "#verify"))) + + const char* code; + if ((code = strstr(request->getLink(), "#verify"))) { code += strlen("#verify"); + + if (!checkPassword(request->getPassword())) + { + fireOnRequestFinish(request, MegaError(API_ENOENT)); + return; + } + + int creqtag = client->reqtag; + client->reqtag = client->restag; + if (client->accountversion == 1) + { + byte pwkey[SymmCipher::KEYLENGTH]; + client->pw_key(request->getPassword(), pwkey); + client->confirmemaillink(code, request->getEmail(), pwkey); + } + else if (client->accountversion == 2) + { + client->confirmemaillink(code, request->getEmail(), NULL); + } + else + { + LOG_warn << "Version of account not supported"; + fireOnRequestFinish(request, MegaError(API_EINTERNAL)); + } + client->reqtag = creqtag; } else { fireOnRequestFinish(request, MegaError(API_EARGS)); - return; } - - client->pw_key(request->getPassword(), pwkey); - - int creqtag = client->reqtag; - client->reqtag = client->restag; - client->validatepwd(pwkey); - client->reqtag = creqtag; } } @@ -10111,9 +10203,6 @@ void MegaApiImpl::getprivatekey_result(error e, const byte *privk, const size_t return; } - byte pwkey[SymmCipher::KEYLENGTH]; - client->pw_key(request->getPassword(), pwkey); - byte mk[SymmCipher::KEYLENGTH]; Base64::atob(request->getPrivateKey(), mk, sizeof mk); @@ -10129,14 +10218,13 @@ void MegaApiImpl::getprivatekey_result(error e, const byte *privk, const size_t if (!uk.setkey(AsymmCipher::PRIVKEY, privkbuf, len_privk)) { fireOnRequestFinish(request, MegaError(API_EKEY)); + return; } - else - { - int creqtag = client->reqtag; - client->reqtag = client->restag; - client->confirmrecoverylink(code, request->getEmail(), pwkey, mk); - client->reqtag = creqtag; - } + + int creqtag = client->reqtag; + client->reqtag = client->restag; + client->confirmrecoverylink(code, request->getEmail(), request->getPassword(), mk, request->getParamType()); + client->reqtag = creqtag; } void MegaApiImpl::confirmrecoverylink_result(error e) @@ -10157,57 +10245,6 @@ void MegaApiImpl::confirmcancellink_result(error e) fireOnRequestFinish(request, MegaError(e)); } -void MegaApiImpl::validatepassword_result(error e) -{ - if(requestMap.find(client->restag) == requestMap.end()) return; - MegaRequestPrivate* request = requestMap.at(client->restag); - if(!request || ((request->getType() != MegaRequest::TYPE_CONFIRM_CANCEL_LINK) && - (request->getType() != MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK))) return; - - if (e) - { - fireOnRequestFinish(request, MegaError(e)); - return; - } - - if (request->getType() == MegaRequest::TYPE_CONFIRM_CANCEL_LINK) - { - const char *link = request->getLink(); - const char* code; - if ((code = strstr(link, "#cancel"))) - { - code += strlen("#cancel"); - int creqtag = client->reqtag; - client->reqtag = client->restag; - client->confirmcancellink(code); - client->reqtag = creqtag; - } - else - { - fireOnRequestFinish(request, MegaError(API_EARGS)); - } - } - else if (request->getType() == MegaRequest::TYPE_CONFIRM_CHANGE_EMAIL_LINK) - { - byte pwkey[SymmCipher::KEYLENGTH]; - client->pw_key(request->getPassword(), pwkey); - - const char* code; - if ((code = strstr(request->getLink(), "#verify"))) - { - code += strlen("#verify"); - int creqtag = client->reqtag; - client->reqtag = client->restag; - client->confirmemaillink(code, request->getEmail(), pwkey); - client->reqtag = creqtag; - } - else - { - fireOnRequestFinish(request, MegaError(API_EARGS)); - } - } -} - void MegaApiImpl::getemaillink_result(error e) { if(requestMap.find(client->restag) == requestMap.end()) return; @@ -11056,24 +11093,43 @@ void MegaApiImpl::fetchnodes_result(error e) client->putua(ATTR_LASTNAME, (const byte*) request->getText(), strlen(request->getText())); } + // ...and finally send confirmation link + client->reqtag = client->restag; byte pwkey[SymmCipher::KEYLENGTH]; if (!request->getPrivateKey()) { - client->pw_key(request->getPassword(), pwkey); + if (client->nsr_enabled) + { + string derivedKey = client->sendsignuplink2(request->getEmail(), request->getPassword(), request->getName()); + string b64derivedKey; + Base64::btoa(derivedKey, b64derivedKey); + request->setPrivateKey(b64derivedKey.c_str()); + + char buf[SymmCipher::KEYLENGTH * 4 / 3 + 3]; + Base64::btoa((byte*) &client->me, sizeof client->me, buf); + string sid; + sid.append(buf); + sid.append("#"); + Base64::btoa((byte *)derivedKey.data(), SymmCipher::KEYLENGTH, buf); + sid.append(buf); + request->setSessionKey(sid.c_str()); + } + else + { + client->pw_key(request->getPassword(), pwkey); + client->sendsignuplink(request->getEmail(), request->getName(), pwkey); - char* buf = new char[SymmCipher::KEYLENGTH * 4 / 3 + 4]; - Base64::btoa((byte *)pwkey, SymmCipher::KEYLENGTH, buf); - request->setPrivateKey(buf); - delete [] buf; + char* buf = new char[SymmCipher::KEYLENGTH * 4 / 3 + 4]; + Base64::btoa((byte *)pwkey, SymmCipher::KEYLENGTH, buf); + request->setPrivateKey(buf); + delete [] buf; + } } else { Base64::atob(request->getPrivateKey(), (byte *)pwkey, sizeof pwkey); + client->sendsignuplink(request->getEmail(), request->getName(), pwkey); } - - // ...and finally send confirmation link - client->reqtag = client->restag; - client->sendsignuplink(request->getEmail(), request->getName(), pwkey); client->reqtag = creqtag; } } @@ -11084,9 +11140,9 @@ void MegaApiImpl::putnodes_result(error e, targettype_t t, NewNode* nn) handle h = UNDEF; Node *n = NULL; - if(!e && t != USER_HANDLE) + if (!e && t != USER_HANDLE) { - if(client->nodenotify.size()) + if (client->nodenotify.size()) { n = client->nodenotify.back(); } @@ -11113,6 +11169,18 @@ void MegaApiImpl::putnodes_result(error e, targettype_t t, NewNode* nn) pendingUploads--; } + //scale to get the handle of the new node + Node *ntmp; + if (n) + { + handle ph = transfer->getParentHandle(); + for (ntmp = n; ((ntmp->parent != NULL) && (ntmp->parent->nodehandle != ph) ); ntmp = ntmp->parent); + if ((ntmp->parent != NULL) && (ntmp->parent->nodehandle == ph)) + { + h = ntmp->nodehandle; + } + } + transfer->setNodeHandle(h); transfer->setTransferredBytes(transfer->getTotalBytes()); @@ -11152,8 +11220,10 @@ void MegaApiImpl::putnodes_result(error e, targettype_t t, NewNode* nn) if (n) { for (ntmp = n; ((ntmp->parent != NULL) && (ntmp->parent->nodehandle != request->getParentHandle()) ); ntmp = ntmp->parent); - if ((ntmp->parent != NULL) && (ntmp->parent->nodehandle == request->getParentHandle()) ) + if ((ntmp->parent != NULL) && (ntmp->parent->nodehandle == request->getParentHandle())) + { h = ntmp->nodehandle; + } } } @@ -11452,7 +11522,11 @@ void MegaApiImpl::additem_result(error e) //MegaRequest::TYPE_UPGRADE_ACCOUNT int method = int(request->getNumber()); + + int creqtag = client->reqtag; + client->reqtag = client->restag; client->purchase_checkout(method); + client->reqtag = creqtag; } void MegaApiImpl::checkout_result(const char *errortype, error e) @@ -11757,6 +11831,147 @@ void MegaApiImpl::request_response_progress(m_off_t currentProgress, m_off_t tot } } +void MegaApiImpl::prelogin_result(int version, string* email, string *salt, error e) +{ + if(requestMap.find(client->restag) == requestMap.end()) return; + MegaRequestPrivate* request = requestMap.at(client->restag); + if (!request || ((request->getType() != MegaRequest::TYPE_LOGIN) + && (request->getType() != MegaRequest::TYPE_CONFIRM_RECOVERY_LINK))) + { + return; + } + + if (e) + { + fireOnRequestFinish(request, MegaError(e)); + return; + } + + if (request->getType() == MegaRequest::TYPE_LOGIN) + { + const char* pin = request->getText(); + if (version == 1) + { + const char *password = request->getPassword(); + const char* base64pwkey = request->getPrivateKey(); + if (base64pwkey) + { + byte pwkey[SymmCipher::KEYLENGTH]; + Base64::atob(base64pwkey, (byte *)pwkey, sizeof pwkey); + if (password) + { + uint64_t emailhash; + Base64::atob(password, (byte *)&emailhash, sizeof emailhash); + + int creqtag = client->reqtag; + client->reqtag = client->restag; + client->fastlogin(email->c_str(), pwkey, emailhash); + client->reqtag = creqtag; + } + else + { + int creqtag = client->reqtag; + client->reqtag = client->restag; + client->login(email->c_str(), pwkey, pin); + client->reqtag = creqtag; + } + } + else + { + error e; + byte pwkey[SymmCipher::KEYLENGTH]; + if ((e = client->pw_key(password, pwkey))) + { + fireOnRequestFinish(request, MegaError(e)); + return; + } + + int creqtag = client->reqtag; + client->reqtag = client->restag; + client->login(email->c_str(), pwkey, pin); + client->reqtag = creqtag; + } + } + else if (version == 2 && salt) + { + const char *password = request->getPassword(); + const char* base64pwkey = request->getPrivateKey(); + if (base64pwkey) + { + byte derivedKey[2 * SymmCipher::KEYLENGTH]; + Base64::atob(base64pwkey, derivedKey, sizeof derivedKey); + + int creqtag = client->reqtag; + client->reqtag = client->restag; + client->login2(email->c_str(), derivedKey, pin); + client->reqtag = creqtag; + } + else + { + int creqtag = client->reqtag; + client->reqtag = client->restag; + client->login2(email->c_str(), password, salt, pin); + client->reqtag = creqtag; + } + } + else + { + fireOnRequestFinish(request, MegaError(API_EINTERNAL)); + } + } + else if (request->getType() == MegaRequest::TYPE_CONFIRM_RECOVERY_LINK) + { + request->setParamType(version); + const char *link = request->getLink(); + const char* code; + const char *mk64; + + if ((code = strstr(link, "#recover"))) + { + code += strlen("#recover"); + } + else + { + fireOnRequestFinish(request, MegaError(API_EARGS)); + return; + } + + int type = request->getNumber(); + switch (type) + { + case RECOVER_WITH_MASTERKEY: + { + mk64 = request->getPrivateKey(); + if (!mk64) + { + fireOnRequestFinish(request, MegaError(API_EARGS)); + return; + } + + int creqtag = client->reqtag; + client->reqtag = client->restag; + client->getprivatekey(code); + client->reqtag = creqtag; + break; + } + case RECOVER_WITHOUT_MASTERKEY: + { + int creqtag = client->reqtag; + client->reqtag = client->restag; + client->confirmrecoverylink(code, email->c_str(), request->getPassword(), NULL, version); + client->reqtag = creqtag; + break; + } + + default: + LOG_debug << "Unknown type of recovery link"; + + fireOnRequestFinish(request, MegaError(API_EARGS)); + return; + } + } +} + // login result void MegaApiImpl::login_result(error result) { @@ -12113,6 +12328,7 @@ void MegaApiImpl::openfilelink_result(handle ph, const byte* key, m_off_t size, { if (ffp.isvalid && ovn->isvalid && ffp == *(FileFingerprint*)ovn) { + request->setNodeHandle(ovn->nodehandle); fireOnRequestFinish(request, MegaError(API_OK)); return; } @@ -12395,7 +12611,8 @@ void MegaApiImpl::getua_result(byte* data, unsigned len) else if (attrType == MegaApi::USER_ATTR_PWD_REMINDER) { m_time_t currenttime = m_time(); - if (!User::getPwdReminderData(User::PWD_MK_EXPORTED, (const char*)data, len) + bool isMasterKeyExported = User::getPwdReminderData(User::PWD_MK_EXPORTED, (const char*)data, len); + if (!isMasterKeyExported && !User::getPwdReminderData(User::PWD_DONT_SHOW, (const char*)data, len) && (currenttime - client->accountsince) > User::PWD_SHOW_AFTER_ACCOUNT_AGE && (currenttime - User::getPwdReminderData(User::PWD_LAST_SUCCESS, (const char*)data, len)) > User::PWD_SHOW_AFTER_LASTSUCCESS @@ -12405,6 +12622,7 @@ void MegaApiImpl::getua_result(byte* data, unsigned len) { request->setFlag(true); // the password reminder dialog should be shown } + request->setAccess(isMasterKeyExported ? 1 : 0); } } break; @@ -12805,7 +13023,7 @@ void MegaApiImpl::querysignuplink_result(handle, const char* email, const char* if (*(uint64_t*)(signuppwchallenge+4)) { - fireOnRequestFinish(request, MegaError(API_ENOENT)); + fireOnRequestFinish(request, MegaError(API_EKEY)); } else { @@ -12823,11 +13041,28 @@ void MegaApiImpl::confirmsignuplink_result(error e) MegaError megaError(e); if(requestMap.find(client->restag) == requestMap.end()) return; MegaRequestPrivate* request = requestMap.at(client->restag); - if(!request) return; + if(!request || (request->getType() != MegaRequest::TYPE_CONFIRM_ACCOUNT)) return; fireOnRequestFinish(request, megaError); } +void MegaApiImpl::confirmsignuplink2_result(handle, const char *name, const char *email, error e) +{ + MegaError megaError(e); + if (requestMap.find(client->restag) == requestMap.end()) return; + MegaRequestPrivate* request = requestMap.at(client->restag); + if (!request || ((request->getType() != MegaRequest::TYPE_CONFIRM_ACCOUNT) && + (request->getType() != MegaRequest::TYPE_QUERY_SIGNUP_LINK))) return; + + if (!e) + { + request->setName(name); + request->setEmail(email); + request->setFlag(true); + } + fireOnRequestFinish(request, megaError); +} + void MegaApiImpl::setkeypair_result(error) { @@ -13922,11 +14157,11 @@ bool MegaApiImpl::nodeComparatorDefaultDESC(Node *i, Node *j) { if (i->type < j->type) { - return 1; + return 0; } if (i->type > j->type) { - return 0; + return 1; } if (naturalsorting_compare(i->displayname(), j->displayname()) <= 0) { @@ -15503,7 +15738,6 @@ void MegaApiImpl::sendPendingRequests() const char* megaFolderLink = request->getLink(); const char* base64pwkey = request->getPrivateKey(); const char* sessionKey = request->getSessionKey(); - const char* pin = request->getText(); if (!megaFolderLink && (!(login && password)) && !sessionKey && (!(login && base64pwkey))) { @@ -15512,11 +15746,11 @@ void MegaApiImpl::sendPendingRequests() } string slogin; - if(login) + if (login) { slogin = login; - slogin.erase(slogin.begin(), std::find_if(slogin.begin(), slogin.end(), std::not1(std::ptr_fun(std::isspace)))); - slogin.erase(std::find_if(slogin.rbegin(), slogin.rend(), std::not1(std::ptr_fun(std::isspace))).base(), slogin.end()); + slogin.erase(slogin.begin(), std::find_if(slogin.begin(), slogin.end(), char_is_not_space)); + slogin.erase(std::find_if(slogin.rbegin(), slogin.rend(), char_is_not_space).base(), slogin.end()); } requestMap.erase(request->getTag()); @@ -15548,33 +15782,16 @@ void MegaApiImpl::sendPendingRequests() requestMap[request->getTag()]=request; - if(sessionKey) + client->locallogout(); + if (sessionKey) { byte session[MAX_SESSION_LENGTH]; int size = Base64::atob(sessionKey, (byte *)session, sizeof session); client->login(session, size); } - else if(login && base64pwkey) - { - byte pwkey[SymmCipher::KEYLENGTH]; - Base64::atob(base64pwkey, (byte *)pwkey, sizeof pwkey); - - if(password) - { - uint64_t emailhash; - Base64::atob(password, (byte *)&emailhash, sizeof emailhash); - client->fastlogin(slogin.c_str(), pwkey, emailhash); - } - else - { - client->login(slogin.c_str(), pwkey); - } - } - else if(login && password) + else if (login && (base64pwkey || password)) { - byte pwkey[SymmCipher::KEYLENGTH]; - if((e = client->pw_key(password,pwkey))) break; - client->login(slogin.c_str(), pwkey, pin); + client->prelogin(slogin.c_str()); } else { @@ -15845,6 +16062,7 @@ void MegaApiImpl::sendPendingRequests() { if (fp->isvalid && ovn->isvalid && *fp == *(FileFingerprint*)ovn) { + request->setNodeHandle(ovn->nodehandle); fireOnRequestFinish(request, MegaError(API_OK)); delete fp; break; @@ -15925,6 +16143,7 @@ void MegaApiImpl::sendPendingRequests() { if (node->isvalid && ovn->isvalid && *(FileFingerprint*)node == *(FileFingerprint*)ovn) { + request->setNodeHandle(ovn->nodehandle); fireOnRequestFinish(request, MegaError(API_OK)); break; } @@ -16219,18 +16438,14 @@ void MegaApiImpl::sendPendingRequests() break; } - byte newpwkey[SymmCipher::KEYLENGTH]; if (oldPassword && !checkPassword(oldPassword)) { e = API_EARGS; break; } - if ((e = client->pw_key(newPassword, newpwkey))) - { - break; - } - e = client->changepw(newpwkey, pin); - break; + + e = client->changepw(newPassword, pin); + break; } case MegaRequest::TYPE_LOGOUT: { @@ -16921,6 +17136,7 @@ void MegaApiImpl::sendPendingRequests() requestMap[request->getTag()]=request; + client->locallogout(); if (resumeProcess) { client->resumeephemeral(uh, pwbuf); @@ -16938,38 +17154,41 @@ void MegaApiImpl::sendPendingRequests() const char *base64pwkey = request->getPrivateKey(); const char *name = request->getName(); - if(client->loggedin() != EPHEMERALACCOUNT) + if (client->loggedin() != EPHEMERALACCOUNT) { e = API_EACCESS; break; } - if (!email || !name || (!password && !base64pwkey)) + if (!email || !name || (client->accountversion == 1 && !base64pwkey && !password)) { e = API_EARGS; break; } - - byte pwkey[SymmCipher::KEYLENGTH]; - if (password) + if (client->accountversion == 1) { - e = client->pw_key(password, pwkey); - } - else // pwcipher provided - { - if (Base64::atob(base64pwkey, (byte *)pwkey, sizeof pwkey) != sizeof pwkey) + byte pwkey[SymmCipher::KEYLENGTH]; + if (password) + { + client->pw_key(password, pwkey); + } + else if (Base64::atob(base64pwkey, (byte *)pwkey, sizeof pwkey) != SymmCipher::KEYLENGTH) { e = API_EARGS; + break; } + client->sendsignuplink(email, name, pwkey); } - - if (e) + else if (client->accountversion == 2) { + client->resendsignuplink2(email, name); + } + else + { + e = API_EINTERNAL; break; } - - client->sendsignuplink(email, name, pwkey); break; } case MegaRequest::TYPE_QUERY_SIGNUP_LINK: @@ -16993,7 +17212,14 @@ void MegaApiImpl::sendPendingRequests() len = Base64::atob(ptr,c,len); if (len) { - client->querysignuplink(c,len); + if (len > 13 && !memcmp("ConfirmCodeV2", c, 13)) + { + client->confirmsignuplink2(c, len); + } + else + { + client->querysignuplink(c, len); + } } else { @@ -17046,7 +17272,7 @@ void MegaApiImpl::sendPendingRequests() const char *password = request->getPassword(); const char *pwkey = request->getPrivateKey(); - if(!link || (request->getType() == MegaRequest::TYPE_CONFIRM_ACCOUNT && !password && !pwkey)) + if (!link) { e = API_EARGS; break; @@ -17062,7 +17288,18 @@ void MegaApiImpl::sendPendingRequests() len = Base64::atob(ptr,c,len); if (len) { - client->querysignuplink(c,len); + if (len > 13 && !memcmp("ConfirmCodeV2", c, 13)) + { + client->confirmsignuplink2(c, len); + } + else if (!password && !pwkey) + { + e = API_EARGS; + } + else + { + client->querysignuplink(c, len); + } } else { @@ -17158,6 +17395,7 @@ void MegaApiImpl::sendPendingRequests() if (client->loggedin() != FULLACCOUNT) { e = API_EACCESS; + break; } const char* code; @@ -17166,12 +17404,15 @@ void MegaApiImpl::sendPendingRequests() e = API_EARGS; break; } + + if (!checkPassword(pwd)) + { + e = API_ENOENT; + break; + } - byte pwkey[SymmCipher::KEYLENGTH]; - client->pw_key(pwd, pwkey); - - // concatenate login + confirm requests - e = client->validatepwd(pwkey); + code += strlen("#cancel"); + client->confirmcancellink(code); break; } case MegaRequest::TYPE_GET_CHANGE_EMAIL_LINK: @@ -17201,6 +17442,7 @@ void MegaApiImpl::sendPendingRequests() if (client->loggedin() != FULLACCOUNT) { e = API_EACCESS; + break; } const char* code; @@ -17877,7 +18119,7 @@ void MegaApiImpl::sendPendingRequests() int number = int(request->getNumber()); const char *text = request->getText(); - if(number < 99500 || number >= 99600 || !text) + if(number < 99000 || (number >= 99150 && (number < 99500 || number >= 99600)) || !text) { e = API_EARGS; break; @@ -18208,7 +18450,14 @@ void MegaApiImpl::sendPendingRequests() int type = request->getParamType(); if (type == 1) { - client->sendchatstats(json); + int port = request->getNumber(); + if (port < 0 || port > 65535) + { + e = API_EARGS; + break; + } + + client->sendchatstats(json, port); } else if (type == 2) { @@ -18411,6 +18660,7 @@ void MegaApiImpl::update() << " " << client->syncscanstate << " " << client->statecurrent << " " << client->syncadding << " " << client->syncdebrisadding << " " << client->umindex.size() << " " << client->uhindex.size(); + LOG_debug << "UL speed: " << httpio->uploadSpeed << " DL speed: " << httpio->downloadSpeed; sdkMutex.unlock(); #endif @@ -18988,7 +19238,7 @@ void ExternalLogger::log(const char *time, int loglevel, const char *source, con if (logToConsole) { - cout << "[" << time << "][" << SimpleLogger::toStr((LogLevel)loglevel) << "] " << message << endl; + std::cout << "[" << time << "][" << SimpleLogger::toStr((LogLevel)loglevel) << "] " << message << std::endl; } mutex.unlock(); } @@ -20420,7 +20670,7 @@ int64_t MegaBackupController::getLastBackupTime() if (timeofbackup) { backupTimesPaths[timeofbackup]=childNode; - latesttime = max(latesttime, timeofbackup); + latesttime = (std::max)(latesttime, timeofbackup); } else { @@ -20689,7 +20939,7 @@ void MegaBackupController::start(bool skip) string backupname = ossremotename.str(); currentName = backupname; - lastbackuptime = max(lastbackuptime,offsetds+startTime); + lastbackuptime = (std::max)(lastbackuptime,offsetds+startTime); megaApi->fireOnBackupStart(this); @@ -24588,8 +24838,9 @@ void MegaFTPServer::getPermissionsString(int permissions, char *permsString) bool write = (curperm >> 1) & 0x1; bool exec = (curperm >> 0) & 0x1; - char rwx[3]; + char rwx[4]; sprintf(rwx,"%c%c%c",read?'r':'-' ,write?'w':'-', exec?'x':'-'); + rwx[3]='\0'; ps = rwx + ps; } strcat(permsString, ps.c_str()); @@ -27879,3 +28130,5 @@ MegaFolderInfo *TreeProcFolderInfo::getResult() { return new MegaFolderInfoPrivate(numFiles, numFolders - 1, numVersions, currentSize, versionsSize); } + +} diff --git a/src/megaclient.cpp b/src/megaclient.cpp index b02965b153..fc2f2daa28 100644 --- a/src/megaclient.cpp +++ b/src/megaclient.cpp @@ -21,6 +21,7 @@ #include "mega.h" #include "mega/mediafileattribute.h" +#include namespace mega { @@ -38,7 +39,7 @@ string MegaClient::APIURL = "https://g.api.mega.co.nz/"; string MegaClient::GELBURL = "https://gelb.karere.mega.nz/"; // root URL for chat stats -string MegaClient::CHATSTATSURL = "https://stats.karere.mega.nz/"; +string MegaClient::CHATSTATSURL = "https://stats.karere.mega.nz"; // maximum number of concurrent transfers (uploads + downloads) const unsigned MegaClient::MAXTOTALTRANSFERS = 30; @@ -72,6 +73,9 @@ const char MegaClient::PAYMENT_PUBKEY[] = // default number of seconds to wait after a bandwidth overquota dstime MegaClient::DEFAULT_BW_OVERQUOTA_BACKOFF_SECS = 3600; +// default number of seconds to wait after a bandwidth overquota +dstime MegaClient::USER_DATA_EXPIRATION_BACKOFF_SECS = 86400; // 1 day + // stats id char* MegaClient::statsid = NULL; @@ -87,7 +91,7 @@ bool MegaClient::decryptkey(const char* sk, byte* tk, int tl, SymmCipher* sc, in ptr++; } - sl = ptr - sk; + sl = int(ptr - sk); if (sl > 4 * FILENODEKEYLENGTH / 3 + 1) { @@ -195,10 +199,7 @@ void MegaClient::mergenewshare(NewShare *s, bool notify) { if (!fetchingnodes) { - int creqtag = reqtag; - reqtag = 0; - sendevent(99428,"Replacing share key"); - reqtag = creqtag; + sendevent(99428,"Replacing share key", 0); } delete n->sharekey; } @@ -471,7 +472,7 @@ void MegaClient::setsid(const byte* newsid, unsigned len) { auth = "&sid="; - int t = auth.size(); + size_t t = auth.size(); auth.resize(t + len * 4 / 3 + 4); auth.resize(t + Base64::btoa(newsid, len, (char*)(auth.c_str() + t))); @@ -663,38 +664,95 @@ void MegaClient::getprivatekey(const char *code) reqs.add(new CommandGetPrivateKey(this, code)); } -void MegaClient::confirmrecoverylink(const char *code, const char *email, const byte *pwkey, const byte *masterkey) +void MegaClient::confirmrecoverylink(const char *code, const char *email, const char *password, const byte *masterkey, int accountversion) { - SymmCipher pwcipher(pwkey); + if (accountversion == 1) + { + byte pwkey[SymmCipher::KEYLENGTH]; + pw_key(password, pwkey); + SymmCipher pwcipher(pwkey); - string emailstr = email; - uint64_t loginHash = stringhash64(&emailstr, &pwcipher); + string emailstr = email; + uint64_t loginHash = stringhash64(&emailstr, &pwcipher); - if (masterkey) - { - // encrypt provided masterkey using the new password - byte encryptedMasterKey[SymmCipher::KEYLENGTH]; - memcpy(encryptedMasterKey, masterkey, sizeof encryptedMasterKey); - pwcipher.ecb_encrypt(encryptedMasterKey); + if (masterkey) + { + // encrypt provided masterkey using the new password + byte encryptedMasterKey[SymmCipher::KEYLENGTH]; + memcpy(encryptedMasterKey, masterkey, sizeof encryptedMasterKey); + pwcipher.ecb_encrypt(encryptedMasterKey); - reqs.add(new CommandConfirmRecoveryLink(this, code, loginHash, encryptedMasterKey, NULL)); + reqs.add(new CommandConfirmRecoveryLink(this, code, (byte*)&loginHash, sizeof(loginHash), NULL, encryptedMasterKey, NULL)); + } + else + { + // create a new masterkey + byte masterkey[SymmCipher::KEYLENGTH]; + PrnGen::genblock(masterkey, sizeof masterkey); + + // generate a new session + byte initialSession[2 * SymmCipher::KEYLENGTH]; + PrnGen::genblock(initialSession, sizeof initialSession); + key.setkey(masterkey); + key.ecb_encrypt(initialSession, initialSession + SymmCipher::KEYLENGTH, SymmCipher::KEYLENGTH); + + // and encrypt the master key to the new password + pwcipher.ecb_encrypt(masterkey); + + reqs.add(new CommandConfirmRecoveryLink(this, code, (byte*)&loginHash, sizeof(loginHash), NULL, masterkey, initialSession)); + } } else { - // create a new masterkey - byte masterkey[SymmCipher::KEYLENGTH]; - PrnGen::genblock(masterkey, sizeof masterkey); + byte clientkey[SymmCipher::KEYLENGTH]; + PrnGen::genblock(clientkey, sizeof(clientkey)); + + string salt; + HashSHA256 hasher; + string buffer = "mega.nz"; + buffer.resize(200, 'P'); + buffer.append((char *)clientkey, sizeof(clientkey)); + hasher.add((const byte*)buffer.data(), unsigned(buffer.size())); + hasher.get(&salt); - // generate a new session - byte initialSession[2 * SymmCipher::KEYLENGTH]; - PrnGen::genblock(initialSession, sizeof initialSession); - key.setkey(masterkey); - key.ecb_encrypt(initialSession, initialSession + SymmCipher::KEYLENGTH, SymmCipher::KEYLENGTH); + byte derivedKey[2 * SymmCipher::KEYLENGTH]; + CryptoPP::PKCS5_PBKDF2_HMAC pbkdf2; + pbkdf2.DeriveKey(derivedKey, sizeof(derivedKey), 0, (byte *)password, strlen(password), + (const byte *)salt.data(), salt.size(), 100000); - // and encrypt the master key to the new password - pwcipher.ecb_encrypt(masterkey); + string hashedauthkey; + byte *authkey = derivedKey + SymmCipher::KEYLENGTH; + hasher.add(authkey, SymmCipher::KEYLENGTH); + hasher.get(&hashedauthkey); + hashedauthkey.resize(SymmCipher::KEYLENGTH); - reqs.add(new CommandConfirmRecoveryLink(this, code, loginHash, masterkey, initialSession)); + SymmCipher cipher; + cipher.setkey(derivedKey); + + if (masterkey) + { + // encrypt provided masterkey using the new password + byte encryptedMasterKey[SymmCipher::KEYLENGTH]; + memcpy(encryptedMasterKey, masterkey, sizeof encryptedMasterKey); + cipher.ecb_encrypt(encryptedMasterKey); + reqs.add(new CommandConfirmRecoveryLink(this, code, (byte*)hashedauthkey.data(), SymmCipher::KEYLENGTH, clientkey, encryptedMasterKey, NULL)); + } + else + { + // create a new masterkey + byte masterkey[SymmCipher::KEYLENGTH]; + PrnGen::genblock(masterkey, sizeof masterkey); + + // generate a new session + byte initialSession[2 * SymmCipher::KEYLENGTH]; + PrnGen::genblock(initialSession, sizeof initialSession); + key.setkey(masterkey); + key.ecb_encrypt(initialSession, initialSession + SymmCipher::KEYLENGTH, SymmCipher::KEYLENGTH); + + // and encrypt the master key to the new password + cipher.ecb_encrypt(masterkey); + reqs.add(new CommandConfirmRecoveryLink(this, code, (byte*)hashedauthkey.data(), SymmCipher::KEYLENGTH, clientkey, masterkey, initialSession)); + } } } @@ -715,12 +773,17 @@ void MegaClient::getemaillink(const char *email, const char *pin) void MegaClient::confirmemaillink(const char *code, const char *email, const byte *pwkey) { - SymmCipher pwcipher(pwkey); - - string emailstr = email; - uint64_t loginHash = stringhash64(&emailstr, &pwcipher); - - reqs.add(new CommandConfirmEmailLink(this, code, email, loginHash, true)); + if (pwkey) + { + SymmCipher pwcipher(pwkey); + string emailstr = email; + uint64_t loginHash = stringhash64(&emailstr, &pwcipher); + reqs.add(new CommandConfirmEmailLink(this, code, email, (const byte*)&loginHash, true)); + } + else + { + reqs.add(new CommandConfirmEmailLink(this, code, email, NULL, true)); + } } void MegaClient::contactlinkcreate(bool renew) @@ -876,9 +939,13 @@ MegaClient::MegaClient(MegaApp* a, Waiter* w, HttpIO* h, FileSystemAccess* f, Db tsLogin = 0; versions_disabled = false; accountsince = 0; + accountversion = 0; gmfa_enabled = false; gfxdisabled = false; ssrs_enabled = false; + nsr_enabled = false; + loggingout = 0; + cachedug = false; #ifndef EMSCRIPTEN autodownport = true; @@ -1027,10 +1094,7 @@ void MegaClient::exec() { if (disconnecttimestamp <= Waiter::ds) { - int creqtag = reqtag; - reqtag = 0; - sendevent(99427, "Timeout (server idle)"); - reqtag = creqtag; + sendevent(99427, "Timeout (server idle)", 0); disconnect(); } @@ -1072,6 +1136,12 @@ void MegaClient::exec() looprequested = false; + if (cachedug && btugexpiration.armed()) + { + LOG_debug << "Cached user data expired"; + getuserdata(); + } + if (pendinghttp.size()) { pendinghttp_map::iterator it = pendinghttp.begin(); @@ -1098,7 +1168,7 @@ void MegaClient::exec() app->http_result(req->httpstatus ? API_OK : API_EFAILED, req->httpstatus, req->buf ? (byte *)req->buf : (byte *)req->in.data(), - req->buf ? req->bufpos : req->in.size()); + int(req->buf ? req->bufpos : req->in.size())); delete req; pendinghttp.erase(it++); break; @@ -1221,10 +1291,7 @@ void MegaClient::exec() // reduce the number of required attributes to let the upload continue transfer->minfa--; checkfacompletion(fa->th); - int creqtag = reqtag; - reqtag = 0; - sendevent(99407,"Attribute attach failed during active upload"); - reqtag = creqtag; + sendevent(99407,"Attribute attach failed during active upload", 0); } else { @@ -1295,10 +1362,7 @@ void MegaClient::exec() usehttps = true; app->notify_change_to_https(); - int creqtag = reqtag; - reqtag = 0; - sendevent(99436, "Automatic change to HTTPS"); - reqtag = creqtag; + sendevent(99436, "Automatic change to HTTPS", 0); } else { @@ -1344,10 +1408,7 @@ void MegaClient::exec() usehttps = true; app->notify_change_to_https(); - int creqtag = reqtag; - reqtag = 0; - sendevent(99436, "Automatic change to HTTPS"); - reqtag = creqtag; + sendevent(99436, "Automatic change to HTTPS", 0); } fc->failed(this); @@ -1444,6 +1505,7 @@ void MegaClient::exec() delete pendingcs; pendingcs = NULL; + notifypurge(); if (sctable && pendingsccommit && !reqs.cmdspending()) { LOG_debug << "Executing postponed DB commit"; @@ -1608,183 +1670,106 @@ void MegaClient::exec() } // handle API server-client requests - if (!jsonsc.pos && pendingsc) + if (!jsonsc.pos && pendingsc && !loggingout) { - if (scnotifyurl.size()) + switch (pendingsc->status) { - // pendingsc is a scnotifyurl connection - switch (pendingsc->status) + case REQ_SUCCESS: + if (pendingsc->contentlength == 1 + && pendingsc->in.size() + && pendingsc->in[0] == '0') { - case REQ_SUCCESS: - if (pendingsc->contenttype.find("text/html") != string::npos - && !memcmp(pendingsc->posturl.c_str(), "http:", 5)) - { - LOG_warn << "Invalid Content-Type detected in connection to waitd: " << pendingsc->contenttype; - usehttps = true; - app->notify_change_to_https(); - - int creqtag = reqtag; - reqtag = 0; - sendevent(99436, "Automatic change to HTTPS"); - reqtag = creqtag; - - // go to API - delete pendingsc; - pendingsc = NULL; - scnotifyurl.clear(); - btsc.reset(); - break; - } - - if (pendingsc->contentlength == 1 - && pendingsc->in.size() - && pendingsc->in[0] == '0') - { - LOG_debug << "Waitd keep-alive received"; - delete pendingsc; - pendingsc = NULL; - if (Waiter::ds >= (scnotifyurlts + HttpIO::NETWORKTIMEOUT)) - { - LOG_debug << "Waitd timeout expired after keep-alive"; - scnotifyurl.clear(); - } - btsc.reset(); - break; - } + LOG_debug << "Waitd keep-alive received"; + delete pendingsc; + pendingsc = NULL; + btsc.reset(); + break; + } - if (pendingsc->contentlength == 0) + if (*pendingsc->in.c_str() == '{') + { + jsonsc.begin(pendingsc->in.c_str()); + jsonsc.enterobject(); + break; + } + else + { + error e = (error)atoi(pendingsc->in.c_str()); + if (e == API_ESID) { - LOG_debug << "Waitd connection closed"; - delete pendingsc; - pendingsc = NULL; - scnotifyurl.clear(); - btsc.reset(); - break; + app->request_error(API_ESID); + *scsn = 0; } - - LOG_err << "Unexpected response from waitd"; - // fall through - case REQ_FAILURE: - if (pendingsc->contenttype.find("text/html") != string::npos - && !memcmp(pendingsc->posturl.c_str(), "http:", 5)) + else if (e == API_ETOOMANY) { - LOG_warn << "Invalid Content-Type detected in failed connection to waitd: " << pendingsc->contenttype; - usehttps = true; - app->notify_change_to_https(); - + LOG_warn << "Too many pending updates - reloading local state"; int creqtag = reqtag; - reqtag = 0; - sendevent(99436, "Automatic change to HTTPS"); + reqtag = fetchnodestag; // associate with ongoing request, if any + fetchingnodes = false; + fetchnodestag = 0; + fetchnodes(true); reqtag = creqtag; - - // go to API - delete pendingsc; - pendingsc = NULL; - scnotifyurl.clear(); - btsc.reset(); - break; } - - delete pendingsc; - pendingsc = NULL; - if (Waiter::ds >= (scnotifyurlts + HttpIO::NETWORKTIMEOUT)) + else if (e == API_EAGAIN || e == API_ERATELIMIT) { - LOG_debug << "Waitd timeout expired after a failed request"; - scnotifyurl.clear(); - btsc.reset(); + if (!statecurrent) + { + fnstats.eAgainCount++; + } } else { - LOG_debug << "Waitd request error, retrying..."; - btsc.backoff(); - } - break; - - case REQ_INFLIGHT: - if (Waiter::ds >= (pendingsc->lastdata + HttpIO::WAITREQUESTTIMEOUT)) - { - LOG_debug << "Waitd timeout expired"; - delete pendingsc; - pendingsc = NULL; - scnotifyurl.clear(); - btsc.reset(); + LOG_err << "Unexpected sc response: " << pendingsc->in; } - break; } - } - else - { - // pendingsc is a server-client API request - switch (pendingsc->status) + + // fall through + case REQ_FAILURE: + if (pendingsc) { - case REQ_SUCCESS: - if (*pendingsc->in.c_str() == '{') + if (!statecurrent && pendingsc->httpstatus != 200) + { + if (pendingsc->httpstatus == 500) { - jsonsc.begin(pendingsc->in.c_str()); - jsonsc.enterobject(); - break; + fnstats.e500Count++; } else { - error e = (error)atoi(pendingsc->in.c_str()); - if (e == API_ESID) - { - app->request_error(API_ESID); - *scsn = 0; - } - else if (e == API_ETOOMANY) - { - LOG_warn << "Too many pending updates - reloading local state"; - int creqtag = reqtag; - reqtag = fetchnodestag; // associate with ongoing request, if any - fetchingnodes = false; - fetchnodestag = 0; - fetchnodes(true); - reqtag = creqtag; - } - else if (e == API_EAGAIN || e == API_ERATELIMIT) - { - if (!statecurrent) - { - fnstats.eAgainCount++; - } - } - } - // fall through - case REQ_FAILURE: - if (pendingsc && !statecurrent && pendingsc->httpstatus != 200) - { - if (pendingsc->httpstatus == 500) - { - fnstats.e500Count++; - } - else - { - fnstats.eOthersCount++; - } + fnstats.eOthersCount++; } + } - if (pendingsc && pendingsc->sslcheckfailed) - { - sslfakeissuer = pendingsc->sslfakeissuer; - app->request_error(API_ESSL); - sslfakeissuer.clear(); + if (pendingsc->sslcheckfailed) + { + sslfakeissuer = pendingsc->sslfakeissuer; + app->request_error(API_ESSL); + sslfakeissuer.clear(); - if (!retryessl) - { - *scsn = 0; - } + if (!retryessl) + { + *scsn = 0; } + } - // failure, repeat with capped exponential backoff - delete pendingsc; - pendingsc = NULL; + delete pendingsc; + pendingsc = NULL; + } - btsc.backoff(); + // failure, repeat with capped exponential backoff + btsc.backoff(); + break; - default: - ; + case REQ_INFLIGHT: + if (Waiter::ds >= (pendingsc->lastdata + HttpIO::SCREQUESTTIMEOUT)) + { + LOG_debug << "sc timeout expired"; + delete pendingsc; + pendingsc = NULL; + btsc.reset(); } + break; + default: + break; } } @@ -1831,21 +1816,17 @@ void MegaClient::exec() } else { - pendingsc->protect = true; pendingsc->posturl = APIURL; - pendingsc->posturl.append("sc?sn="); - pendingsc->posturl.append(scsn); - pendingsc->posturl.append(auth); - - if (usehttps) - { - pendingsc->posturl.append("&ssl=1"); - } + pendingsc->posturl.append("wsc"); + scnotifyurl = pendingsc->posturl; } + pendingsc->protect = true; + pendingsc->posturl.append("?sn="); + pendingsc->posturl.append(scsn); + pendingsc->posturl.append(auth); pendingsc->type = REQ_JSON; pendingsc->post(this); - jsonsc.pos = NULL; } @@ -1883,10 +1864,7 @@ void MegaClient::exec() } else if (workinglockcs->in == "0") { - int creqtag = reqtag; - reqtag = 0; - sendevent(99425, "Timeout (server busy)"); - reqtag = creqtag; + sendevent(99425, "Timeout (server busy)", 0); pendingcs->lastdata = Waiter::ds; } @@ -2153,8 +2131,8 @@ void MegaClient::exec() } } - unsigned totalpending = 0; - unsigned scanningpending = 0; + size_t totalpending = 0; + size_t scanningpending = 0; for (int q = DirNotify::RETRY; q >= DirNotify::DIREVENTS; q--) { for (it = syncs.begin(); it != syncs.end(); ) @@ -2262,10 +2240,15 @@ void MegaClient::exec() if (localsyncnotseen.size() && !synccreate.size()) { // ... execute all pending deletions + string path; + FileAccess *fa = fsaccess->newfileaccess(); while (localsyncnotseen.size()) { - delete *localsyncnotseen.begin(); + LocalNode* l = *localsyncnotseen.begin(); + unlinkifexists(l, fa, &path); + delete l; } + delete fa; } // process filesystem notifications for active syncs unless we @@ -2649,6 +2632,11 @@ int MegaClient::preparewait() } } + if (cachedug) + { + btugexpiration.update(&nds); + } + #ifdef ENABLE_SYNC // sync rescan if (syncscanfailed) @@ -2752,9 +2740,9 @@ int MegaClient::preparewait() } } - if (!jsonsc.pos && scnotifyurl.size() && pendingsc && pendingsc->status == REQ_INFLIGHT) + if (!jsonsc.pos && pendingsc && pendingsc->status == REQ_INFLIGHT) { - dstime timeout = pendingsc->lastdata + HttpIO::WAITREQUESTTIMEOUT; + dstime timeout = pendingsc->lastdata + HttpIO::SCREQUESTTIMEOUT; if (timeout > Waiter::ds && timeout < nds) { nds = timeout; @@ -3050,7 +3038,7 @@ bool MegaClient::dispatch(direction_t d) nexttransfer->pos = 0; nexttransfer->progresscompleted = 0; - if (d == GET || nexttransfer->cachedtempurl.size()) + if (d == GET || nexttransfer->tempurl.size()) { m_off_t p = 0; @@ -3084,8 +3072,10 @@ bool MegaClient::dispatch(direction_t d) nexttransfer->progresscompleted = nexttransfer->size; } + ts->updatecontiguousprogress(); LOG_debug << "Resuming transfer at " << nexttransfer->pos << " Completed: " << nexttransfer->progresscompleted + << " Contiguous: " << ts->progresscontiguous << " Partial: " << p << " Size: " << nexttransfer->size << " ultoken: " << (nexttransfer->ultoken != NULL); } @@ -3139,11 +3129,9 @@ bool MegaClient::dispatch(direction_t d) } // dispatch request for temporary source/target URL - if (nexttransfer->cachedtempurl.size()) + if (nexttransfer->tempurl.size()) { app->transfer_prepare(nexttransfer); - ts->tempurl = nexttransfer->cachedtempurl; - nexttransfer->cachedtempurl.clear(); } else { @@ -3376,6 +3364,7 @@ void MegaClient::logout() return; } + loggingout++; reqs.add(new CommandLogout(this)); } @@ -3397,6 +3386,9 @@ void MegaClient::locallogout() accountsince = 0; gmfa_enabled = false; ssrs_enabled = false; + nsr_enabled = false; + loggingout = 0; + cachedug = false; freeq(GET); freeq(PUT); @@ -3470,6 +3462,8 @@ void MegaClient::locallogout() memset((char*)auth.c_str(), 0, auth.size()); auth.clear(); sessionkey.clear(); + accountversion = 0; + accountsalt.clear(); sid.clear(); k.clear(); @@ -3559,14 +3553,21 @@ void MegaClient::gelbrequest(const char *service, int timeoutds, int retries) req->get(this); } -void MegaClient::sendchatstats(const char *json) +void MegaClient::sendchatstats(const char *json, int port) { GenericHttpReq *req = new GenericHttpReq(); req->tag = reqtag; req->maxretries = 0; pendinghttp[reqtag] = req; req->posturl = CHATSTATSURL; - req->posturl.append("stats"); + if (port > 0) + { + req->posturl.append(":"); + char stringPort[6]; + sprintf(stringPort, "%d", port); + req->posturl.append(stringPort); + } + req->posturl.append("/stats"); req->protect = true; req->out->assign(json); req->post(this); @@ -3579,7 +3580,7 @@ void MegaClient::sendchatlogs(const char *json, const char *aid) req->maxretries = 0; pendinghttp[reqtag] = req; req->posturl = CHATSTATSURL; - req->posturl.append("msglog?aid="); + req->posturl.append("/msglog?aid="); req->posturl.append(aid); req->posturl.append("&t=e"); req->protect = true; @@ -3629,6 +3630,35 @@ bool MegaClient::procsc() switch (jsonsc.getnameid()) { case 'w': + jsonsc.storeobject(&scnotifyurl); + break; + + case MAKENAMEID2('s', 'n'): + // the sn element is guaranteed to be the last in sequence + setscsn(&jsonsc); + notifypurge(); + if (sctable) + { + if (!pendingcs && !csretrying && !reqs.cmdspending()) + { + sctable->commit(); + sctable->begin(); + app->notify_dbcommit(); + pendingsccommit = false; + } + else + { + LOG_debug << "Postponing DB commit until cs requests finish"; + pendingsccommit = true; + } + } + break; + + case EOO: + LOG_debug << "Processing of action packets finished"; + mergenewshares(1); + applykeys(); + if (!statecurrent) { if (fetchingnodes) @@ -3696,58 +3726,26 @@ bool MegaClient::procsc() string report; fnstats.toJsonArray(&report); - int creqtag = reqtag; - reqtag = 0; - sendevent(99426, report.c_str()); - reqtag = creqtag; + sendevent(99426, report.c_str(), 0); // NULL vector: "notify all elements" - app->nodes_updated(NULL, nodes.size()); - app->users_updated(NULL, users.size()); - app->pcrs_updated(NULL, pcrindex.size()); + app->nodes_updated(NULL, int(nodes.size())); + app->users_updated(NULL, int(users.size())); + app->pcrs_updated(NULL, int(pcrindex.size())); #ifdef ENABLE_CHAT - app->chats_updated(NULL, chats.size()); + app->chats_updated(NULL, int(chats.size())); #endif for (node_map::iterator it = nodes.begin(); it != nodes.end(); it++) { memset(&(it->second->changed), 0, sizeof it->second->changed); } } - - jsonsc.storeobject(&scnotifyurl); - scnotifyurlts = Waiter::ds; - scnotifyurl.append("/1"); - break; - - case MAKENAMEID2('s', 'n'): - // the sn element is guaranteed to be the last in sequence - setscsn(&jsonsc); - notifypurge(); - if (sctable) - { - if (!pendingcs && !csretrying && !reqs.cmdspending()) - { - sctable->commit(); - sctable->begin(); - app->notify_dbcommit(); - pendingsccommit = false; - } - else - { - LOG_debug << "Postponing DB commit until cs requests finish"; - pendingsccommit = true; - } - } - break; - - case EOO: - mergenewshares(1); - applykeys(); return true; case 'a': if (jsonsc.enterarray()) { + LOG_debug << "Processing action packets"; insca = true; break; } @@ -4321,7 +4319,7 @@ void MegaClient::putfa(handle th, fatype t, SymmCipher* key, string* data, bool { // CBC-encrypt attribute data (padded to next multiple of BLOCKSIZE) data->resize((data->size() + SymmCipher::BLOCKSIZE - 1) & -SymmCipher::BLOCKSIZE); - key->cbc_encrypt((byte*)data->data(), data->size()); + key->cbc_encrypt((byte*)data->data(), int(data->size())); queuedfa.push_back(new HttpReqCommandPutFA(this, th, t, data, checkAccess)); LOG_debug << "File attribute added to queue - " << th << " : " << queuedfa.size() << " queued, " << activefa.size() << " active"; @@ -4773,11 +4771,9 @@ void MegaClient::sc_keys() break; case 'h': - // security feature: we only distribute node keys for our own - // outgoing shares - if (!ISUNDEF(h = jsonsc.gethandle()) && (n = nodebyhandle(h)) && n->sharekey && !n->inshare) + if (!ISUNDEF(h = jsonsc.gethandle()) && (n = nodebyhandle(h)) && n->sharekey) { - kshares.push_back(n); + kshares.push_back(n); // n->inshare is checked in cr_response } break; @@ -5765,7 +5761,7 @@ void MegaClient::notifypurge(void) #endif } - if ((t = nodenotify.size())) + if ((t = int(nodenotify.size()))) { #ifdef ENABLE_SYNC // check for deleted syncs @@ -5823,7 +5819,7 @@ void MegaClient::notifypurge(void) nodenotify.clear(); } - if ((t = pcrnotify.size())) + if ((t = int(pcrnotify.size()))) { if (!fetchingnodes) { @@ -5851,7 +5847,7 @@ void MegaClient::notifypurge(void) } // users are never deleted (except at account cancellation) - if ((t = usernotify.size())) + if ((t = int(usernotify.size()))) { if (!fetchingnodes) { @@ -5874,15 +5870,12 @@ void MegaClient::notifypurge(void) Node *n = nodebyhandle(*it); if (n && !n->changed.removed) { - int creqtag = reqtag; - reqtag = 0; - sendevent(99435, "Orphan incoming share"); - reqtag = creqtag; + sendevent(99435, "Orphan incoming share", 0); } } u->sharing.clear(); - discarduser(u->userhandle); + discarduser(u->userhandle, false); } } @@ -5890,7 +5883,7 @@ void MegaClient::notifypurge(void) } #ifdef ENABLE_CHAT - if ((t = chatnotify.size())) + if ((t = int(chatnotify.size()))) { if (!fetchingnodes) { @@ -5978,7 +5971,7 @@ void MegaClient::makeattr(SymmCipher* key, string* attrstring, const char* json, { if (l < 0) { - l = strlen(json); + l = int(strlen(json)); } int ll = (l + 6 + SymmCipher::KEYLENGTH - 1) & - SymmCipher::KEYLENGTH; byte* buf = new byte[ll]; @@ -6264,7 +6257,7 @@ char* MegaClient::str_to_a32(const char* str, int* len) return NULL; } - int t = strlen(str); + int t = int(strlen(str)); int t2 = 4 * ((t + 3) >> 2); char* result = new char[t2](); uint32_t* a32 = (uint32_t*)result; @@ -6573,10 +6566,7 @@ int MegaClient::readnodes(JSON* j, int notify, putsource_t source, NewNode* nn, static bool reloadnotified = false; if (!reloadnotified) { - int creqtag = reqtag; - reqtag = 0; - sendevent(99437, "Node inconsistency"); - reqtag = creqtag; + sendevent(99437, "Node inconsistency", 0); reloadnotified = true; } } @@ -6712,7 +6702,7 @@ int MegaClient::readnodes(JSON* j, int notify, putsource_t source, NewNode* nn, } // any child nodes that arrived before their parents? - for (int i = dp.size(); i--; ) + for (size_t i = dp.size(); i--; ) { if ((n = nodebyhandle(dp[i]->parenthandle))) { @@ -7256,8 +7246,6 @@ error MegaClient::folderaccess(const char *folderlink) handle h = 0; byte folderkey[SymmCipher::KEYLENGTH]; - locallogout(); - if (Base64::atob(f, (byte*)&h, NODEHANDLE) != NODEHANDLE) { return API_EARGS; @@ -7274,11 +7262,14 @@ error MegaClient::folderaccess(const char *folderlink) return API_OK; } +void MegaClient::prelogin(const char *email) +{ + reqs.add(new CommandPrelogin(this, email)); +} + // create new session void MegaClient::login(const char* email, const byte* pwkey, const char* pin) { - locallogout(); - string lcemail(email); key.setkey((byte*)pwkey); @@ -7288,23 +7279,47 @@ void MegaClient::login(const char* email, const byte* pwkey, const char* pin) byte sek[SymmCipher::KEYLENGTH]; PrnGen::genblock(sek, sizeof sek); - reqs.add(new CommandLogin(this, email, emailhash, sek, 0, pin)); + reqs.add(new CommandLogin(this, email, (byte*)&emailhash, sizeof(emailhash), sek, 0, pin)); } -void MegaClient::fastlogin(const char* email, const byte* pwkey, uint64_t emailhash) +// create new session (v2) +void MegaClient::login2(const char *email, const char *password, string *salt, const char *pin) { - locallogout(); + string bsalt; + Base64::atob(*salt, bsalt); + + byte derivedKey[2 * SymmCipher::KEYLENGTH]; + CryptoPP::PKCS5_PBKDF2_HMAC pbkdf2; + pbkdf2.DeriveKey(derivedKey, sizeof(derivedKey), 0, (byte *)password, strlen(password), + (const byte *)bsalt.data(), bsalt.size(), 100000); + login2(email, derivedKey, pin); +} + +void MegaClient::login2(const char *email, const byte *derivedKey, const char* pin) +{ + key.setkey((byte*)derivedKey); + const byte *authKey = derivedKey + SymmCipher::KEYLENGTH; + + byte sek[SymmCipher::KEYLENGTH]; + PrnGen::genblock(sek, sizeof sek); + + reqs.add(new CommandLogin(this, email, authKey, SymmCipher::KEYLENGTH, sek, 0, pin)); +} + +void MegaClient::fastlogin(const char* email, const byte* pwkey, uint64_t emailhash) +{ key.setkey((byte*)pwkey); byte sek[SymmCipher::KEYLENGTH]; PrnGen::genblock(sek, sizeof sek); - reqs.add(new CommandLogin(this, email, emailhash, sek)); + reqs.add(new CommandLogin(this, email, (byte*)&emailhash, sizeof(emailhash), sek)); } void MegaClient::getuserdata() { + cachedug = false; reqs.add(new CommandGetUserData(this)); } @@ -7315,9 +7330,7 @@ void MegaClient::getpubkey(const char *user) // resume session - load state from local cache, if available void MegaClient::login(const byte* session, int size) -{ - locallogout(); - +{ int sessionversion = 0; if (size == sizeof key.key + SIDLEN + 1) { @@ -7351,7 +7364,7 @@ void MegaClient::login(const byte* session, int size) byte sek[SymmCipher::KEYLENGTH]; PrnGen::genblock(sek, sizeof sek); - reqs.add(new CommandLogin(this, NULL, UNDEF, sek, sessionversion)); + reqs.add(new CommandLogin(this, NULL, NULL, 0, sek, sessionversion)); getuserdata(); } else @@ -7407,7 +7420,7 @@ int MegaClient::dumpsession(byte* session, size_t size) byte k[SymmCipher::KEYLENGTH]; SymmCipher cipher; - cipher.setkey((const byte *)sessionkey.data(), sessionkey.size()); + cipher.setkey((const byte *)sessionkey.data(), int(sessionkey.size())); cipher.ecb_encrypt(key.key, k); memcpy(session, k, sizeof k); } @@ -7419,7 +7432,7 @@ int MegaClient::dumpsession(byte* session, size_t size) memcpy(session + sizeof key.key, sid.data(), sid.size()); - return size; + return int(size); } void MegaClient::copysession() @@ -7453,7 +7466,7 @@ string *MegaClient::sessiontransferdata(const char *url, string *session) { string sids; sids.resize(sid.size() * 4 / 3 + 4); - sids.resize(Base64::btoa((byte *)sid.data(), sid.size(), (char *)sids.data())); + sids.resize(Base64::btoa((byte *)sid.data(), int(sid.size()), (char *)sids.data())); ss << sids; } ss << "\",\""; @@ -7469,7 +7482,7 @@ string *MegaClient::sessiontransferdata(const char *url, string *session) string json = ss.str(); string *base64 = new string; base64->resize(json.size() * 4 / 3 + 4); - base64->resize(Base64::btoa((byte *)json.data(), json.size(), (char *)base64->data())); + base64->resize(Base64::btoa((byte *)json.data(), int(json.size()), (char *)base64->data())); std::replace(base64->begin(), base64->end(), '-', '+'); std::replace(base64->begin(), base64->end(), '_', '/'); return base64; @@ -7682,7 +7695,7 @@ void MegaClient::mapuser(handle uh, const char* email) } } -void MegaClient::discarduser(handle uh) +void MegaClient::discarduser(handle uh, bool discardnotified) { User *u = finduser(uh); if (!u) @@ -7702,7 +7715,10 @@ void MegaClient::discarduser(handle uh) u->pkrs.pop_front(); } - discardnotifieduser(u); + if (discardnotified) + { + discardnotifieduser(u); + } umindex.erase(u->email); users.erase(uhindex[uh]); @@ -7922,7 +7938,7 @@ void MegaClient::rewriteforeignkeys(Node* n) // otherwise, queue and request public key if not already pending void MegaClient::setshare(Node* n, const char* user, accesslevel_t a, const char* personal_representation) { - int total = n->outshares ? n->outshares->size() : 0; + size_t total = n->outshares ? n->outshares->size() : 0; total += n->pendingshares ? n->pendingshares->size() : 0; if (a == ACCESS_UNKNOWN && total == 1) { @@ -7998,25 +8014,25 @@ error MegaClient::creditcardstore(const char *ccplain) return API_EARGS; } - string::iterator it = find_if(ccnumber.begin(), ccnumber.end(), not1(ptr_fun(static_cast(isdigit)))); + string::iterator it = find_if(ccnumber.begin(), ccnumber.end(), char_is_not_digit); if (it != ccnumber.end()) { return API_EARGS; } - it = find_if(expm.begin(), expm.end(), not1(ptr_fun(static_cast(isdigit)))); + it = find_if(expm.begin(), expm.end(), char_is_not_digit); if (it != expm.end() || atol(expm.c_str()) > 12) { return API_EARGS; } - it = find_if(expy.begin(), expy.end(), not1(ptr_fun(static_cast(isdigit)))); + it = find_if(expy.begin(), expy.end(), char_is_not_digit); if (it != expy.end() || atol(expy.c_str()) < 2015) { return API_EARGS; } - it = find_if(cv2.begin(), cv2.end(), not1(ptr_fun(static_cast(isdigit)))); + it = find_if(cv2.begin(), cv2.end(), char_is_not_digit); if (it != cv2.end()) { return API_EARGS; @@ -8025,7 +8041,7 @@ error MegaClient::creditcardstore(const char *ccplain) //Luhn algorithm int odd = 1, sum = 0; - for (int i = ccnumber.size(); i--; odd = !odd) + for (size_t i = ccnumber.size(); i--; odd = !odd) { int digit = ccnumber[i] - '0'; sum += odd ? digit : ((digit < 5) ? 2 * digit : 2 * (digit - 5) + 1); @@ -8062,7 +8078,7 @@ error MegaClient::creditcardstore(const char *ccplain) HashSHA256 hash; string binaryhash; - hash.add((byte *)hashstring, strlen(hashstring)); + hash.add((byte *)hashstring, int(strlen(hashstring))); hash.get(&binaryhash); static const char hexchars[] = "0123456789abcdef"; @@ -8077,7 +8093,7 @@ error MegaClient::creditcardstore(const char *ccplain) string base64cc; base64cc.resize(ccenc.size()*4/3+4); - base64cc.resize(Base64::btoa((byte *)ccenc.data(), ccenc.size(), (char *)base64cc.data())); + base64cc.resize(Base64::btoa((byte *)ccenc.data(), int(ccenc.size()), (char *)base64cc.data())); std::replace( base64cc.begin(), base64cc.end(), '-', '+'); std::replace( base64cc.begin(), base64cc.end(), '_', '/'); @@ -8142,7 +8158,7 @@ void MegaClient::putua(attr_t at, const byte* av, unsigned avl, int ctag) } av = (const byte*) data.data(); - avl = data.size(); + avl = unsigned(data.size()); } int tag = (ctag != -1) ? ctag : reqtag; @@ -8245,7 +8261,7 @@ void MegaClient::getua(User* u, const attr_t at, int ctag) else { restag = tag; - app->getua_result((byte*) cachedav->data(), cachedav->size()); + app->getua_result((byte*) cachedav->data(), unsigned(cachedav->size())); return; } } @@ -8291,7 +8307,7 @@ void MegaClient::notifynode(Node* n) // report a "NO_KEY" event char* buf = new char[n->nodekey.size() * 4 / 3 + 4]; - Base64::btoa((byte *)n->nodekey.data(), n->nodekey.size(), buf); + Base64::btoa((byte *)n->nodekey.data(), int(n->nodekey.size()), buf); int changed = 0; changed |= (int)n->changed.removed; @@ -8304,20 +8320,17 @@ void MegaClient::notifynode(Node* n) changed |= n->changed.parent << 7; changed |= n->changed.publiclink << 8; - int attrlen = n->attrstring->size(); + int attrlen = int(n->attrstring->size()); string base64attrstring; base64attrstring.resize(attrlen * 4 / 3 + 4); - base64attrstring.resize(Base64::btoa((byte *)n->attrstring->data(), n->attrstring->size(), (char *)base64attrstring.data())); + base64attrstring.resize(Base64::btoa((byte *)n->attrstring->data(), int(n->attrstring->size()), (char *)base64attrstring.data())); char report[512]; Base64::btoa((const byte *)&n->nodehandle, MegaClient::NODEHANDLE, report); sprintf(report + 8, " %d %" PRIu64 " %d %X %.200s %.200s", n->type, n->size, attrlen, changed, buf, base64attrstring.c_str()); - int creqtag = reqtag; - reqtag = 0; - reportevent("NK", report); - sendevent(99400, report); - reqtag = creqtag; + reportevent("NK", report, 0); + sendevent(99400, report, 0); delete [] buf; } @@ -8579,9 +8592,9 @@ void MegaClient::procsnk(JSON* j) { byte keybuf[FILENODEKEYLENGTH]; - sn->sharekey->ecb_encrypt((byte*)n->nodekey.data(), keybuf, n->nodekey.size()); + sn->sharekey->ecb_encrypt((byte*)n->nodekey.data(), keybuf, unsigned(n->nodekey.size())); - reqs.add(new CommandSingleKeyCR(sh, nh, keybuf, n->nodekey.size())); + reqs.add(new CommandSingleKeyCR(sh, nh, keybuf, unsigned(n->nodekey.size()))); } } @@ -8885,7 +8898,7 @@ unsigned MegaClient::addnode(node_vector* v, Node* n) const { // linear search not particularly scalable, but fine for the relatively // small real-world requests - for (int i = v->size(); i--; ) + for (unsigned i = unsigned(v->size()); i--; ) { if ((*v)[i] == n) { @@ -8894,7 +8907,7 @@ unsigned MegaClient::addnode(node_vector* v, Node* n) const } v->push_back(n); - return v->size() - 1; + return unsigned(v->size() - 1); } // generate crypto key response @@ -8912,19 +8925,25 @@ void MegaClient::cr_response(node_vector* shares, node_vector* nodes, JSON* sele // for security reasons, we only respond to key requests affecting our own // shares - for (si = shares->size(); si--; ) + for (si = unsigned(shares->size()); si--; ) { if ((*shares)[si] && ((*shares)[si]->inshare || !(*shares)[si]->sharekey)) { + // security feature: we only distribute node keys for our own outgoing shares. LOG_warn << "Attempt to obtain node key for invalid/third-party share foiled"; (*shares)[si] = NULL; + sendevent(99445, "Inshare key request rejected", 0); } } if (!selector) { si = 0; - ni = 0; + ni = -1; + if (shares->empty() || nodes->empty()) + { + return; + } } // estimate required size for requested keys @@ -8960,6 +8979,10 @@ void MegaClient::cr_response(node_vector* shares, node_vector* nodes, JSON* sele { setkey = selector->storebinary(keybuf, sizeof keybuf); } + else + { + setkey = -1; + } } else { @@ -8984,7 +9007,7 @@ void MegaClient::cr_response(node_vector* shares, node_vector* nodes, JSON* sele { if (setkey == (int)n->nodekey.size()) { - sn->sharekey->ecb_decrypt(keybuf, n->nodekey.size()); + sn->sharekey->ecb_decrypt(keybuf, unsigned(n->nodekey.size())); n->setkey(keybuf); setkey = -1; } @@ -9003,8 +9026,8 @@ void MegaClient::cr_response(node_vector* shares, node_vector* nodes, JSON* sele sprintf(buf, "\",%u,%u,\"", nsi, nni); // generate & queue share nodekey - sn->sharekey->ecb_encrypt((byte*)n->nodekey.data(), keybuf, n->nodekey.size()); - Base64::btoa(keybuf, n->nodekey.size(), strchr(buf + 7, 0)); + sn->sharekey->ecb_encrypt((byte*)n->nodekey.data(), keybuf, unsigned(n->nodekey.size())); + Base64::btoa(keybuf, int(n->nodekey.size()), strchr(buf + 7, 0)); crkeys.append(buf); } else @@ -9258,7 +9281,7 @@ error MegaClient::decryptlink(const char *link, const char *pwd, string* decrypt { // verify HMAC with macKey(alg, f/F, ph, salt, encKey) HMACSHA256 hmacsha256(derivedKey + 32, 32); - hmacsha256.add((byte *)linkBin.data(), 40 + encKeyLen); + hmacsha256.add((byte *)linkBin.data(), unsigned(40 + encKeyLen)); hmacsha256.get(hmacComputed); } if (memcmp(hmac, hmacComputed, 32)) @@ -9281,7 +9304,7 @@ error MegaClient::decryptlink(const char *link, const char *pwd, string* decrypt char keyStr[FILENODEKEYLENGTH*4/3+3]; Base64::btoa((byte*) &ph, MegaClient::NODEHANDLE, phStr); - Base64::btoa(key, encKeyLen, keyStr); + Base64::btoa(key, int(encKeyLen), keyStr); decryptedLink->clear(); decryptedLink->append("https://mega.nz/#"); @@ -9353,7 +9376,7 @@ error MegaClient::encryptlink(const char *link, const char *pwd, string *encrypt size_t linkKeySize = isFolder ? FOLDERNODEKEYLENGTH : FILENODEKEYLENGTH; string linkKey; linkKey.resize(linkKeySize); - if ((size_t) Base64::atob(ptr, (byte *)linkKey.data(), linkKey.size()) != linkKeySize) + if ((size_t) Base64::atob(ptr, (byte *)linkKey.data(), int(linkKey.size())) != linkKeySize) { LOG_err << "Invalid encryption key in the public link"; return API_EKEY; @@ -9402,7 +9425,7 @@ error MegaClient::encryptlink(const char *link, const char *pwd, string *encrypt else if (algorithm == 2) // fix legacy Webclient bug: swap data and key { HMACSHA256 hmacsha256(derivedKey + 32, 32); - hmacsha256.add((byte *)payload.data(), payload.size()); + hmacsha256.add((byte *)payload.data(), unsigned(payload.size())); hmacsha256.get(hmac); } else @@ -9458,7 +9481,7 @@ void MegaClient::whyamiblocked() reqs.add(new CommandWhyAmIblocked(this)); } -error MegaClient::changepw(const byte* newpwkey, const char *pin) +error MegaClient::changepw(const char* password, const char *pin) { User* u; @@ -9467,14 +9490,56 @@ error MegaClient::changepw(const byte* newpwkey, const char *pin) return API_EACCESS; } - byte newkey[SymmCipher::KEYLENGTH]; - SymmCipher pwcipher; - memcpy(newkey, key.key, sizeof newkey); - pwcipher.setkey(newpwkey); - pwcipher.ecb_encrypt(newkey); + if (accountversion == 1) + { + error e; + byte newpwkey[SymmCipher::KEYLENGTH]; + if ((e = pw_key(password, newpwkey))) + { + return e; + } + + byte newkey[SymmCipher::KEYLENGTH]; + SymmCipher pwcipher; + memcpy(newkey, key.key, sizeof newkey); + pwcipher.setkey(newpwkey); + pwcipher.ecb_encrypt(newkey); + + string email = u->email; + uint64_t stringhash = stringhash64(&email, &pwcipher); + reqs.add(new CommandSetMasterKey(this, newkey, (const byte *)&stringhash, sizeof(stringhash), NULL, pin)); + return API_OK; + } + + byte clientRandomValue[SymmCipher::KEYLENGTH]; + PrnGen::genblock(clientRandomValue, sizeof(clientRandomValue)); + + string salt; + HashSHA256 hasher; + string buffer = "mega.nz"; + buffer.resize(200, 'P'); + buffer.append((char *)clientRandomValue, sizeof(clientRandomValue)); + hasher.add((const byte*)buffer.data(), unsigned(buffer.size())); + hasher.get(&salt); + + byte derivedKey[2 * SymmCipher::KEYLENGTH]; + CryptoPP::PKCS5_PBKDF2_HMAC pbkdf2; + pbkdf2.DeriveKey(derivedKey, sizeof(derivedKey), 0, (byte *)password, strlen(password), + (const byte *)salt.data(), salt.size(), 100000); - string email = u->email; - reqs.add(new CommandSetMasterKey(this, newkey, stringhash64(&email, &pwcipher), pin)); + byte encmasterkey[SymmCipher::KEYLENGTH]; + SymmCipher cipher; + cipher.setkey(derivedKey); + cipher.ecb_encrypt(key.key, encmasterkey); + + string hashedauthkey; + byte *authkey = derivedKey + SymmCipher::KEYLENGTH; + hasher.add(authkey, SymmCipher::KEYLENGTH); + hasher.get(&hashedauthkey); + hashedauthkey.resize(SymmCipher::KEYLENGTH); + + // Pass the salt and apply to this->accountsalt if the command succeed to allow posterior checks of the password without getting it from the server + reqs.add(new CommandSetMasterKey(this, encmasterkey, (byte*)hashedauthkey.data(), SymmCipher::KEYLENGTH, clientRandomValue, pin, &salt)); return API_OK; } @@ -9485,8 +9550,6 @@ void MegaClient::createephemeral() byte pwbuf[SymmCipher::KEYLENGTH]; byte sscbuf[2 * SymmCipher::KEYLENGTH]; - locallogout(); - PrnGen::genblock(keybuf, sizeof keybuf); PrnGen::genblock(pwbuf, sizeof pwbuf); PrnGen::genblock(sscbuf, sizeof sscbuf); @@ -9520,6 +9583,46 @@ void MegaClient::sendsignuplink(const char* email, const char* name, const byte* reqs.add(new CommandSendSignupLink(this, email, name, c)); } +string MegaClient::sendsignuplink2(const char *email, const char *password, const char* name) +{ + byte clientrandomvalue[SymmCipher::KEYLENGTH]; + PrnGen::genblock(clientrandomvalue, sizeof(clientrandomvalue)); + + string salt; + HashSHA256 hasher; + string buffer = "mega.nz"; + buffer.resize(200, 'P'); + buffer.append((char *)clientrandomvalue, sizeof(clientrandomvalue)); + hasher.add((const byte*)buffer.data(), unsigned(buffer.size())); + hasher.get(&salt); + + byte derivedKey[2 * SymmCipher::KEYLENGTH]; + CryptoPP::PKCS5_PBKDF2_HMAC pbkdf2; + pbkdf2.DeriveKey(derivedKey, sizeof(derivedKey), 0, (byte *)password, strlen(password), + (const byte *)salt.data(), salt.size(), 100000); + + byte encmasterkey[SymmCipher::KEYLENGTH]; + SymmCipher cipher; + cipher.setkey(derivedKey); + cipher.ecb_encrypt(key.key, encmasterkey); + + string hashedauthkey; + byte *authkey = derivedKey + SymmCipher::KEYLENGTH; + hasher.add(authkey, SymmCipher::KEYLENGTH); + hasher.get(&hashedauthkey); + hashedauthkey.resize(SymmCipher::KEYLENGTH); + + accountversion = 2; + accountsalt = salt; + reqs.add(new CommandSendSignupLink2(this, email, name, clientrandomvalue, encmasterkey, (byte*)hashedauthkey.data())); + return string((const char*)derivedKey, 2 * SymmCipher::KEYLENGTH); +} + +void MegaClient::resendsignuplink2(const char *email, const char *name) +{ + reqs.add(new CommandSendSignupLink2(this, email, name)); +} + // if query is 0, actually confirm account; just decode/query signup link // details otherwise void MegaClient::querysignuplink(const byte* code, unsigned len) @@ -9532,6 +9635,11 @@ void MegaClient::confirmsignuplink(const byte* code, unsigned len, uint64_t emai reqs.add(new CommandConfirmSignupLink(this, code, len, emailhash)); } +void MegaClient::confirmsignuplink2(const byte *code, unsigned len) +{ + reqs.add(new CommandConfirmSignupLink2(this, code, len)); +} + // generate and configure encrypted private key, plaintext public key void MegaClient::setkeypair() { @@ -9545,18 +9653,18 @@ void MegaClient::setkeypair() AsymmCipher::serializeintarray(asymkey.key, AsymmCipher::PRIVKEY, &privks); // add random padding and ECB-encrypt with master key - unsigned t = privks.size(); + unsigned t = unsigned(privks.size()); privks.resize((t + SymmCipher::BLOCKSIZE - 1) & - SymmCipher::BLOCKSIZE); - PrnGen::genblock((byte*)(privks.data() + t), privks.size() - t); + PrnGen::genblock((byte*)(privks.data() + t), int(privks.size() - t)); key.ecb_encrypt((byte*)privks.data(), (byte*)privks.data(), (unsigned)privks.size()); reqs.add(new CommandSetKeyPair(this, (const byte*)privks.data(), - privks.size(), + unsigned(privks.size()), (const byte*)pubks.data(), - pubks.size())); + unsigned(pubks.size()))); } bool MegaClient::fetchsc(DbTable* sctable) @@ -9647,7 +9755,7 @@ bool MegaClient::fetchsc(DbTable* sctable) fnstats.timeToLastByte = Waiter::ds - fnstats.startTime; // any child nodes arrived before their parents? - for (int i = dp.size(); i--; ) + for (size_t i = dp.size(); i--; ) { if ((n = nodebyhandle(dp[i]->parenthandle))) { @@ -9740,7 +9848,7 @@ void MegaClient::enabletransferresumption(const char *loggedoutid) string lok; Hash hash; - hash.add((const byte *)dbname.c_str(), dbname.size() + 1); + hash.add((const byte *)dbname.c_str(), unsigned(dbname.size() + 1)); hash.get(&lok); tckey.setkey((const byte*)lok.data()); } @@ -10055,10 +10163,7 @@ void MegaClient::initializekeys() { LOG_warn << "Public key for Ed25519 mismatch."; - int creqtag = reqtag; - reqtag = 0; - sendevent(99417, "Ed25519 public key mismatch"); - reqtag = creqtag; + sendevent(99417, "Ed25519 public key mismatch", 0); clearKeys(); resetKeyring(); @@ -10070,10 +10175,7 @@ void MegaClient::initializekeys() { LOG_warn << "Public key for Cu25519 mismatch."; - int creqtag = reqtag; - reqtag = 0; - sendevent(99412, "Cu25519 public key mismatch"); - reqtag = creqtag; + sendevent(99412, "Cu25519 public key mismatch", 0); clearKeys(); resetKeyring(); @@ -10089,10 +10191,7 @@ void MegaClient::initializekeys() { LOG_warn << "Signature of public key for Cu25519 not found or mismatch"; - int creqtag = reqtag; - reqtag = 0; - sendevent(99413, "Signature of Cu25519 public key mismatch"); - reqtag = creqtag; + sendevent(99413, "Signature of Cu25519 public key mismatch", 0); clearKeys(); resetKeyring(); @@ -10108,20 +10207,16 @@ void MegaClient::initializekeys() } if (!pubkstr.size() || !sigPubk.size()) { - int creqtag = reqtag; - reqtag = 0; - if (!pubkstr.size()) { LOG_warn << "Error serializing RSA public key"; - sendevent(99421, "Error serializing RSA public key"); + sendevent(99421, "Error serializing RSA public key", 0); } if (!sigPubk.size()) { LOG_warn << "Signature of public key for RSA not found"; - sendevent(99422, "Signature of public key for RSA not found"); + sendevent(99422, "Signature of public key for RSA not found", 0); } - reqtag = creqtag; clearKeys(); resetKeyring(); @@ -10134,10 +10229,7 @@ void MegaClient::initializekeys() { LOG_warn << "Verification of signature of public key for RSA failed"; - int creqtag = reqtag; - reqtag = 0; - sendevent(99414, "Verification of signature of public key for RSA failed"); - reqtag = creqtag; + sendevent(99414, "Verification of signature of public key for RSA failed", 0); clearKeys(); resetKeyring(); @@ -10156,10 +10248,7 @@ void MegaClient::initializekeys() { LOG_warn << "Public keys and/or signatures found witout their respective private key."; - int creqtag = reqtag; - reqtag = 0; - sendevent(99415, "Incomplete keypair detected"); - reqtag = creqtag; + sendevent(99415, "Incomplete keypair detected", 0); clearKeys(); return; @@ -10225,17 +10314,14 @@ void MegaClient::initializekeys() { LOG_warn << "Keyring exists, but it's incomplete."; - int creqtag = reqtag; - reqtag = 0; if (!chatkey) { - sendevent(99416, "Incomplete keyring detected: private key for Cu25519 not found."); + sendevent(99416, "Incomplete keyring detected: private key for Cu25519 not found.", 0); } else // !signkey { - sendevent(99423, "Incomplete keyring detected: private key for Ed25519 not found."); + sendevent(99423, "Incomplete keyring detected: private key for Ed25519 not found.", 0); } - reqtag = creqtag; resetKeyring(); clearKeys(); @@ -10619,11 +10705,11 @@ error MegaClient::addsync(string* rootpath, const char* debris, string* localdeb } if (rootpath->size() >= fsaccess->localseparator.size() - && !memcmp(rootpath->data() + (rootpath->size() & -fsaccess->localseparator.size()) - fsaccess->localseparator.size(), + && !memcmp(rootpath->data() + (int(rootpath->size()) & -int(fsaccess->localseparator.size())) - fsaccess->localseparator.size(), fsaccess->localseparator.data(), fsaccess->localseparator.size())) { - rootpath->resize((rootpath->size() & -fsaccess->localseparator.size()) - fsaccess->localseparator.size()); + rootpath->resize((int(rootpath->size()) & -int(fsaccess->localseparator.size())) - fsaccess->localseparator.size()); } bool isnetwork = false; @@ -11020,9 +11106,9 @@ bool MegaClient::syncdown(LocalNode* l, string* localpath, bool rubbish) bool download = true; FileAccess *f = fsaccess->newfileaccess(); if (rit->second->localnode != (LocalNode*)~0 - && f->fopen(localpath, true, false)) + && (f->fopen(localpath) || f->type == FOLDERNODE)) { - LOG_debug << "Skipping download over an unscanned file/folder"; + LOG_debug << "Skipping download over an unscanned file/folder, or the file/folder is not to be synced (special attributes)"; download = false; } delete f; @@ -11046,9 +11132,13 @@ bool MegaClient::syncdown(LocalNode* l, string* localpath, bool rubbish) else { LOG_debug << "Creating local folder"; - + FileAccess *f = fsaccess->newfileaccess(); + if (f->fopen(localpath) || f->type == FOLDERNODE) + { + LOG_debug << "Skipping folder creation over an unscanned file/folder, or the file/folder is not to be synced (special attributes)"; + } // create local path, add to LocalNodes and recurse - if (fsaccess->mkdirlocal(localpath)) + else if (fsaccess->mkdirlocal(localpath)) { LocalNode* ll = l->sync->checkpath(l, localpath, &localname); @@ -11080,6 +11170,7 @@ bool MegaClient::syncdown(LocalNode* l, string* localpath, bool rubbish) { LOG_debug << "Non transient error creating folder"; } + delete f; } } @@ -11126,7 +11217,7 @@ bool MegaClient::syncup(LocalNode* l, dstime* nds) if (!l->reported) { char* buf = new char[(*it)->nodekey.size() * 4 / 3 + 4]; - Base64::btoa((byte *)(*it)->nodekey.data(), (*it)->nodekey.size(), buf); + Base64::btoa((byte *)(*it)->nodekey.data(), int((*it)->nodekey.size()), buf); LOG_warn << "Sync: Undecryptable child node. " << buf; @@ -11139,10 +11230,7 @@ bool MegaClient::syncup(LocalNode* l, dstime* nds) sprintf(report + 8, " %d %.200s", (*it)->type, buf); // report an "undecrypted child" event - int creqtag = reqtag; - reqtag = 0; - reportevent("CU", report); - reqtag = creqtag; + reportevent("CU", report, 0); delete [] buf; } @@ -11160,10 +11248,7 @@ bool MegaClient::syncup(LocalNode* l, dstime* nds) l->reported = true; // report a "no-name child" event - int creqtag = reqtag; - reqtag = 0; - reportevent("CN"); - reqtag = creqtag; + reportevent("CN", NULL, 0); } continue; @@ -11198,10 +11283,7 @@ bool MegaClient::syncup(LocalNode* l, dstime* nds) sprintf(report, "%d %d %d %d", (int)lit->first->size(), (int)localname.size(), (int)ll->name.size(), (int)ll->type); // report a "no-name localnode" event - int creqtag = reqtag; - reqtag = 0; - reportevent("LN", report); - reqtag = creqtag; + reportevent("LN", report, 0); } continue; } @@ -11331,7 +11413,7 @@ bool MegaClient::syncup(LocalNode* l, dstime* nds) string lpath; ll->getlocalpath(&lpath); string stream = lpath; - stream.append((char *)L":$CmdTcID:$DATA", 30); + stream.append((const char *)(const wchar_t*)L":$CmdTcID:$DATA", 30); if (f->fopen(&stream)) { LOG_warn << "COMODO detected"; @@ -11360,7 +11442,7 @@ bool MegaClient::syncup(LocalNode* l, dstime* nds) } } - lpath.append((char *)L":OECustomProperty", 34); + lpath.append((const char *)(const wchar_t*)L":OECustomProperty", 34); if (f->fopen(&lpath)) { LOG_warn << "Windows Search detected"; @@ -11565,7 +11647,7 @@ bool MegaClient::syncup(LocalNode* l, dstime* nds) if ((ait = ll->node->attrs.map.find('n')) != ll->node->attrs.map.end()) { - namelen = ait->second.size(); + namelen = int(ait->second.size()); } else { @@ -11578,10 +11660,7 @@ bool MegaClient::syncup(LocalNode* l, dstime* nds) } // report a "dupe" event - int creqtag = reqtag; - reqtag = 0; - reportevent("D2", report); - reqtag = creqtag; + reportevent("D2", report, 0); } else { @@ -11745,7 +11824,7 @@ void MegaClient::syncupdate() reqs.add(new CommandPutNodes(this, synccreate[start]->parent->node->nodehandle, - NULL, nn, nnp - nn, + NULL, nn, int(nnp - nn), synccreate[start]->sync->tag, PUTNODES_SYNC)); @@ -11861,6 +11940,41 @@ void MegaClient::proclocaltree(LocalNode* n, LocalTreeProc* tp) tp->proc(this, n); } +void MegaClient::unlinkifexists(LocalNode *l, FileAccess *fa, std::string *path) +{ + l->getlocalpath(path); + if (fa->fopen(path) || fa->type == FOLDERNODE) + { + LOG_warn << "Deletion of existing file avoided"; + static bool reported99446 = false; + if (!reported99446) + { + sendevent(99446, "Deletion of existing file avoided", 0); + reported99446 = true; + } + + // The local file or folder seems to be still there, but invisible + // for the sync engine, so we just stop syncing it + LocalTreeProcUnlinkNodes tpunlink; + proclocaltree(l, &tpunlink); + } +#ifdef _WIN32 + else if (fa->errorcode != ERROR_FILE_NOT_FOUND && fa->errorcode != ERROR_PATH_NOT_FOUND) + { + LOG_warn << "Unexpected error code for deleted file: " << fa->errorcode; + static bool reported99447 = false; + if (!reported99447) + { + ostringstream oss; + oss << fa->errorcode; + string message = oss.str(); + sendevent(99447, message.c_str(), 0); + reported99447 = true; + } + } +#endif +} + void MegaClient::execsyncunlink() { Node* n; @@ -12145,7 +12259,7 @@ bool MegaClient::startxfer(direction_t d, File* f, bool skipdupes) if ((d == GET && !t->pos) || ((m_time() - t->lastaccesstime) >= 172500)) { LOG_warn << "Discarding temporary URL (" << t->pos << ", " << t->lastaccesstime << ")"; - t->cachedtempurl.clear(); + t->tempurl.clear(); if (d == PUT) { @@ -12182,7 +12296,7 @@ bool MegaClient::startxfer(direction_t d, File* f, bool skipdupes) if (f->genfingerprint(fa)) { LOG_warn << "The local file has been modified"; - t->cachedtempurl.clear(); + t->tempurl.clear(); t->chunkmacs.clear(); t->progresscompleted = 0; delete [] t->ultoken; @@ -12257,6 +12371,14 @@ void MegaClient::stopxfer(File* f) // last file for this transfer removed? shut down transfer. if (!transfer->files.size()) { + if (transfer->slot && transfer->slot->delayedchunk) + { + int creqtag = reqtag; + reqtag = 0; + sendevent(99444, "Upload with delayed chunks cancelled"); + reqtag = creqtag; + } + looprequested = true; transfer->finished = true; transfer->state = TRANSFERSTATE_CANCELLED; @@ -12319,7 +12441,7 @@ void MegaClient::setmaxconnections(direction_t d, int num) if (connections[d] != num) { - connections[d] = num; + connections[d] = (unsigned char)num; for (transferslot_list::iterator it = tslots.begin(); it != tslots.end(); ) { TransferSlot *slot = *it++; @@ -12327,7 +12449,6 @@ void MegaClient::setmaxconnections(direction_t d, int num) { slot->transfer->state = TRANSFERSTATE_QUEUED; slot->transfer->bt.arm(); - slot->transfer->cachedtempurl = slot->tempurl; delete slot; } } @@ -12404,6 +12525,14 @@ void MegaClient::reportevent(const char* event, const char* details) reqs.add(new CommandReportEvent(this, event, details)); } +void MegaClient::reportevent(const char* event, const char* details, int tag) +{ + int creqtag = reqtag; + reqtag = tag; + reportevent(event, details); + reqtag = creqtag; +} + bool MegaClient::setmaxdownloadspeed(m_off_t bpslimit) { return httpio->setmaxdownloadspeed(bpslimit >= 0 ? bpslimit : 0); @@ -12446,7 +12575,7 @@ void MegaClient::userfeedbackstore(const char *message) string base64userAgent; base64userAgent.resize(useragent.size() * 4 / 3 + 4); - Base64::btoa((byte *)useragent.data(), useragent.size(), (char *)base64userAgent.data()); + Base64::btoa((byte *)useragent.data(), int(useragent.size()), (char *)base64userAgent.data()); type.append(base64userAgent); reqs.add(new CommandUserFeedbackStore(this, type.c_str(), message, NULL)); @@ -12458,6 +12587,14 @@ void MegaClient::sendevent(int event, const char *desc) reqs.add(new CommandSendEvent(this, event, desc)); } +void MegaClient::sendevent(int event, const char *message, int tag) +{ + int creqtag = reqtag; + reqtag = tag; + sendevent(event, message); + reqtag = creqtag; +} + void MegaClient::cleanrubbishbin() { reqs.add(new CommandCleanRubbishBin(this)); diff --git a/src/node.cpp b/src/node.cpp index 51e2236a8d..3e8f83eb67 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -697,6 +697,53 @@ const char* Node::displayname() const return it->second.c_str(); } +string Node::displaypath() const +{ + // factored from nearly identical functions in megapi_impl and megacli + string path; + const Node* n = this; + for (; n; n = n->parent) + { + switch (n->type) + { + case FOLDERNODE: + path.insert(0, n->displayname()); + + if (n->inshare) + { + path.insert(0, ":"); + if (n->inshare->user) + { + path.insert(0, n->inshare->user->email); + } + else + { + path.insert(0, "UNKNOWN"); + } + return path; + } + break; + + case INCOMINGNODE: + path.insert(0, "//in"); + return path; + + case ROOTNODE: + return path.empty() ? "/" : path; + + case RUBBISHNODE: + path.insert(0, "//bin"); + return path; + + case TYPE_UNKNOWN: + case FILENODE: + path.insert(0, n->displayname()); + } + path.insert(0, "/"); + } + return path; +} + // returns position of file attribute or 0 if not present int Node::hasfileattribute(fatype t) const { @@ -1103,6 +1150,11 @@ void LocalNode::bumpnagleds() nagleds = sync->client->waiter->ds + 11; } +LocalNode::LocalNode() +{ + checked = false; +} + // initialize fresh LocalNode object - must be called exactly once void LocalNode::init(Sync* csync, nodetype_t ctype, LocalNode* cparent, string* cfullpath) { @@ -1113,7 +1165,6 @@ void LocalNode::init(Sync* csync, nodetype_t ctype, LocalNode* cparent, string* deleted = false; created = false; reported = false; - checked = false; syncxfer = true; newnode = NULL; parent_dbid = 0; @@ -1604,6 +1655,7 @@ LocalNode* LocalNode::unserialize(Sync* sync, string* d) // FIXME: serialize/unserialize l->created = false; l->reported = false; + l->checked = h != UNDEF; return l; } diff --git a/src/posix/console.cpp b/src/posix/console.cpp index 6edf750763..94ed16cda4 100644 --- a/src/posix/console.cpp +++ b/src/posix/console.cpp @@ -22,6 +22,8 @@ #include "mega.h" namespace mega { +using namespace std; + PosixConsole::PosixConsole() { // set up the console diff --git a/src/posix/fs.cpp b/src/posix/fs.cpp index 859e95da00..fa8638ac4f 100644 --- a/src/posix/fs.cpp +++ b/src/posix/fs.cpp @@ -40,7 +40,8 @@ extern JavaVM *MEGAjvm; #endif namespace mega { - +using namespace std; + #ifdef USE_IOS char* PosixFileSystemAccess::appbasepath = NULL; #endif @@ -119,6 +120,7 @@ bool PosixFileAccess::sysstat(m_time_t* mtime, m_off_t* size) type = TYPE_UNKNOWN; if (!stat(localname.c_str(), &statbuf)) { + errorcode = 0; if (S_ISDIR(statbuf.st_mode)) { type = FOLDERNODE; @@ -134,6 +136,7 @@ bool PosixFileAccess::sysstat(m_time_t* mtime, m_off_t* size) return true; } + errorcode = errno; return false; } diff --git a/src/posix/net.cpp b/src/posix/net.cpp index 4f4294c625..6912834775 100644 --- a/src/posix/net.cpp +++ b/src/posix/net.cpp @@ -1321,6 +1321,12 @@ void CurlHttpIO::send_request(CurlHttpContext* httpctx) curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 4096L); } + if (req->minspeed) + { + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 60L); + curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 30L); + } + if (!MegaClient::disablepkp && req->protect) { #if LIBCURL_VERSION_NUM >= 0x072c00 // At least cURL 7.44.0 @@ -1993,7 +1999,8 @@ bool CurlHttpIO::multidoio(CURLM *curlmhandle) curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &req->httpstatus); - LOG_debug << "CURLMSG_DONE with HTTP status: " << req->httpstatus; + LOG_debug << "CURLMSG_DONE with HTTP status: " << req->httpstatus << " from " + << (req->httpiohandle ? ((CurlHttpContext*)req->httpiohandle)->hostname : "(unknown)"); if (req->httpstatus) { if (req->method == METHOD_NONE) @@ -2523,6 +2530,7 @@ CURLcode CurlHttpIO::ssl_ctx_function(CURL*, void* sslctx, void*req) #define EVP_PKEY_get0_RSA(_pkey_) ((_pkey_)->pkey.rsa) #endif +#if (OPENSSL_VERSION_NUMBER < 0x1010100fL) const BIGNUM *RSA_get0_n(const RSA *rsa) { #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined (LIBRESSL_VERSION_NUMBER) @@ -2555,6 +2563,7 @@ const BIGNUM *RSA_get0_d(const RSA *rsa) return result; #endif } +#endif // SSL public key pinning int CurlHttpIO::cert_verify_callback(X509_STORE_CTX* ctx, void* req) diff --git a/src/proxy.cpp b/src/proxy.cpp index 1be133614a..62c5c2b580 100644 --- a/src/proxy.cpp +++ b/src/proxy.cpp @@ -21,9 +21,10 @@ #include "mega/proxy.h" -using namespace mega; using namespace std; +namespace mega { + Proxy::Proxy() { proxyType = AUTO; @@ -69,3 +70,5 @@ string Proxy::getPassword() { return password; } + +} diff --git a/src/sync.cpp b/src/sync.cpp index db5f2bc4fd..ade63a956b 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -68,7 +68,7 @@ Sync::Sync(MegaClient* cclient, string* crootpath, const char* cdebris, debris = cdebris; client->fsaccess->path2local(&debris, &localdebris); - dirnotify = auto_ptr(client->fsaccess->newdirnotify(crootpath, &localdebris)); + dirnotify.reset(client->fsaccess->newdirnotify(crootpath, &localdebris)); localdebris.insert(0, client->fsaccess->localseparator); localdebris.insert(0, *crootpath); @@ -78,7 +78,7 @@ Sync::Sync(MegaClient* cclient, string* crootpath, const char* cdebris, localdebris = *clocaldebris; // FIXME: pass last segment of localdebris - dirnotify = auto_ptr(client->fsaccess->newdirnotify(crootpath, &localdebris)); + dirnotify.reset(client->fsaccess->newdirnotify(crootpath, &localdebris)); } dirnotify->sync = this; @@ -600,7 +600,7 @@ LocalNode* Sync::checkpath(LocalNode* l, string* localpath, string* localname, d if (initializing || fullscan) { // find corresponding LocalNode by file-/foldername - int lastpart = client->fsaccess->lastpartlocal(localname ? localpath : &tmppath); + size_t lastpart = client->fsaccess->lastpartlocal(localname ? localpath : &tmppath); string fname(localname ? *localpath : tmppath, lastpart, @@ -1191,10 +1191,17 @@ dstime Sync::procscanq(int q) // delete all child LocalNodes that have been missing for two consecutive scans (*l must still exist) void Sync::deletemissing(LocalNode* l) { + string path; + FileAccess *fa = NULL; for (localnode_map::iterator it = l->children.begin(); it != l->children.end(); ) { if (scanseqno-it->second->scanseqno > 1) { + if (!fa) + { + fa = client->fsaccess->newfileaccess(); + } + client->unlinkifexists(it->second, fa, &path); delete it++->second; } else @@ -1203,6 +1210,7 @@ void Sync::deletemissing(LocalNode* l) it++; } } + delete fa; } bool Sync::movetolocaldebris(string* localpath) diff --git a/src/thread/win32thread.cpp b/src/thread/win32thread.cpp index 81efdc7010..704eab3169 100644 --- a/src/thread/win32thread.cpp +++ b/src/thread/win32thread.cpp @@ -39,8 +39,9 @@ Win32Thread::Win32Thread() DWORD WINAPI Win32Thread::run(LPVOID lpParameter) { - Win32Thread *object = (Win32Thread *)lpParameter; - return (DWORD)object->start_routine(object->pointer); + Win32Thread *object = (Win32Thread *)lpParameter; + object->start_routine(object->pointer); + return 0; } void Win32Thread::start(void *(*start_routine)(void*), void *parameter) diff --git a/src/transfer.cpp b/src/transfer.cpp index a51076b688..9092267bdc 100644 --- a/src/transfer.cpp +++ b/src/transfer.cpp @@ -162,18 +162,9 @@ bool Transfer::serialize(string *d) d->append((const char*)&hasUltoken, sizeof(char)); } - if (slot) - { - ll = (unsigned short)slot->tempurl.size(); - d->append((char*)&ll, sizeof(ll)); - d->append(slot->tempurl.data(), ll); - } - else - { - ll = (unsigned short)cachedtempurl.size(); - d->append((char*)&ll, sizeof(ll)); - d->append(cachedtempurl.data(), ll); - } + ll = (unsigned short)tempurl.size(); + d->append((char*)&ll, sizeof(ll)); + d->append(tempurl.data(), ll); char s = state; d->append((const char*)&s, sizeof(s)); @@ -307,7 +298,7 @@ Transfer *Transfer::unserialize(MegaClient *client, string *d, transfer_map* tra return NULL; } - t->cachedtempurl.assign(ptr, ll); + t->tempurl.assign(ptr, ll); ptr += ll; char state = MemAccess::get(ptr); @@ -367,6 +358,14 @@ void Transfer::failed(error e, dstime timeleft) LOG_debug << "Transfer failed with error " << e; + if (slot && slot->delayedchunk) + { + int creqtag = client->reqtag; + client->reqtag = 0; + client->sendevent(99442, "Upload with delayed chunks failed"); + client->reqtag = creqtag; + } + if (!timeleft || e != API_EOVERQUOTA) { bt.backoff(); @@ -387,14 +386,14 @@ void Transfer::failed(error e, dstime timeleft) if ( (*it)->failed(e) || (e == API_ENOENT // putnodes returned -9, file-storage server unavailable && type == PUT - && slot && slot->tempurl.empty() + && slot && slot->transfer->tempurl.empty() && failcount < 16) ) { defer = true; } } - cachedtempurl.clear(); + tempurl.clear(); if (type == PUT) { chunkmacs.clear(); @@ -893,8 +892,20 @@ void Transfer::complete() else { LOG_debug << "Upload complete: " << (files.size() ? files.front()->name : "NO_FILES") << " " << files.size(); - delete slot->fa; - slot->fa = NULL; + + if (slot->fa) + { + if (slot->delayedchunk) + { + int creqtag = client->reqtag; + client->reqtag = 0; + client->sendevent(99443, "Upload with delayed chunks completed"); + client->reqtag = creqtag; + } + + delete slot->fa; + slot->fa = NULL; + } // files must not change during a PUT transfer for (file_list::iterator it = files.begin(); it != files.end(); ) @@ -1009,11 +1020,11 @@ void Transfer::completefiles() m_off_t Transfer::nextpos() { - while (chunkmacs.find(ChunkedHash::chunkfloor(pos)) != chunkmacs.end()) + while (chunkmacs.find(ChunkedHash::chunkfloor(pos)) != chunkmacs.end() && pos < size) { if (chunkmacs[ChunkedHash::chunkfloor(pos)].finished) { - pos = ChunkedHash::chunkceil(pos); + pos = ChunkedHash::chunkceil(pos, size); } else { @@ -1717,7 +1728,6 @@ error TransferList::pause(Transfer *transfer, bool enable) if (transfer->slot) { transfer->bt.arm(); - transfer->cachedtempurl = transfer->slot->tempurl; delete transfer->slot; } transfer->state = TRANSFERSTATE_PAUSED; @@ -1805,7 +1815,6 @@ void TransferList::prepareIncreasePriority(Transfer *transfer, transfer_list::it if (lastActiveTransfer) { lastActiveTransfer->bt.arm(); - lastActiveTransfer->cachedtempurl = lastActiveTransfer->slot->tempurl; delete lastActiveTransfer->slot; lastActiveTransfer->state = TRANSFERSTATE_QUEUED; client->transfercacheadd(lastActiveTransfer); @@ -1824,7 +1833,6 @@ void TransferList::prepareDecreasePriority(Transfer *transfer, transfer_list::it if (!(*cit)->slot && isReady(*cit)) { transfer->bt.arm(); - transfer->cachedtempurl = (*it)->slot->tempurl; delete transfer->slot; transfer->state = TRANSFERSTATE_QUEUED; break; diff --git a/src/transferslot.cpp b/src/transferslot.cpp index 2ac6b2455e..5fee1bccb1 100644 --- a/src/transferslot.cpp +++ b/src/transferslot.cpp @@ -47,12 +47,16 @@ const dstime TransferSlot::PROGRESSTIMEOUT = 10; const m_off_t TransferSlot::MAX_REQ_SIZE = 4194304; // 4 MB #endif +const m_off_t TransferSlot::MAX_UPLOAD_GAP = 62914560; // 60 MB (up to 63 chunks) + TransferSlot::TransferSlot(Transfer* ctransfer) { starttime = 0; lastprogressreport = 0; progressreported = 0; speed = meanSpeed = 0; + progresscontiguous = 0; + delayedchunk = false; lastdata = Waiter::ds; errorcount = 0; @@ -176,7 +180,7 @@ TransferSlot::~TransferSlot() downloadRequest->finalize(transfer); m_off_t dlpos = downloadRequest->dlpos; m_off_t bufsize = downloadRequest->bufpos; - if (fa->fwrite(downloadRequest->buf, bufsize, dlpos)) + if (fa->fwrite(downloadRequest->buf, unsigned(bufsize), dlpos)) { LOG_verbose << "Sync write succeeded"; for (chunkmac_map::iterator it = downloadRequest->chunkmacs.begin(); it != downloadRequest->chunkmacs.end(); it++) @@ -347,7 +351,7 @@ void TransferSlot::doio(MegaClient* client) retrying = false; - if (!tempurl.size()) + if (!transfer->tempurl.size()) { return; } @@ -382,7 +386,7 @@ void TransferSlot::doio(MegaClient* client) lastdata = Waiter::ds; transfer->lastaccesstime = m_time(); - LOG_debug << "Chunk finished OK (" << transfer->type << ") Pos: " << transfer->pos + LOG_debug << "Transfer request finished (" << transfer->type << ") Position: " << reqs[i]->pos << " (" << transfer->pos << ") Size: " << reqs[i]->size << " Completed: " << (transfer->progresscompleted + reqs[i]->size) << " of " << transfer->size; if (transfer->type == PUT) @@ -426,6 +430,8 @@ void TransferSlot::doio(MegaClient* client) startpos = ChunkedHash::chunkceil(startpos, finalpos); } + updatecontiguousprogress(); + transfer->progresscompleted += reqs[i]->size; memcpy(transfer->filekey, transfer->transferkey, sizeof transfer->transferkey); ((int64_t*)transfer->filekey)[2] = transfer->ctriv; @@ -466,15 +472,22 @@ void TransferSlot::doio(MegaClient* client) break; } - if (reqs[i]->contenttype.find("text/html") != string::npos - && !memcmp(reqs[i]->posturl.c_str(), "http:", 5)) + if (e == API_ERATELIMIT || (reqs[i]->contenttype.find("text/html") != string::npos + && !memcmp(reqs[i]->posturl.c_str(), "http:", 5))) { - LOG_warn << "Invalid Content-Type detected during upload: " << reqs[i]->contenttype; client->usehttps = true; client->app->notify_change_to_https(); int creqtag = client->reqtag; client->reqtag = 0; + if (e == API_ERATELIMIT) + { + client->sendevent(99440, "Retry requested by storage server"); + } + else + { + LOG_warn << "Invalid Content-Type detected during upload: " << reqs[i]->contenttype; + } client->sendevent(99436, "Automatic change to HTTPS"); client->reqtag = creqtag; @@ -495,6 +508,8 @@ void TransferSlot::doio(MegaClient* client) } transfer->progresscompleted += reqs[i]->size; + updatecontiguousprogress(); + if (transfer->progresscompleted == transfer->size) { int creqtag = client->reqtag; @@ -531,13 +546,13 @@ void TransferSlot::doio(MegaClient* client) p += reqs[i]->size; LOG_debug << "Writting data asynchronously at " << downloadRequest->dlpos; - asyncIO[i] = fa->asyncfwrite(downloadRequest->buf, downloadRequest->bufpos, downloadRequest->dlpos); + asyncIO[i] = fa->asyncfwrite(downloadRequest->buf, unsigned(downloadRequest->bufpos), downloadRequest->dlpos); reqs[i]->status = REQ_ASYNCIO; } else { downloadRequest->finalize(transfer); - if (fa->fwrite(downloadRequest->buf, downloadRequest->bufpos, downloadRequest->dlpos)) + if (fa->fwrite(downloadRequest->buf, unsigned(downloadRequest->bufpos), downloadRequest->dlpos)) { LOG_verbose << "Sync write succeeded"; for (chunkmac_map::iterator it = downloadRequest->chunkmacs.begin(); it != downloadRequest->chunkmacs.end(); it++) @@ -550,6 +565,8 @@ void TransferSlot::doio(MegaClient* client) LOG_debug << "Saved data at: " << downloadRequest->dlpos << " Size: " << downloadRequest->bufpos; errorcount = 0; transfer->failcount = 0; + + updatecontiguousprogress(); } else { @@ -643,11 +660,11 @@ void TransferSlot::doio(MegaClient* client) LOG_verbose << "Async read succeeded"; m_off_t npos = asyncIO[i]->pos + asyncIO[i]->len; - string finaltempurl = tempurl; - if (client->usealtupport && !memcmp(tempurl.c_str(), "http:", 5)) + string finaltempurl = transfer->tempurl; + if (client->usealtupport && !memcmp(finaltempurl.c_str(), "http:", 5)) { - size_t index = tempurl.find("/", 8); - if(index != string::npos && tempurl.find(":", 8) == string::npos) + size_t index = finaltempurl.find("/", 8); + if(index != string::npos && finaltempurl.find(":", 8) == string::npos) { finaltempurl.insert(index, ":8080"); } @@ -674,6 +691,8 @@ void TransferSlot::doio(MegaClient* client) errorcount = 0; transfer->failcount = 0; + updatecontiguousprogress(); + if (transfer->progresscompleted == transfer->size) { if (transfer->progresscompleted) @@ -784,7 +803,7 @@ void TransferSlot::doio(MegaClient* client) LOG_warn << "Bandwidth overquota from storage server"; if (reqs[i]->timeleft > 0) { - backoff = reqs[i]->timeleft * 10; + backoff = dstime(reqs[i]->timeleft * 10); } else { @@ -805,13 +824,13 @@ void TransferSlot::doio(MegaClient* client) failure = true; bool changeport = false; - if (transfer->type == GET && client->autodownport && !memcmp(tempurl.c_str(), "http:", 5)) + if (transfer->type == GET && client->autodownport && !memcmp(transfer->tempurl.c_str(), "http:", 5)) { LOG_debug << "Automatically changing download port"; client->usealtdownport = !client->usealtdownport; changeport = true; } - else if (transfer->type == PUT && client->autoupport && !memcmp(tempurl.c_str(), "http:", 5)) + else if (transfer->type == PUT && client->autoupport && !memcmp(transfer->tempurl.c_str(), "http:", 5)) { LOG_debug << "Automatically changing upload port"; client->usealtupport = !client->usealtupport; @@ -839,14 +858,9 @@ void TransferSlot::doio(MegaClient* client) if (!reqs[i] || (reqs[i]->status == REQ_READY)) { m_off_t npos = ChunkedHash::chunkceil(transfer->nextpos(), transfer->size); - if (!transfer->size) - { - transfer->pos = 0; - } - if ((npos > transfer->pos) || !transfer->size || (transfer->type == PUT && asyncIO[i])) { - if (transfer->size && (transfer->type == GET || !asyncIO[i])) + if (transfer->size && transfer->type == GET) { m_off_t maxReqSize = (transfer->size - transfer->progresscompleted) / connections / 2; if (maxReqSize > maxRequestSize) @@ -880,9 +894,33 @@ void TransferSlot::doio(MegaClient* client) reqSize = npos - transfer->pos; it = transfer->chunkmacs.find(npos); } - LOG_debug << "Starting chunk of size " << reqSize; } + if (transfer->type == PUT && (npos - progresscontiguous) > MAX_UPLOAD_GAP) + { + if (!delayedchunk) + { + int creqtag = client->reqtag; + client->reqtag = 0; + client->sendevent(99441, "Management of delayed chunks active"); + client->reqtag = creqtag; + delayedchunk = true; + } + + if (fa->asyncavailable() && asyncIO[i]) + { + LOG_warn << "Retrying a failed read"; + m_off_t pos = asyncIO[i]->pos; + unsigned size = asyncIO[i]->len; + npos = pos + size; + delete asyncIO[i]; + asyncIO[i] = fa->asyncfread(reqs[i]->out, size, (-(int)size) & (SymmCipher::BLOCKSIZE - 1), pos); + reqs[i]->status = REQ_ASYNCIO; + } + continue; + } + + LOG_debug << "Starting chunk of size " << (npos - transfer->pos); if (!reqs[i]) { reqs[i] = transfer->type == PUT ? (HttpReqXfer*)new HttpReqUL() : (HttpReqXfer*)new HttpReqDL(); @@ -930,22 +968,22 @@ void TransferSlot::doio(MegaClient* client) if (prepare) { - string finaltempurl = tempurl; + string finaltempurl = transfer->tempurl; if (transfer->type == GET && client->usealtdownport - && !memcmp(tempurl.c_str(), "http:", 5)) + && !memcmp(finaltempurl.c_str(), "http:", 5)) { - size_t index = tempurl.find("/", 8); - if(index != string::npos && tempurl.find(":", 8) == string::npos) + size_t index = finaltempurl.find("/", 8); + if(index != string::npos && finaltempurl.find(":", 8) == string::npos) { finaltempurl.insert(index, ":8080"); } } if (transfer->type == PUT && client->usealtupport - && !memcmp(tempurl.c_str(), "http:", 5)) + && !memcmp(finaltempurl.c_str(), "http:", 5)) { - size_t index = tempurl.find("/", 8); - if(index != string::npos && tempurl.find(":", 8) == string::npos) + size_t index = finaltempurl.find("/", 8); + if(index != string::npos && finaltempurl.find(":", 8) == string::npos) { finaltempurl.insert(index, ":8080"); } @@ -983,6 +1021,7 @@ void TransferSlot::doio(MegaClient* client) if (reqs[i] && (reqs[i]->status == REQ_PREPARED)) { + reqs[i]->minspeed = true; reqs[i]->post(client); } } @@ -1020,13 +1059,13 @@ void TransferSlot::doio(MegaClient* client) failure = true; bool changeport = false; - if (transfer->type == GET && client->autodownport && !memcmp(tempurl.c_str(), "http:", 5)) + if (transfer->type == GET && client->autodownport && !memcmp(transfer->tempurl.c_str(), "http:", 5)) { LOG_debug << "Automatically changing download port due to a timeout"; client->usealtdownport = !client->usealtdownport; changeport = true; } - else if (transfer->type == PUT && client->autoupport && !memcmp(tempurl.c_str(), "http:", 5)) + else if (transfer->type == PUT && client->autoupport && !memcmp(transfer->tempurl.c_str(), "http:", 5)) { LOG_debug << "Automatically changing upload port due to a timeout"; client->usealtupport = !client->usealtupport; @@ -1085,4 +1124,17 @@ void TransferSlot::progress() (*it)->progress(); } } + +void TransferSlot::updatecontiguousprogress() +{ + chunkmac_map::iterator pcit; + chunkmac_map &pcchunkmacs = transfer->chunkmacs; + while ((pcit = pcchunkmacs.find(progresscontiguous)) != pcchunkmacs.end() + && pcit->second.finished) + { + progresscontiguous = ChunkedHash::chunkceil(progresscontiguous, transfer->size); + } + LOG_debug << "Contiguous progress: " << progresscontiguous << " (" << (transfer->pos - progresscontiguous) << ")"; +} + } // namespace diff --git a/src/treeproc.cpp b/src/treeproc.cpp index 67251c6812..f9008bc0b7 100644 --- a/src/treeproc.cpp +++ b/src/treeproc.cpp @@ -137,5 +137,14 @@ void LocalTreeProcUpdateTransfers::proc(MegaClient *, LocalNode *localnode) } } +void LocalTreeProcUnlinkNodes::proc(MegaClient *, LocalNode *localnode) +{ + if (localnode->node) + { + localnode->node->localnode = NULL; + localnode->node = NULL; + } +} + #endif } // namespace diff --git a/src/win32/autocomplete.cpp b/src/win32/autocomplete.cpp index bf0d5fd627..511e3a82fa 100644 --- a/src/win32/autocomplete.cpp +++ b/src/win32/autocomplete.cpp @@ -31,6 +31,7 @@ namespace fs = std::experimental::filesystem; namespace mega { namespace autocomplete { +using namespace std; template static T clamp(T v, T lo, T hi) @@ -78,6 +79,7 @@ void ACState::quoting::applyQuotes(std::string& w) if (quoted && quote_char != 0) { // reapply quotes as the user had them + w.reserve(w.size() + 2); w.insert(0, 1, quote_char); w.push_back(quote_char); } @@ -105,6 +107,13 @@ ACState::quoted_word::quoted_word(const std::string &str, const quoting& quot) { } +string ACState::quoted_word::getQuoted() +{ + string qs = s; + q.applyQuotes(qs); + return qs; +} + void ACState::addCompletion(const std::string& s, bool caseInsensitive) { // add if it matches the prefix. Doing the check here keeps subclasses simple @@ -140,6 +149,18 @@ void ACState::addPathCompletion(std::string& f, const std::string& relativeRootP { f.erase(0, relativeRootPath.size()); } + if (dir_sep != '\\') + { + string from = "\\"; + string to(1,dir_sep); + size_t start_pos = 0; + while (( start_pos = f.find(from, start_pos)) != std::string::npos) + { + f.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + } + if (unixStyle && isFolder) { f.push_back(dir_sep); @@ -147,8 +168,6 @@ void ACState::addPathCompletion(std::string& f, const std::string& relativeRootP addCompletion(f, caseInsensitive); } - - std::ostream& operator<<(std::ostream& s, const ACNode& n) { return n.describe(s); @@ -481,9 +500,10 @@ bool LocalFS::addCompletions(ACState& s) { if (s.atCursor()) { - fs::path searchPath(s.word().s + (s.word().s.empty() || s.word().s.back() == '\\' ? "*" : "")); + fs::path searchPath(s.word().s + (s.word().s.empty() || (s.word().s.back() == '\\' || s.word().s.back() == '/' ) ? "*" : "")); + char sep = (!s.word().s.empty() && s.word().s.find('/') != string::npos ) ?'/':'\\'; bool relative = !searchPath.is_absolute(); - searchPath = relative ? fs::current_path() / searchPath : searchPath; + searchPath = relative ? fs::current_path().append("\\").append(searchPath) : searchPath; std::string cp = relative ? fs::current_path().u8string() + "\\" : ""; if ((searchPath.filename() == ".." || searchPath.filename() == ".") && fs::exists(searchPath)) { @@ -492,12 +512,18 @@ bool LocalFS::addCompletions(ACState& s) else { searchPath.remove_filename(); // iterate the whole directory, and filter + std::string spath = searchPath.u8string(); + if (spath.back() == ':') + { + searchPath = spath.append("\\"); + } + for (fs::directory_iterator iter(searchPath); iter != fs::directory_iterator(); ++iter) { if (reportFolders && fs::is_directory(iter->status()) || reportFiles && fs::is_regular_file(iter->status())) { - s.addPathCompletion(iter->path().u8string(), cp, fs::is_directory(iter->status()), '\\', true); + s.addPathCompletion(iter->path().u8string(), cp, fs::is_directory(iter->status()), sep , true); } } } @@ -693,7 +719,7 @@ std::pair identifyNextWord(const std::string& line, int startPos) return ret; } -ACState prepACState(const std::string line, size_t insertPos, ACN syntax, bool unixStyle) +ACState prepACState(const std::string line, size_t insertPos, bool unixStyle) { if (insertPos == std::string::npos) { @@ -740,7 +766,7 @@ ACState prepACState(const std::string line, size_t insertPos, ACN syntax, bool u CompletionState autoComplete(const std::string line, size_t insertPos, ACN syntax, bool unixStyle) { - ACState acs = prepACState(line, insertPos, syntax, unixStyle); + ACState acs = prepACState(line, insertPos, unixStyle); acs.i = 0; syntax->addCompletions(acs); @@ -751,14 +777,14 @@ CompletionState autoComplete(const std::string line, size_t insertPos, ACN synta cs.originalWord = acs.words.back(); cs.completions = acs.completions; cs.unixStyle = acs.unixStyle; - + cs.tidyCompletions(); return cs; } -void autoExec(const std::string line, size_t insertPos, ACN syntax, bool unixStyle, string& consoleOutput) +bool autoExec(const std::string line, size_t insertPos, ACN syntax, bool unixStyle, string& consoleOutput, bool reportNoMatch) { - ACState acs = prepACState(line, insertPos, syntax, unixStyle); + ACState acs = prepACState(line, insertPos, unixStyle); if (!acs.words.empty() && (acs.words[0].s.size() || acs.words.size() > 1)) { @@ -787,6 +813,10 @@ void autoExec(const std::string line, size_t insertPos, ACN syntax, bool unixSty } if (v.empty()) { + if (!reportNoMatch) + { + return false; + } conout << "Invalid syntax"; if (firstWordMatches.empty()) { @@ -823,6 +853,7 @@ void autoExec(const std::string line, size_t insertPos, ACN syntax, bool unixSty consoleOutput = conout.str(); } } + return true; } @@ -872,6 +903,13 @@ unsigned CompletionState::calcUnixColumnWidthInGlyphs(int col, int rows) return width; } +void CompletionState::tidyCompletions() +{ + // sort and eliminate duplicates + std::sort(completions.begin(), completions.end(), [](const ACState::Completion& c1, const ACState::Completion& c2) { return c1.s < c2.s; }); + completions.erase(std::unique(completions.begin(), completions.end(), [](const ACState::Completion& c1, const ACState::Completion& c2) { return c1.s == c2.s; }), completions.end()); +} + void applyCompletion(CompletionState& s, bool forwards, unsigned consoleWidth, CompletionTextOut& textOut) { if (!s.completions.empty()) @@ -888,6 +926,11 @@ void applyCompletion(CompletionState& s, bool forwards, unsigned consoleWidth, C s.line.replace(s.wordPos.first, s.wordPos.second - s.wordPos.first, w); s.wordPos.second = int(w.size() + s.wordPos.first); s.lastAppliedIndex = index; + + if (s.completions.size()==1) + { + s.active = false; + } } else { diff --git a/src/win32/console.cpp b/src/win32/console.cpp index 9737997470..dc81b0875a 100644 --- a/src/win32/console.cpp +++ b/src/win32/console.cpp @@ -34,6 +34,8 @@ namespace mega { +using namespace std; + #ifdef NO_READLINE template static T clamp(T v, T lo, T hi) @@ -292,6 +294,21 @@ void ConsoleModel::autoComplete(bool forwards, unsigned consoleWidth) std::string u8line = WinConsole::toUtf8String(buffer); size_t u8InsertPos = WinConsole::toUtf8String(buffer.substr(0, insertPos)).size(); autocompleteState = autocomplete::autoComplete(u8line, u8InsertPos, autocompleteSyntax, unixCompletions); + + if (autocompleteFunction) + { + // also get additional app specific options, and merge + std::vector appcompletions = autocompleteFunction(WinConsole::toUtf8String(getInputLineToCursor())); + autocomplete::ACState acs; + acs.words.push_back(autocompleteState.originalWord); + acs.completions.swap(autocompleteState.completions); + for (auto& c : appcompletions) + { + acs.addCompletion(c.s, c.caseInsensitive); + } + autocompleteState.completions.swap(acs.completions); + autocompleteState.tidyCompletions(); + } autocompleteState.active = true; } @@ -394,6 +411,12 @@ bool ConsoleModel::checkForCompletedInputLine(std::wstring& ws) } return false; } + +std::wstring ConsoleModel::getInputLineToCursor() +{ + insertPos = clamp(insertPos, 0, buffer.size()); + return buffer.substr(0, insertPos); +} #endif WinConsole::WinConsole() @@ -406,6 +429,7 @@ WinConsole::WinConsole() GetConsoleMode(hInput, &dwMode); SetConsoleMode(hInput, dwMode & ~(ENABLE_MOUSE_INPUT)); FlushConsoleInputBuffer(hInput); + blockingConsolePeek = false; #endif } @@ -501,6 +525,11 @@ void WinConsole::setAutocompleteSyntax(autocomplete::ACN a) model.autocompleteSyntax = a; } +void WinConsole::setAutocompleteFunction(std::function(string)> f) +{ + model.autocompleteFunction = f; +} + void WinConsole::setAutocompleteStyle(bool unix) { model.unixCompletions = unix; @@ -519,10 +548,15 @@ HANDLE WinConsole::inputAvailableHandle() } bool WinConsole::consolePeek() +{ + return blockingConsolePeek?consolePeekBlocking():consolePeekNonBlocking(); +} + +bool WinConsole::consolePeekNonBlocking() { std::cout << std::flush; - // Read keypreses up to the first newline (or multiple newlines if + // Read keypreses up to the first newline (or multiple newlines if bool checkPromptOnce = true; for (;;) { @@ -542,7 +576,6 @@ bool WinConsole::consolePeek() if (isCharacterGeneratingKeypress && (currentPrompt.empty() || model.newlinesBuffered)) { - // wait until the next prompt is output before echoing and processing break; } @@ -593,6 +626,76 @@ bool WinConsole::consolePeek() return model.newlinesBuffered; } +bool WinConsole::consolePeekBlocking() +{ + std::cout << std::flush; + + // Read keypreses up to the first newline (or multiple newlines if + bool checkPromptOnce = true; + bool isCharacterGeneratingKeypress = false; + + INPUT_RECORD ir; + DWORD nRead; + BOOL ok = ReadConsoleInputW(hInput, &ir, 1, &nRead); // discard the event record + + irs.push_back(ir); + + isCharacterGeneratingKeypress = + ir.EventType == 1 && ir.Event.KeyEvent.uChar.UnicodeChar != 0 && + (ir.Event.KeyEvent.bKeyDown || // key press + (!ir.Event.KeyEvent.bKeyDown && ((ir.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) || ir.Event.KeyEvent.wVirtualKeyCode == VK_MENU))); // key release that emits a unicode char + + if (!(isCharacterGeneratingKeypress && (currentPrompt.empty() || model.newlinesBuffered))) + { + while(!irs.empty()) + { + INPUT_RECORD &ir = irs.front(); + ConsoleModel::lineEditAction action = interpretLineEditingKeystroke(ir); + + if ((action != ConsoleModel::nullAction || isCharacterGeneratingKeypress) && checkPromptOnce) + { + redrawPromptIfLoggingOccurred(); + checkPromptOnce = false; + } + if (action != ConsoleModel::nullAction) + { + CONSOLE_SCREEN_BUFFER_INFO sbi; + BOOL ok = GetConsoleScreenBufferInfo(hOutput, &sbi); + assert(ok); + unsigned consoleWidth = ok ? sbi.dwSize.X : 50; + + model.performLineEditingAction(action, consoleWidth); + } + else if (isCharacterGeneratingKeypress) + { + for (int i = ir.Event.KeyEvent.wRepeatCount; i--; ) + { + model.addInputChar(ir.Event.KeyEvent.uChar.UnicodeChar); + } + if (model.newlinesBuffered) // todo: address case where multiple newlines were added from this one record (as we may get stuck in wait()) + { + irs.pop_front(); + break; + } + } + irs.pop_front(); + } + } + if (model.redrawInputLineNeeded && model.echoOn) + { + redrawInputLine(&model.redrawInputLineConsoleFeedback); + } + if (model.consoleNewlineNeeded) + { + DWORD written = 0; + BOOL b = WriteConsoleW(hOutput, L"\n", 1, &written, NULL); + assert(b && written == 1); + } + model.redrawInputLineNeeded = false; + model.consoleNewlineNeeded = false; + return model.newlinesBuffered; +} + ConsoleModel::lineEditAction WinConsole::interpretLineEditingKeystroke(INPUT_RECORD &ir) { if (ir.EventType == 1 && ir.Event.KeyEvent.bKeyDown) @@ -672,10 +775,11 @@ void WinConsole::redrawInputLine(::mega::autocomplete::CompletionTextOut* autoco assert(ok); if (ok) { - std::string prompt = model.searchingHistory ? ("history-" + std::string(model.searchingHistoryForward ? "F:'" : "R:'") + toUtf8String(model.historySearchString) + "'> ") + std::string sprompt = model.searchingHistory ? ("history-" + std::string(model.searchingHistoryForward ? "F:'" : "R:'") + toUtf8String(model.historySearchString) + "'> ") : currentPrompt; + std::wstring wprompt = toUtf16String(sprompt); - if (long(prompt.size() + model.buffer.size() + 1) < sbi.dwSize.X || !model.echoOn) + if (long(wprompt.size() + model.buffer.size() + 1) < sbi.dwSize.X || !model.echoOn) { inputLineOffset = 0; } @@ -686,13 +790,13 @@ void WinConsole::redrawInputLine(::mega::autocomplete::CompletionTextOut* autoco if (inputLineOffset + showleft >= model.insertPos) { inputLineOffset = model.insertPos - std::min(showleft, model.insertPos); - } else if (prompt.size() + model.insertPos + 1 >= inputLineOffset + sbi.dwSize.X) + } else if (wprompt.size() + model.insertPos + 1 >= inputLineOffset + sbi.dwSize.X) { - inputLineOffset = prompt.size() + model.insertPos + 1 - sbi.dwSize.X; + inputLineOffset = wprompt.size() + model.insertPos + 1 - sbi.dwSize.X; } } - size_t width = std::max(prompt.size() + model.buffer.size() + 1 + inputLineOffset, sbi.dwSize.X); // +1 to show character under cursor + size_t width = std::max(wprompt.size() + model.buffer.size() + 1 + inputLineOffset, sbi.dwSize.X); // +1 to show character under cursor std::unique_ptr line(new CHAR_INFO[width]); for (size_t i = width; i--; ) @@ -702,20 +806,20 @@ void WinConsole::redrawInputLine(::mega::autocomplete::CompletionTextOut* autoco { line[i].Char.UnicodeChar = ' '; } - else if (inputLineOffset && i + 1 == inputLineOffset + prompt.size()) + else if (inputLineOffset && i + 1 == inputLineOffset + wprompt.size()) { line[i].Char.UnicodeChar = '|'; line[i].Attributes |= FOREGROUND_INTENSITY | FOREGROUND_GREEN; line[i].Attributes &= ~(FOREGROUND_RED | FOREGROUND_BLUE); } - else if (i < inputLineOffset + prompt.size()) + else if (i < inputLineOffset + wprompt.size()) { - line[i].Char.UnicodeChar = prompt[i - inputLineOffset]; + line[i].Char.UnicodeChar = wprompt[i - inputLineOffset]; line[i].Attributes |= FOREGROUND_INTENSITY; } - else if (i < prompt.size() + model.buffer.size() && model.echoOn) + else if (i < wprompt.size() + model.buffer.size() && model.echoOn) { - line[i].Char.UnicodeChar = model.buffer[i - prompt.size()]; + line[i].Char.UnicodeChar = model.buffer[i - wprompt.size()]; } else { @@ -727,7 +831,7 @@ void WinConsole::redrawInputLine(::mega::autocomplete::CompletionTextOut* autoco ok = WriteConsoleOutputW(hOutput, line.get(), COORD{ SHORT(width), 1 }, COORD{ SHORT(inputLineOffset), 0 }, &screenarea2); assert(ok); - COORD cpos{ SHORT(prompt.size() + model.insertPos - inputLineOffset), sbi.dwCursorPosition.Y }; + COORD cpos{ SHORT(wprompt.size() + model.insertPos - inputLineOffset), sbi.dwCursorPosition.Y }; ok = SetConsoleCursorPosition(hOutput, cpos); assert(ok); diff --git a/src/win32/fs.cpp b/src/win32/fs.cpp index 628d3bfe70..47dc70622a 100644 --- a/src/win32/fs.cpp +++ b/src/win32/fs.cpp @@ -137,10 +137,25 @@ bool WinFileAccess::sysstat(m_time_t* mtime, m_off_t* size) if (!GetFileAttributesExW((LPCWSTR)localname.data(), GetFileExInfoStandard, (LPVOID)&fad)) { DWORD e = GetLastError(); + errorcode = e; retry = WinFileSystemAccess::istransient(e); return false; } + errorcode = 0; + if (SimpleLogger::logCurrentLevel >= logDebug && skipattributes(fad.dwFileAttributes)) + { + string utf8path; + utf8path.resize((localname.size() + 1) * 4 / sizeof(wchar_t)); + utf8path.resize(WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)localname.data(), + localname.size() / sizeof(wchar_t), + (char*)utf8path.data(), + utf8path.size() + 1, + NULL, NULL)); + + LOG_debug << "Incompatible attributes (" << fad.dwFileAttributes << ") for file " << utf8path; + } + if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { type = FOLDERNODE; diff --git a/tests/crypto_test.cpp b/tests/crypto_test.cpp index 74579b6d24..abfcf31c23 100644 --- a/tests/crypto_test.cpp +++ b/tests/crypto_test.cpp @@ -205,7 +205,7 @@ TEST(Crypto, Ed25519_Signing) // Initialize variables - int keySeedLen = EdDSA::SEED_KEY_LENGTH; + const int keySeedLen = EdDSA::SEED_KEY_LENGTH; unsigned char keySeed[keySeedLen]; ASSERT_EQ(keySeedLen, Base64::atob(prEd255str.data(), keySeed, keySeedLen)) << "Failed to convert Ed25519 private key to binary"; @@ -306,7 +306,7 @@ TEST(Crypto, Ed25519_Signing) // ____ Create and verify signatures of random messages ____ - unsigned keylen = SymmCipher::KEYLENGTH; + const unsigned keylen = SymmCipher::KEYLENGTH; byte key[keylen]; string sig; for (int i = 0; i < 100; i++) diff --git a/tests/purge_account.cpp b/tests/purge_account.cpp index ec3da00f33..a24fd08cd3 100644 --- a/tests/purge_account.cpp +++ b/tests/purge_account.cpp @@ -28,6 +28,16 @@ static const string APP_KEY = "V8ZGDDBA"; static const unsigned int pollingT = 500000; // (microseconds) to check if response from server is received static const unsigned int maxTimeout = 300; // Maximum time (seconds) to wait for response from server + +void WaitMillisec(unsigned n) +{ +#ifdef _WIN32 + Sleep(n); +#else + usleep(n * 1000); +#endif +} + class PurgeAcc: public MegaListener, MegaRequestListener { public: @@ -183,7 +193,7 @@ void PurgeAcc::waitForResponse(bool *responseReceived, int timeout) int tWaited = 0; // microseconds while(!(*responseReceived)) { - usleep(pollingT); + WaitMillisec(pollingT/1000); if (timeout) { diff --git a/tests/sdk_test.cpp b/tests/sdk_test.cpp index f0be786800..47dc17b50f 100644 --- a/tests/sdk_test.cpp +++ b/tests/sdk_test.cpp @@ -21,12 +21,13 @@ #include "sdk_test.h" #include "megaapi_impl.h" +#include #ifdef _WIN32 #include #endif - +using namespace std; MegaFileSystemAccess fileSystemAccess; @@ -2105,7 +2106,7 @@ TEST_F(SdkTest, SdkTestShares) */ #ifdef WIN32 -bool cmp(const autocomplete::CompletionState& c, const std::vector& s) +bool cmp(const autocomplete::CompletionState& c, std::vector& s) { bool result = true; if (c.completions.size() != s.size()) @@ -2114,6 +2115,7 @@ bool cmp(const autocomplete::CompletionState& c, const std::vector& } else { + std::sort(s.begin(), s.end()); for (size_t i = c.completions.size(); i--; ) { if (c.completions[i].s != s[i]) diff --git a/tests/sdk_test.h b/tests/sdk_test.h index f380f590e5..7e7fec6a7c 100644 --- a/tests/sdk_test.h +++ b/tests/sdk_test.h @@ -57,7 +57,7 @@ class MegaLoggerSDK : public MegaLogger { ~MegaLoggerSDK(); private: - ofstream sdklog; + std::ofstream sdklog; protected: void log(const char *time, int loglevel, const char *source, const char *message); diff --git a/tests/synctests.cpp b/tests/synctests.cpp new file mode 100644 index 0000000000..fd06ec4989 --- /dev/null +++ b/tests/synctests.cpp @@ -0,0 +1,2006 @@ +/** + * @file tests/synctests.cpp + * @brief Mega SDK test file + * + * (c) 2018 by Mega Limited, Wellsford, New Zealand + * + * This file is part of the MEGA SDK - Client Access Engine. + * + * Applications using the MEGA API must present a valid application key + * and comply with the the rules set forth in the Terms of Service. + * + * The MEGA SDK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * @copyright Simplified (2-clause) BSD License. + * + * You should have received a copy of the license along with this + * program. + */ + +// Many of these tests are still being worked on. +// The file uses some C++17 mainly for the very convenient std::filesystem library, though the main SDK must still build with C++11 (and prior) + +#include +#include "gtest/gtest.h" +#include +#include +#include +#include +//#include +#include + +bool suppressfiles = false; + +using namespace ::mega; +using namespace ::std; +namespace fs = ::std::filesystem; +namespace { + +typedef ::mega::byte byte; + +#if defined(WIN32) && defined(NO_READLINE) +WinConsole* wc = NULL; +#endif + +void WaitMillisec(unsigned n) +{ +#ifdef _WIN32 + Sleep(n); +#else + usleep(n * 1000); +#endif +} +struct Model +{ + // records what we think the tree should look like after sync so we can confirm it + + struct ModelNode + { + enum nodetype { file, folder }; + nodetype type = folder; + string name; + vector> kids; + ModelNode* parent = nullptr; + string path() + { + string s; + for (auto p = this; p; p = p->parent) + s = "/" + p->name + s; + return s; + } + void addkid(unique_ptr&& p) + { + p->parent = this; + kids.emplace_back(move(p)); + } + bool typematchesnodetype(nodetype_t nodetype) + { + switch (type) + { + case file: return nodetype == FILENODE; + case folder: return nodetype == FOLDERNODE; + } + return false; + } + }; + + unique_ptr makeModelSubfolder(const string& utf8Name) + { + unique_ptr n(new ModelNode); + n->name = utf8Name; + return n; + } + + unique_ptr makeModelSubfile(const string& utf8Name) + { + unique_ptr n(new ModelNode); + n->name = utf8Name; + n->type = ModelNode::file; + return n; + } + + unique_ptr buildModelSubdirs(const string& prefix, int n, int recurselevel, int filesperdir) + { + if (suppressfiles) filesperdir = 0; + + unique_ptr nn = makeModelSubfolder(prefix); + + for (int i = 0; i < filesperdir; ++i) + { + nn->addkid(makeModelSubfile("file" + to_string(i) + "_" + prefix)); + } + + if (recurselevel > 0) + { + for (int i = 0; i < n; ++i) + { + unique_ptr sn = buildModelSubdirs(prefix + "_" + to_string(i), n, recurselevel - 1, filesperdir); + sn->parent = nn.get(); + nn->addkid(move(sn)); + } + } + return nn; + } + + ModelNode* childnodebyname(ModelNode* n, const std::string& s) + { + for (auto& m : n->kids) + { + if (m->name == s) + { + return m.get(); + } + } + return nullptr; + } + + ModelNode* findnode(string path, ModelNode* startnode = nullptr) + { + ModelNode* n = startnode ? startnode : root.get(); + while (n && !path.empty()) + { + auto pos = path.find("/"); + n = childnodebyname(n, path.substr(0, pos)); + path.erase(0, pos == string::npos ? path.size() : pos + 1); + } + return n; + } + + unique_ptr removenode(const string& path) + { + ModelNode* n = findnode(path); + if (n && n->parent) + { + unique_ptr extracted; + ModelNode* parent = n->parent; + auto newend = std::remove_if(parent->kids.begin(), parent->kids.end(), [&extracted, n](unique_ptr& v) { if (v.get() == n) return extracted = move(v), true; else return false; }); + parent->kids.erase(newend, parent->kids.end()); + return std::move(extracted); + } + return false; + } + + bool movenode(const string& sourcepath, const string& destpath) + { + ModelNode* source = findnode(sourcepath); + ModelNode* dest = findnode(destpath); + if (source && source && source->parent && dest) + { + unique_ptr n; + ModelNode* parent = source->parent; + auto newend = std::remove_if(parent->kids.begin(), parent->kids.end(), [&n, source](unique_ptr& v) { if (v.get() == source) return n = move(v), true; else return false; }); + parent->kids.erase(newend, parent->kids.end()); + if (n) + { + dest->addkid(move(n)); + return true; + } + } + return false; + } + + bool movetosynctrash(const string& path, const string& syncrootpath) + { + ModelNode* syncroot; + if (!(syncroot = findnode(syncrootpath))) + { + return false; + } + + ModelNode* trash; + if (!(trash = childnodebyname(syncroot, DEBRISFOLDER))) + { + auto uniqueptr = makeModelSubfolder(DEBRISFOLDER); + trash = uniqueptr.get(); + syncroot->addkid(move(uniqueptr)); + } + + char today[50]; + auto rawtime = time(NULL); + strftime(today, sizeof today, "%F", localtime(&rawtime)); + + ModelNode* dayfolder; + if (!(dayfolder = findnode(today, trash))) + { + auto uniqueptr = makeModelSubfolder(today); + dayfolder = uniqueptr.get(); + trash->addkid(move(uniqueptr)); + } + + if (auto uniqueptr = removenode(path)) + { + dayfolder->addkid(move(uniqueptr)); + return true; + } + return false; + } + + void ensureLocalDebrisTmpLock(const string& syncrootpath) + { + // if we've downloaded a file then it's put in debris/tmp initially, and there is a lock file + ModelNode* syncroot; + if (syncroot = findnode(syncrootpath)) + { + ModelNode* trash; + if (!(trash = childnodebyname(syncroot, DEBRISFOLDER))) + { + auto uniqueptr = makeModelSubfolder(DEBRISFOLDER); + trash = uniqueptr.get(); + syncroot->addkid(move(uniqueptr)); + } + + ModelNode* tmpfolder; + if (!(tmpfolder = findnode("tmp", trash))) + { + auto uniqueptr = makeModelSubfolder("tmp"); + tmpfolder = uniqueptr.get(); + trash->addkid(move(uniqueptr)); + } + + ModelNode* lockfile; + if (!(lockfile = findnode("lock", tmpfolder))) + { + tmpfolder->addkid(makeModelSubfile("lock")); + } + } + } + + bool removesynctrash(const string& syncrootpath, const string& subpath = "") + { + if (subpath.empty()) + { + return removenode(syncrootpath + "/" + DEBRISFOLDER).get(); + } + else + { + char today[50]; + auto rawtime = time(NULL); + strftime(today, sizeof today, "%F", localtime(&rawtime)); + + return removenode(syncrootpath + "/" + DEBRISFOLDER + "/" + today + "/" + subpath).get(); + } + } + + Model() : root(makeModelSubfolder("root")) + { + } + + unique_ptr root; +}; + + +bool waitonresults(future* r1 = nullptr, future* r2 = nullptr, future* r3 = nullptr, future* r4 = nullptr) +{ + if (r1) r1->wait(); + if (r2) r2->wait(); + if (r3) r3->wait(); + if (r4) r4->wait(); + return (!r1 || r1->get()) && (!r2 || r2->get()) && (!r3 || r3->get()) && (!r4 || r4->get()); +} + +struct StandardClient : public MegaApp +{ + HANDLE functionEventMC, functionEventSC; + WAIT_CLASS waiter; +#ifdef GFX_CLASS + GFX_CLASS gfx; +#endif + + MegaClient client; + bool clientthreadexit = false; + bool fatalerror = false; + string clientname; + std::function&)> nextfunctionMC; + std::promise nextfunctionMCpromise; + std::function&)> nextfunctionSC; + std::promise nextfunctionSCpromise; + std::condition_variable functionDone; + std::mutex functionDoneMutex; + + + fs::path fsBasePath; + + handle basefolderhandle = UNDEF; + + // thread as last member so everything else is initialised before we start it + std::thread clientthread; + + fs::path ensureDir(const fs::path& p) + { + fs::create_directories(p); + return p; + } + + StandardClient(const fs::path& basepath, const string& name) + : functionEventMC(CreateEvent(NULL, TRUE, FALSE, NULL)) + , functionEventSC(CreateEvent(NULL, TRUE, FALSE, NULL)) + , client(this, &waiter, new HTTPIO_CLASS, new FSACCESS_CLASS, +#ifdef DBACCESS_CLASS + new DBACCESS_CLASS(&(ensureDir(basepath / name).u8string() + "\\")), // client takes ownership of this one +#else + NULL, +#endif +#ifdef GFX_CLASS + &gfx, +#else + NULL, +#endif + "N9tSBJDC", "synctests") + , clientname(name) + , fsBasePath(basepath / name) + , clientthread([this]() { threadloop(); }) + { + //client.clientname = clientname + " "; + } + + ~StandardClient() + { + // shut down any syncs on the same thread, or they stall the client destruction (CancelIo instead of CancelIoEx on the WinDirNotify) + thread_do([](MegaClient& mc, promise&) { mc.locallogout(); /* mc.purgenodesusersabortsc();*/ }); + + clientthreadexit = true; + SetEvent(functionEventMC); + clientthread.join(); + } + + static mutex om; + bool logcb = false; + chrono::steady_clock::time_point lastcb = std::chrono::steady_clock::now(); + string lp(LocalNode* ln) { string lp; ln->getlocalpath(&lp); client.fsaccess->local2name(&lp); return lp; } + void syncupdate_state(Sync*, syncstate_t state) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_state() " << state << endl; } } + void syncupdate_scanning(bool b) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_scanning()" << b << endl; } } + //void syncupdate_local_folder_addition(Sync* s, LocalNode* ln, const char* cp) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_local_folder_addition() " << lp(ln) << " " << cp << endl; }} + //void syncupdate_local_folder_deletion(Sync*, LocalNode* ln) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_local_folder_deletion() " << lp(ln) << endl; }} + void syncupdate_local_folder_addition(Sync*, LocalNode* ln, const char* cp) override { lastcb = chrono::steady_clock::now(); } + void syncupdate_local_folder_deletion(Sync*, LocalNode* ln) override { lastcb = chrono::steady_clock::now(); } + void syncupdate_local_file_addition(Sync*, LocalNode* ln, const char* cp) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_local_file_addition() " << lp(ln) << " " << cp << endl; }} + void syncupdate_local_file_deletion(Sync*, LocalNode* ln) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_local_file_deletion() " << lp(ln) << endl; }} + void syncupdate_local_file_change(Sync*, LocalNode* ln, const char* cp) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_local_file_change() " << lp(ln) << " " << cp << endl; }} + void syncupdate_local_move(Sync*, LocalNode* ln, const char* cp) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_local_move() " << lp(ln) << " " << cp << endl; }} + void syncupdate_local_lockretry(bool b) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_local_lockretry() " << b << endl; }} + //void syncupdate_get(Sync*, Node* n, const char* cp) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_get()" << n->displaypath() << " " << cp << endl; }} + void syncupdate_put(Sync*, LocalNode* ln, const char* cp) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_put()" << lp(ln) << " " << cp << endl; }} + //void syncupdate_remote_file_addition(Sync*, Node* n) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_remote_file_addition() " << n->displaypath() << endl; }} + //void syncupdate_remote_file_deletion(Sync*, Node* n) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_remote_file_deletion() " << n->displaypath() << endl; }} + //void syncupdate_remote_folder_addition(Sync*, Node* n) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_remote_folder_addition() " << n->displaypath() << endl; }} + //void syncupdate_remote_folder_deletion(Sync*, Node* n) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_remote_folder_deletion() " << n->displaypath() << endl; }} + void syncupdate_remote_folder_addition(Sync*, Node* n) override { lastcb = chrono::steady_clock::now(); } + void syncupdate_remote_folder_deletion(Sync*, Node* n) override { lastcb = chrono::steady_clock::now(); } + void syncupdate_remote_copy(Sync*, const char* cp) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_remote_copy() " << cp << endl; }} + //void syncupdate_remote_move(Sync*, Node* n1, Node* n2) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_remote_move() " << n1->displaypath() << " " << n2->displaypath() << endl; }} + //void syncupdate_remote_rename(Sync*, Node* n, const char* cp) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_remote_rename() " << n->displaypath() << " " << cp << endl; }} + //void syncupdate_treestate(LocalNode* ln) override { if (logcb) { lock_guard g(om); cout << clientname << " syncupdate_treestate() " << ln->ts << " " << ln->dts << " " << lp(ln) << endl; }} + + + + void threadloop() + try + { + while (!clientthreadexit) + { + waiter.addhandle(functionEventMC, Waiter::HAVESTDIN); + waiter.addhandle(functionEventSC, Waiter::HAVESTDIN); + int r = client.wait(); + if (WAIT_OBJECT_0 == WaitForSingleObject(functionEventMC, 0)) + { + nextfunctionMC(client, nextfunctionMCpromise); + unique_lock guard(functionDoneMutex); + ResetEvent(functionEventMC); + functionDone.notify_all(); + r = Waiter::NEEDEXEC; + } + if (WAIT_OBJECT_0 == WaitForSingleObject(functionEventSC, 0)) + { + nextfunctionSC(*this, nextfunctionSCpromise); + unique_lock guard(functionDoneMutex); + ResetEvent(functionEventSC); + functionDone.notify_all(); + r = Waiter::NEEDEXEC; + } + if (r & Waiter::NEEDEXEC) + { + client.exec(); + } + } + cout << clientname << " thread exiting naturally" << endl; + } + catch (std::exception& e) + { + cout << clientname << " thread exception, StandardClient " << clientname << " terminated: " << e.what() << endl; + } + catch (...) + { + cout << clientname << " thread exception, StandardClient " << clientname << " terminated" << endl; + } + + static bool debugging; // turn this on to prevent the main thread timing out when stepping in the MegaClient + + future thread_do(std::function&)>&& f) + { + nextfunctionMCpromise = promise(); + nextfunctionMC = std::move(f); + SetEvent(functionEventMC); + std::mutex functionDoneMutex; + unique_lock guard(functionDoneMutex); + while (!functionDone.wait_until(guard, chrono::steady_clock::now() + 600s, [this]() { return WAIT_TIMEOUT == WaitForSingleObject(functionEventMC, 0); })) + { + if (!debugging) + { + nextfunctionMCpromise.set_value(false); + break; + } + } + return nextfunctionMCpromise.get_future(); + } + + future thread_do(std::function&)>&& f) + { + nextfunctionSCpromise = promise(); + nextfunctionSC = std::move(f); + SetEvent(functionEventSC); + unique_lock guard(functionDoneMutex); + while (!functionDone.wait_until(guard, chrono::steady_clock::now() + 600s, [this]() { return WAIT_TIMEOUT == WaitForSingleObject(functionEventSC, 0); })) + { + if (!debugging) + { + nextfunctionSCpromise.set_value(false); + break; + } + } + return nextfunctionSCpromise.get_future(); + } + + enum resultprocenum { LOGIN, FETCHNODES, PUTNODES, UNLINK }; + + void loginFromEnv(const string& userenv, const string& pwdenv, promise& pb) + { + string user = getenv(userenv.c_str()); + string pwd = getenv(pwdenv.c_str()); + + ASSERT_FALSE(user.empty()); + ASSERT_FALSE(pwd.empty()); + + byte pwkey[SymmCipher::KEYLENGTH]; + ASSERT_TRUE(API_OK == client.pw_key(pwd.c_str(), pwkey)); + + resultproc.prepresult(LOGIN, [this, &pb](error e) { pb.set_value(!e); }); + client.login(user.c_str(), pwkey); + } + + void loginFromSession(const string& session, promise& pb) + { + resultproc.prepresult(LOGIN, [this, &pb](error e) { pb.set_value(!e); }); + client.login((byte*)session.data(), (int)session.size()); + } + + + class TreeProcPrintTree : public TreeProc + { + public: + void proc(MegaClient* client, Node* n) override + { + //cout << "fetchnodes tree: " << n->displaypath() << endl;; + } + }; + + // mark node as removed and notify + + std::function& pb)> onFetchNodes; + + void fetchnodes(promise& pb) + { + resultproc.prepresult(FETCHNODES, [this, &pb](error e) + { + if (e) + { + pb.set_value(false); + } + else + { + TreeProcPrintTree tppt; + client.proctree(client.nodebyhandle(client.rootnodes[0]), &tppt); + + if (onFetchNodes) + { + onFetchNodes(*this, pb); + } + else + { + pb.set_value(true); + } + } + onFetchNodes = nullptr; + }); + client.fetchnodes(); + } + + NewNode* makeSubfolder(const string& utf8Name) + { + SymmCipher key; + string attrstring; + byte buf[FOLDERNODEKEYLENGTH]; + NewNode* newnode = new NewNode[1]; + + // set up new node as folder node + newnode->source = NEW_NODE; + newnode->type = FOLDERNODE; + newnode->nodehandle = 0; + newnode->parenthandle = UNDEF; + + // generate fresh random key for this folder node + PrnGen::genblock(buf, FOLDERNODEKEYLENGTH); + newnode->nodekey.assign((char*)buf, FOLDERNODEKEYLENGTH); + key.setkey(buf); + + // generate fresh attribute object with the folder name + AttrMap attrs; + string& n = attrs.map['n']; + client.fsaccess->normalize(&(n = utf8Name)); + + // JSON-encode object and encrypt attribute string + attrs.getjson(&attrstring); + newnode->attrstring = new string; + client.makeattr(&key, newnode->attrstring, attrstring.c_str()); + + return newnode; + } + + + struct ResultProc + { + map>> m; + + void prepresult(resultprocenum rpe, std::function&& f) + { + auto& entry = m[rpe]; + entry.emplace_back(move(f)); + } + + void processresult(resultprocenum rpe, error e) + { + //cout << "procenum " << rpe << " result " << e << endl; + auto& entry = m[rpe]; + if (!entry.empty()) + { + entry.front()(e); + entry.pop_front(); + } + else + { + assert(!entry.empty()); + } + } + } resultproc; + + void deleteTestBaseFolder(bool mayneeddeleting, promise& pb) + { + if (Node* root = client.nodebyhandle(client.rootnodes[0])) + { + if (Node* basenode = client.childnodebyname(root, "mega_test_sync", false)) + { + if (mayneeddeleting) + { + resultproc.prepresult(UNLINK, [this, &pb](error e) { + if (e) + { + cout << "delete of test base folder reply reports: " << e << endl; + } + deleteTestBaseFolder(false, pb); + }); + //cout << "old test base folder found, deleting" << endl; + client.unlink(basenode); + return; + } + cout << "base folder found, but not expected, failing" << endl; + pb.set_value(false); + return; + } + else + { + //cout << "base folder not found, wasn't present or delete successful" << endl; + pb.set_value(true); + return; + } + } + cout << "base folder not found, as root was not found!" << endl; + pb.set_value(false); + } + + void ensureTestBaseFolder(bool mayneedmaking, promise& pb) + { + if (Node* root = client.nodebyhandle(client.rootnodes[0])) + { + if (Node* basenode = client.childnodebyname(root, "mega_test_sync", false)) + { + if (basenode->type == FOLDERNODE) + { + basefolderhandle = basenode->nodehandle; + pb.set_value(true); + return; + } + } + else if (mayneedmaking) + { + resultproc.prepresult(PUTNODES, [this, &pb](error e) { + ensureTestBaseFolder(false, pb); + }); + client.putnodes(root->nodehandle, makeSubfolder("mega_test_sync"), 1); + return; + } + } + pb.set_value(false); + } + + NewNode* buildSubdirs(vector& nodes, const string& prefix, int n, int recurselevel) + { + NewNode* nn = makeSubfolder(prefix); + nodes.push_back(nn); + nn->nodehandle = nodes.size(); + + if (recurselevel > 0) + { + for (int i = 0; i < n; ++i) + { + buildSubdirs(nodes, prefix + "_" + to_string(i), n, recurselevel - 1)->parenthandle = nn->nodehandle; + } + } + + return nn; + } + + void makeCloudSubdirs(const string& prefix, int depth, int fanout, promise& pb, const string& atpath = "") + { + assert(basefolderhandle != UNDEF); + + vector nodes; + NewNode* nn = buildSubdirs(nodes, prefix, fanout, depth); + nn->parenthandle = UNDEF; + nn->ovhandle = UNDEF; + + NewNode* nodearray = new NewNode[nodes.size()]; + for (size_t i = 0; i < nodes.size(); ++i) + { + nodearray[i] = *nodes[i]; // todo: need move semantics + //delete nodes[i]; + } + + Node* atnode = client.nodebyhandle(basefolderhandle); + if (atnode && !atpath.empty()) + { + atnode = drillchildnodebyname(atnode, atpath); + } + if (!atnode) + { + cout << "path not found: " << atpath << endl; + pb.set_value(false); + } + else + { + resultproc.prepresult(PUTNODES, [this, &pb](error e) { + pb.set_value(!e); + if (e) + { + cout << "putnodes result: " << e << endl; + } + }); + client.putnodes(atnode->nodehandle, nodearray, (int)nodes.size()); + } + } + + struct SyncInfo { handle h; fs::path localpath; }; + map syncSet; + + Node* drillchildnodebyname(Node* n, const string& path) + { + for (size_t p = 0; n && p < path.size(); ) + { + auto pos = path.find("/", p); + if (pos == string::npos) pos = path.size(); + n = client.childnodebyname(n, path.substr(p, pos - p).c_str(), false); + p = pos == string::npos ? path.size() : pos + 1; + } + return n; + } + + bool setupSync_inthread(int syncid, const string& subfoldername, const fs::path& localpath) + { + if (Node* n = client.nodebyhandle(basefolderhandle)) + { + if (Node* m = drillchildnodebyname(n, subfoldername)) + { + string local, orig = localpath.u8string(); + client.fsaccess->path2local(&orig, &local); + error e = client.addsync(&local, DEBRISFOLDER, NULL, m, 0, syncid); // use syncid as tag + if (!e) + { + syncSet[syncid] = SyncInfo{ m->nodehandle, localpath }; + return true; + } + } + } + return false; + } + + + bool recursiveConfirm(Model::ModelNode* mn, Node* n, int& descendants, const string& identifier, int depth) + { + // top level names can differ so we don't check those + if (!mn || !n) return false; + if (depth && mn->name != n->displayname()) + { + cout << "Node name mismatch: " << mn->path() << " " << n->displaypath() << endl; + return false; + } + if (!mn->typematchesnodetype(n->type)) + { + cout << "Node type mismatch: " << mn->path() << ":" << mn->type << " " << n->displaypath() << ":" << n->type << endl; + return false; + } + + multimap ms; + multimap ns; + for (auto& m : mn->kids) ms.emplace(m->name, m.get()); + for (auto& n : n->children) ns.emplace(n->displayname(), n); + + int matched = 0; + vector matchedlist; + for (auto m_iter = ms.begin(); m_iter != ms.end(); ) + { + if (!depth && m_iter->first == DEBRISFOLDER) + { + m_iter = ms.erase(m_iter); // todo: add checks of the remote debris folder later + continue; + } + + auto er = ns.equal_range(m_iter->first); + auto next_m = m_iter; + ++next_m; + bool any_equal_matched = false; + for (auto i = er.first; i != er.second; ++i) + { + int rdescendants = 0; + if (recursiveConfirm(m_iter->second, i->second, rdescendants, identifier, depth+1)) + { + ++matched; + matchedlist.push_back(m_iter->first); + ns.erase(i); + ms.erase(m_iter); + descendants += rdescendants; + any_equal_matched = true; + break; + } + } + if (!any_equal_matched) + { + break; + } + m_iter = next_m; + } + if (ns.empty() && ms.empty()) + { + descendants += matched; + return true; + } + else + { + cout << identifier << " after matching " << matched << " child nodes ["; + for (auto& ml : matchedlist) cout << ml << " "; + cout << "](with " << descendants << " descendants) in " << mn->path() << ", ended up with unmatched model nodes:"; + for (auto& m : ms) cout << " " << m.first; + cout << " and unmatched remote nodes:"; + for (auto& n : ns) cout << " " << n.first; + cout << endl; + return false; + }; + } + + bool recursiveConfirm(Model::ModelNode* mn, LocalNode* n, int& descendants, const string& identifier, int depth) + { + // top level names can differ so we don't check those + if (!mn || !n) return false; + if (depth && mn->name != n->name) + { + cout << "LocalNode name mismatch: " << mn->path() << " " << n->name << endl; + return false; + } + if (!mn->typematchesnodetype(n->type)) + { + cout << "LocalNode type mismatch: " << mn->path() << ":" << mn->type << " " << n->name << ":" << n->type << endl; + return false; + } + + multimap ms; + multimap ns; + for (auto& m : mn->kids) ms.emplace(m->name, m.get()); + for (auto& n : n->children) if (!n.second->deleted) ns.emplace(n.second->name, n.second); // todo: should LocalNodes marked as deleted actually have been removed by now? + + int matched = 0; + vector matchedlist; + for (auto m_iter = ms.begin(); m_iter != ms.end(); ) + { + if (!depth && m_iter->first == DEBRISFOLDER) + { + m_iter = ms.erase(m_iter); // todo: are there LocalNodes representing the trash? + continue; + } + + auto er = ns.equal_range(m_iter->first); + auto next_m = m_iter; + ++next_m; + bool any_equal_matched = false; + for (auto i = er.first; i != er.second; ++i) + { + int rdescendants = 0; + if (recursiveConfirm(m_iter->second, i->second, rdescendants, identifier, depth+1)) + { + ++matched; + matchedlist.push_back(m_iter->first); + ns.erase(i); + ms.erase(m_iter); + descendants += rdescendants; + any_equal_matched = true; + break; + } + } + if (!any_equal_matched) + { + break; + } + m_iter = next_m; + } + if (ns.empty() && ms.empty()) + { + return true; + } + else + { + cout << identifier << " after matching " << matched << " child nodes ["; + for (auto& ml : matchedlist) cout << ml << " "; + cout << "](with " << descendants << " descendants) in " << mn->path() << ", ended up with unmatched model nodes:"; + for (auto& m : ms) cout << " " << m.first; + cout << " and unmatched LocalNodes:"; + for (auto& n : ns) cout << " " << n.first; + cout << endl; + return false; + }; + } + + + bool recursiveConfirm(Model::ModelNode* mn, fs::path p, int& descendants, const string& identifier, int depth) + { + if (!mn) return false; + if (depth && mn->name != p.filename().u8string()) + { + cout << "filesystem name mismatch: " << mn->path() << " " << p << endl; + return false; + } + nodetype_t pathtype = fs::is_directory(p) ? FOLDERNODE : fs::is_regular_file(p) ? FILENODE : TYPE_UNKNOWN; + if (!mn->typematchesnodetype(pathtype)) + { + cout << "Path type mismatch: " << mn->path() << ":" << mn->type << " " << p.u8string() << ":" << pathtype << endl; + return false; + } + + if (pathtype != FOLDERNODE) + { + return true; + } + + multimap ms; + multimap ps; + for (auto& m : mn->kids) ms.emplace(m->name, m.get()); + for (fs::directory_iterator pi(p); pi != fs::directory_iterator(); ++pi) ps.emplace(pi->path().filename().u8string(), pi->path()); + + int matched = 0; + vector matchedlist; + for (auto m_iter = ms.begin(); m_iter != ms.end(); ) + { + auto er = ps.equal_range(m_iter->first); + auto next_m = m_iter; + ++next_m; + bool any_equal_matched = false; + for (auto i = er.first; i != er.second; ++i) + { + int rdescendants = 0; + if (recursiveConfirm(m_iter->second, i->second, rdescendants, identifier, depth+1)) + { + ++matched; + matchedlist.push_back(m_iter->first); + ps.erase(i); + ms.erase(m_iter); + descendants += rdescendants; + any_equal_matched = true; + break; + } + } + if (!any_equal_matched) + { + break; + } + m_iter = next_m; + } + //if (ps.size() == 1 && !mn->parent && ps.begin()->first == DEBRISFOLDER) + //{ + // ps.clear(); + //} + if (ps.empty() && ms.empty()) + { + return true; + } + else + { + cout << identifier << " after matching " << matched << " child nodes ["; + for (auto& ml : matchedlist) cout << ml << " "; + cout << "](with " << descendants << " descendants) in " << mn->path() << ", ended up with unmatched model nodes:"; + for (auto& m : ms) cout << " " << m.first; + cout << " and unmatched filesystem paths:"; + for (auto& p : ps) cout << " " << p.second; + cout << endl; + return false; + }; + } + + Node* getBaseNode() + { + if (Node* root = client.nodebyhandle(client.rootnodes[0])) + { + return client.childnodebyname(root, "mega_test_sync", false); + } + return nullptr; + } + + Sync* syncByTag(int tag) + { + for (Sync* s : client.syncs) + { + if (s->tag == tag) + return s; + } + return nullptr; + } + + bool confirmModel(int syncid, Model::ModelNode* mnode) + { + auto si = syncSet.find(syncid); + if (si == syncSet.end()) + { + cout << "syncid " << syncid << " not found " << endl; + return false; + } + + // compare model aganst nodes representing remote state + int descendants = 0; + if (!recursiveConfirm(mnode, client.nodebyhandle(si->second.h), descendants, "Sync " + to_string(syncid), 0)) + { + cout << "syncid " << syncid << " comparison against remote nodes failed" << endl; + return false; + } + + // compare model against LocalNodes + descendants = 0; + if (Sync* sync = syncByTag(syncid)) + { + if (!recursiveConfirm(mnode, &sync->localroot, descendants, "Sync " + to_string(syncid), 0)) + { + cout << "syncid " << syncid << " comparison against LocalNodes failed" << endl; + return false; + } + } + + // compare model against local filesystem + descendants = 0; + if (!recursiveConfirm(mnode, si->second.localpath, descendants, "Sync " + to_string(syncid), 0)) + { + cout << "syncid " << syncid << " comparison against local filesystem failed" << endl; + return false; + } + + return true; + } + + void login_result(error e) override + { + cout << clientname << " Login: " << e << endl; + resultproc.processresult(LOGIN, e); + } + + void fetchnodes_result(error e) override + { + cout << clientname << " Fetchnodes: " << e << endl; + resultproc.processresult(FETCHNODES, e); + } + + void unlink_result(handle, error e) + { + resultproc.processresult(UNLINK, e); + } + + void putnodes_result(error e, targettype_t tt, NewNode* nn) override + { + if (nn) // ignore sync based putnodes + { + resultproc.processresult(PUTNODES, e); + } + } + + void deleteremote(string path, promise& pb ) + { + Node* n = getBaseNode(); + while (n && !path.empty()) + { + auto pos = path.find("/"); + n = client.childnodebyname(n, path.substr(0, pos).c_str(), false); + path.erase(0, pos == string::npos ? path.size() : pos + 1); + } + if (n) + { + resultproc.prepresult(UNLINK, [this, &pb](error e) { pb.set_value(!e); }); + client.unlink(n); + } + else + { + pb.set_value(false); + } + } + + void waitonsyncs(chrono::seconds d = 2s) + { + auto start = chrono::steady_clock::now(); + for (;;) + { + bool any_add_del = false;; + vector syncstates; + + thread_do([&syncstates, &any_add_del, this](StandardClient& mc, promise&) + { + for (auto& sync : mc.client.syncs) + { + syncstates.push_back(sync->state); + if (sync->deleteq.size() || sync->insertq.size()) + any_add_del = true; + } + if (!(client.todebris.empty() && client.tounlink.empty() && client.synccreate.empty())) + { + any_add_del = true; + } + }); + bool allactive = true; + { + lock_guard g(StandardClient::om); + //std::cout << "sync state: "; + //for (auto n : syncstates) + //{ + // cout << n; + // if (n != SYNC_ACTIVE) allactive = false; + //} + //cout << endl; + } + + if (any_add_del || debugging) + { + start = chrono::steady_clock::now(); + } + + if (allactive && ((chrono::steady_clock::now() - start) > d) && ((chrono::steady_clock::now() - lastcb) > d)) + break; + + Sleep(500); + } + + } + + bool login_reset(const string& user, const string& pw) + { + future p1; + p1 = thread_do([=](StandardClient& sc, promise& pb) { sc.loginFromEnv(user, pw, pb); }); + if (!waitonresults(&p1)) + { + cout << "loginFromEnv failed" << endl; + return false; + } + p1 = thread_do([](StandardClient& sc, promise& pb) { sc.fetchnodes(pb); }); + if (!waitonresults(&p1)) { + cout << "fetchnodes failed" << endl; + return false; + } + p1 = thread_do([](StandardClient& sc, promise& pb) { sc.deleteTestBaseFolder(true, pb); }); + if (!waitonresults(&p1)) { + cout << "deleteTestBaseFolder failed" << endl; + return false; + } + p1 = thread_do([](StandardClient& sc, promise& pb) { sc.ensureTestBaseFolder(true, pb); }); + if (!waitonresults(&p1)) { + cout << "ensureTestBaseFolder failed" << endl; + return false; + } + return true; + } + + bool login_reset_makeremotenodes(const string& user, const string& pw, const string& prefix, int depth, int fanout) + { + if (!login_reset(user, pw)) + { + cout << "login_reset failed" << endl; + return false; + } + future p1 = thread_do([=](StandardClient& sc, promise& pb) { sc.makeCloudSubdirs(prefix, depth, fanout, pb); }); + if (!waitonresults(&p1)) + { + cout << "makeCloudSubdirs failed" << endl; + return false; + } + return true; + } + + bool login_fetchnodes(const string& user, const string& pw) + { + future p2; + p2 = thread_do([=](StandardClient& sc, promise& pb) { sc.loginFromEnv(user, pw, pb); }); + if (!waitonresults(&p2)) return false; + p2 = thread_do([](StandardClient& sc, promise& pb) { sc.fetchnodes(pb); }); + if (!waitonresults(&p2)) return false; + p2 = thread_do([](StandardClient& sc, promise& pb) { sc.ensureTestBaseFolder(false, pb); }); + if (!waitonresults(&p2)) return false; + return true; + } + + bool login_fetchnodes(const string& session) + { + future p2; + p2 = thread_do([=](StandardClient& sc, promise& pb) { sc.loginFromSession(session, pb); }); + if (!waitonresults(&p2)) return false; + p2 = thread_do([](StandardClient& sc, promise& pb) { sc.fetchnodes(pb); }); + if (!waitonresults(&p2)) return false; + p2 = thread_do([](StandardClient& sc, promise& pb) { sc.ensureTestBaseFolder(false, pb); }); + if (!waitonresults(&p2)) return false; + return true; + } + + bool login_fetchnodes_resumesync(const string& session, const string& localsyncpath, const std::string& remotesyncrootfolder, int syncid) + { + future p2; + p2 = thread_do([=](StandardClient& sc, promise& pb) { sc.loginFromSession(session, pb); }); + if (!waitonresults(&p2)) return false; + + assert(!onFetchNodes); + onFetchNodes = [=](StandardClient& mc, promise& pb) + { + promise tp; + mc.ensureTestBaseFolder(false, tp); + pb.set_value(tp.get_future().get() ? mc.setupSync_inthread(syncid, remotesyncrootfolder, localsyncpath) : false); + }; + + p2 = thread_do([](StandardClient& sc, promise& pb) { sc.fetchnodes(pb); }); + if (!waitonresults(&p2)) return false; + p2 = thread_do([](StandardClient& sc, promise& pb) { sc.ensureTestBaseFolder(false, pb); }); + if (!waitonresults(&p2)) return false; + return true; + } + + bool setupSync_mainthread(const std::string& localsyncrootfolder, const std::string& remotesyncrootfolder, int syncid) + { + fs::path syncdir = fsBasePath / localsyncrootfolder; + fs::create_directory(syncdir); + future fb = thread_do([=](StandardClient& mc, promise& pb) { pb.set_value(mc.setupSync_inthread(syncid, remotesyncrootfolder, syncdir)); }); + return fb.get(); + } + + bool confirmModel_mainthread(Model::ModelNode* mnode, int syncid) + { + future fb; + fb = thread_do([syncid, mnode](StandardClient& sc, promise& pb) { pb.set_value(sc.confirmModel(syncid, mnode)); }); + return fb.get(); + } +}; + + +void waitonsyncs(chrono::seconds d = 4s, StandardClient* c1 = nullptr, StandardClient* c2 = nullptr, StandardClient* c3 = nullptr, StandardClient* c4 = nullptr) +{ + auto start = chrono::steady_clock::now(); + std::vector v{ c1, c2, c3, c4 }; + bool onelastsyncdown = true; + for (;;) + { + bool any_add_del = false; + vector syncstates; + + for (auto vn : v) if (vn) + { + vn->thread_do([&syncstates, &any_add_del](StandardClient& mc, promise&) + { + for (auto& sync : mc.client.syncs) + { + syncstates.push_back(sync->state); + if (sync->deleteq.size() || sync->insertq.size()) + any_add_del = true; + } + if (!(mc.client.todebris.empty() && mc.client.tounlink.empty() && mc.client.synccreate.empty())) + { + any_add_del = true; + } + }); + } + + bool allactive = true; + { + //lock_guard g(StandardClient::om); + //std::cout << "sync state: "; + //for (auto n : syncstates) + //{ + // cout << n; + // if (n != SYNC_ACTIVE) allactive = false; + //} + //cout << endl; + } + + if (any_add_del || StandardClient::debugging) + { + start = chrono::steady_clock::now(); + } + + if (onelastsyncdown && (chrono::steady_clock::now() - start + 1s) > d) + { + // synced folders that were removed remotely don't have the corresponding local folder removed unless we prompt an extra syncdown. // todo: do we need to fix + for (auto vn : v) if (vn) vn->client.syncdownrequired = true; + onelastsyncdown = false; + } + + for (auto vn : v) if (vn) + { + if (allactive && ((chrono::steady_clock::now() - start) > d) && ((chrono::steady_clock::now() - vn->lastcb) > d)) + return; + } + + Sleep(400); + } + +} + + +mutex StandardClient::om; +bool StandardClient::debugging = false; + +void moveToTrash(const fs::path& p) +{ + fs::path trashpath = p.parent_path() / "trash"; + fs::create_directory(trashpath); + fs::path newpath = trashpath / p.filename(); + for (int i = 2; fs::exists(newpath); ++i) + { + newpath = trashpath / (p.filename().stem().u8string() + "_" + to_string(i) + p.extension().u8string()); + } + fs::rename(p, newpath); +} + +fs::path makeNewTestRoot(fs::path p) +{ + if (fs::exists(p)) + { + moveToTrash(p); + } + bool b = fs::create_directory(p); + assert(b); + return p; +} + +bool buildLocalFolders(fs::path targetfolder, const string& prefix, int n, int recurselevel, int filesperfolder) +{ + if (suppressfiles) filesperfolder = 0; + + fs::path p = targetfolder / prefix; + if (!fs::create_directory(p)) + return false; + + for (int i = 0; i < filesperfolder; ++i) + { + string filename = "file" + to_string(i) + "_" + prefix; + fs::path fp = p / filename; + ofstream fs(fp.string()); + fs << filename; + } + + if (recurselevel > 0) + { + for (int i = 0; i < n; ++i) + { + if (!buildLocalFolders(p, prefix + "_" + to_string(i), n, recurselevel - 1, filesperfolder)) + return false; + } + } + + return true; +} + +GTEST_TEST(BasicSync, DelRemoteFolder) +{ + // delete a remote folder and confirm the client sending the request and another also synced both correctly update the disk + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1", "f", 3, 3)); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); + + ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "f", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); + waitonsyncs(4s, &clientA1, &clientA2); + clientA1.logcb = clientA2.logcb = true; + + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); + + // delete something remotely and let sync catch up + future fb = clientA1.thread_do([](StandardClient& sc, promise& pb) { sc.deleteremote("f/f_2/f_2_1", pb); }); + ASSERT_TRUE(waitonresults(&fb)); + waitonsyncs(4s, &clientA1, &clientA2); + + // check everything matches in both syncs (model has expected state of remote and local) + ASSERT_TRUE(model.movetosynctrash("f/f_2/f_2_1", "f")); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); +} + +GTEST_TEST(BasicSync, DelLocalFolder) +{ + // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1", "f", 3, 3)); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); + + // set up sync for A1, it should build matching local folders + ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "f", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); + waitonsyncs(4s, &clientA1, &clientA2); + clientA1.logcb = clientA2.logcb = true; + + // check everything matches (model has expected state of remote and local) + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); + + // delete something in the local filesystem and see if we catch up in A1 and A2 (deleter and observer syncs) + error_code e; + ASSERT_TRUE(fs::remove_all(clientA1.syncSet[1].localpath / "f_2" / "f_2_1", e) != static_cast(-1)) << e; + + // let them catch up + waitonsyncs(4s, &clientA1, &clientA2); + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(model.movetosynctrash("f/f_2/f_2_1", "f")); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); + ASSERT_TRUE(model.removesynctrash("f")); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); +} + +GTEST_TEST(BasicSync, MoveLocalFolder) +{ + // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1", "f", 3, 3)); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); + + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + + // set up sync for A1, it should build matching local folders + ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "f", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); + waitonsyncs(4s, &clientA1, &clientA2); + clientA1.logcb = clientA2.logcb = true; + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); + + // move something in the local filesystem and see if we catch up in A1 and A2 (deleter and observer syncs) + error_code rename_error; + fs::rename(clientA1.syncSet[1].localpath / "f_2" / "f_2_1", clientA1.syncSet[1].localpath / "f_2_1", rename_error); + ASSERT_TRUE(!rename_error) << rename_error; + + // let them catch up + waitonsyncs(4s, &clientA1, &clientA2); + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(model.movenode("f/f_2/f_2_1", "f")); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); +} + +GTEST_TEST(BasicSync, MoveLocalFolderBetweenSyncs) +{ + // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + StandardClient clientA3(localtestroot, "clientA3"); // user 1 client 3 + + ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1", "f", 3, 3)); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_TRUE(clientA3.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); + + // set up sync for A1 and A2, it should build matching local folders + ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "f/f_0", 11)); + ASSERT_TRUE(clientA1.setupSync_mainthread("sync2", "f/f_2", 12)); + ASSERT_TRUE(clientA2.setupSync_mainthread("syncA2_1", "f/f_0", 21)); + ASSERT_TRUE(clientA2.setupSync_mainthread("syncA2_2", "f/f_2", 22)); + ASSERT_TRUE(clientA3.setupSync_mainthread("syncA3", "f", 31)); + waitonsyncs(4s, &clientA1, &clientA2, &clientA3); + clientA1.logcb = clientA2.logcb = clientA3.logcb = true; + + // check everything matches (model has expected state of remote and local) + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f/f_0"), 11)); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f/f_2"), 12)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f/f_0"), 21)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f/f_2"), 22)); + ASSERT_TRUE(clientA3.confirmModel_mainthread(model.findnode("f"), 31)); + + // move a folder form one local synced folder to another local synced folder and see if we sync correctly and catch up in A2 and A3 (mover and observer syncs) + error_code rename_error; + fs::path path1 = clientA1.syncSet[11].localpath / "f_0_1"; + fs::path path2 = clientA1.syncSet[12].localpath / "f_2_1" / "f_2_1_0" / "f_0_1"; + fs::rename(path1, path2, rename_error); + ASSERT_TRUE(!rename_error) << rename_error; + + // let them catch up + waitonsyncs(4s, &clientA1, &clientA2, &clientA3); + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(model.movenode("f/f_0/f_0_1", "f/f_2/f_2_1/f_2_1_0")); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f/f_0"), 11)); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f/f_2"), 12)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f/f_0"), 21)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f/f_2"), 22)); + ASSERT_TRUE(clientA3.confirmModel_mainthread(model.findnode("f"), 31)); +} + + + +GTEST_TEST(BasicSync, AddLocalFolder) +{ + // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1", "f", 3, 3)); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); + + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + + // set up sync for A1, it should build matching local folders + ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "f", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); + waitonsyncs(4s, &clientA1, &clientA2); + clientA1.logcb = clientA2.logcb = true; + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); + + // make new folders (and files) in the local filesystem and see if we catch up in A1 and A2 (adder and observer syncs) + ASSERT_TRUE(buildLocalFolders(clientA1.syncSet[1].localpath / "f_2", "newkid", 2, 2, 2)); + + // let them catch up + waitonsyncs(120s, &clientA1, &clientA2); // two minutes should be long enough to get past API_ETEMPUNAVAIL == -18 for sync2 downloading the files uploaded by sync1 + + // check everything matches (model has expected state of remote and local) + model.findnode("f/f_2")->addkid(model.buildModelSubdirs("newkid", 2, 2, 2)); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + model.ensureLocalDebrisTmpLock("f"); // since we downloaded files + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); +} + + +GTEST_TEST(BasicSync, MAX_NEWNODES1) +{ + // create more nodes than we can upload in one putnodes. + // this tree is 5x5 and the algorithm ends up creating nodes one at a time so it's pretty slow (and doesn't hit MAX_NEWNODES as a result) + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1", "f", 3, 3)); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); + + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + + // set up sync for A1, it should build matching local folders + ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "f", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); + waitonsyncs(4s, &clientA1, &clientA2); + clientA1.logcb = clientA2.logcb = true; + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); + + // make new folders in the local filesystem and see if we catch up in A1 and A2 (adder and observer syncs) + assert(MegaClient::MAX_NEWNODES < 3125); + ASSERT_TRUE(buildLocalFolders(clientA1.syncSet[1].localpath, "g", 5, 5, 0)); // 5^5=3125 leaf folders, 625 pre-leaf etc + + // let them catch up + waitonsyncs(300s, &clientA1, &clientA2); + + // check everything matches (model has expected state of remote and local) + model.findnode("f")->addkid(model.buildModelSubdirs("g", 5, 5, 0)); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); +} + +GTEST_TEST(BasicSync, MAX_NEWNODES2) +{ + // create more nodes than we can upload in one putnodes. + // this tree is 5x5 and the algorithm ends up creating nodes one at a time so it's pretty slow (and doesn't hit MAX_NEWNODES as a result) + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1", "f", 3, 3)); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); + + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + + // set up sync for A1, it should build matching local folders + ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "f", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); + waitonsyncs(4s, &clientA1, &clientA2); + clientA1.logcb = clientA2.logcb = true; + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); + + // make new folders in the local filesystem and see if we catch up in A1 and A2 (adder and observer syncs) + assert(MegaClient::MAX_NEWNODES < 3000); + ASSERT_TRUE(buildLocalFolders(clientA1.syncSet[1].localpath, "g", 3000, 1, 0)); + + // let them catch up + waitonsyncs(300s, &clientA1, &clientA2); + + // check everything matches (model has expected state of remote and local) + model.findnode("f")->addkid(model.buildModelSubdirs("g", 3000, 1, 0)); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); +} + + +GTEST_TEST(BasicSync, MoveExistingIntoNewLocalFolder) +{ + // historic case: in the local filesystem, create a new folder then move an existing file/folder into it + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1", "f", 3, 3)); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); + + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + + // set up sync for A1, it should build matching local folders + ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "f", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); + waitonsyncs(4s, &clientA1, &clientA2); + clientA1.logcb = clientA2.logcb = true; + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); + + // make new folder in the local filesystem + ASSERT_TRUE(buildLocalFolders(clientA1.syncSet[1].localpath, "new", 1, 0, 0)); + // move an already synced folder into it + error_code rename_error; + fs::path path1 = clientA1.syncSet[1].localpath / "f_2"; // / "f_2_0" / "f_2_0_0"; + fs::path path2 = clientA1.syncSet[1].localpath / "new" / "f_2"; // "f_2_0_0"; + fs::rename(path1, path2, rename_error); + ASSERT_TRUE(!rename_error) << rename_error; + + // let them catch up + waitonsyncs(4s, &clientA1, &clientA2); + + // check everything matches (model has expected state of remote and local) + auto f = model.makeModelSubfolder("new"); + f->addkid(model.removenode("f/f_2")); // / f_2_0 / f_2_0_0")); + model.findnode("f")->addkid(move(f)); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); +} + +GTEST_TEST(BasicSync, MoveSeveralExistingIntoDeepNewLocalFolders) +{ + // historic case: in the local filesystem, create a new folder then move an existing file/folder into it + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(clientA1.login_reset_makeremotenodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1", "f", 3, 3)); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); + + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + + // set up sync for A1, it should build matching local folders + ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "f", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); + waitonsyncs(4s, &clientA1, &clientA2); + clientA1.logcb = clientA2.logcb = true; + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); + + // make new folder tree in the local filesystem + ASSERT_TRUE(buildLocalFolders(clientA1.syncSet[1].localpath, "new", 3, 3, 3)); + // move already synced folders to serveral parts of it - one under another moved folder too + error_code rename_error; + fs::rename(clientA1.syncSet[1].localpath / "f_0", clientA1.syncSet[1].localpath / "new" / "new_0" / "new_0_1" / "new_0_1_2" / "f_0", rename_error); + ASSERT_TRUE(!rename_error) << rename_error; + fs::rename(clientA1.syncSet[1].localpath / "f_1", clientA1.syncSet[1].localpath / "new" / "new_1" / "new_1_2" / "f_1", rename_error); + ASSERT_TRUE(!rename_error) << rename_error; + fs::rename(clientA1.syncSet[1].localpath / "f_2", clientA1.syncSet[1].localpath / "new" / "new_1" / "new_1_2" / "f_1" / "f_1_2" / "f_2", rename_error); + ASSERT_TRUE(!rename_error) << rename_error; + + // let them catch up + waitonsyncs(30s, &clientA1, &clientA2); + + // check everything matches (model has expected state of remote and local) + model.findnode("f")->addkid(model.buildModelSubdirs("new", 3, 3, 3)); + model.findnode("f/new/new_0/new_0_1/new_0_1_2")->addkid(model.removenode("f/f_0")); + model.findnode("f/new/new_1/new_1_2")->addkid(model.removenode("f/f_1")); + model.findnode("f/new/new_1/new_1_2/f_1/f_1_2")->addkid(model.removenode("f/f_2")); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.findnode("f"), 1)); + model.ensureLocalDebrisTmpLock("f"); // since we downloaded files + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); +} + + +GTEST_TEST(BasicSync, SyncDuplicateNames) +{ + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(clientA1.login_reset("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); + + + NewNode* nodearray = new NewNode[3]; + nodearray[0] = *clientA1.makeSubfolder("samename"); + nodearray[1] = *clientA1.makeSubfolder("samename"); + nodearray[2] = *clientA1.makeSubfolder("Samename"); + clientA1.resultproc.prepresult(StandardClient::PUTNODES, [this](error e) { + }); + clientA1.client.putnodes(clientA1.basefolderhandle, nodearray, 3); + + // set up syncs, they should build matching local folders + ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "", 2)); + waitonsyncs(4s, &clientA1, &clientA2); + clientA1.logcb = clientA2.logcb = true; + + // check everything matches (model has expected state of remote and local) + Model model; + model.root->addkid(model.makeModelSubfolder("samename")); + model.root->addkid(model.makeModelSubfolder("samename")); + model.root->addkid(model.makeModelSubfolder("Samename")); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.root.get(), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.root.get(), 2)); +} + +GTEST_TEST(BasicSync, RemoveLocalNodeBeforeSessionResume) +{ + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + auto pclientA1 = make_unique(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(pclientA1->login_reset_makeremotenodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1", "f", 3, 3)); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(pclientA1->basefolderhandle, clientA2.basefolderhandle); + + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + + // set up sync for A1, it should build matching local folders + ASSERT_TRUE(pclientA1->setupSync_mainthread("sync1", "f", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); + waitonsyncs(4s, pclientA1.get(), &clientA2); + pclientA1->logcb = clientA2.logcb = true; + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(pclientA1->confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); + + // save session + byte session[64]; + int sessionsize = pclientA1->client.dumpsession(session, sizeof session); + + // logout (but keep caches) + fs::path sync1path = pclientA1->syncSet[1].localpath; + pclientA1.reset(); + + // remove local folders + error_code e; + ASSERT_TRUE(fs::remove_all(sync1path / "f_2", e) != static_cast(-1)) << e; + + // resume session, see if nodes and localnodes get in sync + pclientA1.reset(new StandardClient(localtestroot, "clientA1")); + ASSERT_TRUE(pclientA1->login_fetchnodes_resumesync(string((char*)session, sessionsize), sync1path.u8string(), "f", 1)); + + waitonsyncs(4s, pclientA1.get(), &clientA2); + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(model.movetosynctrash("f/f_2", "f")); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); + ASSERT_TRUE(model.removesynctrash("f")); + ASSERT_TRUE(pclientA1->confirmModel_mainthread(model.findnode("f"), 1)); +} + +/* +// SN tagging needed for this one + +GTEST_TEST(BasicSync, RemoteFolderCreationRaceSamename) +{ + // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder + + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(clientA1.login_reset("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); + + // set up sync for both, it should build matching local folders (empty initially) + ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "", 2)); + waitonsyncs(4s, &clientA1, &clientA2); + clientA1.logcb = clientA2.logcb = true; + + // now have both clients create the same remote folder structure simultaneously. We should end up with just one copy of it on the server and in both syncs + future p1 = clientA1.thread_do([=](StandardClient& sc, promise& pb) { sc.makeCloudSubdirs("f", 3, 3, pb); }); + future p2 = clientA2.thread_do([=](StandardClient& sc, promise& pb) { sc.makeCloudSubdirs("f", 3, 3, pb); }); + ASSERT_TRUE(waitonresults(&p1, &p2)); + + // let them catch up + waitonsyncs(4s, &clientA1, &clientA2); + + // check everything matches (model has expected state of remote and local) + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.root.get(), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.root.get(), 2)); +} +*/ + +/* +// SN tagging needed for this one + +GTEST_TEST(BasicSync, LocalFolderCreationRaceSamename) +{ + // confirm change is synced to remote, and also seen and applied in a second client that syncs the same folder + // SN tagging needed for this one + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + StandardClient clientA1(localtestroot, "clientA1"); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(clientA1.login_reset("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(clientA1.basefolderhandle, clientA2.basefolderhandle); + + // set up sync for both, it should build matching local folders (empty initially) + ASSERT_TRUE(clientA1.setupSync_mainthread("sync1", "", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "", 2)); + waitonsyncs(4s, &clientA1, &clientA2); + clientA1.logcb = clientA2.logcb = true; + + // now have both clients create the same folder structure simultaneously. We should end up with just one copy of it on the server and in both syncs + future p1 = clientA1.thread_do([=](StandardClient& sc, promise& pb) { buildLocalFolders(sc.syncSet[1].localpath, "f", 3, 3, 0); pb.set_value(true); }); + future p2 = clientA2.thread_do([=](StandardClient& sc, promise& pb) { buildLocalFolders(sc.syncSet[2].localpath, "f", 3, 3, 0); pb.set_value(true); }); + ASSERT_TRUE(waitonresults(&p1, &p2)); + + // let them catch up + waitonsyncs(30s, &clientA1, &clientA2); + + // check everything matches (model has expected state of remote and local) + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + ASSERT_TRUE(clientA1.confirmModel_mainthread(model.root.get(), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.root.get(), 2)); +} +*/ + +GTEST_TEST(BasicSync, ResumeSyncFromSessionAfterNonclashingLocalAndRemoteChanges ) +{ + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + unique_ptr pclientA1(new StandardClient(localtestroot, "clientA1")); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(pclientA1->login_reset_makeremotenodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1", "f", 3, 3)); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(pclientA1->basefolderhandle, clientA2.basefolderhandle); + + // set up sync for A1, it should build matching local folders + ASSERT_TRUE(pclientA1->setupSync_mainthread("sync1", "f", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); + waitonsyncs(4s, pclientA1.get(), &clientA2); + pclientA1->logcb = clientA2.logcb = true; + + // check everything matches (model has expected state of remote and local) + Model model1, model2; + model1.root->addkid(model1.buildModelSubdirs("f", 3, 3, 0)); + model2.root->addkid(model2.buildModelSubdirs("f", 3, 3, 0)); + ASSERT_TRUE(pclientA1->confirmModel_mainthread(model1.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model2.findnode("f"), 2)); + + // save session A1 + byte session[64]; + int sessionsize = pclientA1->client.dumpsession(session, sizeof session); + + // logout A1 (but keep caches on disk) + fs::path sync1path = pclientA1->syncSet[1].localpath; + pclientA1.reset(); + + // add remote folders via A2 + future p1 = clientA2.thread_do([](StandardClient& sc, promise& pb) { sc.makeCloudSubdirs("newremote", 2, 2, pb, "f/f_1/f_1_0"); }); + model1.findnode("f/f_1/f_1_0")->addkid(model1.buildModelSubdirs("newremote", 2, 2, 0)); + model2.findnode("f/f_1/f_1_0")->addkid(model2.buildModelSubdirs("newremote", 2, 2, 0)); + ASSERT_TRUE(waitonresults(&p1)); + + // remove remote folders via A2 + p1 = clientA2.thread_do([](StandardClient& sc, promise& pb) { sc.deleteremote("f/f_0", pb); }); + model1.movetosynctrash("f/f_0", "f"); + model2.movetosynctrash("f/f_0", "f"); + ASSERT_TRUE(waitonresults(&p1)); + + // add local folders in A1 + ASSERT_TRUE(buildLocalFolders(sync1path / "f_1/f_1_2", "newlocal", 2, 2, 2)); + model1.findnode("f/f_1/f_1_2")->addkid(model1.buildModelSubdirs("newlocal", 2, 2, 0)); + model2.findnode("f/f_1/f_1_2")->addkid(model2.buildModelSubdirs("newlocal", 2, 2, 0)); + + // remove local folders in A1 + error_code e; + ASSERT_TRUE(fs::remove_all(sync1path / "f_2", e) != static_cast(-1)) << e; + model1.removenode("f/f_2"); + model2.movetosynctrash("f/f_2", "f"); + + // get sync2 activity out of the way + waitonsyncs(4s, &clientA2); + + // resume A1 session (with sync), see if A2 nodes and localnodes get in sync again + pclientA1.reset(new StandardClient(localtestroot, "clientA1")); + ASSERT_TRUE(pclientA1->login_fetchnodes_resumesync(string((char*)session, sessionsize), sync1path.u8string(), "f", 1)); + ASSERT_EQ(pclientA1->basefolderhandle, clientA2.basefolderhandle); + waitonsyncs(60s, pclientA1.get(), &clientA2); + waitonsyncs(60s, pclientA1.get(), &clientA2); + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(pclientA1->confirmModel_mainthread(model1.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model2.findnode("f"), 2)); +} + +GTEST_TEST(BasicSync, ResumeSyncFromSessionAfterClashingLocalAddRemoteDelete) +{ + fs::path localtestroot = makeNewTestRoot("c:\\tmp\\synctests"); + unique_ptr pclientA1(new StandardClient(localtestroot, "clientA1")); // user 1 client 1 + StandardClient clientA2(localtestroot, "clientA2"); // user 1 client 2 + + ASSERT_TRUE(pclientA1->login_reset_makeremotenodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1", "f", 3, 3)); + ASSERT_TRUE(clientA2.login_fetchnodes("MEGAAUTOTESTUSER1", "MEGAAUTOTESTPWD1")); + ASSERT_EQ(pclientA1->basefolderhandle, clientA2.basefolderhandle); + + Model model; + model.root->addkid(model.buildModelSubdirs("f", 3, 3, 0)); + + // set up sync for A1, it should build matching local folders + ASSERT_TRUE(pclientA1->setupSync_mainthread("sync1", "f", 1)); + ASSERT_TRUE(clientA2.setupSync_mainthread("sync2", "f", 2)); + waitonsyncs(4s, pclientA1.get(), &clientA2); + pclientA1->logcb = clientA2.logcb = true; + + // check everything matches (model has expected state of remote and local) + ASSERT_TRUE(pclientA1->confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); + + // save session A1 + byte session[64]; + int sessionsize = pclientA1->client.dumpsession(session, sizeof session); + fs::path sync1path = pclientA1->syncSet[1].localpath; + + // logout A1 (but keep caches on disk) + pclientA1.reset(); + + // remove remote folder via A2 + future p1 = clientA2.thread_do([](StandardClient& sc, promise& pb) { sc.deleteremote("f/f_1", pb); }); + ASSERT_TRUE(waitonresults(&p1)); + + // add local folders in A1 on disk folder + ASSERT_TRUE(buildLocalFolders(sync1path / "f_1/f_1_2", "newlocal", 2, 2, 2)); + + // get sync2 activity out of the way + waitonsyncs(4s, &clientA2); + + // resume A1 session (with sync), see if A2 nodes and localnodes get in sync again + pclientA1.reset(new StandardClient(localtestroot, "clientA1")); + ASSERT_TRUE(pclientA1->login_fetchnodes_resumesync(string((char*)session, sessionsize), sync1path.u8string(), "f", 1)); + ASSERT_EQ(pclientA1->basefolderhandle, clientA2.basefolderhandle); + waitonsyncs(4s, pclientA1.get(), &clientA2); + + // check everything matches (model has expected state of remote and local) + model.findnode("f/f_1/f_1_2")->addkid(model.buildModelSubdirs("newlocal", 2, 2, 0)); + ASSERT_TRUE(model.movetosynctrash("f/f_1", "f")); + ASSERT_TRUE(pclientA1->confirmModel_mainthread(model.findnode("f"), 1)); + ASSERT_TRUE(model.removesynctrash("f", "f_1/f_1_2/newlocal")); + ASSERT_TRUE(clientA2.confirmModel_mainthread(model.findnode("f"), 2)); +} + + + +class MegaCLILogger : public ::mega::Logger { +public: + virtual void log(const char *time, int loglevel, const char *source, const char *message) + { +#ifdef _WIN32 + OutputDebugStringA(message); + OutputDebugStringA("\r\n"); +#endif + + if (loglevel <= logWarning) + { + std::cout << message << std::endl; + } + } +}; + +MegaCLILogger logger; +}; // unnamed namespace + +int main (int argc, char *argv[]) +{ + remove("synctests.log"); + +#ifdef _WIN32 + SimpleLogger::setLogLevel(logDebug); // warning and stronger to console; info and weaker to VS output window + SimpleLogger::setOutputClass(&logger); +#else + SimpleLogger::setAllOutputs(&std::cout); +#endif + + +#if defined(WIN32) && defined(NO_READLINE) + wc = new CONSOLE_CLASS; + wc->setShellConsole(); +#endif + + ::testing::InitGoogleTest(&argc, argv); + auto x = RUN_ALL_TESTS(); + return x; +}