Windows/NTFS: Do not attempt to upload inaccessible files #5544
authorChristian Kamm <mail@ckamm.de>
Tue, 7 Mar 2017 10:47:38 +0000 (11:47 +0100)
committerckamm <mail@ckamm.de>
Tue, 14 Mar 2017 15:03:08 +0000 (16:03 +0100)
It is possible to create files with filenames that differ
only by case in NTFS, but most operations such as stat and
open only target one of these by default.

When that happens, we want to avoid uploading incorrect data
and give up on the file.

Typically this situation should never occurr during normal use
of Windows. It can happen, however, when a NTFS partition is
mounted in another OS.

src/libsync/owncloudpropagator.cpp
src/libsync/owncloudpropagator.h
src/libsync/propagateupload.cpp

index c651c1caa2692f12e07b023b21e8d066dc897c3a..390ac7e858fa3faeedab2ca9afb6fc88da3b3023 100644 (file)
@@ -601,6 +601,36 @@ bool OwncloudPropagator::localFileNameClash( const QString& relFile )
     return re;
 }
 
+bool OwncloudPropagator::hasCaseClashAccessibilityProblem(const QString &relfile)
+{
+#ifdef Q_OS_WIN
+    bool result = false;
+    const QString file( _localDir + relfile );
+    WIN32_FIND_DATA FindFileData;
+    HANDLE hFind;
+
+    hFind = FindFirstFileW(reinterpret_cast<const wchar_t*>(file.utf16()), &FindFileData);
+    if (hFind != INVALID_HANDLE_VALUE) {
+        QString firstFile = QString::fromWCharArray( FindFileData.cFileName );
+        if (FindNextFile(hFind, &FindFileData)) {
+            QString secondFile = QString::fromWCharArray( FindFileData.cFileName );
+            // This extra check shouldn't be necessary, but ensures that there
+            // are two different filenames that are identical when case is ignored.
+            if (firstFile != secondFile
+                    && QString::compare(firstFile, secondFile, Qt::CaseInsensitive) == 0) {
+                result = true;
+                qDebug() << "Found two filepaths that only differ in case: " << firstFile << secondFile;
+            }
+        }
+        FindClose(hFind);
+    }
+    return result;
+#else
+    Q_UNUSED(relfile);
+    return false;
+#endif
+}
+
 QString OwncloudPropagator::getFilePath(const QString& tmp_file_name) const
 {
     return _localDir + tmp_file_name;
index e16e2aee5370b7dba502999365aaa9c24e782206..8fd4720c18b0e2e682dbedbca3bb225caa435467 100644 (file)
@@ -308,7 +308,23 @@ public:
     int hardMaximumActiveJob();
 
     bool isInSharedDirectory(const QString& file);
+
+    /** Check whether a download would clash with an existing file
+     * in filesystems that are only case-preserving.
+     */
     bool localFileNameClash(const QString& relfile);
+
+    /** Check whether a file is properly accessible for upload.
+     *
+     * It is possible to create files with filenames that differ
+     * only by case in NTFS, but most operations such as stat and
+     * open only target one of these by default.
+     *
+     * When that happens, we want to avoid uploading incorrect data
+     * and give up on the file.
+     */
+    bool hasCaseClashAccessibilityProblem(const QString& relfile);
+
     QString getFilePath(const QString& tmp_file_name) const;
 
     void abort() {
index 0ad02c09ac2c5d58983c4973672f8ddd8e68ada7..56a46d4e4f27798b301046c9dad1691a31f98965 100644 (file)
@@ -199,6 +199,13 @@ void PropagateUploadFileCommon::start()
         return;
     }
 
+    // Check if the specific file can be accessed
+    if( propagator()->hasCaseClashAccessibilityProblem(_item->_file) ) {
+        done( SyncFileItem::NormalError, tr("File %1 cannot be uploaded because another file with the same name, differing only in case, exists")
+              .arg(QDir::toNativeSeparators(_item->_file)) );
+        return;
+    }
+
     propagator()->_activeJobList.append(this);
 
     if (!_deleteExisting) {