Skip to content

Commit 3c5a024

Browse files
authored
Added support for Dropbox, Google Drive and Mega (#2311)
1 parent d9139c3 commit 3c5a024

37 files changed

+537
-348
lines changed

Files.Launcher/Files.Launcher.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
8989
<Prefer32Bit>true</Prefer32Bit>
9090
</PropertyGroup>
91-
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
91+
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
9292
<DebugSymbols>true</DebugSymbols>
9393
<OutputPath>bin\ARM64\Debug\</OutputPath>
9494
<DefineConstants>DEBUG;TRACE</DefineConstants>
@@ -170,4 +170,4 @@
170170
<None Include="packages.config" />
171171
</ItemGroup>
172172
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
173-
</Project>
173+
</Project>

Files/Files.csproj

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@
178178
<DependentUpon>RestartDialog.xaml</DependentUpon>
179179
</Compile>
180180
<Compile Include="Filesystem\CloudDriveSyncStatus.cs" />
181+
<Compile Include="Filesystem\CloudProvider.cs" />
181182
<Compile Include="Filesystem\PropertiesData.cs" />
182183
<Compile Include="Filesystem\StorageFileHelpers\FilesystemResult.cs" />
183184
<Compile Include="Filesystem\StorageFileHelpers\StorageFileExtensions.cs" />
@@ -191,6 +192,7 @@
191192
<Compile Include="Helpers\JumpListManager.cs" />
192193
<Compile Include="Helpers\NativeDirectoryChangesHelper.cs" />
193194
<Compile Include="Helpers\NativeFindStorageItemHelper.cs" />
195+
<Compile Include="Helpers\NativeWinApiHelper.cs" />
194196
<Compile Include="Helpers\NaturalStringComparer.cs" />
195197
<Compile Include="Helpers\PackageHelper.cs" />
196198
<Compile Include="Helpers\PathNormalization.cs" />
@@ -650,6 +652,9 @@
650652
<PackageReference Include="ByteSize">
651653
<Version>2.0.0</Version>
652654
</PackageReference>
655+
<PackageReference Include="ini-parser-netstandard">
656+
<Version>2.5.2</Version>
657+
</PackageReference>
653658
<PackageReference Include="JetBrains.Annotations">
654659
<Version>2020.1.0</Version>
655660
</PackageReference>
@@ -659,6 +664,9 @@
659664
<PackageReference Include="Microsoft.AppCenter.Crashes">
660665
<Version>3.4.3</Version>
661666
</PackageReference>
667+
<PackageReference Include="Microsoft.Data.Sqlite.Core">
668+
<Version>5.0.0</Version>
669+
</PackageReference>
662670
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
663671
<Version>6.2.10</Version>
664672
</PackageReference>
@@ -704,6 +712,9 @@
704712
<PackageReference Include="NLog.Schema">
705713
<Version>4.7.5</Version>
706714
</PackageReference>
715+
<PackageReference Include="SQLitePCLRaw.bundle_green">
716+
<Version>2.0.4</Version>
717+
</PackageReference>
707718
</ItemGroup>
708719
<ItemGroup>
709720
<AppxManifest Include="..\Files.Package\Package.appxmanifest">

Files/Filesystem/CloudProvider.cs

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
using Files.Common;
2+
using Files.Helpers;
3+
using IniParser.Parser;
4+
using Microsoft.Data.Sqlite;
5+
using Newtonsoft.Json.Linq;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Diagnostics;
9+
using System.IO;
10+
using System.Linq;
11+
using System.Runtime.InteropServices;
12+
using System.Security.Cryptography;
13+
using System.Text;
14+
using System.Threading.Tasks;
15+
using Windows.Storage;
16+
17+
namespace Files.Filesystem
18+
{
19+
public enum KnownCloudProviders
20+
{
21+
ONEDRIVE,
22+
ONEDRIVE_BUSINESS,
23+
MEGASYNC,
24+
GOOGLEDRIVE,
25+
DROPBOX
26+
}
27+
28+
public class CloudProvider
29+
{
30+
public KnownCloudProviders ID { get; set; }
31+
public string SyncFolder { get; set; }
32+
public string Name { get; set; }
33+
34+
public static async Task<List<CloudProvider>> GetInstalledCloudProviders()
35+
{
36+
var ret = new List<CloudProvider>();
37+
DetectOneDrive(ret);
38+
await DetectGoogleDrive(ret);
39+
await DetectDropbox(ret);
40+
await DetectMegaSync(ret);
41+
return ret;
42+
}
43+
44+
#region DROPBOX
45+
private static async Task DetectDropbox(List<CloudProvider> ret)
46+
{
47+
try
48+
{
49+
var infoPath = @"Dropbox\info.json";
50+
var jsonPath = Path.Combine(UserDataPaths.GetDefault().LocalAppData, infoPath);
51+
var configFile = await StorageFile.GetFileFromPathAsync(jsonPath);
52+
var jsonObj = JObject.Parse(await FileIO.ReadTextAsync(configFile));
53+
var dropboxPath = (string)(jsonObj["personal"]["path"]);
54+
ret.Add(new CloudProvider() { ID = KnownCloudProviders.DROPBOX, SyncFolder = dropboxPath, Name = "Dropbox" });
55+
}
56+
catch
57+
{
58+
// Not detected
59+
}
60+
}
61+
#endregion
62+
63+
#region MEGASYNC
64+
private static async Task DetectMegaSync(List<CloudProvider> ret)
65+
{
66+
try
67+
{
68+
//var sidstring = System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString();
69+
//using var sid = AdvApi32.ConvertStringSidToSid(sidstring);
70+
var infoPath = @"Mega Limited\MEGAsync\MEGAsync.cfg";
71+
var configPath = Path.Combine(UserDataPaths.GetDefault().LocalAppData, infoPath);
72+
var configFile = await StorageFile.GetFileFromPathAsync(configPath);
73+
var parser = new IniDataParser();
74+
var data = parser.Parse(await FileIO.ReadTextAsync(configFile));
75+
byte[] fixedSeed = Encoding.UTF8.GetBytes("$JY/X?o=h·&%v/M(");
76+
byte[] localKey = getLocalStorageKey(); /*sid.GetBinaryForm()*/
77+
byte[] xLocalKey = XOR(fixedSeed, localKey);
78+
var sh = SHA1.Create();
79+
byte[] hLocalKey = sh.ComputeHash(xLocalKey);
80+
var encryptionKey = hLocalKey;
81+
82+
var mainSection = data.Sections.First(s => s.SectionName == "General");
83+
string currentGroup = "";
84+
85+
var currentAccountKey = hash("currentAccount", currentGroup, encryptionKey);
86+
var currentAccountStr = mainSection.Keys.First(s => s.KeyName == currentAccountKey);
87+
var currentAccountDecrypted = decrypt(currentAccountKey, currentAccountStr.Value.Replace("\"", ""), currentGroup);
88+
89+
var currentAccountSectionKey = hash(currentAccountDecrypted, "", encryptionKey);
90+
var currentAccountSection = data.Sections.First(s => s.SectionName == currentAccountSectionKey);
91+
92+
var syncKey = hash("Syncs", currentAccountSectionKey, encryptionKey);
93+
var syncGroups = currentAccountSection.Keys.Where(s => s.KeyName.StartsWith(syncKey)).Select(x => x.KeyName.Split('\\')[1]).Distinct();
94+
foreach (var sync in syncGroups)
95+
{
96+
currentGroup = string.Join("/", currentAccountSectionKey, syncKey, sync);
97+
var syncNameKey = hash("syncName", currentGroup, encryptionKey);
98+
var syncNameStr = currentAccountSection.Keys.First(s => s.KeyName == string.Join("\\", syncKey, sync, syncNameKey));
99+
var syncNameDecrypted = decrypt(syncNameKey, syncNameStr.Value.Replace("\"", ""), currentGroup);
100+
var localFolderKey = hash("localFolder", currentGroup, encryptionKey);
101+
var localFolderStr = currentAccountSection.Keys.First(s => s.KeyName == string.Join("\\", syncKey, sync, localFolderKey));
102+
var localFolderDecrypted = decrypt(localFolderKey, localFolderStr.Value.Replace("\"", ""), currentGroup);
103+
ret.Add(new CloudProvider() { ID = KnownCloudProviders.MEGASYNC, SyncFolder = localFolderDecrypted, Name = $"MEGA ({syncNameDecrypted})" });
104+
}
105+
}
106+
catch
107+
{
108+
// Not detected
109+
}
110+
}
111+
112+
private static byte[] getLocalStorageKey()
113+
{
114+
if (!NativeWinApiHelper.OpenProcessToken(NativeWinApiHelper.GetCurrentProcess(), NativeWinApiHelper.TokenAccess.TOKEN_QUERY, out var hToken))
115+
{
116+
return null;
117+
}
118+
119+
NativeWinApiHelper.GetTokenInformation(hToken, NativeWinApiHelper.TOKEN_INFORMATION_CLASS.TokenUser, IntPtr.Zero, 0, out int dwBufferSize);
120+
if (dwBufferSize == 0)
121+
{
122+
NativeWinApiHelper.CloseHandle(hToken);
123+
return null;
124+
}
125+
126+
IntPtr userToken = Marshal.AllocHGlobal(dwBufferSize);
127+
if (!NativeWinApiHelper.GetTokenInformation(hToken, NativeWinApiHelper.TOKEN_INFORMATION_CLASS.TokenUser, userToken, dwBufferSize, out var dwInfoBufferSize))
128+
{
129+
NativeWinApiHelper.CloseHandle(hToken);
130+
Marshal.FreeHGlobal(userToken);
131+
return null;
132+
}
133+
134+
var userStruct = (NativeWinApiHelper.TOKEN_USER)Marshal.PtrToStructure(userToken, typeof(NativeWinApiHelper.TOKEN_USER));
135+
/*if (!userStruct.User.Sid.IsValidSid())
136+
{
137+
NativeWinApiHelper.CloseHandle(hToken);
138+
Marshal.FreeHGlobal(userToken);
139+
return null;
140+
}*/
141+
142+
int dwLength = NativeWinApiHelper.GetLengthSid(userStruct.User.Sid);
143+
byte[] result = new byte[dwLength];
144+
Marshal.Copy((IntPtr)userStruct.User.Sid, result, 0, dwLength);
145+
NativeWinApiHelper.CloseHandle(hToken);
146+
Marshal.FreeHGlobal(userToken);
147+
return result;
148+
}
149+
150+
private static string decrypt(string key, string value, string group)
151+
{
152+
byte[] k = Encoding.ASCII.GetBytes(key);
153+
byte[] xValue = XOR(k, Convert.FromBase64String(value));
154+
byte[] xKey = XOR(k, Encoding.ASCII.GetBytes(group));
155+
156+
IntPtr xValuePtr = Marshal.AllocHGlobal(xValue.Length);
157+
Marshal.Copy(xValue, 0, xValuePtr, xValue.Length);
158+
IntPtr xKeyPtr = Marshal.AllocHGlobal(xKey.Length);
159+
Marshal.Copy(xKey, 0, xKeyPtr, xKey.Length);
160+
161+
NativeWinApiHelper.CRYPTOAPI_BLOB dataIn = new NativeWinApiHelper.CRYPTOAPI_BLOB();
162+
NativeWinApiHelper.CRYPTOAPI_BLOB entropy = new NativeWinApiHelper.CRYPTOAPI_BLOB();
163+
dataIn.pbData = xValuePtr;
164+
dataIn.cbData = (uint)xValue.Length;
165+
entropy.pbData = xKeyPtr;
166+
entropy.cbData = (uint)xKey.Length;
167+
168+
if (!NativeWinApiHelper.CryptUnprotectData(dataIn, null, entropy, IntPtr.Zero, IntPtr.Zero, 0, out var dataOut))
169+
{
170+
Marshal.FreeHGlobal(xValuePtr);
171+
Marshal.FreeHGlobal(xKeyPtr);
172+
return null;
173+
}
174+
175+
byte[] managedArray = new byte[dataOut.cbData];
176+
Marshal.Copy(dataOut.pbData, managedArray, 0, (int)dataOut.cbData);
177+
byte[] xDecrypted = XOR(k, managedArray);
178+
Marshal.FreeHGlobal(dataOut.pbData);
179+
Marshal.FreeHGlobal(xValuePtr);
180+
Marshal.FreeHGlobal(xKeyPtr);
181+
return Encoding.UTF8.GetString(xDecrypted);
182+
}
183+
184+
private static string hash(string key, string group, byte[] encryptionKey)
185+
{
186+
var sh = SHA1.Create();
187+
byte[] xPath = XOR(encryptionKey, Encoding.UTF8.GetBytes(key + group));
188+
byte[] keyHash = sh.ComputeHash(xPath);
189+
byte[] xKeyHash = XOR(Encoding.UTF8.GetBytes(key), keyHash);
190+
return ByteArrayToString(xKeyHash);
191+
}
192+
193+
public static string ByteArrayToString(byte[] ba)
194+
{
195+
return BitConverter.ToString(ba).Replace("-", "").ToLowerInvariant();
196+
}
197+
198+
private static byte[] XOR(byte[] key, byte[] data)
199+
{
200+
int keyLen = key.Length;
201+
if (keyLen == 0)
202+
{
203+
return data;
204+
}
205+
206+
var result = new List<byte>();
207+
var k3 = key[keyLen / 3] >= 128 ? key[keyLen / 3] - 256 : key[keyLen / 3];
208+
var k5 = key[keyLen / 5] >= 128 ? key[keyLen / 5] - 256 : key[keyLen / 5];
209+
var k2 = key[keyLen / 2] >= 128 ? key[keyLen / 2] - 256 : key[keyLen / 2];
210+
var k7 = key[keyLen / 7] >= 128 ? key[keyLen / 7] - 256 : key[keyLen / 7];
211+
int rotation = Math.Abs(k3 * k5) % keyLen;
212+
int increment = Math.Abs(k2 * k7) % keyLen;
213+
for (int i = 0, j = rotation; i < data.Length; i++, j -= increment)
214+
{
215+
if (j < 0)
216+
{
217+
j += keyLen;
218+
}
219+
result.Add((byte)(data[i] ^ key[j]));
220+
}
221+
return result.ToArray();
222+
}
223+
#endregion
224+
225+
#region ONEDRIVE
226+
private static void DetectOneDrive(List<CloudProvider> ret)
227+
{
228+
try
229+
{
230+
var onedrive_personal = Environment.GetEnvironmentVariable("OneDriveConsumer");
231+
if (!string.IsNullOrEmpty(onedrive_personal))
232+
{
233+
ret.Add(new CloudProvider() { ID = KnownCloudProviders.ONEDRIVE, SyncFolder = onedrive_personal, Name = $"OneDrive" });
234+
}
235+
var onedrive_commercial = Environment.GetEnvironmentVariable("OneDriveCommercial");
236+
if (!string.IsNullOrEmpty(onedrive_commercial))
237+
{
238+
ret.Add(new CloudProvider() { ID = KnownCloudProviders.ONEDRIVE_BUSINESS, SyncFolder = onedrive_commercial, Name = $"OneDrive Commercial" });
239+
}
240+
}
241+
catch
242+
{
243+
// Not detected
244+
}
245+
}
246+
#endregion
247+
248+
#region GOOGLEDRIVE
249+
private static async Task DetectGoogleDrive(List<CloudProvider> ret)
250+
{
251+
try
252+
{
253+
// Google Drive's sync database can be in a couple different locations. Go find it.
254+
string appDataPath = UserDataPaths.GetDefault().LocalAppData;
255+
string dbPath = @"Google\Drive\user_default\sync_config.db";
256+
var configFile = await StorageFile.GetFileFromPathAsync(Path.Combine(appDataPath, dbPath));
257+
await configFile.CopyAsync(ApplicationData.Current.TemporaryFolder, "google_drive.db", NameCollisionOption.ReplaceExisting);
258+
var syncDbPath = Path.Combine(ApplicationData.Current.TemporaryFolder.Path, "google_drive.db");
259+
260+
// Build the connection and sql command
261+
SQLitePCL.Batteries_V2.Init();
262+
using (var con = new SqliteConnection($"Data Source='{syncDbPath}'"))
263+
using (var cmd = new SqliteCommand("select * from data where entry_key='local_sync_root_path'", con))
264+
{
265+
// Open the connection and execute the command
266+
con.Open();
267+
var reader = cmd.ExecuteReader();
268+
reader.Read();
269+
270+
// Extract the data from the reader
271+
string path = reader["data_value"]?.ToString();
272+
if (string.IsNullOrWhiteSpace(path)) return;
273+
274+
// By default, the path will be prefixed with "\\?\" (unless another app has explicitly changed it).
275+
// \\?\ indicates to Win32 that the filename may be longer than MAX_PATH (see MSDN).
276+
// Parts of .NET (e.g. the File class) don't handle this very well, so remove this prefix.
277+
if (path.StartsWith(@"\\?\"))
278+
path = path.Substring(@"\\?\".Length);
279+
280+
ret.Add(new CloudProvider() { ID = KnownCloudProviders.GOOGLEDRIVE, SyncFolder = path, Name = $"Google Drive" });
281+
}
282+
}
283+
catch
284+
{
285+
// Not detected
286+
}
287+
}
288+
#endregion
289+
}
290+
}

0 commit comments

Comments
 (0)