1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    The code included in this file is provided under the terms of the ISC license
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12    To use, copy, modify, and/or distribute this software for any purpose with or
13    without fee is hereby granted provided that the above copyright notice and
14    this permission notice appear in all copies.
15 
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18    DISCLAIMED.
19 
20   ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 #ifndef INVALID_FILE_ATTRIBUTES
27  #define INVALID_FILE_ATTRIBUTES ((DWORD) -1)
28 #endif
29 
30 //==============================================================================
31 namespace WindowsFileHelpers
32 {
33     //==============================================================================
34    #if JUCE_WINDOWS
35     typedef struct _REPARSE_DATA_BUFFER {
36       ULONG  ReparseTag;
37       USHORT ReparseDataLength;
38       USHORT Reserved;
39       union {
40         struct {
41           USHORT SubstituteNameOffset;
42           USHORT SubstituteNameLength;
43           USHORT PrintNameOffset;
44           USHORT PrintNameLength;
45           ULONG  Flags;
46           WCHAR  PathBuffer[1];
47         } SymbolicLinkReparseBuffer;
48         struct {
49           USHORT SubstituteNameOffset;
50           USHORT SubstituteNameLength;
51           USHORT PrintNameOffset;
52           USHORT PrintNameLength;
53           WCHAR  PathBuffer[1];
54         } MountPointReparseBuffer;
55         struct {
56           UCHAR DataBuffer[1];
57         } GenericReparseBuffer;
58       } DUMMYUNIONNAME;
59     } *PREPARSE_DATA_BUFFER, REPARSE_DATA_BUFFER;
60    #endif
61 
62     //==============================================================================
getAtts(const String & path)63     DWORD getAtts (const String& path) noexcept
64     {
65         return GetFileAttributes (path.toWideCharPointer());
66     }
67 
changeAtts(const String & path,DWORD bitsToSet,DWORD bitsToClear)68     bool changeAtts (const String& path, DWORD bitsToSet, DWORD bitsToClear) noexcept
69     {
70         auto oldAtts = getAtts (path);
71 
72         if (oldAtts == INVALID_FILE_ATTRIBUTES)
73             return false;
74 
75         auto newAtts = ((oldAtts | bitsToSet) & ~bitsToClear);
76 
77         return newAtts == oldAtts
78                 || SetFileAttributes (path.toWideCharPointer(), newAtts) != FALSE;
79     }
80 
fileTimeToTime(const FILETIME * const ft)81     int64 fileTimeToTime (const FILETIME* const ft) noexcept
82     {
83         static_assert (sizeof (ULARGE_INTEGER) == sizeof (FILETIME),
84                        "ULARGE_INTEGER is too small to hold FILETIME: please report!");
85 
86         return (int64) ((reinterpret_cast<const ULARGE_INTEGER*> (ft)->QuadPart - 116444736000000000LL) / 10000);
87     }
88 
timeToFileTime(const int64 time,FILETIME * const ft)89     FILETIME* timeToFileTime (const int64 time, FILETIME* const ft) noexcept
90     {
91         if (time <= 0)
92             return nullptr;
93 
94         reinterpret_cast<ULARGE_INTEGER*> (ft)->QuadPart = (ULONGLONG) (time * 10000 + 116444736000000000LL);
95         return ft;
96     }
97 
getDriveFromPath(String path)98     String getDriveFromPath (String path)
99     {
100         if (path.isNotEmpty() && path[1] == ':' && path[2] == 0)
101             path << '\\';
102 
103         const size_t numBytes = CharPointer_UTF16::getBytesRequiredFor (path.getCharPointer()) + 4;
104         HeapBlock<WCHAR> pathCopy;
105         pathCopy.calloc (numBytes, 1);
106         path.copyToUTF16 (pathCopy, numBytes);
107 
108         if (PathStripToRoot (pathCopy))
109             path = static_cast<const WCHAR*> (pathCopy);
110 
111         return path;
112     }
113 
getDiskSpaceInfo(const String & path,const bool total)114     int64 getDiskSpaceInfo (const String& path, const bool total)
115     {
116         ULARGE_INTEGER spc, tot, totFree;
117 
118         if (GetDiskFreeSpaceEx (getDriveFromPath (path).toWideCharPointer(), &spc, &tot, &totFree))
119             return total ? (int64) tot.QuadPart
120                          : (int64) spc.QuadPart;
121 
122         return 0;
123     }
124 
getWindowsDriveType(const String & path)125     unsigned int getWindowsDriveType (const String& path)
126     {
127         return GetDriveType (getDriveFromPath (path).toWideCharPointer());
128     }
129 
getSpecialFolderPath(int type)130     File getSpecialFolderPath (int type)
131     {
132         WCHAR path[MAX_PATH + 256];
133 
134         if (SHGetSpecialFolderPath (nullptr, path, type, FALSE))
135             return File (String (path));
136 
137         return {};
138     }
139 
getModuleFileName(HINSTANCE moduleHandle)140     File getModuleFileName (HINSTANCE moduleHandle)
141     {
142         WCHAR dest[MAX_PATH + 256];
143         dest[0] = 0;
144         GetModuleFileName (moduleHandle, dest, (DWORD) numElementsInArray (dest));
145         return File (String (dest));
146     }
147 
getResultForLastError()148     Result getResultForLastError()
149     {
150         TCHAR messageBuffer[256] = {};
151 
152         FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
153                        nullptr, GetLastError(), MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
154                        messageBuffer, (DWORD) numElementsInArray (messageBuffer) - 1, nullptr);
155 
156         return Result::fail (String (messageBuffer));
157     }
158 }
159 
160 //==============================================================================
161 JUCE_DECLARE_DEPRECATED_STATIC (const juce_wchar File::separator = '\\';)
162 JUCE_DECLARE_DEPRECATED_STATIC (const StringRef File::separatorString ("\\");)
163 
getSeparatorChar()164 juce_wchar File::getSeparatorChar()    { return '\\'; }
getSeparatorString()165 StringRef File::getSeparatorString()   { return "\\"; }
166 
167 void* getUser32Function (const char*);
168 
169 //==============================================================================
exists() const170 bool File::exists() const
171 {
172     return fullPath.isNotEmpty()
173             && WindowsFileHelpers::getAtts (fullPath) != INVALID_FILE_ATTRIBUTES;
174 }
175 
existsAsFile() const176 bool File::existsAsFile() const
177 {
178     return fullPath.isNotEmpty()
179             && (WindowsFileHelpers::getAtts (fullPath) & FILE_ATTRIBUTE_DIRECTORY) == 0;
180 }
181 
isDirectory() const182 bool File::isDirectory() const
183 {
184     auto attr = WindowsFileHelpers::getAtts (fullPath);
185     return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 && attr != INVALID_FILE_ATTRIBUTES;
186 }
187 
hasWriteAccess() const188 bool File::hasWriteAccess() const
189 {
190     if (fullPath.isEmpty())
191         return true;
192 
193     auto attr = WindowsFileHelpers::getAtts (fullPath);
194 
195     // NB: According to MS, the FILE_ATTRIBUTE_READONLY attribute doesn't work for
196     // folders, and can be incorrectly set for some special folders, so we'll just say
197     // that folders are always writable.
198     return attr == INVALID_FILE_ATTRIBUTES
199             || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0
200             || (attr & FILE_ATTRIBUTE_READONLY) == 0;
201 }
202 
setFileReadOnlyInternal(bool shouldBeReadOnly) const203 bool File::setFileReadOnlyInternal (bool shouldBeReadOnly) const
204 {
205     return WindowsFileHelpers::changeAtts (fullPath,
206                                            shouldBeReadOnly ? FILE_ATTRIBUTE_READONLY : 0,
207                                            shouldBeReadOnly ? 0 : FILE_ATTRIBUTE_READONLY);
208 }
209 
setFileExecutableInternal(bool) const210 bool File::setFileExecutableInternal (bool /*shouldBeExecutable*/) const
211 {
212     // XXX is this possible?
213     return false;
214 }
215 
isHidden() const216 bool File::isHidden() const
217 {
218     return (WindowsFileHelpers::getAtts (fullPath) & FILE_ATTRIBUTE_HIDDEN) != 0;
219 }
220 
221 //==============================================================================
deleteFile() const222 bool File::deleteFile() const
223 {
224     if (! exists())
225         return true;
226 
227     return isDirectory() ? RemoveDirectory (fullPath.toWideCharPointer()) != 0
228                          : DeleteFile (fullPath.toWideCharPointer()) != 0;
229 }
230 
moveToTrash() const231 bool File::moveToTrash() const
232 {
233     if (! exists())
234         return true;
235 
236     // The string we pass in must be double null terminated..
237     const size_t numBytes = CharPointer_UTF16::getBytesRequiredFor (fullPath.getCharPointer()) + 8;
238     HeapBlock<WCHAR> doubleNullTermPath;
239     doubleNullTermPath.calloc (numBytes, 1);
240     fullPath.copyToUTF16 (doubleNullTermPath, numBytes);
241 
242     SHFILEOPSTRUCT fos = {};
243     fos.wFunc = FO_DELETE;
244     fos.pFrom = doubleNullTermPath;
245     fos.fFlags = FOF_ALLOWUNDO | FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION
246                    | FOF_NOCONFIRMMKDIR | FOF_RENAMEONCOLLISION;
247 
248     return SHFileOperation (&fos) == 0;
249 }
250 
copyInternal(const File & dest) const251 bool File::copyInternal (const File& dest) const
252 {
253     return CopyFile (fullPath.toWideCharPointer(),
254                      dest.getFullPathName().toWideCharPointer(), false) != 0;
255 }
256 
moveInternal(const File & dest) const257 bool File::moveInternal (const File& dest) const
258 {
259     return MoveFile (fullPath.toWideCharPointer(),
260                      dest.getFullPathName().toWideCharPointer()) != 0;
261 }
262 
replaceInternal(const File & dest) const263 bool File::replaceInternal (const File& dest) const
264 {
265     return ReplaceFile (dest.getFullPathName().toWideCharPointer(),
266                         fullPath.toWideCharPointer(),
267                         nullptr, REPLACEFILE_IGNORE_MERGE_ERRORS | 4 /*REPLACEFILE_IGNORE_ACL_ERRORS*/,
268                         nullptr, nullptr) != 0;
269 }
270 
createDirectoryInternal(const String & fileName) const271 Result File::createDirectoryInternal (const String& fileName) const
272 {
273     return CreateDirectory (fileName.toWideCharPointer(), nullptr) ? Result::ok()
274                                                                    : WindowsFileHelpers::getResultForLastError();
275 }
276 
277 //==============================================================================
juce_fileSetPosition(void * handle,int64 pos)278 int64 juce_fileSetPosition (void* handle, int64 pos)
279 {
280     LARGE_INTEGER li;
281     li.QuadPart = pos;
282     li.LowPart = SetFilePointer ((HANDLE) handle, (LONG) li.LowPart,
283                                  &li.HighPart, FILE_BEGIN);  // (returns -1 if it fails)
284     return li.QuadPart;
285 }
286 
openHandle()287 void FileInputStream::openHandle()
288 {
289     auto h = CreateFile (file.getFullPathName().toWideCharPointer(),
290                          GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
291                          OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
292 
293     if (h != INVALID_HANDLE_VALUE)
294         fileHandle = (void*) h;
295     else
296         status = WindowsFileHelpers::getResultForLastError();
297 }
298 
~FileInputStream()299 FileInputStream::~FileInputStream()
300 {
301     CloseHandle ((HANDLE) fileHandle);
302 }
303 
readInternal(void * buffer,size_t numBytes)304 size_t FileInputStream::readInternal (void* buffer, size_t numBytes)
305 {
306     if (fileHandle != nullptr)
307     {
308         DWORD actualNum = 0;
309 
310         if (! ReadFile ((HANDLE) fileHandle, buffer, (DWORD) numBytes, &actualNum, nullptr))
311             status = WindowsFileHelpers::getResultForLastError();
312 
313         return (size_t) actualNum;
314     }
315 
316     return 0;
317 }
318 
319 //==============================================================================
openHandle()320 void FileOutputStream::openHandle()
321 {
322     auto h = CreateFile (file.getFullPathName().toWideCharPointer(),
323                          GENERIC_WRITE, FILE_SHARE_READ, nullptr,
324                          OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
325 
326     if (h != INVALID_HANDLE_VALUE)
327     {
328         LARGE_INTEGER li;
329         li.QuadPart = 0;
330         li.LowPart = SetFilePointer (h, 0, &li.HighPart, FILE_END);
331 
332         if (li.LowPart != INVALID_SET_FILE_POINTER)
333         {
334             fileHandle = (void*) h;
335             currentPosition = li.QuadPart;
336             return;
337         }
338     }
339 
340     status = WindowsFileHelpers::getResultForLastError();
341 }
342 
closeHandle()343 void FileOutputStream::closeHandle()
344 {
345     CloseHandle ((HANDLE) fileHandle);
346 }
347 
writeInternal(const void * bufferToWrite,size_t numBytes)348 ssize_t FileOutputStream::writeInternal (const void* bufferToWrite, size_t numBytes)
349 {
350     DWORD actualNum = 0;
351 
352     if (fileHandle != nullptr)
353         if (! WriteFile ((HANDLE) fileHandle, bufferToWrite, (DWORD) numBytes, &actualNum, nullptr))
354             status = WindowsFileHelpers::getResultForLastError();
355 
356     return (ssize_t) actualNum;
357 }
358 
flushInternal()359 void FileOutputStream::flushInternal()
360 {
361     if (fileHandle != nullptr)
362         if (! FlushFileBuffers ((HANDLE) fileHandle))
363             status = WindowsFileHelpers::getResultForLastError();
364 }
365 
truncate()366 Result FileOutputStream::truncate()
367 {
368     if (fileHandle == nullptr)
369         return status;
370 
371     flush();
372     return SetEndOfFile ((HANDLE) fileHandle) ? Result::ok()
373                                               : WindowsFileHelpers::getResultForLastError();
374 }
375 
376 //==============================================================================
openInternal(const File & file,AccessMode mode,bool exclusive)377 void MemoryMappedFile::openInternal (const File& file, AccessMode mode, bool exclusive)
378 {
379     jassert (mode == readOnly || mode == readWrite);
380 
381     if (range.getStart() > 0)
382     {
383         SYSTEM_INFO systemInfo;
384         GetNativeSystemInfo (&systemInfo);
385 
386         range.setStart (range.getStart() - (range.getStart() % systemInfo.dwAllocationGranularity));
387     }
388 
389     DWORD accessMode = GENERIC_READ, createType = OPEN_EXISTING;
390     DWORD protect = PAGE_READONLY, access = FILE_MAP_READ;
391 
392     if (mode == readWrite)
393     {
394         accessMode = GENERIC_READ | GENERIC_WRITE;
395         createType = OPEN_ALWAYS;
396         protect = PAGE_READWRITE;
397         access = FILE_MAP_ALL_ACCESS;
398     }
399 
400     auto h = CreateFile (file.getFullPathName().toWideCharPointer(), accessMode,
401                          exclusive ? 0 : (FILE_SHARE_READ | FILE_SHARE_DELETE | (mode == readWrite ? FILE_SHARE_WRITE : 0)), nullptr,
402                          createType, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
403 
404     if (h != INVALID_HANDLE_VALUE)
405     {
406         fileHandle = (void*) h;
407 
408         auto mappingHandle = CreateFileMapping (h, nullptr, protect,
409                                                 (DWORD) (range.getEnd() >> 32),
410                                                 (DWORD) range.getEnd(), nullptr);
411 
412         if (mappingHandle != nullptr)
413         {
414             address = MapViewOfFile (mappingHandle, access, (DWORD) (range.getStart() >> 32),
415                                      (DWORD) range.getStart(), (SIZE_T) range.getLength());
416 
417             if (address == nullptr)
418                 range = Range<int64>();
419 
420             CloseHandle (mappingHandle);
421         }
422     }
423 }
424 
~MemoryMappedFile()425 MemoryMappedFile::~MemoryMappedFile()
426 {
427     if (address != nullptr)
428         UnmapViewOfFile (address);
429 
430     if (fileHandle != nullptr)
431         CloseHandle ((HANDLE) fileHandle);
432 }
433 
434 //==============================================================================
getSize() const435 int64 File::getSize() const
436 {
437     WIN32_FILE_ATTRIBUTE_DATA attributes;
438 
439     if (GetFileAttributesEx (fullPath.toWideCharPointer(), GetFileExInfoStandard, &attributes))
440         return (((int64) attributes.nFileSizeHigh) << 32) | attributes.nFileSizeLow;
441 
442     return 0;
443 }
444 
getFileTimesInternal(int64 & modificationTime,int64 & accessTime,int64 & creationTime) const445 void File::getFileTimesInternal (int64& modificationTime, int64& accessTime, int64& creationTime) const
446 {
447     using namespace WindowsFileHelpers;
448     WIN32_FILE_ATTRIBUTE_DATA attributes;
449 
450     if (GetFileAttributesEx (fullPath.toWideCharPointer(), GetFileExInfoStandard, &attributes))
451     {
452         modificationTime = fileTimeToTime (&attributes.ftLastWriteTime);
453         creationTime     = fileTimeToTime (&attributes.ftCreationTime);
454         accessTime       = fileTimeToTime (&attributes.ftLastAccessTime);
455     }
456     else
457     {
458         creationTime = accessTime = modificationTime = 0;
459     }
460 }
461 
setFileTimesInternal(int64 modificationTime,int64 accessTime,int64 creationTime) const462 bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64 creationTime) const
463 {
464     using namespace WindowsFileHelpers;
465 
466     bool ok = false;
467     auto h = CreateFile (fullPath.toWideCharPointer(),
468                          GENERIC_WRITE, FILE_SHARE_READ, nullptr,
469                          OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
470 
471     if (h != INVALID_HANDLE_VALUE)
472     {
473         FILETIME m, a, c;
474 
475         ok = SetFileTime (h,
476                           timeToFileTime (creationTime, &c),
477                           timeToFileTime (accessTime, &a),
478                           timeToFileTime (modificationTime, &m)) != 0;
479 
480         CloseHandle (h);
481     }
482 
483     return ok;
484 }
485 
486 //==============================================================================
findFileSystemRoots(Array<File> & destArray)487 void File::findFileSystemRoots (Array<File>& destArray)
488 {
489     TCHAR buffer[2048] = {};
490     GetLogicalDriveStrings (2048, buffer);
491 
492     const TCHAR* n = buffer;
493     StringArray roots;
494 
495     while (*n != 0)
496     {
497         roots.add (String (n));
498 
499         while (*n++ != 0)
500         {}
501     }
502 
503     roots.sort (true);
504 
505     for (int i = 0; i < roots.size(); ++i)
506         destArray.add (roots[i]);
507 }
508 
509 //==============================================================================
getVolumeLabel() const510 String File::getVolumeLabel() const
511 {
512     TCHAR dest[64];
513 
514     if (! GetVolumeInformation (WindowsFileHelpers::getDriveFromPath (getFullPathName()).toWideCharPointer(), dest,
515                                 (DWORD) numElementsInArray (dest), nullptr, nullptr, nullptr, nullptr, 0))
516         dest[0] = 0;
517 
518     return dest;
519 }
520 
getVolumeSerialNumber() const521 int File::getVolumeSerialNumber() const
522 {
523     TCHAR dest[64];
524     DWORD serialNum;
525 
526     if (! GetVolumeInformation (WindowsFileHelpers::getDriveFromPath (getFullPathName()).toWideCharPointer(), dest,
527                                 (DWORD) numElementsInArray (dest), &serialNum, nullptr, nullptr, nullptr, 0))
528         return 0;
529 
530     return (int) serialNum;
531 }
532 
getBytesFreeOnVolume() const533 int64 File::getBytesFreeOnVolume() const
534 {
535     return WindowsFileHelpers::getDiskSpaceInfo (getFullPathName(), false);
536 }
537 
getVolumeTotalSize() const538 int64 File::getVolumeTotalSize() const
539 {
540     return WindowsFileHelpers::getDiskSpaceInfo (getFullPathName(), true);
541 }
542 
getFileIdentifier() const543 uint64 File::getFileIdentifier() const
544 {
545     uint64 result = 0;
546 
547     String path = getFullPathName();
548 
549     if (isRoot())
550         path += "\\";
551 
552     auto h = CreateFile (path.toWideCharPointer(),
553                          GENERIC_READ, FILE_SHARE_READ, nullptr,
554                          OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
555 
556     if (h != INVALID_HANDLE_VALUE)
557     {
558         BY_HANDLE_FILE_INFORMATION info;
559         zerostruct (info);
560 
561         if (GetFileInformationByHandle (h, &info))
562             result = (((uint64) info.nFileIndexHigh) << 32) | info.nFileIndexLow;
563 
564         CloseHandle (h);
565     }
566 
567     return result;
568 }
569 
570 //==============================================================================
isOnCDRomDrive() const571 bool File::isOnCDRomDrive() const
572 {
573     return WindowsFileHelpers::getWindowsDriveType (getFullPathName()) == DRIVE_CDROM;
574 }
575 
isOnHardDisk() const576 bool File::isOnHardDisk() const
577 {
578     if (fullPath.isEmpty())
579         return false;
580 
581     auto n = WindowsFileHelpers::getWindowsDriveType (getFullPathName());
582 
583     return n != DRIVE_REMOVABLE
584         && n != DRIVE_CDROM
585         && n != DRIVE_REMOTE
586         && n != DRIVE_NO_ROOT_DIR;
587 }
588 
isOnRemovableDrive() const589 bool File::isOnRemovableDrive() const
590 {
591     if (fullPath.isEmpty())
592         return false;
593 
594     auto n = WindowsFileHelpers::getWindowsDriveType (getFullPathName());
595 
596     return n == DRIVE_CDROM
597         || n == DRIVE_REMOTE
598         || n == DRIVE_REMOVABLE
599         || n == DRIVE_RAMDISK;
600 }
601 
602 //==============================================================================
getSpecialLocation(const SpecialLocationType type)603 File JUCE_CALLTYPE File::getSpecialLocation (const SpecialLocationType type)
604 {
605     int csidlType = 0;
606 
607     switch (type)
608     {
609         case userHomeDirectory:                 csidlType = CSIDL_PROFILE; break;
610         case userDocumentsDirectory:            csidlType = CSIDL_PERSONAL; break;
611         case userDesktopDirectory:              csidlType = CSIDL_DESKTOP; break;
612         case userApplicationDataDirectory:      csidlType = CSIDL_APPDATA; break;
613         case commonApplicationDataDirectory:    csidlType = CSIDL_COMMON_APPDATA; break;
614         case commonDocumentsDirectory:          csidlType = CSIDL_COMMON_DOCUMENTS; break;
615         case globalApplicationsDirectory:       csidlType = CSIDL_PROGRAM_FILES; break;
616         case globalApplicationsDirectoryX86:    csidlType = CSIDL_PROGRAM_FILESX86; break;
617         case userMusicDirectory:                csidlType = 0x0d; /*CSIDL_MYMUSIC*/ break;
618         case userMoviesDirectory:               csidlType = 0x0e; /*CSIDL_MYVIDEO*/ break;
619         case userPicturesDirectory:             csidlType = 0x27; /*CSIDL_MYPICTURES*/ break;
620 
621         case tempDirectory:
622         {
623             WCHAR dest[2048];
624             dest[0] = 0;
625             GetTempPath ((DWORD) numElementsInArray (dest), dest);
626             return File (String (dest));
627         }
628 
629         case windowsSystemDirectory:
630         {
631             WCHAR dest[2048];
632             dest[0] = 0;
633             GetSystemDirectoryW (dest, (UINT) numElementsInArray (dest));
634             return File (String (dest));
635         }
636 
637         case invokedExecutableFile:
638         case currentExecutableFile:
639         case currentApplicationFile:
640             return WindowsFileHelpers::getModuleFileName ((HINSTANCE) Process::getCurrentModuleInstanceHandle());
641 
642         case hostApplicationPath:
643             return WindowsFileHelpers::getModuleFileName (nullptr);
644 
645         default:
646             jassertfalse; // unknown type?
647             return {};
648     }
649 
650     return WindowsFileHelpers::getSpecialFolderPath (csidlType);
651 }
652 
653 //==============================================================================
getCurrentWorkingDirectory()654 File File::getCurrentWorkingDirectory()
655 {
656     WCHAR dest[MAX_PATH + 256];
657     dest[0] = 0;
658     GetCurrentDirectory ((DWORD) numElementsInArray (dest), dest);
659     return File (String (dest));
660 }
661 
setAsCurrentWorkingDirectory() const662 bool File::setAsCurrentWorkingDirectory() const
663 {
664     return SetCurrentDirectory (getFullPathName().toWideCharPointer()) != FALSE;
665 }
666 
667 //==============================================================================
getVersion() const668 String File::getVersion() const
669 {
670     String result;
671 
672     DWORD handle = 0;
673     DWORD bufferSize = GetFileVersionInfoSize (getFullPathName().toWideCharPointer(), &handle);
674     HeapBlock<char> buffer;
675     buffer.calloc (bufferSize);
676 
677     if (GetFileVersionInfo (getFullPathName().toWideCharPointer(), 0, bufferSize, buffer))
678     {
679         VS_FIXEDFILEINFO* vffi;
680         UINT len = 0;
681 
682         if (VerQueryValue (buffer, (LPTSTR) _T("\\"), (LPVOID*) &vffi, &len))
683         {
684             result << (int) HIWORD (vffi->dwFileVersionMS) << '.'
685                    << (int) LOWORD (vffi->dwFileVersionMS) << '.'
686                    << (int) HIWORD (vffi->dwFileVersionLS) << '.'
687                    << (int) LOWORD (vffi->dwFileVersionLS);
688         }
689     }
690 
691     return result;
692 }
693 
694 //==============================================================================
isSymbolicLink() const695 bool File::isSymbolicLink() const
696 {
697     return (GetFileAttributes (fullPath.toWideCharPointer()) & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
698 }
699 
isShortcut() const700 bool File::isShortcut() const
701 {
702     return hasFileExtension (".lnk");
703 }
704 
readWindowsLnkFile(File lnkFile,bool wantsAbsolutePath)705 static String readWindowsLnkFile (File lnkFile, bool wantsAbsolutePath)
706 {
707     if (! lnkFile.exists())
708         lnkFile = File (lnkFile.getFullPathName() + ".lnk");
709 
710     if (lnkFile.exists())
711     {
712         ComSmartPtr<IShellLink> shellLink;
713         ComSmartPtr<IPersistFile> persistFile;
714 
715         if (SUCCEEDED (shellLink.CoCreateInstance (CLSID_ShellLink))
716              && SUCCEEDED (shellLink.QueryInterface (persistFile))
717              && SUCCEEDED (persistFile->Load (lnkFile.getFullPathName().toWideCharPointer(), STGM_READ))
718              && (! wantsAbsolutePath || SUCCEEDED (shellLink->Resolve (nullptr, SLR_ANY_MATCH | SLR_NO_UI))))
719         {
720             WIN32_FIND_DATA winFindData;
721             WCHAR resolvedPath[MAX_PATH];
722 
723             DWORD flags = SLGP_UNCPRIORITY;
724 
725             if (! wantsAbsolutePath)
726                 flags |= SLGP_RAWPATH;
727 
728             if (SUCCEEDED (shellLink->GetPath (resolvedPath, MAX_PATH, &winFindData, flags)))
729                 return resolvedPath;
730         }
731     }
732 
733     return {};
734 }
735 
readWindowsShortcutOrLink(const File & shortcut,bool wantsAbsolutePath)736 static String readWindowsShortcutOrLink (const File& shortcut, bool wantsAbsolutePath)
737 {
738    #if JUCE_WINDOWS
739     if (! wantsAbsolutePath)
740     {
741         HANDLE h = CreateFile (shortcut.getFullPathName().toWideCharPointer(),
742                                GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
743                                FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
744                                nullptr);
745 
746         if (h != INVALID_HANDLE_VALUE)
747         {
748             HeapBlock<WindowsFileHelpers::REPARSE_DATA_BUFFER> reparseData;
749 
750             reparseData.calloc (1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
751             DWORD bytesReturned = 0;
752 
753             bool success = DeviceIoControl (h, FSCTL_GET_REPARSE_POINT, nullptr, 0,
754                                             reparseData.getData(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
755                                             &bytesReturned, nullptr) != 0;
756              CloseHandle (h);
757 
758             if (success)
759             {
760                 if (IsReparseTagMicrosoft (reparseData->ReparseTag))
761                 {
762                     String targetPath;
763 
764                     switch (reparseData->ReparseTag)
765                     {
766                         case IO_REPARSE_TAG_SYMLINK:
767                         {
768                             auto& symlinkData = reparseData->SymbolicLinkReparseBuffer;
769                             targetPath = {symlinkData.PathBuffer + (symlinkData.SubstituteNameOffset / sizeof (WCHAR)),
770                                           symlinkData.SubstituteNameLength / sizeof (WCHAR)};
771                         }
772                         break;
773 
774                         case IO_REPARSE_TAG_MOUNT_POINT:
775                         {
776                             auto& mountData = reparseData->MountPointReparseBuffer;
777                             targetPath = {mountData.PathBuffer + (mountData.SubstituteNameOffset / sizeof (WCHAR)),
778                                           mountData.SubstituteNameLength / sizeof (WCHAR)};
779                         }
780                         break;
781 
782                         default:
783                             break;
784                     }
785 
786                     if (targetPath.isNotEmpty())
787                     {
788                         const StringRef prefix ("\\??\\");
789 
790                         if (targetPath.startsWith (prefix))
791                             targetPath = targetPath.substring (prefix.length());
792 
793                         return targetPath;
794                     }
795                 }
796             }
797         }
798     }
799 
800     if (! wantsAbsolutePath)
801         return readWindowsLnkFile (shortcut, false);
802 
803     typedef DWORD (WINAPI* GetFinalPathNameByHandleFunc) (HANDLE, LPTSTR, DWORD, DWORD);
804 
805     static GetFinalPathNameByHandleFunc getFinalPathNameByHandle
806              = (GetFinalPathNameByHandleFunc) getUser32Function ("GetFinalPathNameByHandle");
807 
808     if (getFinalPathNameByHandle != nullptr)
809     {
810         HANDLE h = CreateFile (shortcut.getFullPathName().toWideCharPointer(),
811                                GENERIC_READ, FILE_SHARE_READ, nullptr,
812                                OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
813 
814         if (h != INVALID_HANDLE_VALUE)
815         {
816             if (DWORD requiredSize = getFinalPathNameByHandle (h, nullptr, 0, 0 /* FILE_NAME_NORMALIZED */))
817             {
818                 HeapBlock<WCHAR> buffer (requiredSize + 2, true);
819 
820                 if (getFinalPathNameByHandle (h, buffer, requiredSize, 0 /* FILE_NAME_NORMALIZED */) > 0)
821                 {
822                     CloseHandle (h);
823 
824                     const StringRef prefix ("\\\\?\\");
825                     const String path (buffer.get());
826 
827                     // It turns out that GetFinalPathNameByHandleW prepends \\?\ to the path.
828                     // This is not a bug, it's feature. See MSDN for more information.
829                     return path.startsWith (prefix) ? path.substring (prefix.length()) : path;
830                 }
831             }
832 
833             CloseHandle (h);
834         }
835     }
836    #endif
837 
838     // as last resort try the resolve method of the ShellLink
839     return readWindowsLnkFile (shortcut, true);
840 }
841 
getNativeLinkedTarget() const842 String File::getNativeLinkedTarget() const
843 {
844     return readWindowsShortcutOrLink (*this, false);
845 }
846 
getLinkedTarget() const847 File File::getLinkedTarget() const
848 {
849     auto target = readWindowsShortcutOrLink (*this, true);
850 
851     if (target.isNotEmpty() && File::isAbsolutePath (target))
852         return File (target);
853 
854     return *this;
855 }
856 
createShortcut(const String & description,const File & linkFileToCreate) const857 bool File::createShortcut (const String& description, const File& linkFileToCreate) const
858 {
859     linkFileToCreate.deleteFile();
860 
861     ComSmartPtr<IShellLink> shellLink;
862     ComSmartPtr<IPersistFile> persistFile;
863 
864     CoInitialize (nullptr);
865 
866     return SUCCEEDED (shellLink.CoCreateInstance (CLSID_ShellLink))
867         && SUCCEEDED (shellLink->SetPath (getFullPathName().toWideCharPointer()))
868         && SUCCEEDED (shellLink->SetDescription (description.toWideCharPointer()))
869         && SUCCEEDED (shellLink.QueryInterface (persistFile))
870         && SUCCEEDED (persistFile->Save (linkFileToCreate.getFullPathName().toWideCharPointer(), TRUE));
871 }
872 
873 //==============================================================================
874 class DirectoryIterator::NativeIterator::Pimpl
875 {
876 public:
Pimpl(const File & directory,const String & wildCard)877     Pimpl (const File& directory, const String& wildCard)
878         : directoryWithWildCard (directory.getFullPathName().isNotEmpty() ? File::addTrailingSeparator (directory.getFullPathName()) + wildCard : String()),
879           handle (INVALID_HANDLE_VALUE)
880     {
881     }
882 
~Pimpl()883     ~Pimpl()
884     {
885         if (handle != INVALID_HANDLE_VALUE)
886             FindClose (handle);
887     }
888 
next(String & filenameFound,bool * const isDir,bool * const isHidden,int64 * const fileSize,Time * const modTime,Time * const creationTime,bool * const isReadOnly)889     bool next (String& filenameFound,
890                bool* const isDir, bool* const isHidden, int64* const fileSize,
891                Time* const modTime, Time* const creationTime, bool* const isReadOnly)
892     {
893         using namespace WindowsFileHelpers;
894         WIN32_FIND_DATA findData;
895 
896         if (handle == INVALID_HANDLE_VALUE)
897         {
898             handle = FindFirstFile (directoryWithWildCard.toWideCharPointer(), &findData);
899 
900             if (handle == INVALID_HANDLE_VALUE)
901                 return false;
902         }
903         else
904         {
905             if (FindNextFile (handle, &findData) == 0)
906                 return false;
907         }
908 
909         filenameFound = findData.cFileName;
910 
911         if (isDir != nullptr)         *isDir        = ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
912         if (isHidden != nullptr)      *isHidden     = ((findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0);
913         if (isReadOnly != nullptr)    *isReadOnly   = ((findData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0);
914         if (fileSize != nullptr)      *fileSize     = findData.nFileSizeLow + (((int64) findData.nFileSizeHigh) << 32);
915         if (modTime != nullptr)       *modTime      = Time (fileTimeToTime (&findData.ftLastWriteTime));
916         if (creationTime != nullptr)  *creationTime = Time (fileTimeToTime (&findData.ftCreationTime));
917 
918         return true;
919     }
920 
921 private:
922     const String directoryWithWildCard;
923     HANDLE handle;
924 
925     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
926 };
927 
NativeIterator(const File & directory,const String & wildCard)928 DirectoryIterator::NativeIterator::NativeIterator (const File& directory, const String& wildCard)
929     : pimpl (new DirectoryIterator::NativeIterator::Pimpl (directory, wildCard))
930 {
931 }
932 
~NativeIterator()933 DirectoryIterator::NativeIterator::~NativeIterator()
934 {
935 }
936 
next(String & filenameFound,bool * const isDir,bool * const isHidden,int64 * const fileSize,Time * const modTime,Time * const creationTime,bool * const isReadOnly)937 bool DirectoryIterator::NativeIterator::next (String& filenameFound,
938                                               bool* const isDir, bool* const isHidden, int64* const fileSize,
939                                               Time* const modTime, Time* const creationTime, bool* const isReadOnly)
940 {
941     return pimpl->next (filenameFound, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly);
942 }
943 
944 
945 //==============================================================================
openDocument(const String & fileName,const String & parameters)946 bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& parameters)
947 {
948     HINSTANCE hInstance = ShellExecute (nullptr, nullptr, fileName.toWideCharPointer(),
949                                         parameters.toWideCharPointer(), nullptr, SW_SHOWDEFAULT);
950 
951     return hInstance > (HINSTANCE) 32;
952 }
953 
revealToUser() const954 void File::revealToUser() const
955 {
956     DynamicLibrary dll ("Shell32.dll");
957     JUCE_LOAD_WINAPI_FUNCTION (dll, ILCreateFromPathW, ilCreateFromPathW, ITEMIDLIST*, (LPCWSTR))
958     JUCE_LOAD_WINAPI_FUNCTION (dll, ILFree, ilFree, void, (ITEMIDLIST*))
959     JUCE_LOAD_WINAPI_FUNCTION (dll, SHOpenFolderAndSelectItems, shOpenFolderAndSelectItems, HRESULT, (ITEMIDLIST*, UINT, void*, DWORD))
960 
961     if (ilCreateFromPathW != nullptr && shOpenFolderAndSelectItems != nullptr && ilFree != nullptr)
962     {
963         if (ITEMIDLIST* const itemIDList = ilCreateFromPathW (fullPath.toWideCharPointer()))
964         {
965             shOpenFolderAndSelectItems (itemIDList, 0, nullptr, 0);
966             ilFree (itemIDList);
967         }
968     }
969 }
970 
971 //==============================================================================
972 class NamedPipe::Pimpl
973 {
974 public:
Pimpl(const String & pipeName,const bool createPipe,bool mustNotExist)975     Pimpl (const String& pipeName, const bool createPipe, bool mustNotExist)
976         : filename ("\\\\.\\pipe\\" + File::createLegalFileName (pipeName)),
977           pipeH (INVALID_HANDLE_VALUE),
978           cancelEvent (CreateEvent (nullptr, FALSE, FALSE, nullptr)),
979           connected (false), ownsPipe (createPipe), shouldStop (false)
980     {
981         if (createPipe)
982         {
983             pipeH = CreateNamedPipe (filename.toWideCharPointer(),
984                                      PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0,
985                                      PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, nullptr);
986 
987             if (mustNotExist && GetLastError() == ERROR_ALREADY_EXISTS)
988                 closePipeHandle();
989         }
990     }
991 
~Pimpl()992     ~Pimpl()
993     {
994         closePipeHandle();
995         CloseHandle (cancelEvent);
996     }
997 
connect(const int timeOutMs)998     bool connect (const int timeOutMs)
999     {
1000         if (! ownsPipe)
1001         {
1002             if (pipeH != INVALID_HANDLE_VALUE)
1003                 return true;
1004 
1005             const Time timeOutEnd (Time::getCurrentTime() + RelativeTime::milliseconds (timeOutMs));
1006 
1007             for (;;)
1008             {
1009                 {
1010                     const ScopedLock sl (createFileLock);
1011 
1012                     if (pipeH == INVALID_HANDLE_VALUE)
1013                         pipeH = CreateFile (filename.toWideCharPointer(),
1014                                             GENERIC_READ | GENERIC_WRITE, 0, nullptr,
1015                                             OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);
1016                 }
1017 
1018                 if (pipeH != INVALID_HANDLE_VALUE)
1019                     return true;
1020 
1021                 if (shouldStop || (timeOutMs >= 0 && Time::getCurrentTime() > timeOutEnd))
1022                     return false;
1023 
1024                 Thread::sleep (1);
1025             }
1026         }
1027 
1028         if (! connected)
1029         {
1030             OverlappedEvent over;
1031 
1032             if (ConnectNamedPipe (pipeH, &over.over) == 0)
1033             {
1034                 switch (GetLastError())
1035                 {
1036                     case ERROR_PIPE_CONNECTED:   connected = true; break;
1037                     case ERROR_IO_PENDING:
1038                     case ERROR_PIPE_LISTENING:   connected = waitForIO (over, timeOutMs); break;
1039                     default: break;
1040                 }
1041             }
1042         }
1043 
1044         return connected;
1045     }
1046 
disconnectPipe()1047     void disconnectPipe()
1048     {
1049         if (ownsPipe && connected)
1050         {
1051             DisconnectNamedPipe (pipeH);
1052             connected = false;
1053         }
1054     }
1055 
closePipeHandle()1056     void closePipeHandle()
1057     {
1058         if (pipeH != INVALID_HANDLE_VALUE)
1059         {
1060             disconnectPipe();
1061             CloseHandle (pipeH);
1062             pipeH = INVALID_HANDLE_VALUE;
1063         }
1064     }
1065 
read(void * destBuffer,const int maxBytesToRead,const int timeOutMilliseconds)1066     int read (void* destBuffer, const int maxBytesToRead, const int timeOutMilliseconds)
1067     {
1068         while (connect (timeOutMilliseconds))
1069         {
1070             if (maxBytesToRead <= 0)
1071                 return 0;
1072 
1073             OverlappedEvent over;
1074             unsigned long numRead;
1075 
1076             if (ReadFile (pipeH, destBuffer, (DWORD) maxBytesToRead, &numRead, &over.over))
1077                 return (int) numRead;
1078 
1079             const DWORD lastError = GetLastError();
1080 
1081             if (lastError == ERROR_IO_PENDING)
1082             {
1083                 if (! waitForIO (over, timeOutMilliseconds))
1084                     return -1;
1085 
1086                 if (GetOverlappedResult (pipeH, &over.over, &numRead, FALSE))
1087                     return (int) numRead;
1088             }
1089 
1090             if (ownsPipe && (GetLastError() == ERROR_BROKEN_PIPE || GetLastError() == ERROR_PIPE_NOT_CONNECTED))
1091                 disconnectPipe();
1092             else
1093                 break;
1094         }
1095 
1096         return -1;
1097     }
1098 
write(const void * sourceBuffer,int numBytesToWrite,int timeOutMilliseconds)1099     int write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds)
1100     {
1101         if (connect (timeOutMilliseconds))
1102         {
1103             if (numBytesToWrite <= 0)
1104                 return 0;
1105 
1106             OverlappedEvent over;
1107             unsigned long numWritten;
1108 
1109             if (WriteFile (pipeH, sourceBuffer, (DWORD) numBytesToWrite, &numWritten, &over.over))
1110                 return (int) numWritten;
1111 
1112             if (GetLastError() == ERROR_IO_PENDING)
1113             {
1114                 if (! waitForIO (over, timeOutMilliseconds))
1115                     return -1;
1116 
1117                 if (GetOverlappedResult (pipeH, &over.over, &numWritten, FALSE))
1118                     return (int) numWritten;
1119 
1120                 if (GetLastError() == ERROR_BROKEN_PIPE && ownsPipe)
1121                     disconnectPipe();
1122             }
1123         }
1124 
1125         return -1;
1126     }
1127 
1128     const String filename;
1129     HANDLE pipeH, cancelEvent;
1130     bool connected, ownsPipe, shouldStop;
1131     CriticalSection createFileLock;
1132 
1133 private:
1134     struct OverlappedEvent
1135     {
OverlappedEventjuce::NamedPipe::Pimpl::OverlappedEvent1136         OverlappedEvent()
1137         {
1138             zerostruct (over);
1139             over.hEvent = CreateEvent (nullptr, TRUE, FALSE, nullptr);
1140         }
1141 
~OverlappedEventjuce::NamedPipe::Pimpl::OverlappedEvent1142         ~OverlappedEvent()
1143         {
1144             CloseHandle (over.hEvent);
1145         }
1146 
1147         OVERLAPPED over;
1148     };
1149 
waitForIO(OverlappedEvent & over,int timeOutMilliseconds)1150     bool waitForIO (OverlappedEvent& over, int timeOutMilliseconds)
1151     {
1152         if (shouldStop)
1153             return false;
1154 
1155         HANDLE handles[] = { over.over.hEvent, cancelEvent };
1156         DWORD waitResult = WaitForMultipleObjects (2, handles, FALSE,
1157                                                    timeOutMilliseconds >= 0 ? (DWORD) timeOutMilliseconds
1158                                                                             : INFINITE);
1159 
1160         if (waitResult == WAIT_OBJECT_0)
1161             return true;
1162 
1163         CancelIo (pipeH);
1164         return false;
1165     }
1166 
1167     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
1168 };
1169 
close()1170 void NamedPipe::close()
1171 {
1172     if (pimpl != nullptr)
1173     {
1174         pimpl->shouldStop = true;
1175         SetEvent (pimpl->cancelEvent);
1176 
1177         ScopedWriteLock sl (lock);
1178         pimpl.reset();
1179     }
1180 }
1181 
openInternal(const String & pipeName,const bool createPipe,bool mustNotExist)1182 bool NamedPipe::openInternal (const String& pipeName, const bool createPipe, bool mustNotExist)
1183 {
1184     pimpl.reset (new Pimpl (pipeName, createPipe, mustNotExist));
1185 
1186     if (createPipe)
1187     {
1188         if (pimpl->pipeH == INVALID_HANDLE_VALUE)
1189         {
1190             pimpl.reset();
1191             return false;
1192         }
1193     }
1194     else if (! pimpl->connect (200))
1195     {
1196         pimpl.reset();
1197         return false;
1198     }
1199 
1200     return true;
1201 }
1202 
read(void * destBuffer,int maxBytesToRead,int timeOutMilliseconds)1203 int NamedPipe::read (void* destBuffer, int maxBytesToRead, int timeOutMilliseconds)
1204 {
1205     ScopedReadLock sl (lock);
1206     return pimpl != nullptr ? pimpl->read (destBuffer, maxBytesToRead, timeOutMilliseconds) : -1;
1207 }
1208 
write(const void * sourceBuffer,int numBytesToWrite,int timeOutMilliseconds)1209 int NamedPipe::write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds)
1210 {
1211     ScopedReadLock sl (lock);
1212     return pimpl != nullptr ? pimpl->write (sourceBuffer, numBytesToWrite, timeOutMilliseconds) : -1;
1213 }
1214 
1215 } // namespace juce
1216