Created
April 10, 2025 11:06
-
-
Save t-book/01ff93049177717fb4791811e3bbd5d9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import QtQuick | |
import QtQuick.Controls | |
import QtQuick.Layouts | |
import org.qfield | |
import org.qgis | |
import Theme | |
Item { | |
id: plugin | |
property var mainWindow: iface.mainWindow() | |
property string projectFolderPath: qgisProject ? qgisProject.homePath : "" | |
property string readFileContent: "" | |
property bool writeSuccess: false | |
property bool readSuccess: false | |
property bool writeOutsideSuccess: false | |
Component.onCompleted: { | |
iface.addItemToPluginsToolbar(fileTestButton) | |
mainWindow.displayToast("File Test plugin initialized. Project path: " + projectFolderPath) | |
} | |
QfToolButton { | |
id: fileTestButton | |
bgcolor: Theme.darkGray | |
iconSource: Theme.getThemeVectorIcon('ic_file_gray_48dp') | |
iconColor: Theme.mainColor | |
round: true | |
onClicked: { | |
if (!qgisProject) { | |
mainWindow.displayToast("No project loaded") | |
return | |
} | |
if (!qgisProject.homePath) { | |
mainWindow.displayToast("No project home path available") | |
return | |
} | |
createDialog() | |
} | |
} | |
function createDialog() { | |
var component = Qt.createQmlObject(` | |
import QtQuick | |
import QtQuick.Controls | |
import QtQuick.Layouts | |
Dialog { | |
id: fileTestDialog | |
title: "File Operations Test" | |
width: parent ? parent.width * 0.8 : 600 | |
height: parent ? parent.height * 0.7 : 500 | |
x: parent ? (parent.width - width) / 2 : 0 | |
y: parent ? (parent.height - height) / 2 : 0 | |
modal: true | |
property string projectPath: "" | |
property string fileContent: "This is a test file content created at " + new Date().toLocaleString() | |
property string outsidePath: "" | |
standardButtons: Dialog.Close | |
onAccepted: { | |
close() | |
} | |
Component.onCompleted: { | |
projectPath = plugin.projectFolderPath | |
var tempPath = Qt.platform.os === "windows" ? "C:/Temp" : "/tmp" | |
outsidePath = tempPath + "/outside_test_file.txt" | |
} | |
contentItem: Item { | |
ScrollView { | |
id: scrollView | |
anchors.fill: parent | |
clip: true | |
ScrollBar.vertical.policy: ScrollBar.AlwaysOn | |
ColumnLayout { | |
width: scrollView.width - (ScrollBar.vertical.visible ? 20 : 0) | |
spacing: 20 | |
GroupBox { | |
title: "Project Information" | |
Layout.fillWidth: true | |
ColumnLayout { | |
anchors.fill: parent | |
Label { | |
text: "Project folder path: " + projectPath | |
Layout.fillWidth: true | |
wrapMode: Text.Wrap | |
} | |
} | |
} | |
GroupBox { | |
title: "Test 1: Write File in Project Directory" | |
Layout.fillWidth: true | |
ColumnLayout { | |
anchors.fill: parent | |
spacing: 10 | |
Label { | |
text: "File content to write:" | |
Layout.fillWidth: true | |
} | |
ScrollView { | |
Layout.fillWidth: true | |
height: 60 | |
TextArea { | |
id: writeContentText | |
text: fileContent | |
wrapMode: TextEdit.Wrap | |
} | |
} | |
TextField { | |
id: projectFilePathField | |
Layout.fillWidth: true | |
placeholderText: "File path relative to project folder" | |
text: "test_file.txt" | |
} | |
Button { | |
text: "Write File" | |
Layout.alignment: Qt.AlignRight | |
onClicked: { | |
if (projectFilePathField.text.trim() === "") { | |
writeResultText.text = "Error: Provide a file name" | |
writeResultText.color = "red" | |
return | |
} | |
var fullPath = projectPath + "/" + projectFilePathField.text | |
var content = writeContentText.text | |
var result = plugin.writeProjectFile(fullPath, content) | |
if (result) { | |
writeResultText.text = "Success: File written to " + fullPath | |
writeResultText.color = "green" | |
} else { | |
writeResultText.text = "Error: Failed to write file" | |
writeResultText.color = "red" | |
} | |
} | |
} | |
Label { | |
id: writeResultText | |
Layout.fillWidth: true | |
wrapMode: Text.Wrap | |
font.italic: true | |
} | |
} | |
} | |
GroupBox { | |
title: "Test 2: Read File from Project Directory" | |
Layout.fillWidth: true | |
ColumnLayout { | |
anchors.fill: parent | |
spacing: 10 | |
TextField { | |
id: readFilePathField | |
Layout.fillWidth: true | |
placeholderText: "File path relative to project folder" | |
text: projectFilePathField.text | |
} | |
Button { | |
text: "Read File" | |
Layout.alignment: Qt.AlignRight | |
onClicked: { | |
if (readFilePathField.text.trim() === "") { | |
readResultText.text = "Error: Provide a file name" | |
readResultText.color = "red" | |
readContent.text = "" | |
return | |
} | |
var fullPath = projectPath + "/" + readFilePathField.text | |
var result = plugin.readProjectFile(fullPath) | |
if (result.success) { | |
readResultText.text = "Success: File read from " + fullPath | |
readResultText.color = "green" | |
readContent.text = result.content | |
} else { | |
readResultText.text = "Error: Failed to read file" | |
readResultText.color = "red" | |
readContent.text = "" | |
} | |
} | |
} | |
Label { | |
id: readResultText | |
Layout.fillWidth: true | |
wrapMode: Text.Wrap | |
font.italic: true | |
} | |
Label { | |
text: "Content read from file:" | |
Layout.fillWidth: true | |
} | |
ScrollView { | |
Layout.fillWidth: true | |
height: 60 | |
TextArea { | |
id: readContent | |
readOnly: true | |
wrapMode: TextEdit.Wrap | |
background: Rectangle { | |
color: "#f0f0f0" | |
border.color: "#cccccc" | |
border.width: 1 | |
} | |
} | |
} | |
} | |
} | |
// Write file outside project directory | |
GroupBox { | |
title: "Test 3: Write File Outside Project Directory (Should Fail)" | |
Layout.fillWidth: true | |
ColumnLayout { | |
anchors.fill: parent | |
spacing: 10 | |
Label { | |
text: "This operation should fail due to security restrictions." | |
Layout.fillWidth: true | |
wrapMode: Text.Wrap | |
font.italic: true | |
} | |
TextField { | |
id: outsideFilePathField | |
Layout.fillWidth: true | |
placeholderText: "Path outside project directory" | |
text: outsidePath | |
} | |
Button { | |
text: "Attempt Write Outside" | |
Layout.alignment: Qt.AlignRight | |
onClicked: { | |
if (outsideFilePathField.text.trim() === "") { | |
outsideResultText.text = "Error: Provide a file path" | |
outsideResultText.color = "red" | |
return | |
} | |
var result = plugin.writeProjectFile(outsideFilePathField.text, writeContentText.text) | |
if (result) { | |
outsideResultText.text = "Warning: File was written outside project folder! Security check failed." | |
outsideResultText.color = "orange" | |
} else { | |
outsideResultText.text = "Expected behavior: Security check prevented writing outside project folder" | |
outsideResultText.color = "green" | |
} | |
} | |
} | |
Label { | |
id: outsideResultText | |
Layout.fillWidth: true | |
wrapMode: Text.Wrap | |
font.italic: true | |
} | |
} | |
} | |
GroupBox { | |
title: "Test 4: Path Traversal Test" | |
Layout.fillWidth: true | |
ColumnLayout { | |
anchors.fill: parent | |
spacing: 10 | |
Label { | |
text: "Testing path traversal protection (should fail)" | |
Layout.fillWidth: true | |
wrapMode: Text.Wrap | |
font.italic: true | |
} | |
TextField { | |
id: traversalPathField | |
Layout.fillWidth: true | |
placeholderText: "Path with traversal" | |
text: "../outside_traversal.txt" // Path traversal attempt | |
} | |
Button { | |
text: "Test Path Traversal" | |
Layout.alignment: Qt.AlignRight | |
onClicked: { | |
if (traversalPathField.text.trim() === "") { | |
traversalResultText.text = "Error: Provide a file path" | |
traversalResultText.color = "red" | |
return | |
} | |
var fullPath = projectPath + "/" + traversalPathField.text | |
var result = plugin.writeProjectFile(fullPath, "Path traversal test content") | |
if (result) { | |
traversalResultText.text = "Warning: Path traversal protection failed! Security check issue." | |
traversalResultText.color = "orange" | |
} else { | |
traversalResultText.text = "Expected behavior: Path traversal was prevented by security check" | |
traversalResultText.color = "green" | |
} | |
} | |
} | |
Label { | |
id: traversalResultText | |
Layout.fillWidth: true | |
wrapMode: Text.Wrap | |
font.italic: true | |
} | |
} | |
} | |
// Just adding some padding space at the bottom for better scrolling | |
Item { | |
height: 20 | |
Layout.fillWidth: true | |
} | |
} | |
} | |
} | |
} | |
`, mainWindow, "FileTestDialog"); | |
if (component) { | |
component.open(); | |
} else { | |
mainWindow.displayToast("Failed to create dialog"); | |
} | |
} | |
// Function to write file to project directory | |
function writeProjectFile(filePath, content) { | |
try { | |
mainWindow.displayToast("Writing file: " + filePath); | |
// Use FileUtils to write the file - QML will automatically convert the string to QByteArray | |
var result = FileUtils.writeFileContent(filePath, content); | |
writeSuccess = result; | |
mainWindow.displayToast("Write result: " + (result ? "Success" : "Failed")); | |
return result; | |
} catch (e) { | |
mainWindow.displayToast("Error writing file: " + e.toString()); | |
writeSuccess = false; | |
return false; | |
} | |
} | |
function readProjectFile(filePath) { | |
try { | |
mainWindow.displayToast("Reading file: " + filePath); | |
var fileInfo = FileUtils.getFileInfo(filePath); | |
if (fileInfo["exists"] && fileInfo["readable"]) { | |
readFileContent = fileInfo["content"]; | |
readSuccess = true; | |
mainWindow.displayToast("Read success, content length: " + readFileContent.length); | |
return { success: true, content: readFileContent }; | |
} else { | |
var errorMsg = fileInfo["readError"] || fileInfo["error"] || "File does not exist or is not readable"; | |
mainWindow.displayToast("Read failed: " + errorMsg); | |
readSuccess = false; | |
return { success: false, error: errorMsg }; | |
} | |
} catch (e) { | |
mainWindow.displayToast("Error reading file: " + e.toString()); | |
readSuccess = false; | |
return { success: false, error: e.toString() }; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment