Skip to content

Instantly share code, notes, and snippets.

@t-book
Created April 10, 2025 11:06
Show Gist options
  • Save t-book/01ff93049177717fb4791811e3bbd5d9 to your computer and use it in GitHub Desktop.
Save t-book/01ff93049177717fb4791811e3bbd5d9 to your computer and use it in GitHub Desktop.
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